彈性資料流

本文介紹如何將自適性串流多媒體內容的播放新增至通用 Windows 平台 (UWP) 應用程式。 這項功能支援透過 HTTP (DASH) 內容播放 Http 即時串流 (HLS) 和動態串流。

從 Windows 10 版本 1803 開始,AdaptiveMediaSource 會支援 Smooth Streaming。 請注意,對於平滑流式傳輸,僅支持 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 頁面中轉譯內容。 若要這樣做,請將 MediaPlayerElement 控制項新增至 XAML 頁面。

<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 物件。

自適性串流 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;

從 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 事件修改資源要求屬性

您可以使用 DownloadRequested 事件處理常式,透過更新事件參數提供的 AdaptiveMediaSourceDownloadResult 物件的屬性來修改資源請求。 在下列範例中,藉由更新結果物件的 ResourceUri 屬性來修改要擷取資源的 URI。 您也可以重寫媒體區段的位元組範圍位移和長度,或如下列範例所示,變更資源 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);

}

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 事件。 此事件是用於遙測集合,不應該用於在執行階段修改應用程式行為。 基於許多不同的原因,會引發此診斷事件。 檢查傳入事件之 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.CreateFromMediaBinderBinder 建立 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;
        }
    }
}