Изменение громкости, баланса, скорости и позиции воспроизведения
94WPF --- Элементы управления WPF --- Изменение громкости, баланса, скорости и позиции воспроизведения
Для управления громкостью, балансом, скоростью и текущей позицией медиафайла MediaElement предлагает те же свойства, что и MediaPlayer. Ниже показано простое окно, расширяющее пример аудиопроигрывателя из предыдущей статьи, с дополнительными элементами для управления этими параметрами.
Ползунки громкости и баланса привязать проще всего. Поскольку Volume и Balance — свойства зависимости, их элементы управления можно подключить к MediaElement с помощью выражения двунаправленной привязки.
Хотя выражения двунаправленной привязки требуют некоторых дополнительных накладных расходов, они обеспечивают обратную связь: если свойства MediaElement будут изменены каким-то другим способом, эти ползунки останутся синхронизированными с текущими значениями свойств. Свойство SpeedRatio может быть подключено аналогичным образом:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Margin="5">Громкость:</TextBlock>
<Slider Margin="5" Grid.Column="1" Minimum="0" Maximum="1"
Value="{Binding ElementName=my_media,Path=Volume,Mode=TwoWay}"></Slider>
<TextBlock Grid.Row="1" Margin="5">Баланс:</TextBlock>
<Slider Margin="5" Grid.Column="1" Grid.Row="1" Minimum="-1" Maximum="1"
Value="{Binding ElementName=my_media,Path=Balance,Mode=TwoWay}"></Slider>
<TextBlock Grid.Row="2">Скорость:</TextBlock>
<Slider Margin="5" Grid.Column="1" Grid.Row="2" Minimum="0" Maximum="3"
Value="{Binding ElementName=my_media,Path=SpeedRatio}"></Slider>
</Grid>
Однако здесь есть несколько нюансов. Во-первых, SpeedRatio не задействовано в управляемом таймером аудио (где применяется MediaTimeline). Чтобы использовать его, придется установить свойство LoadedBehavior из SpeedRatio в Manual и принять управление воспроизведением на себя через соответствующие методы.
В случае использования MediaTimeline от действия SetStoryboardSpeedRatio получается тот же эффект, что и от установки свойства MediaElement.SpeedRatio. Во-вторых, SpeedRatio не является свойством зависимости, и WPF не принимает уведомлений о его изменении. Это значит, что если свойство SpeedRatio модифицируется в коде, то ползунок соответствующим образом не обновляется. (Одним из обходных путей может быть изменение самого ползунка в коде вместо прямой модификации MediaElement.)
Изменение скорости воспроизведения аудио может исказить звук и вызвать появление эффектов вроде эха. И последняя деталь — текущая позиция, которая представлена свойством Position.
Опять-таки, для установки свойства Position элемент MediaElement должен находиться в режиме Manual, что означает невозможность применения MediaTimeline. (При использовании TimeLine подумайте о применении действия BeginStoryboard вместе с Offset для установки требуемой позиции.)
Чтобы заставить это работать, не применяйте никаких привязок данных в ползунке:
<Slider Minimum="0" Name="sliderPosition"
ValueChanged="sliderPosition_ValueChanged"></Slider>
Для установки позиции ползунка при открытии медиафайла можно использовать код, подобный следующему:
private void media_MediaOpened(object sender, RoutedEventArgs e)
{
sliderPosition.Maximum = media.NaturalDuration.TimeSpan.TotalSeconds;
}
Затем при перемещении ползунка можно перепрыгнуть в определенную позицию:
private void sliderPosition_ValueChanged(object sender, RoutedEventArgs e)
{
// Приостановка воспроизведения перед переходом в другую позицию
// исключит "заикания" при слишком быстрых движениях ползунка,
media.Pause();
media.Position = TimeSpan.FromSeconds(sliderPosition.Value);
media.Play();
}
Недостаток такого решения состоит в том, что ползунок не будет обновляться по мере воспроизведения. Если же это необходимо, придется прибегнуть к обходному маневру (вроде применения элемента DispatcherTimer, который будет выполнять периодическую проверку текущей позиции в процессе воспроизведения и соответственно обновлять положение ползунка). То же самое справедливо и при использовании MediaTimeline. По разным причинам привязаться напрямую к информации MediaElement.Clock невозможно. Вместо этого придется обрабатывать событие Storyboard.CurrentTimeInvalidated.
Синхронизация анимации с аудио
В некоторых случаях может потребоваться синхронизировать другую анимацию с определенной точкой медиафайла (аудио или видео). Например, если есть длинный аудиофайл, инструктирующий о каком-то наборе шагов, может быть решено показывать разные изображения после каждой паузы.
В зависимости от требований, проектное решение может получиться очень сложным и, возможно, для его упрощения и достижения лучшей производительности стоит сегментировать аудио в несколько файлов. Это позволит загружать новый аудиофрагмент и выполнять соответствующие действия одновременно, просто реагируя на соответствующее событие MediaEnded. В таких ситуациях понадобится синхронизировать нечто с продолжительным, непрерывным воспроизведением медиафайла.
Один прием, позволяющий связать воспроизведение с другими действиями, состоит в применении анимации ключевого кадра. Эту анимацию можно поместить вместе с MediaTimeline в одну раскадровку. Подобным образом можно применить определенные смещения времени в анимации, которые будут соответствовать определенным моментам времени в аудиофайле. Фактически, есть возможность даже воспользоваться программой от независимых разработчиков, которая может аннотировать аудио и экспортировать список важных моментов времени.
Затем эта информация применяется для установки времени каждого ключевого кадра. Используя анимацию ключевого кадра, важно установить свойство Storyboard.SlipBehavior в Slip. Это укажет, что анимация ключевого кадра не должна обгонять MediaTimeline, если происходит задержка воспроизведения. Это важно потому, что MediaTimeline может тормозить из-за буферизации (когда зависит от потока с сервера) или же, что бывает чаще, по причине задержки при загрузке.
В следующем коде разметки демонстрируется базовый пример применения аудиофайла с двумя синхронизированными анимациями. Первая изменяет текст в метке по достижении определенного места в аудиофайле. Вторая показывает маленький кружок на полпути воспроизведения аудио, который пульсирует во времени за счет изменения свойства Opacity:
<Window.Resources>
<Storyboard x:Key="Board" SlipBehavior="Slip">
<MediaTimeline Source="music1.mp3" Storyboard.TargetName="my_media"></MediaTimeline>
<StringAnimationUsingKeyFrames Storyboard.TargetName="lbl"
Storyboard.TargetProperty="(Label.Content)"
FillBehavior="HoldEnd">
<DiscreteStringKeyFrame Value="First note..." KeyTime="0:0:3.4"></DiscreteStringKeyFrame>
<DiscreteStringKeyFrame Value="Introducing the main theme" KeyTime="0:0:5.8"></DiscreteStringKeyFrame>
<DiscreteStringKeyFrame Value="Irritating bass begins..." KeyTime="0:0:28.7"></DiscreteStringKeyFrame>
<DiscreteStringKeyFrame Value="Modulation!" KeyTime="0:0:53.2"></DiscreteStringKeyFrame>
<DiscreteStringKeyFrame Value="Back to original theme" KeyTime="0:1:8"></DiscreteStringKeyFrame>
</StringAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ellipse"
Storyboard.TargetProperty="Opacity"
BeginTime="0:0:29.36"
RepeatBehavior="30x">
<LinearDoubleKeyFrame Value="1" KeyTime="0:0:0"></LinearDoubleKeyFrame>
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.64"></LinearDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>