Адаптивная потоковая передача

В этой статье описано, как добавить функцию воспроизведения мультимедийного содержимого адаптивной потоковой передачи в приложения универсальной платформы Windows (UWP). Эта функция поддерживает воспроизведение содержимого Http Live Streaming (HLS) и Dynamic Adaptive Streaming over HTTP (DASH).

Начиная с Windows 10 версии 1803 Smooth Streaming поддерживается в AdaptiveMediaSource. Обратите внимание, что для потоковой передачи Смуос поддерживаются только кодеки H264 Single bitrate и WVC1. Другие типы манифеста не имеют этого ограничения.

Список поддерживаемых тегов протокола HLS см. в разделе Поддержка тегов HLS.

Список поддерживаемых профилей DASH см. в разделе Поддержка профилей DASH.

Примечание

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

Простая адаптивная потоковая передача с использованием MediaPlayer и MediaPlayerElement

Для потокового воспроизведения адаптивного мультимедиа в приложении UWP создайте объект Uri, указывающий на файл манифеста DASH или HLS. Создайте экземпляр класса MediaPlayer. Вызовите MediaSource.CreateFromUri, чтобы создать объект MediaSource, и задайте его в качестве значения свойства Source объекта MediaPlayer. Вызовите Play, чтобы начать воспроизведение мультимедиа-содержимого.

MediaPlayer _mediaPlayer;
System.Uri manifestUri = new Uri("http://amssamples.streaming.mediaservices.windows.net/49b57c87-f5f3-48b3-ba22-c55cfdffa9cb/Sintel.ism/manifest(format=m3u8-aapl)");
_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = MediaSource.CreateFromUri(manifestUri);
_mediaPlayer.Play();

Приведенный выше пример воспроизводит аудио из мультимедиа-содержимого, но не отображает автоматически содержимое в пользовательском интерфейсе. Большинству приложений, которые воспроизводят видео, требуется отображать содержимое на странице XAML. Для этого добавьте элемент управления MediaPlayerElement на страницу XAML.

<MediaPlayerElement x:Name="mediaPlayerElement" HorizontalAlignment="Stretch" AreTransportControlsEnabled="True"/>

Вызовите метод MediaSource.CreateFromUri, чтобы создать MediaSource на основе URI файла манифеста DASH или HLS. Затем задайте значение свойства Source класса MediaPlayerElement. MediaPlayerElement автоматически создаст объект MediaPlayer для содержимого. Вы можете вызвать метод Play объекта MediaPlayer для воспроизведения содержимого.

System.Uri manifestUri = new Uri("http://amssamples.streaming.mediaservices.windows.net/49b57c87-f5f3-48b3-ba22-c55cfdffa9cb/Sintel.ism/manifest(format=m3u8-aapl)");
mediaPlayerElement.Source = MediaSource.CreateFromUri(manifestUri);
mediaPlayerElement.MediaPlayer.Play();

Примечание

Начиная с Windows 10 версии 1607 рекомендуется использовать класс MediaPlayer, чтобы воспроизводить элементы мультимедиа. MediaPlayerElement — это облегченный элемент управления XAML, который используется для отображения содержимого MediaPlayer на странице XAML. Элемент управления MediaElement продолжает поддерживаться для обратной совместимости. Дополнительные сведения об использовании MediaPlayer и MediaPlayerElement для воспроизведения мультимедиа см. в разделе Воспроизведение аудио и видео с помощью MediaPlayer. Сведения об использовании MediaSource и связанных API для работы с мультимедиа см. в разделе Элементы, списки воспроизведения и звуковые дорожки мультимедиа.

Адаптивная потоковая передача с помощью AdaptiveMediaSource

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

Интерфейсы API адаптивной потоковой передачи можно найти в пространстве имен Windows.Media.Streaming.Adaptive. В примерах в этой статьи используются API-интерфейсы из следующих пространств имен.

using Windows.Media.Streaming.Adaptive;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.Media.Playback;
using Windows.Media.Core;

Инициализация объекта AdaptiveMediaSource с помощью URI.

Инициализируйте объект AdaptiveMediaSource, воспользовавшись URI файла манифеста адаптивной потоковой передачи и вызвав метод CreateFromUriAsync. Значение AdaptiveMediaSourceCreationStatus, возвращенное этим методом, позволяет узнать, был ли источник мультимедиа создан успешно. Если был, можно задать объект в качестве источника потока для MediaPlayer путем создания объекта MediaSource, вызвав метод MediaSource.CreateFromAdaptiveMediaSource и затем назначив его свойству Source проигрывателя мультимедиа. В этом примере свойство AvailableBitrates запрашивается, чтобы определить максимальную поддерживаемую скорость передачи для данного потока, а затем установить это значение в качестве начальной скорости. В этом примере также показана регистрация нескольких событий AdaptiveMediaSource, которые описываются далее в этой статье.

async private void InitializeAdaptiveMediaSource(System.Uri uri)
{
    AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(uri);

    if (result.Status == AdaptiveMediaSourceCreationStatus.Success)
    {
        ams = result.MediaSource;
        mediaPlayerElement.SetMediaPlayer(new MediaPlayer());
        mediaPlayerElement.MediaPlayer.Source = MediaSource.CreateFromAdaptiveMediaSource(ams);
        mediaPlayerElement.MediaPlayer.Play();


        ams.InitialBitrate = ams.AvailableBitrates.Max<uint>();

        //Register for download requests
        ams.DownloadRequested += DownloadRequested;

        //Register for download failure and completion events
        ams.DownloadCompleted += DownloadCompleted;
        ams.DownloadFailed += DownloadFailed;

        //Register for bitrate change events
        ams.DownloadBitrateChanged += DownloadBitrateChanged;
        ams.PlaybackBitrateChanged += PlaybackBitrateChanged;

        //Register for diagnostic event
        ams.Diagnostics.DiagnosticAvailable += DiagnosticAvailable;
    }
    else
    {
        // Handle failure to create the adaptive media source
        MyLogMessageFunction($"Adaptive source creation failed: {uri} - {result.ExtendedError}");
    }
}

Инициализация объекта AdaptiveMediaSource с помощью HttpClient

Если необходимо задать настраиваемые заголовки HTTP для получения файла манифеста, можно создать объект HttpClient, задать необходимые заголовки, а затем передать этот объект в перегрузку CreateFromUriAsync.

httpClient = new Windows.Web.Http.HttpClient();
httpClient.DefaultRequestHeaders.TryAppendWithoutValidation("X-CustomHeader", "This is a custom header");
AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(manifestUri, httpClient);

Событие DownloadRequested возникает, когда система собирается получить ресурс от сервера. Класс AdaptiveMediaSourceDownloadRequestedEventArgs, переданный в обработчик событий, представляет свойства, которые предоставляют запрошенные сведения о ресурсе (например, тип и URI ресурса).

Изменение свойства запроса ресурса с помощью события DownloadRequested

Обработчик событий DownloadRequested можно использовать для изменения запроса ресурса путем обновления свойств объекта AdaptiveMediaSourceDownloadResult, предоставленных аргументами события. В приведенном ниже примере URI, из которого будет получен ресурс, изменяется путем обновления свойств ResourceUri объекта результата. Можно также переписать смещение байтового диапазона и длину сегментов мультимедиа или, как показано в примере ниже, изменить URI ресурса, чтобы загрузить полный ресурс и задать для смещения байтового диапазона и длины значение null.

Можно переопределить содержимое запрошенного ресурса, задав свойства Buffer или InputStream объекта результата. В приведенном ниже примере содержимое ресурса манифеста заменено путем присвоения значения свойству Buffer. Обратите внимание, что при обновлении запроса ресурса данными, которые получены асинхронно (например, данные, извлеченные с удаленного сервера или в результате асинхронной проверки подлинности пользователя), следует вызвать метод AdaptiveMediaSourceDownloadRequestedEventArgs.GetDeferral, чтобы получить отсрочку, а затем вызвать метод Complete, когда операция завершится, чтобы сообщить системе, что операция запроса на загрузку может продолжаться.

    private async void DownloadRequested(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadRequestedEventArgs args)
    {

        // rewrite key URIs to replace http:// with https://
        if (args.ResourceType == AdaptiveMediaSourceResourceType.Key)
        {
            string originalUri = args.ResourceUri.ToString();
            string secureUri = originalUri.Replace("http:", "https:");

            // override the URI by setting property on the result sub object
            args.Result.ResourceUri = new Uri(secureUri);
        }

        if (args.ResourceType == AdaptiveMediaSourceResourceType.Manifest)
        {
            AdaptiveMediaSourceDownloadRequestedDeferral deferral = args.GetDeferral();
            args.Result.Buffer = await CreateMyCustomManifest(args.ResourceUri);
            deferral.Complete();
        }

        if (args.ResourceType == AdaptiveMediaSourceResourceType.MediaSegment)
        {
            var resourceUri = args.ResourceUri.ToString() + "?range=" + 
                args.ResourceByteRangeOffset + "-" + (args.ResourceByteRangeLength - 1);

            // override the URI by setting a property on the result sub object
            args.Result.ResourceUri = new Uri(resourceUri);

            // clear the byte range properties on the result sub object
            args.Result.ResourceByteRangeOffset = null;
            args.Result.ResourceByteRangeLength = null;
        }
    }

Использование событий скорости передачи для управления и реагирования на изменение скорости

Объект AdaptiveMediaSource предоставляет события, которые позволяют реагировать, если скорость скачивания или воспроизведения меняется. В этом примере текущие скорости просто обновляются в пользовательском интерфейсе. Обратите внимание: можно изменять коэффициенты, определяющие, когда система изменяет скорость адаптивного потока. Дополнительные сведения см. в описании свойства AdvancedSettings.

private async void DownloadBitrateChanged(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadBitrateChangedEventArgs args)
{
    await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
    {
        txtDownloadBitrate.Text = args.NewValue.ToString();
    }));
}

private async void PlaybackBitrateChanged(AdaptiveMediaSource sender, AdaptiveMediaSourcePlaybackBitrateChangedEventArgs args)
{
    await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
    {
        txtPlaybackBitrate.Text = args.NewValue.ToString();
    }));
}

Обработка событий завершения скачивания и сбоев

Объект AdaptiveMediaSource вызывает событие DownloadFailed, если происходит сбой скачивания запрашиваемого ресурса. Это событие можно использовать для обновления пользовательского интерфейса в ответ на сбой. Можно также использовать событие для записи статистических данных об операции скачивания и сбое.

Объект AdaptiveMediaSourceDownloadFailedEventArgs, переданный в обработчик событий, содержит метаданные о сбое скачивания ресурса, такие как тип ресурса, URI ресурса и положение в потоке, где произошел сбой. Объект RequestId получает созданный системой уникальный идентификатор для запроса, который можно использовать, чтобы соотнести сведения об отдельном запросе в нескольких событиях.

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

private void DownloadFailed(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadFailedEventArgs args)
{
    var statistics = args.Statistics;

    MyLogMessageFunction("download failed for: " + args.ResourceType + 
     " - " + args.ResourceUri +
     " – Error:" + args.ExtendedError.HResult +
     " - RequestId" + args.RequestId + 
     " – Position:" + args.Position +
     " - Duration:" + args.ResourceDuration +
     " - ContentType:" + args.ResourceContentType +
     " - TimeToHeadersReceived:" + statistics.TimeToHeadersReceived + 
     " - TimeToFirstByteReceived:" + statistics.TimeToFirstByteReceived + 
     " - TimeToLastByteReceived:" + statistics.TimeToLastByteReceived +
     " - ContentBytesReceivedCount:" + statistics.ContentBytesReceivedCount);

}

Событие DownloadCompleted происходит после завершения скачивания ресурсов и предоставляет аналогичные данные для события DownloadFailed. Команда RequestId также предоставляется для сопоставления событий для одного запроса. Кроме того, предоставляется объект AdaptiveMediaSourceDownloadStatistics, чтобы была возможность вести журнал статистики скачивания.

private void DownloadCompleted(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadCompletedEventArgs args)
{
    var statistics = args.Statistics;

    MyLogMessageFunction("download completed for: " + args.ResourceType + " - " +
     args.ResourceUri +
     " – RequestId:" + args.RequestId +
     " – Position:" + args.Position +
     " - Duration:" + args.ResourceDuration +
     " - ContentType:" + args.ResourceContentType +
     " - TimeToHeadersReceived:" + statistics.TimeToHeadersReceived + 
     " - TimeToFirstByteReceived:" + statistics.TimeToFirstByteReceived + 
     " - TimeToLastByteReceived:" + statistics.TimeToLastByteReceived +
     " - ContentBytesReceivedCount:" + statistics.ContentBytesReceivedCount);

}

Сбор телеметрических данных адаптивной потоковой передачи с помощью AdaptiveMediaSourceDiagnostics

AdaptiveMediaSource предоставляет свойство Diagnostics, которое возвращает объект AdaptiveMediaSourceDiagnostics. Этот объект используется для регистрации события DiagnosticAvailable. Это событие предназначено для использования для сбора телеметрии и не должно использоваться для изменения поведения приложения во время выполнения. Это событие диагностики создается по разным причинам. Смотрите свойства DiagnosticType объекта AdaptiveMediaSourceDiagnosticAvailableEventArgs, переданного с событием, чтобы определить причину возникновения последнего. К возможным причинам относятся ошибки доступа к запрашиваемому ресурсу и ошибки анализа файла манифеста потоковой передачи. Список ситуаций, которые могут инициировать событие диагностики, см. в разделе AdaptiveMediaSourceDiagnosticType. Как и аргументы для других событий адаптивной потоковой передачи, AdaptiveMediaSourceDiagnosticAvailableEventArgs предоставляет свойство RequestId для сопоставления сведений о запросе между различными событиями.

private void DiagnosticAvailable(AdaptiveMediaSourceDiagnostics sender, AdaptiveMediaSourceDiagnosticAvailableEventArgs args)
{
    MySendTelemetryFunction(args.RequestId, args.Position,
                            args.DiagnosticType, args.SegmentId,
                            args.ResourceType, args.ResourceUri,
                            args.ResourceDuration, args.ResourceContentType,
                            args.ResourceByteRangeOffset,
                            args.ResourceByteRangeLength, 
                            args.Bitrate,
                            args.ExtendedError);

}

Откладывание привязки содержимого адаптивной потоковой передачи для элементов в списке воспроизведения с помощью MediaBinder

Класс MediaBinder позволяет откладывать привязку мультимедийного содержимого в MediaPlaybackList. Начиная с Windows 10 версии 1703, можно предоставить AdaptiveMediaSource как связанное содержимое. Процесс отложенной привязки источника адаптивных мультимедиа в значительной степени аналогичен привязке других типов мультимедиа, описанной в разделе Элементы, списки воспроизведения и звуковые дорожки мультимедиа.

Создайте экземпляр MediaBinder, установите определяемую приложением строку Token, чтобы определить содержимое для привязки, и зарегистрируйте для события Binding. Создайте MediaSource из Binder, вызвав метод MediaSource.CreateFromMediaBinder. Затем создайте MediaPlaybackItem из MediaSource и добавьте его в список воспроизведения.

_mediaPlaybackList = new MediaPlaybackList();

var binder = new MediaBinder();
binder.Token = "MyBindingToken1";
binder.Binding += Binder_Binding;
_mediaPlaybackList.Items.Add(new MediaPlaybackItem(MediaSource.CreateFromMediaBinder(binder)));

binder = new MediaBinder();
binder.Token = "MyBindingToken2";
binder.Binding += Binder_Binding;
_mediaPlaybackList.Items.Add(new MediaPlaybackItem(MediaSource.CreateFromMediaBinder(binder)));

_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = _mediaPlaybackList;
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);

В обработчике событий Binding используйте строку токена для определения содержимого для привязки, а затем создайте источник адаптивных мультимедиа , вызвав одну из перегрузок CreateFromStreamAsync или CreateFromUriAsync. Поскольку это асинхронные методы, необходимо сначала вызвать метод MediaBindingEventArgs.GetDeferral, чтобы указать системе ожидать операции завершения перед продолжением. Задайте источник адаптивных мультимедиа как связанного содержимого, вызвав метод SetAdaptiveMediaSource. Наконец вызовите Deferral.Complete после завершения операции, чтобы указать системе продолжать.

private async void Binder_Binding_AdaptiveMediaSource(MediaBinder sender, MediaBindingEventArgs args)
{
    var deferral = args.GetDeferral();

    var contentUri = new Uri($"http://contoso.com/media/{args.MediaBinder.Token}");
    AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(contentUri);

    if (result.MediaSource != null)
    {
        args.SetAdaptiveMediaSource(result.MediaSource);
    }
    args.SetUri(contentUri);

    deferral.Complete();
}

Если требуется зарегистрировать обработчики событий для источника привязанных адаптивных мультимедиа, это можно сделать в обработчике для события CurrentItemChanged из списка MediaPlaybackList. Свойство CurrentMediaPlaybackItemChangedEventArgs.NewItem содержит новый воспроизводящийся в текущий момент элемент MediaPlaybackItem в списке. Получите экземпляр AdaptiveMediaSource, представляющий новый элемент, обратившись к свойству Source элемента MediaPlaybackItem, а затем к свойству AdaptiveMediaSource источника мультимедиа. Это свойство будет иметь значение null, если новый элемент воспроизведения не является AdaptiveMediaSource, поэтому необходимо проверить на значение null, прежде чем пытаться зарегистрировать обработчики для любого из событий объектов.

private void AMSMediaPlaybackList_CurrentItemChanged(MediaPlaybackList sender, CurrentMediaPlaybackItemChangedEventArgs args)
{
    if (!(args.NewItem is null))
    {
        var ams = args.NewItem.Source.AdaptiveMediaSource;
        if (!(ams is null))
        {
            ams.PlaybackBitrateChanged += Ams_PlaybackBitrateChanged;
        }
    }
}