Данная статья демонстрирует, как написать простейший музыкальный плеер под Windows Phone.
Основные возможности: плеер работает с плейлистом, можно управлять музыкой (переключать мелодии), плеер может играть в фоне.
Автор в первую очередь руководствовался статьей, в которой не были освещены некоторые элементарные, но не всегда очевидные, аспекты написания плеера. Они и разобраны в данной статье.
За основу берем обычное Silverlight for Windows Phone решение, назовем его SimplePlayer.
К уже существующему решению добавим еще один проект вида Windows Phone Audio Playback Agent, назовем его AudioPlaybackAgent.
В проекте SimplePlayer добавим ссылку на AudioPlaybackAgent.
SimplePlayer – основной проект, он содержит и обслуживает графический интерфейс и запускается первым, а AudioPlaybackAgent – это оболочка над внутренним плеером Windows Phone, которая запускается из основного проекта при запросе на воспроизведение трэка.
Общение между двумя проектами, во время выполнения приложения, довольно ограничено и накладывает некоторые ограничения, мы рассмотрим, как их можно преодолеть.
Разберемся с дизайном: он довольно прост и сразу дает понять, каким функционалом должно обладать приложение.
Для этого откройте MainPage.xaml, где содержится разметка интерфейса.
1. Надо заменить на StackPanel с именем TitlePanel на данный код:
2. Надо заменить Grid с именем ContentPanel на код:
После этого интерфейс в дизайнере будет выглядеть так:
На этом мы с дизайном покончили.
Пришло время поговорить о том, как мы будем хранить плэйлист с трэками и откуда будем брать эти трэки.
Добавим в проект SipmlePlayer несколько трэков формата mp3 или wma. (В данном примере: 1.mp3, 2.mp3, 3.mp3)
Теперь откроем файл MainPage.xaml.cs, где представлен код, обслуживающий наш интерфейс. Добавим в класс MainPage поле:
Такое же поле нужно добавить в проект AudioPlaybackAgent в класс AudioPlayer.
В этом поле мы будем хранить наш список файлов, в пользу простоты сразу отказываемся от хранения и отображения названия трэков, исполнителей и альбомов.
Хранить плэйлист мы будем не статически в коде, а в XML файле, который будет десериализорвываться в поле playlist. Добавим в основной проект xml файл, назовем его playlist.xml. Содержимое будет таким:
Для другого списка файлов надо просто добавлять, изменять, удалять тэги .
Внимание: Не забудьте у всех файлов музыки и плэйлиста выставить Build Action в Content, a Copy to Output Directory в Copy always.
Пришло время сделать playlist.xml доступным для обоих проектов. Для этого необходимо скопировать его в IsolatedStorage, доступ к которому есть у обоих проектов. Хранилище, на данный момент, — единственный способ сообщать в фоновый агент информацию помимо событий и текущего трэка.
Функция сохранения файла из проекта в хранилище:
Её нужно добавить в класс MainPage, подробно рассматривать ее в данной статье нет смысла. После чего вызываем ее из конструктора:
А вот теперь мы ее и десериализуем для этого напишем функцию, которую тоже нужно вызвать в конструкторе сразу после предыдущей.
Эту же функцию надо скопировать в проект AudioPlaybackAgent и добавить ее вызов в конструкторе класса AudioPlayer.
Внимание: Для этой функции необходимо добавить ссылку на System.Xml.Serialization.
Позаботимся теперь о загрузке музыкальных файлов, написав функцию, которую надо вызвать в конструкторе класс MainPage сразу после LoadPlaylist().
Внимание: Данный способ является не оптимальным и критично задерживает загрузку приложения, советую для реальных приложений прибегать к BackgroundWorker.
Чтобы наш плэйлист отображался, и пользователь мог с ним работать, в классе MainPage в конструкторе после вызова InitializeComponent() добавим строку:
Когда мы отредактировали XAML код, то объявили обработчики событий, но не описали их. Рассмотрим код:
Может возникнуть вопрос, что же такое BackgroundAudioPlayer?
Это, на самом деле, и есть единственная прямая связь с фоновым агентом.
В этих обработчиках мы задаем поведение нашего агента, но мы также можем и получать от него события (например остановка трэка, начало проигрывания и т.д.), для этого в класс MainPage добавим метод:
А в конструкторе класса MainPage свяжем его с нашим агентом строкой:
Стоит заметить, что поле BackgroundAudioPlayer.Instance.PlayerState содержит текущие состояние фонового агента, т.е. музыка играет, на паузе, закончилась и так далее. Еще есть поле BackgroundAudioPlayer.Instance.Position, которое отражает текущее место проигрывания в файле, что также может пригодиться, мы же опустим эту возможность.
На данном этапе наш проект почти закончен, но не работает переключение трэков, а также не происходит переход на следующий трэк, если текущий закончил проигрываться.
В классе AuidioPlayer, можно увидеть две интересные функции OnPlayStateChanged и OnUserAction. Первая отвечает за обрабатывание изменений состояния агента, в вторая — за обработку воздействия основного проекта. В обеих вызываются методы заглушки, которые подают следующий и предыдущий трэк.
Реализуем:
По непонятным автору причинам, разработчики шаблона решили, что при окончание трэка(PlayState.TrackEnded), следующим будет проигрываться предыдущий трэк, для решения этого недоразумения нужно в обработчике OnPlayStateChanged заменить player.Track = GetPreviousTrack(); на player.Track = GetNextTrack();.
Если пользователь не выбрал трэк, и он нажимает одну из кнопок (play/next/prev), тогда в классе AuidioPlayer также не выбран текущий трэк, и поэтому могут возникнуть критические исключения.
При первом событие пользователя, определим текущим трэк первым, если он еще не выбран, добавив первую строчку в метод OnUserAction:
Теперь наше приложение обладает всей заявленной базовой функциональностью, которая необходима каждому плееру.
Реализация плеера — довольно типичная задача, и я надеюсь, что статья дала понять, как сделать простой плеер.
Исходный код: скачать/посмотреть
Пример приложения на основе статьи: Музыка Нового Года
Основные возможности: плеер работает с плейлистом, можно управлять музыкой (переключать мелодии), плеер может играть в фоне.
Автор в первую очередь руководствовался статьей, в которой не были освещены некоторые элементарные, но не всегда очевидные, аспекты написания плеера. Они и разобраны в данной статье.
Создание проекта
За основу берем обычное 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);
Теперь наше приложение обладает всей заявленной базовой функциональностью, которая необходима каждому плееру.
Заключение
Реализация плеера — довольно типичная задача, и я надеюсь, что статья дала понять, как сделать простой плеер.
Исходный код: скачать/посмотреть
Пример приложения на основе статьи: Музыка Нового Года