This article has been localized into Russian by the community.
Руководство: Создание полноценного Аудио/Видео проигрывателя
Я решил завершить изложенную в последних главах тему проигрывания аудио и видео, построив более сложный пример; в нем мы воспользуемся тем фактом, что классы MediaPlayer/MediaElement могут поддерживать как аудио, так и видео проигрывание.
Я воспользуюсь теми средствами, о которых было рассказано в статьях о проигрывании аудио и видео, и соединю их с несколькими контролями, которые мы раньше обсуждали в этой статье, и превращу всё это в WPF медиа-плейер. Результат будет выглядеть примерно так:
Но это только для случая проигрывания аудио/MP3 файлов. Как только будет загружен и запущен видео-файл, интерфейс автоматически расширится, чтоб показать содержимое видео вот в таком окне:
Теперь позвольте мне немного рассказать о том, как это всё будет построено. В конце рассказа вы, конечно, сможете увидеть законченный и готовый к использованию исходный код.
Интерфейс
Интерфейс разделён вертикально на три части. В верхней части, будет находиться панель инструментов, в средней части будет показываться видео (если был загружен и запущен видео файл), а в нижней части будет расположен статус-бар, состоящий из таймера, Слайдера, позволяющего отслеживать и управлять исполнением, и из Прогресс-бара, показывающего громкость исполнения. Все использованные здесь контроли были объяснены раньше в настоящем пособии, и поэтому мы не станем сейчас подробно их описывать.
Обратите внимание, что для кнопок используются команды WPF, а не клик-события. Это позволяет нам с легкостью модифицировать функциональность программы, если мы, например, захотим добавить главное меню с такой же или чуть отличающейся функциональностью. Это также облегчит нам возможность переключать функциональность в зависимости от текущего состояния плейера.
Отметьте также, что для MediaElement мы установили значение свойства Stretch в None, а для Window значением свойства SizeToContentMode выбрано WidthAndHeight. Это позволит поддерживать минимальный размер окна, необходимый как для показа интерфейса, так и видео, если, конечно, проигрывается видео.
Для демонстрации подержки управления громкостью мы использовали ProgressBar контроль в нижнем правом углу. Напрямую это не даёт пользователю возможность управлять громкостью, а только позволяет отображать значения свойства Volume MediaElement-контроля при помощи классического байндинга - связывания данных. Однако, мы использовали также и небольшой, но изящный трюк, позволяющий, тем не менее, управлять громкостью – об этом чуть ниже.
Код
В Code-behind файле мы использовали некоторые средства, уже продемонстрированные в предыдущих примерах. Например, мы запускаем DispatcherTimer и заставляем его тикать каждую секунду, чтоб показывать текущий прогресс проигрывания на интерфейсе. В событии Tick таймера мы обновляем Slider контроль при помощи присваивания значений его свойствам Minimum, Maximum и Value - в соответствии с состоянием проигрываемого файла и при помощи захвата события слайдера ValueChanged; мы используем это обновление слайдера для показа прогресса проигрывания – в часах, минутах и секундах.
Slider-контроль также позволяет пользователю перескочить на другую часть файла, просто передвинув движок в другое положение. Это обеспечивается использованием событий DragStarted и DragCompleted – первое нужно для того, чтобы установить в true значение переменной (userIsDruggingSlider), что запрещает обновление слайдера, пока мы перемещаем движок, а второе – для перехода на новое положение движка, когда пользователь отпустит кнопку мыши.
Для четырех команд, которые мы используем, имеются обработчики CanExecute и Executed; особенно важны они для команд Pause и Stop. Поскольку мы не можем получить напрямую текущее состояние контроля MediaElement, нет другого выхода кроме как отслеживать это текущее состояние самим. Это выполняется при помощи локальной переменной mediaPlayerIsPlaying, значение которой мы постоянно должны проверять, чтобы установить, не нужно ли активизировать кнопки Pause и Stop .
Последний небольшой деталью, на которую стоит обратить внимание, является событие Grid_MouseWheel. Главный Grid – корневой контроль окна приложения, поэтому подписка на это событие (т.е. его обработка) обеспечивает уведомление о том, что пользователь поворачивает колёсико мыши. Когда это случается, срабатывает небольшой трюк и мы можем повышать или понижать громкость исполнения – в зависимости от направления вращения колёсика (направление вращения легко получить при помощи значения свойства Delta, которое отрицательно когда колёсико вращается вниз и положительно, когда оно вращается вверх). Это незамедлительно находит отражение в пользовательском интерфейсе, потому что ProgressBar связан (is bound) со свойством Volume MediaElement контроля.
Полный исходный код
Вот полный исходный код программы, полученный на основе теории, на которой базируется этот пример.
<Window x:Class="WpfTutorialSamples.Audio_and_Video.AudioVideoPlayerCompleteSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF Media Player" Height="300" Width="300"
MinWidth="300" SizeToContent="WidthAndHeight">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" CanExecute="Open_CanExecute" Executed="Open_Executed" />
<CommandBinding Command="MediaCommands.Play" CanExecute="Play_CanExecute" Executed="Play_Executed" />
<CommandBinding Command="MediaCommands.Pause" CanExecute="Pause_CanExecute" Executed="Pause_Executed" />
<CommandBinding Command="MediaCommands.Stop" CanExecute="Stop_CanExecute" Executed="Stop_Executed" />
</Window.CommandBindings>
<Grid MouseWheel="Grid_MouseWheel">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ToolBar>
<Button Command="ApplicationCommands.Open">
<Image Source="/WpfTutorialSamples;component/Images/folder.png" />
</Button>
<Separator />
<Button Command="MediaCommands.Play">
<Image Source="/WpfTutorialSamples;component/Images/control_play_blue.png" />
</Button>
<Button Command="MediaCommands.Pause">
<Image Source="/WpfTutorialSamples;component/Images/control_pause_blue.png" />
</Button>
<Button Command="MediaCommands.Stop">
<Image Source="/WpfTutorialSamples;component/Images/control_stop_blue.png" />
</Button>
</ToolBar>
<MediaElement Name="mePlayer" Grid.Row="1" LoadedBehavior="Manual" Stretch="None" />
<StatusBar Grid.Row="2">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock Name="lblProgressStatus">00:00:00</TextBlock>
</StatusBarItem>
<StatusBarItem Grid.Column="1" HorizontalContentAlignment="Stretch">
<Slider Name="sliProgress" Thumb.DragStarted="sliProgress_DragStarted" Thumb.DragCompleted="sliProgress_DragCompleted" ValueChanged="sliProgress_ValueChanged" />
</StatusBarItem>
<StatusBarItem Grid.Column="2">
<ProgressBar Name="pbVolume" Width="50" Height="12" Maximum="1" Value="{Binding ElementName=mePlayer, Path=Volume}" />
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Threading;
using Microsoft.Win32;
namespace WpfTutorialSamples.Audio_and_Video
{
public partial class AudioVideoPlayerCompleteSample : Window
{
private bool mediaPlayerIsPlaying = false;
private bool userIsDraggingSlider = false;
public AudioVideoPlayerCompleteSample()
{
InitializeComponent();
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timer_Tick;
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
if((mePlayer.Source != null) && (mePlayer.NaturalDuration.HasTimeSpan) && (!userIsDraggingSlider))
{
sliProgress.Minimum = 0;
sliProgress.Maximum = mePlayer.NaturalDuration.TimeSpan.TotalSeconds;
sliProgress.Value = mePlayer.Position.TotalSeconds;
}
}
private void Open_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Media files (*.mp3;*.mpg;*.mpeg)|*.mp3;*.mpg;*.mpeg|All files (*.*)|*.*";
if(openFileDialog.ShowDialog() == true)
mePlayer.Source = new Uri(openFileDialog.FileName);
}
private void Play_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (mePlayer != null) && (mePlayer.Source != null);
}
private void Play_Executed(object sender, ExecutedRoutedEventArgs e)
{
mePlayer.Play();
mediaPlayerIsPlaying = true;
}
private void Pause_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = mediaPlayerIsPlaying;
}
private void Pause_Executed(object sender, ExecutedRoutedEventArgs e)
{
mePlayer.Pause();
}
private void Stop_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = mediaPlayerIsPlaying;
}
private void Stop_Executed(object sender, ExecutedRoutedEventArgs e)
{
mePlayer.Stop();
mediaPlayerIsPlaying = false;
}
private void sliProgress_DragStarted(object sender, DragStartedEventArgs e)
{
userIsDraggingSlider = true;
}
private void sliProgress_DragCompleted(object sender, DragCompletedEventArgs e)
{
userIsDraggingSlider = false;
mePlayer.Position = TimeSpan.FromSeconds(sliProgress.Value);
}
private void sliProgress_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
lblProgressStatus.Text = TimeSpan.FromSeconds(sliProgress.Value).ToString(@"hh\:mm\:ss");
}
private void Grid_MouseWheel(object sender, MouseWheelEventArgs e)
{
mePlayer.Volume += (e.Delta > 0) ? 0.1 : -0.1;
}
}
}
Резюме
Вышеприведённый код может производить несколько обескураживающее впечатление своим объёмом, но, как вы легко можете видеть, в нём есть много повторений. Если принять это к сведению, то вскорости вы легко поймете, что построить на основе WPF достаточно мощный медиа-плейер – не Бог весть как сложно! Не стесняйтесь расширить возможности этого примера для ваших собственных целей. Например, как насчёт реализации возможности проигрывать список медиа-файлов?