Воспроизведение аудио и видео с помощью MediaPlayer

В этой статье показано, как воспроизводить мультимедиа в универсальном приложении для Windows с помощью класса MediaPlayer . В Windows 10 версии 1607 были внесены значительные улучшения в API воспроизведения мультимедиа, включая упрощенную однопроцессную структуру фонового звука, автоматическую интеграцию с системными элементами управления транспортировкой мультимедиа (SMTC), возможность синхронизации нескольких проигрывателей мультимедиа, возможность отрисовки видеокадров на поверхности Windows.UI.Composition, а также простой интерфейс для создания и планирования перерывов мультимедиа в содержимом. Для реализации этих улучшений рекомендуется использовать класс MediaPlayer вместо MediaElement для воспроизведения мультимедийного содержимого. Добавлен облегченный элемент управления XAML MediaPlayerElement, предназначенный для отрисовки мультимедийного содержимого на странице XAML. Многие API состояния и управления воспроизведением, предоставляемые классом MediaElement, теперь доступны с помощью нового объекта MediaPlaybackSession. MediaElement продолжает выполнять функцию поддержки обратной совместимости, но дополнительные возможности в этот класс добавляться не будут.

Эта статья содержит подробный разбор функций MediaPlayer, которые обычно используются в приложениях для воспроизведения мультимедиа. Обратите внимание, что MediaPlayer использует класс MediaSource в качестве контейнера для всех элементов мультимедиа. Этот класс позволяет загружать и воспроизводить мультимедиа из множества различных источников, включая локальные файлы, потоки в памяти и сетевые источники, с использованием одного интерфейса. Также существуют классы более высокого уровня, совместимые с MediaSource, например MediaPlaybackItem и MediaPlaybackList, которые предоставляют расширенные возможности, такие как списки воспроизведения, и обеспечивают способность работать с источниками мультимедиа, содержащими несколько дорожек видео-, аудио- и метаданных. Подробные сведения о классе MediaSource и связанных с ним API см. в разделе Элементы, плей-листы и звуковые дорожки мультимедиа.

Примечание

В выпусках Windows 10 N и Windows 10 KN нет компонентов мультимедиа, необходимых для использования MediaPlayer для воспроизведения. Эти компоненты можно установить вручную. Дополнительные сведения см. в разделе Пакет дополнительных компонентов для работы с мультимедиа для выпусков Windows 10 N и Windows 10 KN.

Воспроизведение файла мультимедиа с помощью MediaPlayer

Простое воспроизведение мультимедиа с помощью MediaPlayer реализовать очень легко. Сначала создайте новый экземпляр класса MediaPlayer. Ваше приложение может содержать несколько активных экземпляров MediaPlayer одновременно. Затем задайте свойству Source проигрывателя значение объекта, реализующего IMediaPlaybackSource, такого как MediaSource, MediaPlaybackItem или MediaPlaybackList. В этом примере MediaSource создается из файла в локальном хранилище приложения, затем из источника создается MediaPlaybackItem и далее назначается свойству Source проигрывателя.

В отличие от MediaElement, MediaPlayer по умолчанию не начинает воспроизведение автоматически. Начать воспроизведение можно путем вызова Play, путем присвоения свойству AutoPlay значения true, а также можно дождаться, пока пользователь не начнет воспроизведение с помощью встроенных элементов управления мультимедиа.

mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer.Play();

Когда приложение завершает использование MediaPlayer, необходимо вызвать метод Close (соответствующий Dispose в C#), чтобы высвободить ресурсы, используемые проигрывателем.

mediaPlayer.Dispose();

Использование MediaPlayerElement для отрисовки видео в XAML

Вы можете воспроизводить мультимедиа в MediaPlayer без отображения в XAML, но для многих приложений с воспроизведением мультимедиа потребуется отрисовка мультимедиа на странице XAML. Для этого используйте облегченный элемент управления MediaPlayerElement. Как и MediaElement, MediaPlayerElement позволяет указать, следует ли отображать встроенные элементы управления транспортировкой.

<MediaPlayerElement x:Name="_mediaPlayerElement" AreTransportControlsEnabled="False" HorizontalAlignment="Stretch"  Grid.Row="0"/>

Вы можете задать экземпляр MediaPlayer, к которому привязан элемент управления, вызвав метод SetMediaPlayer.

_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

Вы можете также задать источник воспроизведения в MediaPlayerElement, и этот элемент автоматически создаст новый экземпляр MediaPlayer, получить доступ к которому можно с помощью свойства MediaPlayer.

_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer = _mediaPlayerElement.MediaPlayer;
mediaPlayer.Play();

Примечание

Если вы отключите MediaPlaybackCommandManager в объекте MediaPlayer, установив для свойства IsEnabled значение false, это нарушит связь между свойством TransportControls объекта MediaPlayer, предоставляемым классом MediaPlayerElement, поэтому встроенные элементы управления транспортом больше не будут автоматически управлять воспроизведением мультимедиа. Вместо этого вам нужно будет реализовать собственные элементы управления для управления воспроизведением MediaPlayer.

Типичные задачи, связанные с MediaPlayer

В этом разделе рассказывается, как использовать некоторые функции MediaPlayer.

Задание категории аудио

Задайте свойству AudioCategory объекта MediaPlayer одно из значений из перечисления MediaPlayerAudioCategory, чтобы сообщить системе, какой тип мультимедиа воспроизводится. Игры должны относить свои потоки музыки к категории GameMedia, чтобы звук музыки в игре автоматически отключался, если другое приложение воспроизводит музыку в фоновом режиме. Приложения для музыки и видео должны относить свои потоки к категории Media или Movie, чтобы у них был приоритет перед потоками GameMedia.

mediaPlayer.AudioCategory = MediaPlayerAudioCategory.Media;

Вывод на определенную конечную точку аудио

По умолчанию вывод звука из MediaPlayer направляется на конечную точку системы по умолчанию, но вы можете указать собственную конечную точку аудио, которую MediaPlayer будет использовать для вывода. В примере ниже MediaDevice.GetAudioRenderSelector возвращает строку, которая уникальным образом идентифицирует категорию обработки аудио для устройств. Далее вызывается метод FindAllAsync класса DeviceInformation, чтобы получить список всех доступных устройств выбранного типа. Можно программным способом определить, какое устройство требуется использовать, или добавить возвращаемые устройства в ComboBox, чтобы пользователь мог выбрать устройство.

string audioSelector = MediaDevice.GetAudioRenderSelector();
var outputDevices = await DeviceInformation.FindAllAsync(audioSelector);
foreach (var device in outputDevices)
{
    var deviceItem = new ComboBoxItem();
    deviceItem.Content = device.Name;
    deviceItem.Tag = device;
    _audioDeviceComboBox.Items.Add(deviceItem);
}

В событии SelectionChanged для поля со списком устройств свойству AudioDevice объекта MediaPlayer задано значение выбранного устройства, которое хранилось в свойстве Tag объекта ComboBoxItem.

private void _audioDeviceComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    DeviceInformation selectedDevice = (DeviceInformation)((ComboBoxItem)_audioDeviceComboBox.SelectedItem).Tag;
    if (selectedDevice != null)
    {
        mediaPlayer.AudioDevice = selectedDevice;
    }
}

Сеанс воспроизведения

Как описано ранее в этой статье, множество функций, которые предоставляются классом MediaElement, перенесены в класс MediaPlaybackSession. Сюда относятся сведения о состоянии воспроизведения проигрывателя, такие как текущая позиция воспроизведения, включение режима паузы проигрывателя, а также текущая скорость воспроизведения. MediaPlaybackSession также предоставляет ряд событий, предназначенных для уведомления о смене состояния, включая текущую загрузку буфера и процент загрузки воспроизводимого содержимого, а также фактический размер и соотношение сторон воспроизводимого сейчас видео.

В следующем примере показано, как реализовать обработчик нажатия кнопки, который перематывает содержимое на 10 секунд вперед. Сначала с помощью свойства PlaybackSession создается объект MediaPlaybackSession для проигрывателя. Затем свойству Position задается текущая позиция воспроизведения с добавлением 10 секунд.

private void _skipForwardButton_Click(object sender, RoutedEventArgs e)
{
    var session = mediaPlayer.PlaybackSession;
    session.Position = session.Position + TimeSpan.FromSeconds(10);
}

Следующий пример иллюстрирует использование кнопки-переключателя для выбора обычной или двукратной скорости воспроизведения путем изменения свойства PlaybackRate этого сеанса.

private void _speedToggleButton_Checked(object sender, RoutedEventArgs e)
{
    mediaPlayer.PlaybackSession.PlaybackRate = 2.0;
}
private void _speedToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
    mediaPlayer.PlaybackSession.PlaybackRate = 1.0;
}

Начиная с Windows 10 версии 1803 можно настроить поворот видео в MediaPlayer с шагом 90 градусов.

mediaPlayer.PlaybackSession.PlaybackRotation = MediaRotation.Clockwise90Degrees;

Обнаружение ожидаемой и непредвиденной буферизации

Объект MediaPlaybackSession, описанный в предыдущем разделе предоставляет два события для обнаружения момента начала и окончания буферизации проигрываемого в данный момент файла мультимедиа: BufferingStarted и BufferingEnded. Это позволяет показывать в пользовательском интерфейсе состояние буферизации. Момент начала буферизации ожидается при первом открытии файла мультимедиа, а также при включении пользователем нового элемента в списке воспроизведения. Непредвиденная буферизации может произойти при ухудшения скорости сетевой передачи или при возникновении технических сбоев в системе управления содержимым. Начиная с RS3, можно использовать событие BufferingStarted, чтобы определить, является событие буферизации ожидаемым или непредвиденным, и прерывает ли оно воспроизведение. Эти сведения можно использовать в качестве данных телеметрии для вашего приложения или службы доставки содержимого.

Зарегистрируйте обработчики для событий BufferingStarted и BufferingEnded, чтобы получать уведомления о состояния буферизации.

mediaPlayer.PlaybackSession.BufferingStarted += MediaPlaybackSession_BufferingStarted;
mediaPlayer.PlaybackSession.BufferingEnded += MediaPlaybackSession_BufferingEnded;

В обработчике события BufferingStarted приведите аргументы, переданных событию, к типу MediaPlaybackSessionBufferingStartedEventArgs и проверьте свойство IsPlaybackInterruption. Если это значение равно true, то буферизация, ставшее причиной события, является непредвиденным и прерывает воспроизведение. В противном случае — это ожидаемая буферизация.

private void MediaPlaybackSession_BufferingStarted(MediaPlaybackSession sender, object args)
{
    MediaPlaybackSessionBufferingStartedEventArgs bufferingStartedEventArgs = args as MediaPlaybackSessionBufferingStartedEventArgs;
    if (bufferingStartedEventArgs != null && bufferingStartedEventArgs.IsPlaybackInterruption)
    {
        // update the playback quality telemetry report to indicate that
        // playback was interrupted
    }

    // update the UI to indicate that playback is buffering
}
private void MediaPlaybackSession_BufferingEnded(MediaPlaybackSession sender, object args)
{
    // update the UI to indicate that playback is no longer buffering
}

Сжатие и растяжение видео

MediaPlayer позволяет определить в видеосодержимом прямоугольник источника, который подлежит отрисовке, что позволяет увеличивать определенную область видеоизображения. Прямоугольник определяется относительно стандартного прямоугольника (0,0,1,1), где 0,0 соответствует верхнему левому углу кадра, а 1,1 задает полную ширину и высоту кадра. Например, чтобы установить прямоугольник увеличения в верхнюю правую часть отображаемого видео, необходимо задать прямоугольник (.5,0,.5,.5). Важно проверить устанавливаемые значения, чтобы ваш прямоугольник источника находился в пределах (0,0,1,1) стандартного прямоугольника. Попытка установить значения за пределами этого диапазона приведет к возникновению исключения.

Для реализации сжатия и растяжения с помощью мультисенсорных жестов сначала нужно определить, какие жесты вы хотите поддерживать. В этом примере запрашиваются жесты масштабирования и перехода. Событие ManipulationDelta возникает, когда выполняется один из запрошенных жестов. Событие DoubleTapped будет использоваться для сброса масштабирования до полного кадра.

_mediaPlayerElement.ManipulationMode = ManipulationModes.Scale | ManipulationModes.TranslateX | ManipulationModes.TranslateY;
_mediaPlayerElement.ManipulationDelta += _mediaPlayerElement_ManipulationDelta;
_mediaPlayerElement.DoubleTapped += _mediaPlayerElement_DoubleTapped;

Затем создайте объект Rect, который будет хранить текущий прямоугольник источника для масштабирования.

Rect _sourceRect = new Rect(0, 0, 1, 1);

Обработчик ManipulationDelta подстраивает либо масштаб, либо перемещение прямоугольника масштабирования. Если значение изменения масштаба не равно 1, это означает, что пользователь выполнил жест сжатия. Если значение больше 1, прямоугольник источника необходимо уменьшить для увеличения содержимого. Если значение меньше 1, то прямоугольник источника необходимо сделать больше для уменьшения масштаба. Перед установкой новых значений масштабирования проверятся целевой прямоугольник, чтобы он находится в пределах (0,0,1,1).

Если значение масштабирования равно 1, обрабатывается жест перемещения. Прямоугольник просто перемещается на количество пикселей в жесте, разделенное на ширину и высоту элемента управления. И снова образуемый прямоугольник проверяется на предмет полного размещения в границах (0,0,1.1).

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

private void _mediaPlayerElement_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{

    if (e.Delta.Scale != 1)
    {
        var halfWidth = _sourceRect.Width / 2;
        var halfHeight = _sourceRect.Height / 2;

        var centerX = _sourceRect.X + halfWidth;
        var centerY = _sourceRect.Y + halfHeight;

        var scale = e.Delta.Scale;
        var newHalfWidth = (_sourceRect.Width * e.Delta.Scale) / 2;
        var newHalfHeight = (_sourceRect.Height * e.Delta.Scale) / 2;

        if (centerX - newHalfWidth > 0 && centerX + newHalfWidth <= 1.0 &&
            centerY - newHalfHeight > 0 && centerY + newHalfHeight <= 1.0)
        {
            _sourceRect.X = centerX - newHalfWidth;
            _sourceRect.Y = centerY - newHalfHeight;
            _sourceRect.Width *= e.Delta.Scale;
            _sourceRect.Height *= e.Delta.Scale;
        }
    }
    else
    {
        var translateX = -1 * e.Delta.Translation.X / _mediaPlayerElement.ActualWidth;
        var translateY = -1 * e.Delta.Translation.Y / _mediaPlayerElement.ActualHeight;

        if (_sourceRect.X + translateX >= 0 && _sourceRect.X + _sourceRect.Width + translateX <= 1.0 &&
            _sourceRect.Y + translateY >= 0 && _sourceRect.Y + _sourceRect.Height + translateY <= 1.0)
        {
            _sourceRect.X += translateX;
            _sourceRect.Y += translateY;
        }
    }

    mediaPlayer.PlaybackSession.NormalizedSourceRect = _sourceRect;
}

В обработчике событий DoubleTapped прямоугольник источника возвращается к значениям (0,0,1,1), что приводит к отрисовке всего видеокадра.

private void _mediaPlayerElement_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
    _sourceRect = new Rect(0, 0, 1, 1);
    mediaPlayer.PlaybackSession.NormalizedSourceRect = _sourceRect;
}

ПРИМЕЧАНИЕ В этом разделе описывается сенсорный ввод. Сенсорная панель отправляет события указателя и не отправляет события Манипуляции.

Обработка снижения качества воспроизведения на основе политик

В некоторых случаях система уменьшить качество воспроизведения элемента мультимедиа, например, уменьшив разрешение (ограничением) на основе заданной политики, а не из-за проблемы с производительностью. Например, качество видео может снижено системой, если воспроизведение выполняется с использованием неподписанного видеодрайвера. Вы можете вызвать метод MediaPlaybackSession.GetOutputDegradationPolicyState, чтобы определить выполняется ли ухудшение качества на основе политики и почему это происходит, после чего уведомить пользователя или записать причину этого события для сбора телеметрических данных.

В следующем примере показана реализация обработчика события MediaPlayer.MediaOpened, которое возникает при открытии нового элемента мультимедиа в средстве воспроизведения. Вызывается метод GetOutputDegradationPolicyState переданного в обработчик объекта MediaPlayer. Значение VideoConstrictionReason — это причину политики, из-за которой было ограничено воспроизведение видео. Если значение не равно None, то этот пример записывает причину снижения качества для сбора телеметрических данных. В этом примере также показана установка скорости воспроизводимого объекта AdaptiveMediaSource до наименьшего значения в целях экономии трафика, так как видео ограничено и в любом случае не будет воспроизводится в высоком разрешении. Дополнительные сведения об использовании AdaptiveMediaSource см. в разделе Адаптивная потоковая передача.

private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
    MediaPlaybackSessionOutputDegradationPolicyState info = sender.PlaybackSession.GetOutputDegradationPolicyState();

    if (info.VideoConstrictionReason != MediaPlaybackSessionVideoConstrictionReason.None)
    {
        // Switch to lowest bitrate to save bandwidth
        adaptiveMediaSource.DesiredMaxBitrate = adaptiveMediaSource.AvailableBitrates[0];

        // Log the degradation reason or show a message to the user
        System.Diagnostics.Debug.WriteLine("Logging constriction reason: " + info.VideoConstrictionReason);
    }
}

Использование MediaPlayerSurface для отрисовки видео на поверхности Windows.UI.Composition

Начиная c Windows 10 версии 1607, можно использовать MediaPlayer для отрисовки видео на ICompositionSurface, что позволяет проигрывателю взаимодействовать с интерфейсами API в пространстве имен Windows.UI.Composition. Среда построения позволяет работать с изображениями на визуальном уровне между XAML и низкоуровневыми API графики DirectX. Это делает возможным реализацию таких сценариев, как отрисовка видео в любом элементе управления XAML. Подробные сведения об использовании API построения см. в разделе Визуальный уровень.

В следующем примере показано, как отрисовывать содержимое видеопроигрывателя на элементе управления Canvas. В этом примере используются следующие характерные для проигрывателя мультимедиа вызовы: SetSurfaceSize и GetSurface. SetSurfaceSize сообщает системе размер буфера, который необходимо выделить для отрисовки содержимого. GetSurface принимает Compositor в качестве аргумента и запрашивает экземпляр класса MediaPlayerSurface. Этот класс предоставляет доступ к объектам MediaPlayer и Compositor, используемым для создания поверхности, и предоставляет саму поверхность с помощью свойства CompositionSurface.

Остаток кода в этом примере создает объект SpriteVisual, на котором отрисовывается видео, и устанавливает размер в соответствии с размером элемента холста, который будет отображать визуальный элемент. Затем из MediaPlayerSurface создается CompositionBrush и назначается свойству Brush визуального элемента. Затем создается объект ContainerVisual, и SpriteVisual вставляется в верхней части его визуального дерева. Наконец, вызывается SetElementChildVisual для назначения визуального элемента контейнера объекту Canvas.

mediaPlayer.SetSurfaceSize(new Size(_compositionCanvas.ActualWidth, _compositionCanvas.ActualHeight));

var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
MediaPlayerSurface surface = mediaPlayer.GetSurface(compositor);

SpriteVisual spriteVisual = compositor.CreateSpriteVisual();
spriteVisual.Size =
    new System.Numerics.Vector2((float)_compositionCanvas.ActualWidth, (float)_compositionCanvas.ActualHeight);

CompositionBrush brush = compositor.CreateSurfaceBrush(surface.CompositionSurface);
spriteVisual.Brush = brush;

ContainerVisual container = compositor.CreateContainerVisual();
container.Children.InsertAtTop(spriteVisual);

ElementCompositionPreview.SetElementChildVisual(_compositionCanvas, container);

Используйте MediaTimelineController для синхронизации содержимого в нескольких проигрывателях.

Как отмечено ранее в этой статье, ваше приложение может иметь несколько активных объектов MediaPlayer одновременно. По умолчанию каждый создаваемый вами объект MediaPlayer работает независимо. В некоторых случаях, например при синхронизации дорожки комментария с видео, может потребоваться синхронизировать состояние, положение воспроизведения и скорость воспроизведения нескольких проигрывателей. Начиная c Windows 10 версии 1607, можно реализовать это поведение с помощью класса MediaTimelineController.

Реализация элементов управления воспроизведением

В следующем примере показано, как использовать MediaTimelineController для управления двумя экземплярами MediaPlayer. Во-первых, создается каждый экземпляр MediaPlayer, а в качестве Source задается файл мультимедиа. После этого создается новый MediaTimelineController. Для каждого MediaPlayer отключается соответствующий ему MediaPlaybackCommandManager путем установки значения false для свойства IsEnabled. Затем в свойстве TimelineController указывается контроллер временной шкалы.

MediaTimelineController _mediaTimelineController;
mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);


_mediaPlayer2 = new MediaPlayer();
_mediaPlayer2.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video_2.mkv"));
_mediaPlayerElement2.SetMediaPlayer(_mediaPlayer2);

_mediaTimelineController = new MediaTimelineController();

mediaPlayer.CommandManager.IsEnabled = false;
mediaPlayer.TimelineController = _mediaTimelineController;

_mediaPlayer2.CommandManager.IsEnabled = false;
_mediaPlayer2.TimelineController = _mediaTimelineController;

Внимание!MediaPlaybackCommandManager обеспечивает автоматическую интеграцию MediaPlayer и системных элементов управления транспортом мультимедиа (SMTC), однако эта автоматическая интеграция не может использоваться с проигрывателями мультимедиа, которыми управляет MediaTimelineController. Поэтому перед установкой контроллера временной шкалы проигрывателя необходимо отключить менеджер команд для проигрывателя мультимедиа. В противном случае возникнет исключение со следующим сообщением: "Присоединение контроллера временной шкалы мультимедиа заблокировано из-за текущего состояния объекта". Дополнительные сведения об интеграции проигрывателя мультимедиа с SMTC см. в статье Интеграция с системными элементами управления транспортировкой мультимедиа. Если вы используете MediaTimelineController, управление SMTC по-прежнему можно осуществлять вручную. Дополнительные сведения см. в разделе Ручное управление системными элементами управления воспроизведением мультимедиа.

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

private void PlayButton_Click(object sender, RoutedEventArgs e)
{
    _mediaTimelineController.Start();
}

Этот пример иллюстрирует приостановку и возобновление воспроизведения на всех подключенных проигрывателях.

private void PauseButton_Click(object sender, RoutedEventArgs e)
{
    if(_mediaTimelineController.State == MediaTimelineControllerState.Running)
    {
        _mediaTimelineController.Pause();
        _pauseButton.Content = "Resume";
    }
    else
    {
        _mediaTimelineController.Resume();
        _pauseButton.Content = "Pause";
    }
}

Чтобы выполнить перемотку вперед на всех подключенных проигрывателях, задайте скорость воспроизведения выше 1.

private void FastForwardButton_Click(object sender, RoutedEventArgs e)
{
    _mediaTimelineController.ClockRate = 2.0;
}

В следующем примере показано, как использовать элемент управления Slider, чтобы отображать текущую позицию воспроизведения для контроллера временной шкалы относительно длительности содержимого одного из подключенных проигрывателей мультимедиа. Сначала создается новый объект MediaSource и регистрируется обработчик для события OpenOperationCompleted источника мультимедиа.

var mediaSource = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaSource.OpenOperationCompleted += MediaSource_OpenOperationCompleted;
mediaPlayer.Source = mediaSource;
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

Обработчик OpenOperationCompleted используется как возможность для определения продолжительности содержимого источника мультимедиа. После определения длительности максимальное значение элемента управления Slider устанавливается равным общему числу секунд в элементе мультимедиа. Значение задается внутри вызова RunAsync, чтобы обеспечить выполнение в потоке пользовательского интерфейса.

TimeSpan _duration;
private async void MediaSource_OpenOperationCompleted(MediaSource sender, MediaSourceOpenOperationCompletedEventArgs args)
{
    _duration = sender.Duration.GetValueOrDefault();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        _positionSlider.Minimum = 0;
        _positionSlider.Maximum = _duration.TotalSeconds;
        _positionSlider.StepFrequency = 1;
    }); 
}

Затем регистрируется обработчик события PositionChanged контроллера временная шкала. Этот вызов производится системой периодически, примерно 4 раза в секунду.

_mediaTimelineController.PositionChanged += _mediaTimelineController_PositionChanged;

В обработчике PositionChanged значение ползунка обновляется для отражения текущей позиции контроллера временной шкалы.

private async void _mediaTimelineController_PositionChanged(MediaTimelineController sender, object args)
{
    if (_duration != TimeSpan.Zero)
    {
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            _positionSlider.Value = sender.Position.TotalSeconds / (float)_duration.TotalSeconds;
        });
    }
}

Смещение позиции воспроизведения относительно позиции временной шкалы

В некоторых случаях может потребоваться смещение позиции воспроизведения одного или нескольких проигрывателей, связанных с контроллером временной шкалы, относительно других проигрывателей. Этого можно достичь, задав свойство TimelineControllerPositionOffset объекта MediaPlayer, для которого требуется смещение. В следующем примере используются значения длительности содержимого двух проигрывателей мультимедиа для установки минимального и максимального значений двух ползунков управления в соответствии с длительностью элемента мультимедиа в отрицательном и положительном направлениях.

_timelineOffsetSlider1.Minimum = -1 * _duration.TotalSeconds;
_timelineOffsetSlider1.Maximum = _duration.TotalSeconds;
_timelineOffsetSlider1.StepFrequency = 1;

_timelineOffsetSlider2.Minimum = -1 * _duration2.TotalSeconds;
_timelineOffsetSlider2.Maximum = _duration2.TotalSeconds;
_timelineOffsetSlider2.StepFrequency = 1;

В событии ValueChanged для каждого ползунка свойству TimelineControllerPositionOffset каждого проигрывателя задается соответствующее значение.

private void _timelineOffsetSlider1_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    mediaPlayer.TimelineControllerPositionOffset = TimeSpan.FromSeconds(_timelineOffsetSlider1.Value);
}

private void _timelineOffsetSlider2_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    _mediaPlayer2.TimelineControllerPositionOffset = TimeSpan.FromSeconds(_timelineOffsetSlider2.Value);
}

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

Воспроизведение сферической проекции видео с помощью MediaPlayer

Начиная с Windows 10 версии 1703, MediaPlayer поддерживает равноугольную проекцию для сферического воспроизведения видео. Видео в сферической проекции ничем не отличается от обычного плоского видео в том плане, что MediaPlayer выполняет отрисовку видео, если поддерживается кодирование. Если сферическое видео содержит тег метаданных, который указывает, что для видео используется равноугольная проекция, MediaPlayer может выполнить отрисовку с учетом указанного поля зрения и ориентации представления. Это позволяет реализовывать такие сценарии, как воспроизведение видео в виртуальной реальности в видеошлеме или панорамирование сферического видео с помощью мыши или клавиатуры.

Чтобы воспроизвести сферическое видео, выполните инструкции по воспроизведению видео, приведенные ранее в этой статье. Еще одним шагом является регистрация обработчика для события MediaPlayer.MediaOpened . Это событие позволяет включать параметры воспроизведения сферического видео и управлять ими.

mediaPlayer = new MediaPlayer();
mediaPlayer.MediaOpened += _mediaPlayer_MediaOpened;
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video_spherical.mp4"));
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);
mediaPlayer.Play();

В обработчике MediaOpened в первую очередь необходимо проверить формат кадра недавно открытого мультимедийного элемента с помощью свойства PlaybackSession.SphericalVideoProjection.FrameFormat. Если значение равно SphericaVideoFrameFormat.Equirectangular, система может автоматически проецировать видео. Сначала задайте для свойства PlaybackSession.SphericalVideoProjection.IsEnabled значение true. Также можно настроить другие свойства, например ориентацию представления и поле зрения, которые проигрыватель мультимедиа будет использовать при проецировании видео. В этом примере для поля зрения задано значение 120 градусов с помощью свойства HorizontalFieldOfViewInDegrees.

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

private void _mediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
    if (sender.PlaybackSession.SphericalVideoProjection.FrameFormat == SphericalVideoFrameFormat.Equirectangular)
    {
        sender.PlaybackSession.SphericalVideoProjection.IsEnabled = true;
        sender.PlaybackSession.SphericalVideoProjection.HorizontalFieldOfViewInDegrees = 120;

    }
    else if (sender.PlaybackSession.SphericalVideoProjection.FrameFormat == SphericalVideoFrameFormat.Unsupported)
    {
        // If the spherical format is unsupported, you can use frame server mode to implement a custom projection
    }
}

В следующем примере кода показано, как изменить ориентацию представления для сферического видео с помощью клавиш со стрелками влево и вправо.

protected override void OnKeyDown(KeyRoutedEventArgs e)
{
    if (mediaPlayer.PlaybackSession.SphericalVideoProjection.FrameFormat != SphericalVideoFrameFormat.Equirectangular)
    {
        return;
    }

    switch (e.Key)
    {
        case Windows.System.VirtualKey.Right:
            mediaPlayer.PlaybackSession.SphericalVideoProjection.ViewOrientation *= Quaternion.CreateFromYawPitchRoll(.1f, 0, 0);
            break;
        case Windows.System.VirtualKey.Left:
            mediaPlayer.PlaybackSession.SphericalVideoProjection.ViewOrientation *= Quaternion.CreateFromYawPitchRoll(-.1f, 0, 0);
            break;
    }
}

Если ваше приложение поддерживает списки воспроизведения видео, в пользовательском интерфейсе можно определить элементы воспроизведения, содержащие сферическое видео. Списки воспроизведения мультимедиа подробно описаны в статье, Элементы, списки воспроизведения и звуковые дорожки мультимедиа. В следующем примере показано, как создать новый список воспроизведения, добавить элемент и зарегистрировать обработчик для события MediaPlaybackItem.VideoTracksChanged, которое возникает во время разрешения видеодорожек для элемента мультимедиа.

var playbackList = new MediaPlaybackList();
var item = new MediaPlaybackItem(MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/RIFTCOASTER HD_injected.mp4")));
item.VideoTracksChanged += Item_VideoTracksChanged;
playbackList.Items.Add(item);
mediaPlayer.Source = playbackList;

В обработчике события VideoTracksChanged получите свойства кодирования всех добавленных видеодорожек, вызвав метод VideoTrack.GetEncodingProperties. Если значение свойства кодирования SphericalVideoFrameFormat отличается от SphericaVideoFrameFormat.None, то видеодорожка содержит сферическое видео. При необходимости можно внести соответствующие изменения в пользовательский интерфейс.

private void Item_VideoTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    if (args.CollectionChange != CollectionChange.ItemInserted)
    {
        return;
    }
    foreach (var videoTrack in sender.VideoTracks)
    {
        if (videoTrack.GetEncodingProperties().SphericalVideoFrameFormat != SphericalVideoFrameFormat.None)
        {
            // Optionally indicate in the UI that this item contains spherical video
        }
    }
}

Использование MediaPlayer в режиме сервера кадров

Начиная с Windows 10 версии 1703, можно использовать MediaPlayer в режиме сервера кадров. В этом режиме MediaPlayer не выполняет автоматическую отрисовку кадров для соответствующего элемента MediaPlayerElement. Вместо этого ваше приложение копирует текущий кадр из MediaPlayer в объект, реализующий IDirect3DSurface. Основной сценарий, который реализует эта функция, заключается обработке видеокадров, предоставляемых MediaPlayer, с помощью построителей текстуры. Ваше приложение отвечает за отображение каждого кадра после обработки, например отображение кадра в элементе управления XAML Image.

В следующем примере инициализируется новый MediaPlayer и загружается видео. Затем регистрируется обработчик для события VideoFrameAvailable. Чтобы включить режим сервера кадров, задайте для свойства объекта MediaPlayerIsVideoFrameServerEnabled значение true. Наконец, начните воспроизведение мультимедиа, вызвав метод Play.

mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer.VideoFrameAvailable += mediaPlayer_VideoFrameAvailable;
mediaPlayer.IsVideoFrameServerEnabled = true;
mediaPlayer.Play();

В следующем примере показан обработчик для события VideoFrameAvailable, использующий Win2D для добавления простого эффекта размытия в каждый кадр видео. Затем обработанные кадры отображаются в элементе управления XAML Image.

Каждый раз, когда вызывается обработчик события VideoFrameAvailable, метод CopyFrameToVideoSurface используется для копирования содержимого кадра в IDirect3DSurface. Также можно использовать CopyFrameToStereoscopicVideoSurfaces для копирования трехмерного содержимого в две плоскости, с отдельной обработкой данных для левого и правого глаза. Чтобы получить объект, реализующий IDirect3DSurface , в этом примере создается объект SoftwareBitmap , а затем используется этот объект для создания Win2D CanvasBitmap, который реализует необходимый интерфейс. CanvasImageSource — это объект Win2D, который можно использовать в качестве источника для элемента управления Image, поэтому создается новый объект и указывается в качестве источника для элемента управления Image, в котором отображается содержимое. Далее создается CanvasDrawingSession. Win2D использует этот объект для отрисовки эффекта размытия.

После создания экземпляров всех необходимых объектов вызывается метод CopyFrameToVideoSurface, который копирует текущий кадр из MediaPlayer в CanvasBitmap. Затем создается объект Win2D GaussianBlurEffect, а CanvasBitmap указывается в качестве источника операции. Наконец, вызывается функция CanvasDrawingSession.DrawImage для рисования исходного изображения с примененным эффектом размытия. Это изображение передается в CanvasImageSource, связанный с элементом управления Image, и отрисовывается в пользовательском интерфейсе.

private async void mediaPlayer_VideoFrameAvailable(MediaPlayer sender, object args)
{
    CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        if(frameServerDest == null)
        {
            // FrameServerImage in this example is a XAML image control
            frameServerDest = new SoftwareBitmap(BitmapPixelFormat.Rgba8, (int)FrameServerImage.Width, (int)FrameServerImage.Height, BitmapAlphaMode.Ignore);
        }
        if(canvasImageSource == null)
        {
            canvasImageSource = new CanvasImageSource(canvasDevice, (int)FrameServerImage.Width, (int)FrameServerImage.Height, DisplayInformation.GetForCurrentView().LogicalDpi);//96); 
            FrameServerImage.Source = canvasImageSource;
        }

        using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromSoftwareBitmap(canvasDevice, frameServerDest))
        using (CanvasDrawingSession ds = canvasImageSource.CreateDrawingSession(Windows.UI.Colors.Black))
        {

            mediaPlayer.CopyFrameToVideoSurface(inputBitmap);

            var gaussianBlurEffect = new GaussianBlurEffect
            {
                Source = inputBitmap,
                BlurAmount = 5f,
                Optimization = EffectOptimization.Speed
            };

            ds.DrawImage(gaussianBlurEffect);

        }
    });
}

private void FrameServerSubtitlesButton_Click(object sender, RoutedEventArgs e)
{

    mediaPlayer = new MediaPlayer();
    var source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
    var item = new MediaPlaybackItem(source);

    item.TimedMetadataTracksChanged += Item_TimedMetadataTracksChanged;


    mediaPlayer.Source = item;
    mediaPlayer.VideoFrameAvailable += mediaPlayer_VideoFrameAvailable_Subtitle;
    mediaPlayer.IsVideoFrameServerEnabled = true;
    mediaPlayer.Play();

    mediaPlayer.IsMuted = true;

}

private void Item_TimedMetadataTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    if(sender.TimedMetadataTracks.Count > 0)
    {
        sender.TimedMetadataTracks.SetPresentationMode(0, TimedMetadataTrackPresentationMode.PlatformPresented);
    }
}

private async void mediaPlayer_VideoFrameAvailable_Subtitle(MediaPlayer sender, object args)
{
    CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        if (frameServerDest == null)
        {
            // FrameServerImage in this example is a XAML image control
            frameServerDest = new SoftwareBitmap(BitmapPixelFormat.Rgba8, (int)FrameServerImage.Width, (int)FrameServerImage.Height, BitmapAlphaMode.Ignore);
        }
        if (canvasImageSource == null)
        {
            canvasImageSource = new CanvasImageSource(canvasDevice, (int)FrameServerImage.Width, (int)FrameServerImage.Height, DisplayInformation.GetForCurrentView().LogicalDpi);//96); 
            FrameServerImage.Source = canvasImageSource;
        }

        using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromSoftwareBitmap(canvasDevice, frameServerDest))
        {
            using (CanvasDrawingSession ds = canvasImageSource.CreateDrawingSession(Windows.UI.Colors.Black))
            {

                mediaPlayer.CopyFrameToVideoSurface(inputBitmap);

                //Rect subtitleTargetRect = new Rect(0, 0, inputBitmap.Bounds.Width, inputBitmap.Bounds.Bottom * .1);
                Rect subtitleTargetRect = new Rect(0, 0, 100, 100);

                mediaPlayer.RenderSubtitlesToSurface(inputBitmap);//, subtitleTargetRect);

                //var gaussianBlurEffect = new GaussianBlurEffect
                //{
                //    Source = inputBitmap,
                //    BlurAmount = 5f,
                //    Optimization = EffectOptimization.Speed
                //};

                //ds.DrawImage(gaussianBlurEffect);

                ds.DrawImage(inputBitmap);
            }
        }
    });
}

Дополнительные сведения об использовании Win2D см. в репозитории Win2D в GitHub. Чтобы протестировать приведенный выше пример кода, необходимо добавить в проект пакет Win2D NuGet (см. инструкции ниже).

Добавление пакета Win2D NuGet в проект эффекта

  1. В обозревателе решений щелкните проект правой кнопкой мыши и выберите Управление пакетами NuGet.
  2. В верхней части окна выберите вкладку Обзор.
  3. В поле поиска введите Win2D.
  4. Выберите Win2D.uwp, а затем — Установить в области справа.
  5. В диалоговом окне Просмотр изменений отобразится назначенный для установки пакет. Нажмите кнопку ОК.
  6. Примите условия лицензии пакета.

Обнаружение изменения громкости звука системой и реагирование на это событие

Начиная с Windows 10 версии 1803, ваше приложение может определить момент, когда система уменьшает громкость звука или отключает его в MediaPlayer, который осуществляет воспроизведение содержимого. Например, система может снизить уровень звука, если включается будильник. Система отключает звук приложения, когда оно переходит в фоновый режим, если приложение не объявило возможность backgroundMediaPlayback в манифесте приложения. В классе AudioStateMonitor можно зарегистрировать обработчик события, отправляемого когда система изменяет громкость звукового потока. Обратитесь к свойству AudioStateMonitor объекта MediaPlayer и зарегистрируйте обработчик события SoundLevelChanged, чтобы получить уведомление про изменение системой громкости звука для MediaPlayer.

mediaPlayer.AudioStateMonitor.SoundLevelChanged += AudioStateMonitor_SoundLevelChanged;

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

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

bool isPodcast;
bool isPausedDueToAudioStateMonitor;

В обработчике события SoundLevelChanged проверьте свойство SoundLevel отправителя AudioStateMonitor и выясните каков новый уровень громкости. В этом примере проверяется, выставлен ли полный уровень громкости. Это означает, что система отменила выключение звука или уровень громкости был уменьшен, но идет воспроизведение не подкаста. Если любое из этих условий верно, а содержимое ранее было приостановлено программным способом, то воспроизведение возобновляется. Если звук был отключен, или если воспроизводится подкаст, и задана низкий уровень громкости, то воспроизведение приостанавливается и в переменной указывается, что пауза произведена программным способом.

private void AudioStateMonitor_SoundLevelChanged(Windows.Media.Audio.AudioStateMonitor sender, object args)
{
    if ((sender.SoundLevel == SoundLevel.Full) || (sender.SoundLevel == SoundLevel.Low && !isPodcast))
    {
        if (isPausedDueToAudioStateMonitor)
        {
            mediaPlayer.Play();
            isPausedDueToAudioStateMonitor = false;
        }
    }
    else if ((sender.SoundLevel == SoundLevel.Muted) ||
         (sender.SoundLevel == SoundLevel.Low && isPodcast))
    {
        if (mediaPlayer.PlaybackSession.PlaybackState == MediaPlaybackState.Playing)
        {
            mediaPlayer.Pause();
            isPausedDueToAudioStateMonitor = true;
        }
    }

}

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

private void PauseButton_User_Click(object sender, RoutedEventArgs e)
{
    if (isPausedDueToAudioStateMonitor)
    {
        isPausedDueToAudioStateMonitor = false;
    }
    else
    {
        mediaPlayer.Pause();
    }
}

public void PlayButton_User_Click()
{
    isPausedDueToAudioStateMonitor = false;
    mediaPlayer.Play();
}