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

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

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

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

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

Примечание.

Код в этой статье был адаптирован из примера адаптивной потоковой передачи UWP.

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

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

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

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

Создайте экземпляр MediaBinder, задайте строку маркера, определяемую приложением, чтобы определить содержимое для привязки и зарегистрировать событие Привязки. Создайте 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);

В обработчике событий привязки используйте строку маркера, чтобы определить содержимое, которое необходимо привязать, а затем создать адаптивный источник мультимедиа путем вызова одной из перегрузки 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;
        }
    }
}