自适应流式处理

本文介绍如何将自适应流多媒体内容的播放添加到通用 Windows 平台 (UWP) 应用。 此功能支持 HTTP Live Streaming (HLS) 和基于 HTTP 的动态流式处理 (DASH) 内容的播放。

从 Windows 10 版本 1803 开始,AdaptiveMediaSource 支持平滑流式处理。 请注意,对于平滑流式处理,仅支持 H264 和 WVC1 编解码器。 其他清单类型没有此限制。

有关受支持的 HLS 协议标记的列表,请参阅 HLS 标记支持

有关受支持的 DASH 配置文件的列表,请参阅 DASH 配置文件支持

注意

本文中的代码改编自 UWP 自适应流式处理示例

使用 MediaPlayer 和 MediaPlayerElement 的简单自适应流式处理

若要在 UWP 应用中播放自适应流式处理媒体,请创建一个指向 DASH 或 HLS 清单文件的 Uri 对象。 创建 MediaPlayer 类的实例。 调用 MediaSource.CreateFromUri 以创建一个新的 MediaSource 对象,然后将该对象设置为 MediaPlayerSource 属性。 调用 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();

上述示例将播放媒体内容的音频,但它不会在 UI 中自动呈现该内容。 大多数播放视频内容的应用都将希望在 XAML 页面中呈现该内容。 为此,请在 XAML 页面添加一个 MediaPlayerElement 控件。

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

调用 MediaSource.CreateFromUri 以从 DASH 或 HLS 清单文件的 URI 创建一个 MediaSource。 然后设置 MediaPlayerElementSource 属性。 MediaPlayerElement 将为该内容自动创建一个新的 MediaPlayer 对象。 可在 MediaPlayer 上调用 Play 以开始播放内容。

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 控件,用于在 XAML 页面中呈现 MediaPlayer 的内容。 为了实现向后兼容性,继续支持 MediaElement 控件。 有关使用 MediaPlayerMediaPlayerElement 播放媒体内容的详细信息,请查阅使用 MediaPlayer 播放音频和视频。 有关使用 MediaSource 和相关 API 处理媒体内容的信息,请参阅媒体项、播放列表和轨

使用 AdaptiveMediaSource 的自适应流式处理

如果你的应用需要更多高级自适应流式处理功能(如提供自定义 HTTP 标头、监视当前下载和播放比特率或调整用于确定系统何时切换自适应流的比特率的时间),请使用 AdaptiveMediaSource 对象。

Windows.Media.Streaming.Adaptive 命名空间中可以找到自适应流式处理 API。 本文中的示例使用以下命名空间中的 API。

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

通过 URI 初始化 AdaptiveMediaSource。

通过调用 CreateFromUriAsync 使用自适应流式处理清单文件的 URI 初始化 AdaptiveMediaSource。 从此方法返回的 AdaptiveMediaSourceCreationStatus 值可使你知道媒体源是否已成功创建。 如果成功,则通过调用 MediaSource.CreateFromAdaptiveMediaSource 来创建 MediaSource 对象,然后将该对象分配给媒体播放器的 Source 属性,你可以将该对象设置为你的 MediaPlayer 的流源。 在此示例中,将查询 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}");
    }
}

使用 HttpClient 初始化 AdaptiveMediaSource

如果你需要设置自定义 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 事件修改资源请求属性

你可以通过更新事件参数所提供的 AdaptiveMediaSourceDownloadResult 对象的属性来使用 DownloadRequested 事件处理程序修改资源请求。 在以下示例中,将从中检索资源的 URI 通过更新结果对象的 ResourceUri 属性来进行修改。 还可以重写媒体段的字节范围偏移和长度,或者如以下示例所示,更改资源 URI 以下载完整资源并将字节范围偏移和长度设置为 null。

通过设置结果对象的 BufferInputStream 属性,你可以替代所请求的资源的内容。 在以下示例中,清单资源的内容通过设置 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 对象提供可使你在下载或播放比特率发生更改时作出反应的事件。 在此示例中,当前比特率仅在 UI 中更新。 请注意,你可以修改用于确定系统何时切换自适应流的比特率的比率。 有关详细信息,请参阅 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 事件。 可以使用此事件更新 UI 以响应失败。 还可以使用此事件记录有关下载操作和失败的统计信息。

传入事件处理程序中的 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);

}

当资源下载完成并提供与 DownloadFailed 事件类似的数据时,会出现 DownloadCompleted 事件。 会再次提供 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 事件进行注册。 此事件旨在用于遥测收集,而不应用于在运行时修改应用行为。 多种不同原因都会引发此诊断事件。 检查传入到事件中的 AdaptiveMediaSourceDiagnosticAvailableEventArgs 对象的 DiagnosticType 属性,以确定引发事件的原因。 可能原因包括访问请求的资源时出错以及分析流式处理清单文件时出错。 有关可以触发诊断事件的情况的列表,请参阅 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.CreateFromMediaBinder,从 Binder 创建 MediaSource。 然后,通过 MediaSource 创建一个 MediaPlaybackItem 并将它添加到播放列表。

_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 事件处理程序中,使用标记字符串标识要绑定的内容,然后通过调用 CreateFromStreamAsyncCreateFromUriAsync 的一个重载来创建自适应媒体源。 由于这些是异步方法,因此应首先调用 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();
}

如果要为绑定的自适应媒体源注册事件处理程序,可在 MediaPlaybackListCurrentItemChanged 事件的处理程序中执行此操作。 CurrentMediaPlaybackItemChangedEventArgs.NewItem 属性包含列表中当前正在播放的新 MediaPlaybackItem。 通过访问 MediaPlaybackItemSource 属性,然后访问媒体源的 AdaptiveMediaSource 属性,来获取表示新项的 AdaptiveMediaSource 实例。 如果新的播放项不是 AdaptiveMediaSource,则此属性为 NULL,因此应在尝试为对象的任何事件注册处理程序之前测试是否为 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;
        }
    }
}