システムでサポートされる時間指定メタデータ キュー

この記事では、メディア ファイルまたはストリームに埋め込まれる可能性がある、いくつかの形式の時間指定メタデータを利用する方法について説明します。 UWP アプリは、これらのメタデータ キューが検出されるたびに、再生中にメディア パイプラインによって発生するイベントに登録できます。 DataCue クラスを使用すると、アプリは独自のカスタム メタデータ キューを実装できますが、この記事では、メディア パイプラインによって自動的に検出される次のようないくつかのメタデータ標準に焦点を当てます。

  • イメージ ベースのサブタイトル (VobSub 形式)
  • 単語の境界、文の境界、音声合成マークアップ言語 (SSML) ブックマークなど、音声キュー
  • チャプター キュー
  • 拡張 M3U コメント
  • ID3 タグ
  • 断片化された mp4 emsg ボックス

この記事は、「メディア項目、プレイリスト、トラック」の記事で説明されている概念に基づいています。この概念には、MediaSourceMediaPlaybackItemTimedMetadataTrack クラスの操作の基本や、アプリでタイミングが設定されたメタデータを使用するための一般的なガイダンスが含まれます。

基本的な実装手順は、この記事で説明するさまざまな種類の時間指定メタデータすべてで同じです。

  1. MediaSource作成し、再生するコンテンツの MediaPlaybackItem を作成します。
  2. メディア項目の サブトラックがメディア パイプラインによって解決されると発生する MediaPlaybackItem.TimedMetadataTracksChanged イベントに登録します。
  3. 使用する時間指定メタデータ トラックの TimedMetadataTrack.CueEntered イベントと TimedMetadataTrack.CueExited イベントに登録します。
  4. CueEntered イベント ハンドラーで、イベント引数で渡されたメタデータに基づいて UI を更新します。 UI をもう一度更新して、CueExited イベントなどの現在のサブタイトル テキストを削除できます。

この記事では、各種類のメタデータの処理を個別のシナリオとして示しますが、主に共有コードを使用してさまざまな種類のメタデータを処理 (または無視) できます。 TimedMetadataTrack オブジェクトの TimedMetadataKind プロパティは、プロセス内の複数のポイントで確認できます。 そのため、たとえば、TimedMetadataKind.ImageSubtitle の値を持ち、TimedMetadataKind.Speechを持つトラックには登録しないメタデータ トラックに対して CueEntered イベントに登録することを選択できます。 または、すべてのメタデータ トラックの種類のハンドラーを登録し、CueEntered ハンドラー内の TimedMetadataKind 値を確認して、キューに応答して実行するアクションを決定することもできます。

画像ベースの字幕

Windows 10 バージョン 1703 以降、UWP アプリでは、外部の画像ベースのサブタイトルをVobSub形式でサポートできます。 この機能を使用するには、まず、画像の字幕を表示するメディア コンテンツの MediaSource オブジェクトを作成します。 次に、CreateFromUriWithIndex または CreateFromStreamWithIndex を呼び出して TimedTextSource オブジェクトを作成し、サブタイトル画像データを含む .sub ファイルの URI と、字幕のタイミング情報を含む .idx ファイルを渡します。 TimedTextSource を MediaSource に追加するにはソースの ExternalTimedTextSources コレクションに追加します。 MediaSource から MediaPlaybackItem作成します

var contentUri = new Uri("http://contoso.com/content.mp4");
var mediaSource = MediaSource.CreateFromUri(contentUri);

var subUri = new Uri("http://contoso.com/content.sub");
var idxUri = new Uri("http://contoso.com/content.idx");
var timedTextSource = TimedTextSource.CreateFromUriWithIndex(subUri, idxUri);
mediaSource.ExternalTimedTextSources.Add(timedTextSource);

var mediaPlaybackItem = new MediaPlaybackItem(mediaSource);

前の手順で作成した MediaPlaybackItem オブジェクトを使用して、画像サブタイトル メタデータ イベントに登録します。 この例では、 ヘルパー メソッド RegisterMetadataHandlerForImageSubtitles を使用してイベントを登録します。 ラムダ式は、TimedMetadataTracksChanged イベントのハンドラーを実装するために使用されます。これは、MediaPlaybackItem に関連付けられているメタデータ トラックの変更がシステムによって検出されたときに発生します 場合によっては、再生項目が最初に解決されたときにメタデータ トラックを使用できる場合があるため、TimedMetadataTracksChanged ハンドラーの外部では、使用可能なメタデータ トラックをループ処理し、RegisterMetadataHandlerForImageSubtitles を呼び出します

mediaPlaybackItem.TimedMetadataTracksChanged += (MediaPlaybackItem sender, IVectorChangedEventArgs args) =>
{
    if (args.CollectionChange == CollectionChange.ItemInserted)
    {
        RegisterMetadataHandlerForImageSubtitles(sender, (int)args.Index);
    }
    else if (args.CollectionChange == CollectionChange.Reset)
    {
        for (int index = 0; index < sender.TimedMetadataTracks.Count; index++)
        {
            if (sender.TimedMetadataTracks[index].TimedMetadataKind == TimedMetadataKind.ImageSubtitle)
                RegisterMetadataHandlerForImageSubtitles(sender, index);
        }
    }
};

for (int index = 0; index < mediaPlaybackItem.TimedMetadataTracks.Count; index++)
{
    RegisterMetadataHandlerForImageSubtitles(mediaPlaybackItem, index);
}

画像サブタイトル メタデータ イベントに登録すると、MediaItem は MediaPlayerElement 内で再生するために MediaPlayer に割り当てられます。

_mediaPlayer = new MediaPlayer();
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);
_mediaPlayer.Source = mediaPlaybackItem;
_mediaPlayer.Play();

RegisterMetadataHandlerForImageSubtitles ヘルパー メソッドで、MediaPlaybackItem の TimedMetadataTracks コレクションにインデックスを付けることで、TimedMetadataTrack クラスのインスタンスを取得します CueEntered イベントと CueExited イベントに登録します。 次に、再生項目の TimedMetadataTracks コレクションで SetPresentationMode を呼び出して、アプリがこの再生項目のメタデータ キュー イベントを受信することをシステムに指示する必要があります。

private void RegisterMetadataHandlerForImageSubtitles(MediaPlaybackItem item, int index)
{
    var timedTrack = item.TimedMetadataTracks[index];
    timedTrack.CueEntered += metadata_ImageSubtitleCueEntered;
    timedTrack.CueExited += metadata_ImageSubtitleCueExited;
    item.TimedMetadataTracks.SetPresentationMode((uint)index, TimedMetadataTrackPresentationMode.ApplicationPresented);

}

CueEntered イベントのハンドラーでは、ハンドラーに渡された TimedMetadataTrack オブジェクトの TimedMetadataKind のプロパティを確認して、メタデータが画像サブタイトル用であるかどうかを確認できます。 これは、複数の種類のメタデータに同じデータ キュー イベント ハンドラーを使用している場合に必要です。 関連付けられているメタデータ トラックの種類が TimedMetadataKind.ImageSubtitle の場合は、MediaCueEventArgs の Cue プロパティ含まれるデータ キュー ImageCueキャストします。 ImageCueSoftwareBitmap プロパティには、サブタイトル イメージの SoftwareBitmap 表現が含まれています。 SoftwareBitmapSource作成し、SetBitmapAsync を呼び出して、XAML イメージ コントロールにイメージを割り当てます。 ImageCueExtent プロパティと Position プロパティは、字幕画像のサイズと位置に関する情報を提供します。

private async void metadata_ImageSubtitleCueEntered(TimedMetadataTrack timedMetadataTrack, MediaCueEventArgs args)
{
    // Check in case there are different tracks and the handler was used for more tracks 
    if (timedMetadataTrack.TimedMetadataKind == TimedMetadataKind.ImageSubtitle)
    {
        var cue = args.Cue as ImageCue;
        if (cue != null)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
            {
                var source = new SoftwareBitmapSource();
                await source.SetBitmapAsync(cue.SoftwareBitmap);
                SubtitleImage.Source = source;
                SubtitleImage.Width = cue.Extent.Width;
                SubtitleImage.Height = cue.Extent.Height;
                SubtitleImage.SetValue(Canvas.LeftProperty, cue.Position.X);
                SubtitleImage.SetValue(Canvas.TopProperty, cue.Position.Y);
            });
        }
    }
}

音声キュー

Windows 10 バージョン 1703 以降では、UWP アプリは、再生されたメディアの単語の境界、文の境界、および音声合成マークアップ言語 (SSML) ブックマークに応答してイベントを受信するように登録できます。 これにより、SpeechSynthesizer クラスで生成されたオーディオ ストリームを再生し、現在再生中の単語や文のテキストの表示など、これらのイベントに基づいて UI を更新できます。

このセクションに示す例では、クラス メンバー変数を使用して、合成および再生されるテキスト文字列を格納します。

string inputText = "In the lake heading for the mountain, the flea swims";

SpeechSynthesizer クラスの新しいインスタンスを作成します。 シンセサイザーの IncludeWordBoundaryMetadata オプションと IncludeSentenceBoundaryMetadata オプションを true設定して、生成されたメディア ストリームにメタデータを含める必要があることを指定します。 合成された音声と対応するメタデータを含むストリームを生成するには、SynthesizeTextToStreamAsync を呼び出します。 合成ストリームから MediaSourceMediaPlaybackItem を作成します。

var synthesizer = new Windows.Media.SpeechSynthesis.SpeechSynthesizer();

// Enable word marker generation (false by default). 
synthesizer.Options.IncludeWordBoundaryMetadata = true;
synthesizer.Options.IncludeSentenceBoundaryMetadata = true;

var stream = await synthesizer.SynthesizeTextToStreamAsync(inputText);
var mediaSource = MediaSource.CreateFromStream(stream, "");
var mediaPlaybackItem = new MediaPlaybackItem(mediaSource);

MediaPlaybackItem オブジェクトを使用して、音声メタデータ イベントに登録します。 この例では、 ヘルパー メソッド RegisterMetadataHandlerForSpeech を使用してイベントを登録します。 ラムダ式は、TimedMetadataTracksChanged イベントのハンドラーを実装するために使用されます。これは、MediaPlaybackItem に関連付けられているメタデータ トラックの変更がシステムによって検出されたときに発生します 場合によっては、再生項目が最初に解決されたときにメタデータ トラックを使用できる場合があるため、TimedMetadataTracksChanged ハンドラーの外部では、使用可能なメタデータ トラックをループ処理し、RegisterMetadataHandlerForSpeech を呼び出します。

// Since the tracks are added later we will  
// monitor the tracks being added and subscribe to the ones of interest 
mediaPlaybackItem.TimedMetadataTracksChanged += (MediaPlaybackItem sender, IVectorChangedEventArgs args) =>
{
    if (args.CollectionChange == CollectionChange.ItemInserted)
    {
        RegisterMetadataHandlerForSpeech(sender, (int)args.Index);
    }
    else if (args.CollectionChange == CollectionChange.Reset)
    {
        for (int index = 0; index < sender.TimedMetadataTracks.Count; index++)
        {
            RegisterMetadataHandlerForSpeech(sender, index);
        }
    }
};

// If tracks were available at source resolution time, itterate through and register: 
for (int index = 0; index < mediaPlaybackItem.TimedMetadataTracks.Count; index++)
{
    RegisterMetadataHandlerForSpeech(mediaPlaybackItem, index);
}

音声メタデータ イベントに登録すると、MediaItem は MediaPlayerElement 内で再生するために MediaPlayer に割り当てられます。

_mediaPlayer = new MediaPlayer();
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);
_mediaPlayer.Source = mediaPlaybackItem;
_mediaPlayer.Play();

RegisterMetadataHandlerForSpeech ヘルパー メソッドで、MediaPlaybackItem の TimedMetadataTracks コレクションにインデックスを付けることで、TimedMetadataTrack クラスのインスタンスを取得します CueEntered イベントと CueExited イベントに登録します。 次に、再生項目の TimedMetadataTracks コレクションで SetPresentationMode を呼び出して、アプリがこの再生項目のメタデータ キュー イベントを受信することをシステムに指示する必要があります。

private void RegisterMetadataHandlerForSpeech(MediaPlaybackItem item, int index)
{
    var timedTrack = item.TimedMetadataTracks[index];
    timedTrack.CueEntered += metadata_SpeechCueEntered;
    timedTrack.CueExited += metadata_SpeechCueExited;
    item.TimedMetadataTracks.SetPresentationMode((uint)index, TimedMetadataTrackPresentationMode.ApplicationPresented);

}

CueEntered イベントのハンドラーでは、ハンドラーに渡された TimedMetadataTrack オブジェクトの TimedMetadataKind の適切さを確認して、メタデータが音声かどうかを確認できます。 これは、複数の種類のメタデータに同じデータ キュー イベント ハンドラーを使用している場合に必要です。 関連付けられているメタデータ トラックの種類が TimedMetadataKind.Speech の場合は、MediaCueEventArgs の Cue プロパティ含まれるデータ キュー SpeechCueキャストします。 音声キューの場合、メタデータ トラックに含まれる音声キューの種類は、Label プロパティをチェックすることによって決定されます。 このプロパティの値は、単語の境界の場合は "SpeechWord"、文の境界の場合は "SpeechSentence"、SSML ブックマークの場合は "SpeechBookmark" になります。 この例では、"SpeechWord" 値を確認し、この値が見つかった場合は、SpeechCue の StartPositionInInput プロパティと EndPositionInInput プロパティを使用して、現在再生されている単語の入力テキスト内の位置を決定します。 この例では、各単語をデバッグ出力に出力するだけです。

private void metadata_SpeechCueEntered(TimedMetadataTrack timedMetadataTrack, MediaCueEventArgs args)
{
    // Check in case there are different tracks and the handler was used for more tracks 
    if (timedMetadataTrack.TimedMetadataKind == TimedMetadataKind.Speech)
    {
        var cue = args.Cue as SpeechCue;
        if (cue != null)
        {
            if (timedMetadataTrack.Label == "SpeechWord")
            {
                // Do something with the cue 
                System.Diagnostics.Debug.WriteLine($"{cue.StartPositionInInput} - {cue.EndPositionInInput}: {inputText.Substring((int)cue.StartPositionInInput, ((int)cue.EndPositionInInput - (int)cue.StartPositionInInput) + 1)}");
            }
        }
    }
}

チャプター キュー

Windows 10 バージョン 1703 以降では、UWP アプリはメディアアイテム内の章に対応するキューに登録できます。 この機能を使用するには、メディア コンテンツの MediaSource オブジェクトを作成し、MediaSource から MediaPlaybackItem を作成します。

var contentUri = new Uri("http://contoso.com/content.mp4");
var mediaSource = MediaSource.CreateFromUri(contentUri);
var mediaPlaybackItem = new MediaPlaybackItem(mediaSource);

前の手順で作成した MediaPlaybackItem オブジェクトを使用して、チャプター メタデータ イベントに登録します。 この例では、 ヘルパー メソッド RegisterMetadataHandlerForChapterCues を使用してイベントを登録します。 ラムダ式は、TimedMetadataTracksChanged イベントのハンドラーを実装するために使用されます。これは、MediaPlaybackItem に関連付けられているメタデータ トラックの変更がシステムによって検出されたときに発生します 場合によっては、再生項目が最初に解決されたときにメタデータ トラックを使用できる場合があるため、TimedMetadataTracksChanged ハンドラーの外部では、使用可能なメタデータ トラックをループ処理し、RegisterMetadataHandlerForChapterCues を呼び出します

mediaPlaybackItem.TimedMetadataTracksChanged += (MediaPlaybackItem sender, IVectorChangedEventArgs args) =>
{
    if (args.CollectionChange == CollectionChange.ItemInserted)
    {
        RegisterMetadataHandlerForChapterCues(sender, (int)args.Index);
    }
    else if (args.CollectionChange == CollectionChange.Reset)
    {
        for (int index = 0; index < sender.TimedMetadataTracks.Count; index++)
        {
            if (sender.TimedMetadataTracks[index].TimedMetadataKind == TimedMetadataKind.ImageSubtitle)
                RegisterMetadataHandlerForChapterCues(sender, index);
        }
    }
};

for (int index = 0; index < mediaPlaybackItem.TimedMetadataTracks.Count; index++)
{
    RegisterMetadataHandlerForChapterCues(mediaPlaybackItem, index);
}

チャプター メタデータ イベントに登録すると、MediaItem は MediaPlayerElement 内で再生するために MediaPlayer に割り当てられます。

_mediaPlayer = new MediaPlayer();
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);
_mediaPlayer.Source = mediaPlaybackItem;
_mediaPlayer.Play();

RegisterMetadataHandlerForChapterCues ヘルパー メソッドで、MediaPlaybackItem の TimedMetadataTracks コレクションにインデックスを付けて TimedMetadataTrack クラスのインスタンスを取得します CueEntered イベントと CueExited イベントに登録します。 次に、再生項目の TimedMetadataTracks コレクションで SetPresentationMode を呼び出して、アプリがこの再生項目のメタデータ キュー イベントを受信することをシステムに指示する必要があります。

private void RegisterMetadataHandlerForChapterCues(MediaPlaybackItem item, int index)
{
    var timedTrack = item.TimedMetadataTracks[index];
    timedTrack.CueEntered += metadata_ChapterCueEntered;
    timedTrack.CueExited += metadata_ChapterCueExited;
    item.TimedMetadataTracks.SetPresentationMode((uint)index, TimedMetadataTrackPresentationMode.ApplicationPresented);
}

CueEntered イベントのハンドラーでは、ハンドラーに渡された TimedMetadataTrack オブジェクトの TimedMetadataKind のプロパティを確認して、メタデータがチャプター キュー用であるかどうかを確認できます。これは、複数の種類のメタデータに同じデータ キュー イベント ハンドラーを使用している場合に必要です。 関連付けられているメタデータ トラックが TimedMetadataKind.Chapter 型の場合は、MediaCueEventArgs の Cue プロパティ含まれるデータ キューを ChapterCueキャストします。 ChapterCueTitle プロパティには、再生で到達したばかりのチャプターのタイトルが含まれています。

private async void metadata_ChapterCueEntered(TimedMetadataTrack timedMetadataTrack, MediaCueEventArgs args)
{
    // Check in case there are different tracks and the handler was used for more tracks 
    if (timedMetadataTrack.TimedMetadataKind == TimedMetadataKind.Chapter)
    {
        var cue = args.Cue as ChapterCue;
        if (cue != null)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                ChapterTitleTextBlock.Text = cue.Title;
            });
        }
    }
}

章キューを使用して次の章に進む

現在の章がプレイ中のアイテムで変更されたときに通知を受け取るだけでなく、チャプターキューを使用して、プレイ中のアイテム内の次の章を探すこともできます。 次に示すメソッドの例では、MediaPlayer MediaPlaybackItem が現在再生中のメディアアイテムを表す引数として受け取ります。 TimedMetadataTracks コレクションが検索され、いずれかのトラックに TimedMetadataKind の TimedMetadataTrackが TimedMetadataKind.Chapter の適切かどうかを確認します。 チャプター トラックが見つかった場合、メソッドはトラックの Cues コレクション内の各キューをループ処理して、メディア プレーヤーの再生セッションの現在の位置よりも StartTime が大きい最初のキューを見つけます。 正しいキューが見つかると、再生セッションの位置が更新され、章のタイトルが UI で更新されます。

private void GoToNextChapter(MediaPlayer player, MediaPlaybackItem item)
{
    // Find the chapters track if one exists
    TimedMetadataTrack chapterTrack = item.TimedMetadataTracks.FirstOrDefault(track => track.TimedMetadataKind == TimedMetadataKind.Chapter);
    if (chapterTrack == null)
    {
        return;
    }

    // Find the first chapter that starts after current playback position
    TimeSpan currentPosition = player.PlaybackSession.Position;
    foreach (ChapterCue cue in chapterTrack.Cues)
    {
        if (cue.StartTime > currentPosition)
        {
            // Change player position to chapter start time
            player.PlaybackSession.Position = cue.StartTime;

            // Display chapter name
            ChapterTitleTextBlock.Text = cue.Title;
            break;
        }
    }
}

拡張 M3U コメント

Windows 10 バージョン 1703 以降では、UWP アプリは拡張 M3U マニフェスト ファイル内のコメントに対応するキューに登録できます。 この例では、AdaptiveMediaSource を使用してメディア コンテンツを再生します。 詳細については、「アダプティブ ストリーミング」を参照してくださいCreateFromUriAsync または CreateFromStreamAsync を呼び出して、コンテンツの AdaptiveMediaSource を作成します。 CreateFromAdaptiveMediaSource を呼び出して MediaSource オブジェクトを作成し、MediaSource から MediaPlaybackItem を作成します。

AdaptiveMediaSourceCreationResult result =
    await AdaptiveMediaSource.CreateFromUriAsync(new Uri("http://contoso.com/playlist.m3u"));

if (result.Status != AdaptiveMediaSourceCreationStatus.Success)
{
    // TODO: Handle adaptive media source creation errors.
    return;
}
var mediaSource = MediaSource.CreateFromAdaptiveMediaSource(result.MediaSource);
var mediaPlaybackItem = new MediaPlaybackItem(mediaSource);

前の手順で作成した MediaPlaybackItem オブジェクトを使用して、M3U メタデータ イベントに登録します。 この例では、 ヘルパー メソッド RegisterMetadataHandlerForEXTM3UCues を使用してイベントを登録します。 ラムダ式は、TimedMetadataTracksChanged イベントのハンドラーを実装するために使用されます。これは、MediaPlaybackItem に関連付けられているメタデータ トラックの変更がシステムによって検出されたときに発生します 場合によっては、再生項目が最初に解決されたときにメタデータ トラックを使用できる場合があるため、TimedMetadataTracksChanged ハンドラーの外部では、使用可能なメタデータ トラックをループ処理し、RegisterMetadataHandlerForEXTM3UCues を呼び出します

mediaPlaybackItem.TimedMetadataTracksChanged += (MediaPlaybackItem sender, IVectorChangedEventArgs args) =>
{
    if (args.CollectionChange == CollectionChange.ItemInserted)
    {
        RegisterMetadataHandlerForEXTM3UCues(sender, (int)args.Index);
    }
    else if (args.CollectionChange == CollectionChange.Reset)
    {
        for (int index = 0; index < sender.TimedMetadataTracks.Count; index++)
        {
            if (sender.TimedMetadataTracks[index].TimedMetadataKind == TimedMetadataKind.ImageSubtitle)
                RegisterMetadataHandlerForEXTM3UCues(sender, index);
        }
    }
};

for (int index = 0; index < mediaPlaybackItem.TimedMetadataTracks.Count; index++)
{
    RegisterMetadataHandlerForEXTM3UCues(mediaPlaybackItem, index);
}

M3U メタデータ イベントに登録すると、MediaItem は MediaPlayerElement 内で再生するために MediaPlayer に割り当てられます。

_mediaPlayer = new MediaPlayer();
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);
_mediaPlayer.Source = mediaPlaybackItem;
_mediaPlayer.Play();

RegisterMetadataHandlerForEXTM3UCues ヘルパー メソッドで、MediaPlaybackItem の TimedMetadataTracks コレクションにインデックスを付けることで、TimedMetadataTrack クラスのインスタンスを取得します メタデータ トラックの DispatchType プロパティを確認します。トラックが M3U コメントを表す場合は、値が "EXTM3U" になります。 CueEntered イベントと CueExited イベントに登録します。 次に、再生項目の TimedMetadataTracks コレクションで SetPresentationMode を呼び出して、アプリがこの再生項目のメタデータ キュー イベントを受信することをシステムに指示する必要があります。

private void RegisterMetadataHandlerForEXTM3UCues(MediaPlaybackItem item, int index)
{
    var timedTrack = item.TimedMetadataTracks[index];
    var dispatchType = timedTrack.DispatchType;

    if (String.Equals(dispatchType, "EXTM3U", StringComparison.OrdinalIgnoreCase))
    {
        timedTrack.Label = "EXTM3U comments";
        timedTrack.CueEntered += metadata_EXTM3UCueEntered;
        timedTrack.CueExited += metadata_EXTM3UCueExited;
        item.TimedMetadataTracks.SetPresentationMode((uint)index, TimedMetadataTrackPresentationMode.ApplicationPresented);
    }
}

CueEntered イベントのハンドラーで、MediaCueEventArgs の Cue プロパティ含まれるデータ キュー DataCueキャストします。 キューの DataCue プロパティと Data プロパティが null ではないことを確認します。 拡張 EMU コメントは、UTF-16、リトル エンディアン、null 終端文字列の形式で提供されます。 DataReader.FromBuffer を呼び出してキュー データを読み取る新しい DataReader を作成します。 正しい形式でデータを読み取るために、リーダーの UnicodeEncoding プロパティを Utf16LE に設定します。 ReadString を呼び出してデータを読み取ります。各文字のサイズは 2 バイトであるため、データフィールドの長さの半分を指定し、末尾の null 文字を削除するために 1 を減算します。 この例では、M3U コメントは単にデバッグ出力に書き込まれます。

private void metadata_EXTM3UCueEntered(TimedMetadataTrack timedMetadataTrack, MediaCueEventArgs args)
{
    var dataCue = args.Cue as DataCue;
    if (dataCue != null && dataCue.Data != null)
    {
        // The payload is a UTF-16 Little Endian null-terminated string.
        // It is any comment line in a manifest that is not part of the HLS spec.
        var dr = Windows.Storage.Streams.DataReader.FromBuffer(dataCue.Data);
        dr.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf16LE;
        var m3uComment = dr.ReadString(dataCue.Data.Length / 2 - 1);
        System.Diagnostics.Debug.WriteLine(m3uComment);
    }
}

ID3 タグ

Windows 10 バージョン 1703 以降、UWP アプリは Http Live Streaming (HLS) コンテンツ内の ID3 タグに対応するキューを登録できます。 この例では、AdaptiveMediaSource を使用してメディア コンテンツを再生します。 詳細については、「アダプティブ ストリーミング」を参照してくださいCreateFromUriAsync または CreateFromStreamAsync を呼び出して、コンテンツの AdaptiveMediaSource を作成します。 CreateFromAdaptiveMediaSource を呼び出して MediaSource オブジェクトを作成し、MediaSource から MediaPlaybackItem を作成します。

AdaptiveMediaSourceCreationResult result =
    await AdaptiveMediaSource.CreateFromUriAsync(new Uri("http://contoso.com/playlist.m3u"));

if (result.Status != AdaptiveMediaSourceCreationStatus.Success)
{
    // TODO: Handle adaptive media source creation errors.
    return;
}
var mediaSource = MediaSource.CreateFromAdaptiveMediaSource(result.MediaSource);
var mediaPlaybackItem = new MediaPlaybackItem(mediaSource);

前の手順で作成した MediaPlaybackItem オブジェクトを使用して、ID3 タグ イベントに登録します。 この例では、 ヘルパー メソッド RegisterMetadataHandlerForID3Cues を使用してイベントを登録します。 ラムダ式は、TimedMetadataTracksChanged イベントのハンドラーを実装するために使用されます。これは、MediaPlaybackItem に関連付けられているメタデータ トラックの変更がシステムによって検出されたときに発生します 場合によっては、再生項目が最初に解決されたときにメタデータ トラックを使用できる場合があるため、TimedMetadataTracksChanged ハンドラーの外部では、使用可能なメタデータ トラックをループ処理し、RegisterMetadataHandlerForID3Cues を呼び出します

AdaptiveMediaSourceCreationResult result =
    await AdaptiveMediaSource.CreateFromUriAsync(new Uri("http://contoso.com/playlist.m3u"));

if (result.Status != AdaptiveMediaSourceCreationStatus.Success)
{
    // TODO: Handle adaptive media source creation errors.
    return;
}
var mediaSource = MediaSource.CreateFromAdaptiveMediaSource(result.MediaSource);
var mediaPlaybackItem = new MediaPlaybackItem(mediaSource);

ID3 メタデータ イベントに登録すると、MediaItem は MediaPlayerElement 内で再生するために MediaPlayer に割り当てられます。

_mediaPlayer = new MediaPlayer();
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);
_mediaPlayer.Source = mediaPlaybackItem;
_mediaPlayer.Play();

RegisterMetadataHandlerForID3Cues ヘルパー メソッドで、MediaPlaybackItem の TimedMetadataTracks コレクションにインデックスを付けることで、TimedMetadataTrack クラスのインスタンスを取得します トラックが ID3 タグを表す場合は、メタデータ トラックの DispatchType プロパティを確認します。このプロパティには、GUID 文字列 "15260DFFFF49443320FF4944332000F" が含まれます。 CueEntered イベントと CueExited イベントに登録します。 次に、再生項目の TimedMetadataTracks コレクションで SetPresentationMode を呼び出して、アプリがこの再生項目のメタデータ キュー イベントを受信することをシステムに指示する必要があります。

private void RegisterMetadataHandlerForID3Cues(MediaPlaybackItem item, int index)
{
    var timedTrack = item.TimedMetadataTracks[index];
    var dispatchType = timedTrack.DispatchType;

    if (String.Equals(dispatchType, "15260DFFFF49443320FF49443320000F", StringComparison.OrdinalIgnoreCase))
    {
        timedTrack.Label = "ID3 tags";
        timedTrack.CueEntered += metadata_ID3CueEntered;
        timedTrack.CueExited += metadata_ID3CueExited;
        item.TimedMetadataTracks.SetPresentationMode((uint)index, TimedMetadataTrackPresentationMode.ApplicationPresented);
    }
}

CueEntered イベントのハンドラーで、MediaCueEventArgs の Cue プロパティ含まれるデータ キュー DataCueキャストします。 キューの DataCue プロパティと Data プロパティが null ではないことを確認します。 拡張 EMU コメントは、トランスポート ストリームの生バイト形式で提供されます (ID3 を参照)。 DataReader.FromBuffer を呼び出してキュー データを読み取る新しい DataReader を作成します。 この例では、ID3 タグのヘッダー値がキュー データから読み取られ、デバッグ出力に書き込まれます。

private void metadata_ID3CueEntered(TimedMetadataTrack timedMetadataTrack, MediaCueEventArgs args)
{
    var dataCue = args.Cue as DataCue;
    if (dataCue != null && dataCue.Data != null)
    {
        // The payload is the raw ID3 bytes found in a TS stream
        // Ref: http://id3.org/id3v2.4.0-structure
        var dr = Windows.Storage.Streams.DataReader.FromBuffer(dataCue.Data);
        var header_ID3 = dr.ReadString(3);
        var header_version_major = dr.ReadByte();
        var header_version_minor = dr.ReadByte();
        var header_flags = dr.ReadByte();
        var header_tagSize = dr.ReadUInt32();

        System.Diagnostics.Debug.WriteLine($"ID3 tag data: major {header_version_major}, minor: {header_version_minor}");
    }
}

断片化された mp4 emsg ボックス

Windows 10 バージョン 1703 以降、UWP アプリでは、断片化された mp4 ストリーム内の emsg ボックスに対応するキューを登録できます。 この種類のメタデータの使用例は、コンテンツ プロバイダーが、ライブ ストリーミング コンテンツ中に広告を再生するようにクライアント アプリケーションに通知することです。 この例では、AdaptiveMediaSource を使用してメディア コンテンツを再生します。 詳細については、「アダプティブ ストリーミング」を参照してくださいCreateFromUriAsync または CreateFromStreamAsync を呼び出して、コンテンツの AdaptiveMediaSource を作成します。 CreateFromAdaptiveMediaSource を呼び出して MediaSource オブジェクトを作成し、MediaSource から MediaPlaybackItem を作成します。

AdaptiveMediaSourceCreationResult result =
    await AdaptiveMediaSource.CreateFromUriAsync(new Uri("http://contoso.com/playlist.m3u"));

if (result.Status != AdaptiveMediaSourceCreationStatus.Success)
{
    // TODO: Handle adaptive media source creation errors.
    return;
}
var mediaSource = MediaSource.CreateFromAdaptiveMediaSource(result.MediaSource);
var mediaPlaybackItem = new MediaPlaybackItem(mediaSource);

前の手順で作成した MediaPlaybackItem オブジェクトを使用して、emsg ボックス イベントに登録します。 この例では、 ヘルパー メソッド RegisterMetadataHandlerForEmsgCues を使用してイベントを登録します。 ラムダ式は、TimedMetadataTracksChanged イベントのハンドラーを実装するために使用されます。これは、MediaPlaybackItem に関連付けられているメタデータ トラックの変更がシステムによって検出されたときに発生します 場合によっては、再生項目が最初に解決されたときにメタデータ トラックを使用できる場合があるため、TimedMetadataTracksChanged ハンドラーの外部では、使用可能なメタデータ トラックをループ処理し、RegisterMetadataHandlerForEmsgCues を呼び出します

AdaptiveMediaSourceCreationResult result =
    await AdaptiveMediaSource.CreateFromUriAsync(new Uri("http://contoso.com/playlist.m3u"));

if (result.Status != AdaptiveMediaSourceCreationStatus.Success)
{
    // TODO: Handle adaptive media source creation errors.
    return;
}
var mediaSource = MediaSource.CreateFromAdaptiveMediaSource(result.MediaSource);
var mediaPlaybackItem = new MediaPlaybackItem(mediaSource);

emsg ボックス メタデータ イベントに登録すると、MediaItem は MediaPlayerElement 内で再生するために MediaPlayer割り当てられます。

_mediaPlayer = new MediaPlayer();
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);
_mediaPlayer.Source = mediaPlaybackItem;
_mediaPlayer.Play();

RegisterMetadataHandlerForEmsgCues ヘルパー メソッドで、MediaPlaybackItem の TimedMetadataTracks コレクションにインデックスを付けることで、TimedMetadataTrack クラスのインスタンスを取得します メタデータ トラックの DispatchType プロパティをオンにします。トラックが emsg ボックスを表す場合、値は "emsg:mp4" になります。 CueEntered イベントと CueExited イベントに登録します。 次に、再生項目の TimedMetadataTracks コレクションで SetPresentationMode を呼び出して、アプリがこの再生項目のメタデータ キュー イベントを受信することをシステムに指示する必要があります。

private void RegisterMetadataHandlerForEmsgCues(MediaPlaybackItem item, int index)
{
    var timedTrack = item.TimedMetadataTracks[index];
    var dispatchType = timedTrack.DispatchType;

    if (String.Equals(dispatchType, "emsg:mp4", StringComparison.OrdinalIgnoreCase))
    {
        timedTrack.Label = "mp4 Emsg boxes";
        timedTrack.CueEntered += metadata_EmsgCueEntered;
        timedTrack.CueExited += metadata_EmsgCueExited;
        item.TimedMetadataTracks.SetPresentationMode((uint)index, TimedMetadataTrackPresentationMode.ApplicationPresented);
    }
}

CueEntered イベントのハンドラーで、MediaCueEventArgs の Cue プロパティ含まれるデータ キュー DataCueキャストします。 DataCue オブジェクトが null ではないことを確認します。 emsg ボックスのプロパティは、DataCue オブジェクト の Properties コレクションのカスタム プロパティとしてメディア パイプラインによって提供されます。 この例では、TryGetValue メソッドを使用して、いくつかの異なるプロパティ値の抽出を試みます。 このメソッドが null を返す場合は、要求されたプロパティが emsg ボックスに存在しないことを意味するため、代わりに既定値が設定されます。

この例の次の部分は、広告再生がトリガーされるシナリオを示しています。これは、前の手順で取得した scheme_id_uri プロパティの値が "urn:scte:scte35:2013:xml" の場合です。 詳細については、https://dashif.org/identifiers/event_schemes/ をご覧ください。 標準では冗長性のためにこの emsg を複数回送信することをお勧めします。そのため、この例では既に処理され、新しいメッセージのみを処理する emsg ID の一覧が保持されます。 DataReader.FromBuffer を呼び出してキュー データを読み取る新しい DataReader を作成し、UnicodeEncoding プロパティを設定してエンコードを UTF-8 に設定してから、データを読み取る。 この例では、メッセージ ペイロードがデバッグ出力に書き込まれます。 実際のアプリでは、ペイロード データを使用して広告の再生をスケジュールします。

private void metadata_EmsgCueEntered(TimedMetadataTrack timedMetadataTrack, MediaCueEventArgs args)
{
    var dataCue = args.Cue as DataCue;
    if (dataCue != null)
    {
        string scheme_id_uri = string.Empty;
        string value = string.Empty;
        UInt32 timescale = (UInt32)TimeSpan.TicksPerSecond;
        UInt32 presentation_time_delta = (UInt32)dataCue.StartTime.Ticks;
        UInt32 event_duration = (UInt32)dataCue.Duration.Ticks;
        UInt32 id = 0;
        Byte[] message_data = null;

        const string scheme_id_uri_key = "emsg:scheme_id_uri";
        object propValue = null;
        dataCue.Properties.TryGetValue(scheme_id_uri_key, out propValue);
        scheme_id_uri = propValue != null ? (string)propValue : "";

        const string value_key = "emsg:value";
        propValue = null;
        dataCue.Properties.TryGetValue(value_key, out propValue);
        value = propValue != null ? (string)propValue : "";

        const string timescale_key = "emsg:timescale";
        propValue = null;
        dataCue.Properties.TryGetValue(timescale_key, out propValue);
        timescale = propValue != null ? (UInt32)propValue : timescale;

        const string presentation_time_delta_key = "emsg:presentation_time_delta";
        propValue = null;
        dataCue.Properties.TryGetValue(presentation_time_delta_key, out propValue);
        presentation_time_delta = propValue != null ? (UInt32)propValue : presentation_time_delta;

        const string event_duration_key = "emsg:event_duration";
        propValue = null;
        dataCue.Properties.TryGetValue(event_duration_key, out propValue);
        event_duration = propValue != null ? (UInt32)propValue : event_duration;

        const string id_key = "emsg:id";
        propValue = null;
        dataCue.Properties.TryGetValue(id_key, out propValue);
        id = propValue != null ? (UInt32)propValue : 0;

        System.Diagnostics.Debug.WriteLine($"Label: {timedMetadataTrack.Label}, Id: {dataCue.Id}, StartTime: {dataCue.StartTime}, Duration: {dataCue.Duration}");
        System.Diagnostics.Debug.WriteLine($"scheme_id_uri: {scheme_id_uri}, value: {value}, timescale: {timescale}, presentation_time_delta: {presentation_time_delta}, event_duration: {event_duration}, id: {id}");

        if (dataCue.Data != null)
        {
            var dr = Windows.Storage.Streams.DataReader.FromBuffer(dataCue.Data);

            // Check if this is a SCTE ad message:
            // Ref:  http://dashif.org/identifiers/event-schemes/
            if (scheme_id_uri.ToLower() == "urn:scte:scte35:2013:xml")
            {
                // SCTE recommends publishing emsg more than once, so we avoid reprocessing the same message id:
                if (!processedAdIds.Contains(id))
                {
                    processedAdIds.Add(id);
                    dr.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                    var scte35payload = dr.ReadString(dataCue.Data.Length);
                    System.Diagnostics.Debug.WriteLine($", message_data: {scte35payload}");
                    // TODO: ScheduleAdFromScte35Payload(timedMetadataTrack, presentation_time_delta, timescale, event_duration, scte35payload);
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine($"This emsg.Id, {id}, has already been processed.");
                }
            }
            else
            {
                message_data = new byte[dataCue.Data.Length];
                dr.ReadBytes(message_data);
                // TODO: Use the 'emsg' bytes for something useful. 
                System.Diagnostics.Debug.WriteLine($", message_data.Length: {message_data.Length}");
            }
        }
    }
}