Главная » Разработка для Windows Phone 7 » Музыкальные классы XNA: MediaPlayer

0

Для отображения музыки, хранящейся в музыкальной библиотеке, мы используем XNA-класс MediaLibrary и связанные с ним классы. Воспроизведение же музыки осуществляется с помощью статического XNA-класса MediaPlayer (Проигрыватель мультимедиа).

Класс MediaPlayer воспроизводит либо объект Song, либо все песни коллекции SongCollection, либо все песни коллекции SongCollection, начиная с заданного индекса. Такая функциональность обеспечивается тремя разновидностями статического метода MediaPlayer.Play.

Создать объект SongCollection самостоятельно невозможно. Для этого обязательно необходимо получить неизменный SongCollection от одного из классов (например, Album). Это означает, что обеспечить пользователю возможность выбирать определенные песни альбома или менять последовательность треков каким-то образом не так просто. Это потребует от приложения сохранения собственного списка объектов Song с последующим их воспроизведением. Для данного относительно простого демонстрационного приложения я решил не реализовывать ничего подобного.

Кроме Play, MediaPlayer также определяет методы Pause, Resume и Stop, а также методы MovePrevious (Перейти к предыдущему) и MoveNext (Перейти к следующему) для перехода к предыдущему или следующему элементу коллекции SongCollection.

Все самые важные свойства MediaPlayer являются свойствами только для чтения:

•         State, которое возвращает один из членов перечисления MediaState (Состояние мультимедиа): Playing (Воспроизводится), Paused (Воспроизведение приостановлено) или Stopped (Воспроизведение остановлено).

•         PlayPosition (Положение головки воспроизведения) – это объект TimeSpan, указывающий на положение головки воспроизведения в рамках воспроизводимой в настоящий момент песни.

•         Queue (Очередь) – это объект MediaQueue (Очередь мультимедиа), включающий коллекцию объектов Song воспроизводимой в настоящий момент коллекции, а также свойство ActiveSong (Активная песня).

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

MediaPlayer также определяет два события:

•         MediaStateChanged (Состояние мультимедиа изменилось)

•         ActiveSongChanged (Активная песня изменилась)

Файл выделенного кода для AlbumPage отвечает за фактическое воспроизведение альбома. Но сначала давайте рассмотрим части класса, осуществляющего основные рутинные операции:

Проект Silverlight: MusicByComposer Файл: AlbumPage.xaml.cs (фрагмент)

public partial class AlbumPage : PhoneApplicationPage {

// Используется для переключения значков воспроизводить и приостановить static Uri playButtonIconUri =

new Uri("/Images/appbar.transport.play.rest.png", UriKind.Relative); static Uri pauseButtonIconUri =

new Uri("/Images/appbar.transport.pause.rest.png", UriKind.Relative);

int composerInfoIndex; int albumInfoIndex;

public AlbumPage() {

InitializeComponent();

appbarPlayPauseButton = this.ApplicationBar.Buttons[1] as

ApplicationBarIconButton; }

protected override void OnNavigatedFrom(NavigationEventArgs args) {

PhoneApplicationService.Current.State["ComposerInfoIndex"] = composerInfoIndex;

PhoneApplicationService.Current.State["AlbumInfoIndex"] = albumInfoIndex; base.OnNavigatedFrom(args);

}

protected override void OnNavigatedTo(NavigationEventArgs args) {

// Переход с MainPage

if (this.NavigationContext.QueryString.ContainsKey("ComposerInfoIndex")) {

composerInfoIndex =

Int32.Parse(this.NavigationContext.QueryString["ComposerInfoIndex"]); albumInfoIndex =

Int32.Parse(this.NavigationContext.QueryString["AlbumInfoIndex"]);

}

// Повторная активация после захоронения else if

(PhoneApplicationService.Current.State.ContainsKey("ComposerInfoIndex")) {

composerInfoIndex =

(int)PhoneApplicationService.Current.State["ComposerInfoIndex"]; albumInfoIndex =

(int)PhoneApplicationService.Current.State["AlbumInfoIndex"];

}

ComposerInfo composerInfo = MusicPresenter.Current.Composers[composerInfoIndex];

AlbumInfo albumInfo = composerInfo.Albums[albumInfoIndex];

// Задаем заголовок страницы и DataContext PageTitle.Text = composerInfo.Composer; this.DataContext = albumInfo;

// Получаем состояние мультимедиа при его изменении и также прямо сейчас MediaPlayer.MediaStateChanged += OnMediaPlayerMediaStateChanged;

OnMediaPlayerMediaStateChanged(null, EventArgs.Empty); base.OnNavigatedTo(args);

При захоронении метод OnNavigatedFrom сохраняет два поля: composerInfoIndex (Индекс сведений о композиторе) и albumInfoIndex (Индекс сведений об альбоме). Эти же два значения MainPage передает в AlbumPage посредством строки запроса для навигации. Метод OnNavigatedTo получает эти значения либо из строки запроса, либо из свойства State объекта PhoneApplicationService и использует их для задания элемента PageTitle (для отображения имени композитора) и DataContext страницы (чтобы обеспечить работоспособность привязок в AlbumPage.xaml).

Метод OnNavigatedTo также задает обработчик события MediaPlayer.MediaStateChanged для обеспечения отображения соответствующего значка кнопки, сочетающей функции Play и Pause.

Обработчик событий для этой кнопки оказался самым сложным аспектом данного класса: Проект Silverlight: MusicByComposer Файл: AlbumPage.xaml.cs (фрагмент)

void OnAppbarPlayButtonClick(object sender, EventArgs args) {

Album thisPagesAlbum = (this.DataContext as AlbumInfo).Album;

switch (MediaPlayer.State) {

// MediaPlayer в настоящий момент осуществляет воспроизведение, // поэтому приостановить, case MediaState.Playing: MediaPlayer.Pause(); break;

// MediaPlayer в настоящий момент приостановлен… case MediaState.Paused:

MediaQueue queue = MediaPlayer.Queue;

// поэтому если мы находимся на той же странице,

// что и приостановленная песня, возобновить ее воспроизведение, if (queue.ActiveSong != null &&

queue.ActiveSong.Album == thisPagesAlbum)

{

MediaPlayer.Resume();

}

// в противном случае начать воспроизведение альбома данной страницы.

else {

goto case MediaState.Stopped;

}

break;

// Воспроизведение в MediaPlayer остановлено, // начать воспроизведение альбома данной страницы, case MediaState.Stopped:

MediaPlayer.Play(thisPagesAlbum.Songs); break;

}

}

void OnAppbarPreviousButtonClick(object sender, EventArgs args) {

MediaPlayer.MovePrevious();

void OnAppbarNextButtonClick(object sender, EventArgs args) {

MediaPlayer.MoveNext();

}

После того как приложение вызывает метод MediaPlayer.Play объекта Song или SongCollection, воспроизведение продолжается, даже если пользователь закрывает приложение, или телефон выключает и блокирует экран. Именно так и должно быть. Пользователь хочет слушать музыку несмотря ни на что до тех пор, пока не кончится заряд аккумулятора.

По этой причине с большой осторожностью следует относиться к вызову метода MediaPlayer.Stop, поскольку это приведет к остановке воспроизведения без возможности его возобновления. Я вообще не нашел оснований для вызова метода MediaPlayer.Stop в своем приложении.

Приложения, такие как MusicByComposer, должны позволять пользователю выходить из приложения и возвращаться в него, а также переходить к страницам разных альбомов, не оказывая влияния на воспроизведение музыки. При этом пользователь также должен иметь возможность переключения от воспроизводимой в настоящий момент музыки к текущему альбому. Я считаю, что все эти опции можно реализовать как четыре варианта поведения по нажатию кнопки play/pause:

•         Если музыка воспроизводится, на кнопке play/pause отображается значок паузы, и по нажатию этой кнопки воспроизведение должно быть приостановлено.

•         Если воспроизведения не осуществляется, кнопка play/pause отображает значок воспроизведения, и по ее нажатию должно начинаться воспроизведение просматриваемого альбома.

•         Если воспроизведение приостановлено, на кнопке play/pause также отображается значок воспроизведения. Если пользователь находится на странице активного в настоящий момент альбома, нажатие кнопки воспроизведения должно просто обеспечить возобновление воспроизведения приостановленной до этого композиции.

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

На практике эта логика работает хорошо.

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

SongTitleControl наследуется от UserControl и имеет простое дерево визуальных элементов: Проект Silverlight: MusicByComposer Файл: SongTitleControl.xaml (фрагмент)

<Grid x:Name="LayoutRoot">

<StackPanel Margin="0 3">

<TextBlock Name="txtblkTitle"

Text="{Binding Name}" TextWrapping="Wrap" />

<TextBlock Name="txtblkTime" Margin="24 6" Visibility="Collapsed" />

</StackPanel> </Grid>

В файле AlbumPage.xaml класс SongTitleControl включает привязку к свойству Song. Это означает, что SongTitleControl должен определять свойство-зависимость Song XNA-типа Song. Рассмотрим описание этого свойства Song и обработчиков изменения его значения:

Проект Silverlight: MusicByComposer Файл: SongTitleControl.xaml.cs (фрагмент)

public static readonly DependencyProperty SongProperty = DependencyProperty.Register("Song", typeof(Song), typeof(SongTitleControl), new PropertyMetadata(OnSongChanged));

public Song Song {

set { SetValue(SongProperty, value); }

get { return (Song)GetValue(SongProperty); }

}

static void OnSongChanged(DependencyObject obj, DependencyPropertyChangedEventArgs

args) {

(obj as SongTitleControl).OnSongChanged(args);

}

void OnSongChanged(DependencyPropertyChangedEventArgs args) {

if (Song != null)

MediaPlayer.ActiveSongChanged += OnMediaPlayerActiveSongChanged;

else

MediaPlayer.ActiveSongChanged -= OnMediaPlayerActiveSongChanged; OnMediaPlayerActiveSongChanged(null, EventArgs.Empty);

}

Если Song задано значение, отличное от null, задается обработчик для событий MediaPlayer.ActiveSongChanged. Рассмотрим этот обработчик событий:

Проект Silverlight: MusicByComposer Файл: SongTitleControl.xaml.cs (фрагмент)

void OnMediaPlayerActiveSongChanged(object sender, EventArgs args) {

if (this.Song == MediaPlayer.Queue.ActiveSong) {

txtblkTitle.FontWeight = FontWeights.Bold;

txtblkTitle.Foreground = this.Resources["PhoneAccentBrush"] as Brush;

txtblkTime.Visibility = Visibility.Visible;

timer.Start();

}

else {

txtblkTitle.FontWeight = FontWeights.Normal;

txtblkTitle.Foreground = this.Resources["PhoneForegroundBrush"] as Brush;

txtblkTime.Visibility = Visibility.Collapsed;

timer.Stop();

Свойство Text объекта txtblkTitle обрабатывается с помощью привязки в XAML-файле. Если активной песней является Song, ассоциированный с экземпляром SongTitleControl, этот TextBlock выделяется контрастным цветом, становится видимым другой TextBlock с информацией о времени воспроизведения, и запускается DispatcherTimer:

Проект Silverlight: MusicByComposer Файл: SongTitleControl.xaml.cs (фрагмент)

public partial class SongTitleControl : UserControl {

DispatcherTimer timer = new DispatcherTimer();

public SongTitleControl() {

InitializeComponent();

timer.Interval = TimeSpan.FromSeconds(0.25); timer.Tick += OnTimerTick;

}

void OnTimerTick(object sender, EventArgs args) {

TimeSpan dur = this.Song.Duration; TimeSpan pos = MediaPlayer.PlayPosition;

txtblkTime.Text = String.Format("{0}:{1:D2} / {2}:{3:D2}",

(int)pos.TotalMinutes, pos.Seconds, (int)dur.TotalMinutes, dur.Seconds);

}

}

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

Я думал о переносе части этого кода в XAML, для чего потребовалось бы определить свойство для истекшего времени, а также использовать Visual State Manager для состояний ActiveSong и NotActiveSong (Неактивная песня), и затем ввести конвертер StringFormatterConverter для форматирования двух объектов TimeSpan. Но для данного конкретного приложения использование файла кода мне показалось более простым решением.

Мы видели множество примеров мощи XAML, но иногда код является по-настоящему правильным решением.

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии