アダプティブ ストリーミング

この記事では、ユニバーサル Windows プラットフォーム (UWP) アプリにアダプティブ ストリーミング マルチメディア コンテンツの再生を追加する方法について説明します。 この機能では、HTTP ライブ ストリーミング (HLS) と Dynamic Adaptive Streaming over 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 オブジェクトが自動的に作成されます。 MediaPlayerPlay を呼び出して、コンテンツの再生を開始できます。

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コントロールも引き続きサポートされています。 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 の値を利用して、メディア ソースが正しく作成されたかどうかを確認できます。 正しく作成されている場合、オブジェクトを MediaPlayer のストリーム ソースとして設定できます。そのためには、MediaSource.CreateFromAdaptiveMediaSource を呼び出して、MediaSource オブジェクトを作成し、このオブジェクトをメディア プレーヤーの 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}");
    }
}

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 に設定することもできます。

結果オブジェクトの 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 オブジェクトは、ダウンロードや再生のビットレートが変わったときに対応できるようにするイベントを提供します。 この例では、現在のビットレートが 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 イベントと同様のデータを提供します。 ここでも、1 つの要求のイベントを関連付けるために 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 は、AdaptiveMediaSourceDiagnostics オブジェクトを返す Diagnostics プロパティを公開します。 このオブジェクトを使って、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 Version 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 イベント ハンドラーでは、トークン文字列を使用してバインドされるコンテンツを識別し、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();
}

バインドされているアダプティブ メディア ソースのイベント ハンドラーを登録する場合は、MediaPlaybackListCurrentItemChanged イベントのハンドラーでこれを実行できます。 CurrentMediaPlaybackItemChangedEventArgs.NewItem プロパティには、リスト内で現在再生中の新しい MediaPlaybackItem が含まれます。 新しい項目を表す AdaptiveMediaSource インスタンスを取得するには、MediaPlaybackItemSource プロパティにアクセスし、次にメディア ソースの 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;
        }
    }
}