Как стать автором
Поиск
Написать публикацию
Обновить

Пишем простой плеер под Windows Phone

Время на прочтение7 мин
Количество просмотров13K
Данная статья демонстрирует, как написать простейший музыкальный плеер под Windows Phone.

Основные возможности: плеер работает с плейлистом, можно управлять музыкой (переключать мелодии), плеер может играть в фоне.

Автор в первую очередь руководствовался статьей, в которой не были освещены некоторые элементарные, но не всегда очевидные, аспекты написания плеера. Они и разобраны в данной статье.


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




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



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



В проекте SimplePlayer добавим ссылку на AudioPlaybackAgent.


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>

После этого интерфейс в дизайнере будет выглядеть так:


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

Плэйлист


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

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



Теперь откроем файл 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>

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



Внимание: Не забудьте у всех файлов музыки и плэйлиста выставить 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.


Внимание: Для этой функции необходимо добавить ссылку на 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();.

Если пользователь не выбрал трэк, и он нажимает одну из кнопок (play/next/prev), тогда в классе AuidioPlayer также не выбран текущий трэк, и поэтому могут возникнуть критические исключения.
При первом событие пользователя, определим текущим трэк первым, если он еще не выбран, добавив первую строчку в метод OnUserAction:
if (player.Track == null) 
	player.Track = new AudioTrack(new Uri(playlist[0], UriKind.Relative), null, null, null, null);


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

Заключение


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

Исходный код: скачать/посмотреть
Пример приложения на основе статьи: Музыка Нового Года
Теги:
Хабы:
Всего голосов 29: ↑22 и ↓7+15
Комментарии17

Публикации

Ближайшие события