Windows Phone / [Из песочницы] Пишем простой плеер под Windows Phone

в 14:23, , рубрики: Новости, метки:

Данная статья продемонстрирует, как написать простейший музыкальный плеер под Windows Phone, который мог бы играть в фоне и имел плейлист, с помощью которого можно управлять музыкой.
Автор статьи в первую очередь руководствовался статьей, где не были освещены некоторые элементарные аспекты написания плеера, которые не сразу всем очевидны, их я и постараюсь особенно детально рассмотреть в данной статье – по шагам.

Создание проекта

Windows Phone / [Из песочницы] Пишем простой плеер под Windows Phone

За основу берем обычное Silverlight for Windows Phone решение, назовем его SimplePlayer.

Windows Phone / [Из песочницы] Пишем простой плеер под Windows Phone

К уже существующему решению добавим еще один проект типа Windows Phone Audio Playback Agent, назовем его AudioPlaybackAgent.

Windows Phone / [Из песочницы] Пишем простой плеер под Windows Phone

Дальше в проекте SimplePlayer добавим ссылку на проект AudioPlaybackAgent.
Windows Phone / [Из песочницы] Пишем простой плеер под Windows Phone

Стоит сразу пояснить что проект SimplePlayer – основной, он содержит и обслуживает графический интерфейс и запускается первым, а AudioPlaybackAgent – это оболочка над внутренним плеером Windows Phone, которая запускается из основного проекта при запросе на воспроизведение трэка.
Общение между двумя проектами, во время выполнения приложения, довольно ограничено и накладывает некоторые трудности, но в дальнейшем мы их попытаемся преодолеть.

Дизайн интерфейса

Думаю стоит сразу разобраться с дизайном, он прост, и сразу дает понять каким функционалом должно обладать приложение.
Для этого откройте MainPage.xaml, где содержится разметка интерфейса.
1. Надо заменить на StackPanel с именем TitlePanel на данный код:

<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> 	<TextBlock x:Name="ApplicationTitle" Text="SIMPLE PLAYER"  				Style="{StaticResource PhoneTextNormalStyle}"/> 	<TextBlock x:Name="PageTitle" Text="playlist" Margin="9,-7,0,0"  				Style="{StaticResource PhoneTextTitle1Style}"/> </StackPanel> 

2. Надо заменить Grid с именем ContentPanel на код:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> 	<StackPanel Orientation="Vertical"> 		<ListBox x:Name="PlayListBox" SelectionChanged="PlayListBox_SelectionChanged"/> 		<StackPanel Orientation="Horizontal">                 	<Button x:Name="PrevButton" Content="prev" Height="140" Width="140" 						Click="PrevButton_Click"/>                    			<Button x:Name="PlayButton" Content="play" Height="140" Width="140" 						Click="PlayButton_Click"/>                 	<Button x:Name="NextButton" Content="next" Height="140" Width="140" 						Click="NextButton_Click"/> 		</StackPanel> 	</StackPanel> </Grid>

После чего интерфейс в дизайнере будет выглядеть так:
Windows Phone / [Из песочницы] Пишем простой плеер под Windows Phone

На этом мы с дизайном покончили. Идем дальше.

Плэйлист

Пришло время поговорить о том как мы будем хранить плэйлист с трэками, и собственно откуда будем брать эти трэки. Но обо всем по порядку.

Для начала добавим в проект SipmlePlayer несколько трэков формата mp3 или wma. (В данном примере: 1.mp3, 2.mp3, 3.mp3)

Windows Phone / [Из песочницы] Пишем простой плеер под Windows Phone

Теперь откроем файл MainPage.xaml.cs, что является кодом обслуживающим наш интерфейс. Добавим в класс MainPage поле:

private List<string> playlist;  

Такое же поле нужно добавить в проект AudioPlaybackAgent в класс AudioPlayer.

В этом поле мы и будем хранить наш список файлов, в пользу простоты сразу отказываемся от хранения и отображения названия трэков, исполнителей и альбомов.

Хранить плэйлист мы его будем не статически в коде, а в XML файле, который будет десериализорвываца в наше поле playlist. Добавим в основной проект xml файл, назовем его playlist.xml. В моем случае его содержимое будет такое:

<?xml version="1.0" encoding="utf-8"?> <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 			xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 	<string>1.mp3</string> 	<string>2.mp3</string> 	<string>3.mp3</string> </ArrayOfString> 

Для другого списка файлов, надо просто добавлять, изменять, удалять тэги .

Windows Phone / [Из песочницы] Пишем простой плеер под Windows Phone

Внимание: Не забудьте у всех файлов музыки и плэйлиста выставить Build Action в Content, a Copy to Output Directory в Copy always.

Пришло время сделать playlist.xml доступным для обоих проектов, для этого необходимо скопировать его в IsolatedStorage, доступ к которому есть у обоих проектов. Хранилище на данный момент единственный способ сообщать в фоновый агент информацию по мимо событий и текущего трэка.

Вот функция сохранения файла из проекта в хранилище:

private void CopyToIsolatedStorage(string fullFilePath, string storeFileName) { 	using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) 	{ 		if (!storage.FileExists(storeFileName)) 		{ 			StreamResourceInfo resource =  				Application.GetResourceStream(new Uri(fullFilePath,  					UriKind.Relative)); 			 			using (IsolatedStorageFileStream file = storage.CreateFile(storeFileName)) 			{ 				const int chunkSize = 4096; 				byte[] bytes = new byte[chunkSize]; 				int byteCount;  				while ((byteCount = resource.Stream.Read(bytes, 0, chunkSize)) > 0) 				{ 					file.Write(bytes, 0, byteCount); 				} 			} 		} 	} } 

Её нужно добавить в класс MainPage, подробно рассматривать ее в данной статье нет смысла. После чего вызываем ее из конструктора:

CopyToIsolatedStorage("playlist.xml", "playlist.xml");  

А вот теперь мы ее и десериализуем для этого напишем функцию, которую тоже нужно вызвать в конструкторе сразу после предыдущей.

private void LoadPlaylist() { 	using (IsolatedStorageFile myIsolatedStorage =  			IsolatedStorageFile.GetUserStoreForApplication()) 	{ 		using (IsolatedStorageFileStream stream =  			myIsolatedStorage.OpenFile("playlist.xml", FileMode.Open)) 		{ 			XmlSerializer serializer = new XmlSerializer(typeof(List<string>)); 			playlist = (List<string>)serializer.Deserialize(stream); 		} 	} } 

Эту же функцию надо скопировать и в проект AudioPlaybackAgent, и добавить ее вызов в конструкторе класса AudioPlayer.
Windows Phone / [Из песочницы] Пишем простой плеер под Windows Phone

Внимание: Для этой функции необходимо добавить ссылку на System.Xml.Serialization.

Позаботимся теперь о загрузке музыкальных файлов, написав маленькую функцию, которую надо вызвать в конструкторе класс MainPage сразу после LoadPlaylist().

private void LoadMusicFiles() { 	foreach (var filepath in playlist) 	{ 		CopyToIsolatedStorage(filepath, filepath); 	} } 

Внимание: Данный способ является не оптимальным и критично задерживает загрузку приложения, советую для реальных приложений прибегать к BackgroundWorker.

Чтобы наш плэйлист отображался, и пользователь мог с ним работать, в классе MainPage в конструкторе после вызова InitializeComponent(), добавим строку:

PlayListBox.ItemsSource = playlist;

Обработчики событий

Когда мы отредактировали XAML код, то объявили обработчики событий, но не описали их, вот их код:

private void PlayListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { 	string filepath = (string)e.AddedItems[0]; 	BackgroundAudioPlayer.Instance.Track = 		new AudioTrack(new Uri(filepath,UriKind.Relative),null,null,null,null); 	BackgroundAudioPlayer.Instance.Play(); }  private void PrevButton_Click(object sender, RoutedEventArgs e) { 	BackgroundAudioPlayer.Instance.SkipPrevious(); }  private void PlayButton_Click(object sender, RoutedEventArgs e) { 	if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing) 		BackgroundAudioPlayer.Instance.Pause(); 	else BackgroundAudioPlayer.Instance.Play(); }  private void NextButton_Click(object sender, RoutedEventArgs e) { 	BackgroundAudioPlayer.Instance.SkipNext(); } 

Интересна тут наверно одна вещь, что такое BackgroundAudioPlayer?

Это на самом деле и есть наша единственная связь прямая связь с фоновым агентом.
В этих обработчиках мы задаем поведение нашего агента, но мы также можем и получать от него события (например остановка трэка, начало проигрывания и т.д. ), для этого в класс MainPage добавим метод:

void Instance_PlayStateChanged(object sender, EventArgs e) { 	switch (BackgroundAudioPlayer.Instance.PlayerState) 	{ 		case PlayState.Playing: 			PlayButton.Content = "pause"; 			//при переключение трэка, делаем его выделение 			PlayListBox.SelectedItem = 				BackgroundAudioPlayer.Instance.Track.Source.OriginalString;  			break; 		case PlayState.Paused: 		case PlayState.Stopped: 			PlayButton.Content = "play"; 			break; 	} } 

А в конструкторе класса MainPage свяжем его с нашим агентом строкой:

BackgroundAudioPlayer.Instance.PlayStateChanged += Instance_PlayStateChanged;

Стоит заметить, что поле BackgroundAudioPlayer.Instance.PlayerState содержит текущие состояние фонового агента, т.е. музыка играет, на паузе, закончилась и так далее. Еще есть поле BackgroundAudioPlayer.Instance.Position, которое отражает текущее место проигрывания в фале, что так же может пригодиться многим, мы же пропустим эту возможность.

На данном этапе наш проект почти полностью реализован, но не работает переключение трэков вперед и назад, а также если трэк кончается, то не происходит перехода на следующий.

Взглянув на класс AuidioPlayer, можно увидеть две интересные функции OnPlayStateChanged и OnUserAction. Первая отвечает за обрабатывание изменений состояния агента, в вторая за обработку воздействия основного проекта. В обоих них вызываются методы заглушки, которые подают следующий и предыдущий трэк.

Давайте их реализуем:

private AudioTrack GetNextTrack() { 	int next = 		(playlist.IndexOf(BackgroundAudioPlayer.Instance.Track.Source.OriginalString) + 1) 			% (playlist.Count);  	return new AudioTrack(new Uri(playlist[next], UriKind.Relative), null, null, null, null); ; }  private AudioTrack GetPreviousTrack() { 	int prev =  		(playlist.IndexOf(BackgroundAudioPlayer.Instance.Track.Source.OriginalString) -1  			+ playlist.Count) % (playlist.Count);  	return new AudioTrack(new Uri(playlist[prev], UriKind.Relative), null, null, null, null); ; } 

По не понятным мне причинам, разработчики шаблона решили, что при окончание трэка(PlayState.TrackEnded), следующий будет проигрываться предыдущий трэк, для решения этого недоразумения нужно в обработчик OnPlayStateChanged заменить player.Track = GetPreviousTrack(); на player.Track = GetNextTrack();.

Теперь наше приложение обладает всей базовой функциональностью, которая необходима каждому плееру.

Заключение

Реализация плеера довольно типичная задача, и я надеюсь статья дала понять, как реализовать простой плеер.

Исходный код: скачать

Автор: StrangeAndr

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js