Поделиться через


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

В этой статье показано, как воспроизводить носитель в приложении универсальной версии Windows с помощью класса MediaPlayer . В Windows 10 версии 1607 значительные улучшения были улучшены в API воспроизведения мультимедиа, включая упрощенную однопроцессную структуру фонового звука, автоматическую интеграцию с system Media Transport Controls (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 по умолчанию не начинает воспроизведение автоматически. Вы можете начать воспроизведение путем вызова воспроизведения, установив для свойства AutoPlay значение true или ожидая, чтобы пользователь начал воспроизведение с помощью встроенных элементов управления мультимедиа.

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

Когда приложение выполняется с помощью MediaPlayer, необходимо вызвать метод Close (проецируемый для удаления в 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 будут заданы соответствующие свойства в базовом mediaPlayer. Вы можете использовать базовый MediaPlayer напрямую вместо использования свойств MediaPlayerElement. Помните, что использование MediaPlayer непосредственно, где эквивалентное свойство MediaPlayerElement может использоваться в противном случае может привести к непредвиденному поведению. Это связано с тем, что MediaPlayerElement не знает обо всем, что происходит с его базовым MediaPlayer. Например, если задать источник непосредственно в MediaPlayer, свойство MediaPlayerElement Source не будет отражать изменения. По этой причине необходимо быть последовательным в использовании свойств MediaPlayerElement или непосредственно с помощью базового MediaPlayer.

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

Примечание.

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

MediaPlayer отсоединяется от MediaPlayerElement при уничтожении MediaPlayerElement или при установке нового mediaPlayer с помощью SetMediaPlayer. При отключении MediaPlayerElement обрабатывает базовый MediaPlayer по-разному в зависимости от того, был ли он создан MediaPlayerElement или установлен с помощью SetMediaPlayer.

Если MediaPlayer был создан MediaPlayerElement, он будет правильно закрыть MediaPlayer для вас. Если mediaPlayer был установлен в MediaPlayerElement с помощью SetMediaPlayer, вы несете ответственность за обеспечение правильного закрытия MediaPlayer. Не удалось сделать это, может привести к неустранимой ошибке воспроизведения в MediaPlayer. В следующем фрагменте кода показано, как правильно отсоединить и закрыть код.

// Get a reference to the current media source.
IMediaPlaybackSource _mediaPlayerElement = _mediaPlayerElement.Source;

// Pause playback if able.
if (mediaPlayer.PlaybackSession.CanPause)
{
    mediaPlayer.Pause();
}

// Disconnect the MediaPlayer from its source. This can be done by setting 
// the MediaPlayerElement Source property to null or by directly setting the
// source to null on the underlying MediaPlayer.
_mediaPlayerElement.Source = null;

// Disconnect the MediaPlayer from MediaPlayerElement.
_mediaPlayerElement.SetMediaPlayer(null);

// Dispose of the MediaPlayer or Source if they're no longer needed.
if (source is MediaSource mediaSource)
{
    mediaSource.Dispose();
}

mediaPlayer.Dispose();

Распространенные задачи MediaPlayer

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

Настройка категории звука

Задайте для свойства AudioCategory mediaPlayer одно из значений перечисления MediaPlayerAudioCategory, чтобы система знала, какой носитель вы играете. Игры должны классифицировать свои музыкальные потоки как GameMedia , чтобы игра музыка автоматически отключалось, если другое приложение играет музыку в фоновом режиме. Музыкальные или видео-приложения должны классифицировать их потоки как медиа или фильм , чтобы они были приоритетом над потоками GameMedia .

mediaPlayer.AudioCategory = MediaPlayerAudioCategory.Media;

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

По умолчанию выходные данные звука из MediaPlayer направляются в конечную точку звука по умолчанию для системы, но можно указать определенную конечную точку звука, которую mediaPlayer должен использовать для вывода. В приведенном ниже примере MediaDevice.GetAudioRenderSelector возвращает строку, которая однозначно идентифицирует категорию отрисовки звука устройств. Затем вызывается метод DeviceInformation FindAllAsync, чтобы получить список всех доступных устройств выбранного типа. Вы можете программно определить, какое устройство вы хотите использовать или добавить возвращенные устройства в 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 секунд вперед в содержимом. Во-первых, объект MediaPlaybackSession для проигрывателя извлекается со свойствомPlaySession. Далее для свойства Position задано текущее положение воспроизведения плюс 10 секунд.

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

В следующем примере показана кнопка переключателя для переключения между обычной скоростью воспроизведения и скоростью 2X, задав свойство 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, описанный в предыдущем разделе, содержит два события для обнаружения начала и окончания буферизации, буферингаStarted и 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 0 является верхней левой рукой рамки, а 1,1 указывает полную ширину и высоту кадра. Таким образом, например, чтобы задать прямоугольник масштабирования таким образом, чтобы отрисовка видео отображалась в правом верхнем углу, можно указать прямоугольник (.5,0,5,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;
}

Примечание. В этом разделе описывается ввод сенсорного ввода. Touchpad отправляет события указателя и не отправляет события манипуляции.

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

В некоторых случаях система может снизить воспроизведение элемента мультимедиа, например сокращение разрешения (сужения), на основе политики, а не проблемы с производительностью. Например, видео может быть понижено системой, если он воспроизводится с помощью драйвера видео без знака. Вы можете вызвать 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

Начиная с 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 , для которого видео отрисовывается и задает размер элемента холста, отображающего визуальный элемент. Далее создается CompositionBrush из MediaPlayerSurface и назначается свойству Brush визуального элемента. Затем создается контейнерVisual, а spriteVisual вставляется в верхней части визуального дерева. Наконец, Метод SetElementChildVisual вызывается для назначения визуального элемента контейнера холсту.

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 работает независимо. В некоторых сценариях, таких как синхронизация отслеживания комментариев с видео, может потребоваться синхронизировать состояние проигрывателя, положение воспроизведения и скорость воспроизведения нескольких игроков. Начиная с Windows 10 версии 1607, вы можете реализовать это поведение с помощью класса MediaTimelineController.

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

В следующем примере показано, как использовать MediaTimelineController для управления двумя экземплярами MediaPlayer. Во-первых , каждый экземпляр MediaPlayer создается, и источник устанавливается в файл мультимедиа. Далее создается новый MediaTimelineController . Для каждого MediaPlayer mediaPlaybackCommandManager, связанного с каждым проигрывателем, отключен, задав свойству IsEnabled значение false. А затем для свойства 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 к одному или нескольким проигрывателям мультимедиа можно управлять состоянием воспроизведения с помощью методов, предоставляемых контроллером. В следующем примере вызывается запуск воспроизведения всех связанных проигрывателей мультимедиа в начале носителя.

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 используется как возможность обнаружить длительность исходного содержимого носителя. После определения длительности максимальное значение элемента управления Ползунка устанавливается на общее количество секунд элемента мультимедиа. Значение задается внутри вызова 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 будет отображать видео до тех пор, пока кодирование видео поддерживается. Для сферического видео, содержащего тег метаданных, указывающий, что в видео используется equirectangular проекция, 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 .

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

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.

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

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".

Всякий раз, когда вызывается обработчик VideoFrameAvailable, метод CopyFrameToVideoSurface используется для копирования содержимого кадра в IDirect3DSurface. Вы также можете использовать CopyFrameToStereoscopicVideoSurfaces для копирования трехмерного содержимого в две поверхности для обработки левого и правого содержимого глаз отдельно. Чтобы получить объект, реализующий IDirect3DSurface, этот пример создает SoftwareBitmap, а затем использует этот объект для создания Объекта Win2D CanvasBitmap, реализующего необходимый интерфейс. CanvasImageSource — это объект Win2D, который можно использовать в качестве источника элемента управления "Изображение", поэтому создается и устанавливается в качестве источника для изображения, в котором будет отображаться содержимое. Затем создается 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. Чтобы попробовать пример кода, показанный выше, необходимо добавить пакет NuGet Win2D в проект, выполнив следующие инструкции.

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

  1. В обозревателе решений щелкните проект правой кнопкой мыши и выберите Управление пакетами NuGet.
  2. В верхней части окна выберите вкладку "Обзор ".
  3. В поле поиска введите Win2D.
  4. Выберите Win2D.uwp и выберите " Установить " в правой области.
  5. В диалоговом окне "Изменения проверки" отображается установленный пакет. Щелкните OK.
  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, чтобы определить новый уровень звука. В этом примере проверяется, является ли новый уровень звука полным томом, то есть система перестала отключать или оттягивать громкость, или если уровень звука был снижен, но воспроизводит содержимое, отличное от подкаста. Если одно из этих значений имеет значение true, и содержимое было ранее приостановлено программным способом, воспроизведение возобновляется. Если новый уровень звука отключен или если текущее содержимое является подкастом, а уровень звука низкий, воспроизведение приостановлено, а переменная настроена для отслеживания того, что пауза была инициирована программным способом.

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();
}