Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Воспроизведение аудиофайлов в Windows Phone
Впервые прочитав, что в числе усовершенствований в Windows Phone ОС 7.1 появился способ для фонового воспроизведения звуковых и музыкальных файлов, я подумал: «Разве этого еще не было?».
Как оказалось, я был прав, но лишь отчасти. Приложение для Windows Phone ОС 7.0 действительно могло воспроизводить музыкальный файл в фоне, но только в особом случае. Во всех остальных случаях любой музыкальный файл, проигрываемый вашим приложением Windows Phone ОС 7.0, останавливался, когда это приложение становилось фоновым. Конечно, для большинства приложений такое поведение полностью адекватно и, вероятно, как раз то, что нужно.
Но подумайте о приложении, которое доставляет музыку на ваш смартфон отдельно от обычной библиотеки музыки. Для такого приложения крайне желательно продолжать воспроизведение, когда активны другие приложения или когда экран гаснет по тайм-ауту и переходит в состояние блокировки. И даже тем программистам, которым не нужно писать такое приложение, этот механизм предоставляет интересную точку входа в новый мир «фоновых агентов», введенных в Windows Phone ОС 7.1.
В следующей статье я покажу, как написать программу Windows Phone, которая воспроизводит музыкальные файлы в фоне. Но, чтобы у вас сложилась более полная картина о средствах поддержки звука в Windows Phone, я хочу начать эту новую рубрику с более стандартных способов воспроизведения аудиофайлов, поддерживаемых в Windows Phone ОС версий как 7.0, так и 7.1.
MediaElement и его источники
Наиболее распространенный способ для программы Silverlight воспроизвести музыкальный или звуковой файл — использование MediaElement. Ничто не может быть проще: MediaElement наследует от FrameworkElement, поэтому вы можете поместить его в визуальное дерево XAML-файла и просто задать в свойстве Source нужный URL:
<MediaElement Source="http://www.SomeWebSite.com/CoolSong.mp3" />
Когда загружается XAML-файл, музыкальный файл начинает автоматически проигрываться. MediaElement поддерживает MP3-, WMA- и WAV-файлы. Детали документированы в msdn.microsoft.com/library/ff462087(VS.92).
В качестве альтернативы ссылке на файл через Интернет вы можете встроить звуковой или музыкальный файл в исполняемый файл своего приложения. Добавьте файл в программе в Visual Studio и укажите в Build Action либо Content, либо Resource. (Content предпочтительнее, так как при этом файл встраивается в XAP-пакет; в случае Resource файл помещается в DLL для программы.) Задайте в свойстве Source значение URL для ссылки на имя файла с указанием имени папки, если это применимо в вашем случае:
<MediaElement Source="Music/LocalSong.wma" />
Хотя MediaElement может быть очень простым, есть множество способов сделать его более сложным. Один из способов — указывать аудиофайл в период выполнения, как это сделал я в программе MediaElementDemo, которая является частью пакета исходного кода для этой статьи.
В этой программе MediaElement по-прежнему находится в визуальном дереве, но свойство Source не задано, а AutoPlay установлено в False. MediaElementDemo позволяет воспроизводить три части концерта Брамса для скрипки. [Эти файлы взяты из Internet Archive по ссылке archive.org/details/BrahmsViolinConcerto-Heifetz. Запись сделана в 1939 году со скрипачом Яшей Хейфецом (Jascha Heifetz) и дирижером Сергеем Кусевицким (Serge Koussevitzky), и изначально она была доступна на виниловых дисках Victor со скоростью вращения 78 об/мин.] У трех элементов RadioButton свойства Tag настроены на источники этих трех музыкальных файлов. Для первого RadioButton это полный URL музыкального файла на веб-сайте Internet Archive. Для второй части я скачал музыкальный файл (02Ii.Adagio.mp3) на свой ПК, создал папку Music в проекте Visual Studio и добавил файл в эту папку. Третий RadioButton ссылается на файл с именем Music/02Ii.Adagio.mp3. Когда выбирается любая из этих двух кнопок-переключателей, обработчик событий получает свойство Tag, создает из него объект Uri (указывая UriKind.Absolute для веб-ссылки и UriKind.Relative для контента) и присваивает его свойству Source элемента MediaElement.
Вторая часть — это файл размером примерно 4,5 Мб, и, очевидно, он весьма значительно увеличивает размер исполняемого файла. Добавлять файлы такого размера в исполняемый файл не рекомендуется — я сделал это только в демонстрационных целях!
Если вашему приложению нужны файлы подобного размера, возможен следующий компромисс: приложение могло бы скачивать этот файл из Интернета при первом запуске, а потом хранить его в изолированном хранилище. Именно это я и сделал с третьей частью скрипичного концерта. У третьего RadioButton (которому присвоено имя isoStoreRadio¬Button) свойство IsEnabled изначально установлено в false. Процесс скачивания показан на рис. 1. В конструкторе страницы, если файл отсутствует в изолированном хранилище, WebClient инициирует фоновую передачу. Когда передача заканчивается, файл сохраняется в изолированном хранилище и RadioButton становится доступной.
Рис. 1. Скачивание веб-файла в изолированное хранилище
public MainPage()
{
InitializeComponent();
// ...
// Проверяем, находился ли файл в изолированном хранилище;
// если нет, начинаем его скачивание
using (IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication())
{
if (isoStore.FileExists(isoStoreRadioButton.Tag as string))
{
isoStoreRadioButton.IsEnabled = true;
}
else
{
WebClient webClient = new WebClient();
webClient.OpenReadCompleted +=
OnWebClientOpenReadCompleted;
webClient.OpenReadAsync(new Uri(
"http://www.archive.org/....mp3"));
}
}
// ...
}
// Когда музыкальный файл загружен,
// сохраняем его в изолированном хранилище
void OnWebClientOpenReadCompleted(object sender,
OpenReadCompletedEventArgs args)
{
if (!args.Cancelled && args.Error == null)
{
Stream inpStream = args.Result;
byte[] buffer = new byte[inpStream.Length];
inpStream.Read(buffer, 0, buffer.Length);
using (IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication())
{
string isoPathName = isoStoreRadioButton.Tag as string;
string isoDirName = Path.GetDirectoryName(isoPathName);
if (!isoStore.DirectoryExists(isoDirName))
{
isoStore.CreateDirectory(isoDirName);
}
using (IsolatedStorageFileStream isoStream =
isoStore.CreateFile(isoPathName))
{
isoStream.Write(buffer, 0, buffer.Length);
isoStoreRadioButton.IsEnabled = true;
}
}
}
}
В некоторых контекстах Windows Phone ОС 7.1 можно определить URI с префиксом «isostore» для ссылки на файл в изолированном хранилище, но такой вариант не работает для MediaElement. К счастью, у MediaElement есть свойство SetSource, которое принимает объект Stream. На рис. 2 показано, как обработчик Checked для элементов RadioButton обрабатывает эти различия.
Рис. 2. Задание свойства Source в MediaElement
void OnRadioButtonChecked(object sender, RoutedEventArgs args)
{
RadioButton radioButton = sender as RadioButton;
string uriString = radioButton.Tag as string;
// Сохраняем индекс на случай замораживания (tombstoning)
radioButtonIndex =
radioButtonPanel.Children.IndexOf(radioButton);
if (radioButton == isoStoreRadioButton)
{
// Вызываем SetSource в MediaElement,
// используя поток изолированного хранилища
using (IsolatedStorageFile storage =
IsolatedStorageFile.GetUserStoreForApplication())
{
using (Stream isoStream = storage.OpenFile(
uriString, FileMode.Open))
{
mediaElement.SetSource(isoStream);
}
}
}
else
{
// Задаем свойство Source в MediaElement, используя URI
Uri uri = new Uri(uriString, uriString.Contains(':')
? UriKind.Absolute : UriKind.Relative);
mediaElement.Source = uri;
}
}
Навигация и замораживание
Другой способ сделать MediaElement для себя более сложным — добавить элементы управления, позволяющие делать паузу и переходить в начало или конец файла. Еще интереснее элемент управления Slider, который дает возможность «проматывать» файл до определенной точки, как показано на рис. 3.
Рис. 3. Программа MediaElementDemo
Четыре кнопки ApplicationBar реализуются элементарно. Первая задает свойство Position элемента MediaElement равным 0, вторая вызывает метод Play элемента MediaElement, третья вызывает метод Pause, а четвертая устанавливает в свойстве Position значение свойства NaturalDuration.
Самая сложная часть — включение и отключение кнопок. Для этого обрабатывается событие CurrentStateChanged элемента MediaElement. Работая над логикой MediaElement, полезно сначала использовать Debug.WriteLine в обработчике событий, чтобы понять, как изменяется свойство CurrentState при загрузке музыкального файла, его буферизации, воспроизведении, паузе и окончании.
В смартфоне все музыкальные и звуковые файлы проигрываются через единый программно-аппаратный комплекс Zune Media Queue. Если вы используете стандартное приложение Music+Videos для проигрывания песни или альбома из своей музыкальной коллекции, эта музыка будет воспроизводиться и в фоне, когда вы покинете это приложение и запустите другие приложения — даже программу MediaElementDemo. Однако, если вы запускаете воспроизведение одной из частей концерта Брамса для скрипки, фоновая музыка остановится. Теперь управление ею находится «в руках» MediaElementDemo.
Но, если MediaElementDemo становится неактивной — из-за нажатия пользователем кнопки Start или гашения экрана по тайм-ауту, музыка Брамса прекратится, хотя эта программа не заморожена.
Что должно происходить, по-вашему, в такой ситуации, когда пользователь возвращается в программу? Если ваш ответ — ничего, вам повезло! Но если вы хотите, чтобы музыка снова воспроизводилась с того места, где остановилось ее проигрывание, то MediaElementDemo демонстрирует, как это можно сделать. В переопределенной версии OnNavigatedFrom программа сохраняет индекс текущей части, состояние (Playing или Paused) и позицию. В OnNavigatedTo программа проверяет RadioButton и устанавливает состояние и позицию в обработчике MediaOpened.
MediaLibrary и MediaPlayer
Я упоминал, что до появления Windows Phone ОС 7.1 механизм для воспроизведения определенных музыкальных файлов в фоновом режиме уже существовал. Подвох в том, что эти музыкальные файлы должны быть частью музыкальной библиотеки на смартфоне. Ваша программа может воспроизводить одну из этих песен, все песни с какого-либо альбома, все песни конкретного исполнителя или жанра либо все песни в списке.
Классы, предназначенные для этого, являются членами пространства имен Microsoft.Xna.Framework.Media. Чтобы задействовать эти XNA-классы в проекте Silverlight для Phone, вы должны сначала добавить ссылку на библиотеку Microsoft.Xna.Framework. В случае Windows Phone ОС 7.0 среда Visual Studio выдаст предупреждение об этом. Но для Windows Phone ОС 7.1 это предупреждение убрано.
Любая программа Silverlight, использующая XNA-классы для воспроизведения музыки, должна включать специальный класс, который реализует IApplicationService и вызывает FrameworkDispatcher.Update через каждую 30-ю долю секунды. Вы можете присвоить этому классу любое имя, но должны сослаться на него в разделе ApplicationLifetimeObjects файла App.xaml:
<local:XnaFrameworkDispatcherService />
Чтобы воспроизвести песню из музыкальной библиотеки, начните с создания экземпляра класса MusicLibrary. Свойства Artists, Albums, Genres и Playlists предоставляют наборы объектов типа Artist, Album, Genre и Playlist, и у всех этих классов есть свойство Songs типа SongCollection, которое является набором объектов Song. (Эти наборы предназначены только для чтения; ваше приложение не может ничего добавить к музыкальной библиотеке пользователя или каким-либо образом изменить ее.)
Для начала воспроизведения используйте члены статического класса MediaPlayer. Метод MediaPlayer.Play принимает объект Song, SongCollection или SongCollection с индексом, указывающим песню, с которой следует начать.
Программа PlayRandomSong содержит кнопку с меткой «Play Random Song»; когда вы касаетесь этой кнопки, выполняется следующий код:
void OnButtonClick(object sender, RoutedEventArgs args)
{
MediaLibrary mediaLib = new MediaLibrary();
AlbumCollection albums = mediaLib.Albums;
Album album = albums[random.Next(albums.Count)];
SongCollection songs = mediaLib.Songs;
Song song = songs[random.Next(songs.Count)];
MediaPlayer.Play(song);
}
Этот код случайным образом извлекает какой-либо альбом из вашей коллекции, потом случайную песню с него и начинает воспроизведение. (В эмуляторе Windows Phone есть альбом с несколькими крошечными файлами песен, так что эта программа будет нормально работать в эмуляторе.)
Если вы начинаете проигрывать песню с помощью PlayRandomSong, то обнаружите, что вы можете уйти из программы или даже завершить ее, а воспроизведение песни продолжится. Это точно соответствует тому, что было бы в случае ее воспроизведения через стандартное приложение Music+Videos; если вы запустите это приложение, то увидите обложку альбома и название песни. Более того, если нажать на кнопку регулировки громкости на смартфоне, эта информация появится в верхней части экрана и станут доступными кнопки паузы и перехода в начало или конец песни.
Ваша программа может определить, какую песню воспроизводит в данный момент приложение Music+Videos. Эта информация доступна в свойстве Queue элемента MediaPlayer, который предоставляет объект MediaQueue, указывающий текущую проигрываемую песню и набор песен, если воспроизводится альбом или отобранный список. Программа PlayRandomSong использует таймер для проверки свойства ActiveSong очереди и отображает информацию о текущей песне. В качестве альтернативы можно установить обработчики для события ActiveSongChanged в MediaPlayer.
Создание объектов Song
Программа PlayRandomSong получает объект Song от одного из свойств или наборов MediaLibrary, но у Song также есть статическое свойство FromUri, которое создает свойство Song на основе файла, находящегося не в вашей музыкальной библиотеке. Этот URI может ссылаться на музыкальный файл в Интернете или быть частью XAP-пакета программы. (На файл в изолированном хранилище ссылаться таким образом нельзя.) Затем вы можете использовать MediaPlayer для воспроизведения этого объекта Song. (Создавать собственные объекты SongCollection нельзя.)
Программа MediaPlayerDemo показывает, как это делается. Она позволяет прослушать двойной концерт Брамса [и вновь запись 1939 года с archive.org/details/BrahmsDoubleConcerto_339, где на скрипке играет Хейфец, на виолончели — Эммануэль Фойерман (Emanuel Feuermann), а дирижером является Юджин Орманди (Eugene Ormandy)]. Поскольку использовать MediaPlayer с изолированным хранилищем нельзя, первая и последняя части этого произведения хранятся в виде веб-ссылок.
Другое отличие в том, что свойство Position в MediaElement можно и считывать, и записывать, тогда как свойство PlayPosition в MediaPlayer предназначено только для чтения. Соответственно две кнопки в ApplicationBar, которые позволяют переходит в начало и конец трека, в этом случае неприменимы. Кроме того, явно нет способа получить длительность звучания объекта Song, созданного таким путем, поэтому Slider тоже неприменим. Заодно я удалил из этой программы всю логику замораживания, потому что продолжить воспроизведение трека с того места, на котором оно прервалось, невозможно.
Поскольку MediaPlayer воспроизводит объект Song, полученный из музыкальной библиотеки, в фоновом режиме, можно было бы ожидать, что он будет проигрывать в том же режиме любой другой объект Song. Но это не так. В этом отношении MediaPlayer ведет себя подобно MediaElement. Музыка прекращается, как только вы уходите из приложения. Однако, если вы уходите из программы MediaPlayerDemo и она не замораживается (что часто случается в Windows Phone ОС 7.1), то музыка лишь приостанавливается. Когда вы возвращаетесь в приложение, она возобновляется с точки прерывания.
Думаю, когда вы взвесите все плюсы и минусы, то Silverlight MediaElement окажется немного впереди XNA MediaPlayer, однако автоматическое возобновление проигрывания — очень полезная особенность MediaPlayer.
Потоковое воспроизведение и другие возможности
Я рассмотрел воспроизведение звуковых и музыкальных файлов в распространенных форматах WMA и MP3. Windows Phone также позволяет программе динамически генерировать звук. В контексте программирования для Windows Phone это называется потоковым воспроизведением «streaming». Один из подходов я продемонстрировал в программе SpeakMemo в своей статье за февраль 2011 г. (msdn.microsoft.com/magazine/gg598930), где использовал XNA-класс DyanamicSoundEffectInstance. Нечто подобное можно сделать с помощью класса MediaStreamSource в Silverlight. Именно так реализуется синтез электронной музыки на смартфоне. Однако эти варианты, опять же, применимы только для приложений «на переднем плане».
В Windows Phone ОС 7.1 была введена концепция фонового агента (background agent), и вы можете использовать такой агент для воспроизведения либо музыкальных файлов, либо потокового аудио, пока ваша программа приостановлена в фоне.
Как это делается, мы обсудим в следующей статье.
Исходный код можно скачать по ссылке code.msdn.microsoft.com/mag201201TouchAndGo.
Чарльз Петцольд (Charles Petzold) — давний «пишущий» редактор MSDN Magazine. Его веб-сайт находится по адресу charlespetzold.com.
Выражаю благодарность за рецензирование статьи эксперту Марку Хопкинсу (Mark Hopkins).