システムでサポートされているタイミングが設定されたメタデータのキュー

この記事では、メディア ファイルやストリームに埋め込まれる可能性がある、タイミングが設定されたメタデータのいくつかの形式を活用する方法について説明します。 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 を持つメタデータ トラックのCueEntered イベントに登録することを選択できますが、値がTimedMetadataKind.Speechトラックには登録されません。 または、すべてのメタデータ トラックの種類のハンドラーを登録し、CueEntered ハンドラー内の TimedMetadataKind 値を確認して、キューに応答して実行するアクションを決定することもできます。

画像ベースの字幕

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

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

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

RegisterMetadataHandlerForImageSubtitles ヘルパー メソッドで、MediaPlaybackItemTimedMetadataTracks コレクションにインデックスを付けることで、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 の場合は、MediaCueEventArgsCue プロパティに含まれるデータ キューを ImageCue にキャストします。 ImageCueSoftwareBitmap プロパティには、サブタイトル イメージのSoftwareBitmap表現が含まれています。 SoftwareBitmapSourceを作成しSetBitmapAsyncを呼び出して、XAML Image コントロールにイメージを割り当てます。 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 ヘルパー メソッドで、MediaPlaybackItemTimedMetadataTracks コレクションにインデックスを付けることで、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 の場合は、MediaCueEventArgsCue プロパティに含まれるデータ キューを SpeechCue にキャストします。 音声キューの場合、メタデータ トラックに含まれる音声キューの種類は、 Label プロパティを確認することによって決定されます。 このプロパティの値は、単語の境界の場合は "SpeechWord"、文の境界の場合は "SpeechSentence"、SSML ブックマークの場合は "SpeechBookmark" になります。 この例では、"SpeechWord" 値を確認し、この値が見つかった場合は、SpeechCueStartPositionInInput プロパティと 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);
}

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

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

RegisterMetadataHandlerForChapterCues ヘルパー メソッドで、MediaPlaybackItemTimedMetadataTracks コレクションにインデックスを付けることで、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 の場合は、MediaCueEventArgsCue プロパティに含まれるデータ キューを 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 timedMetadataTrackTimedMetadataKind.Chapter の値がかどうかが確認されます。 チャプター トラックが見つかった場合、メソッドはトラックの Cues コレクション内の各キューをループ処理して、メディア プレーヤーの再生セッションの現在の Position より大きい 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 を使用してメディア コンテンツを再生します。 詳細については、「 Adaptive Streaming」を参照してください。 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 ヘルパー メソッドで、MediaPlaybackItemTimedMetadataTracks コレクションにインデックスを付けることで、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 イベントのハンドラーで、MediaCueEventArgsCue プロパティに含まれるデータ キューをDataCueにキャストします。 キューの DataCueData プロパティが null ではないことを確認します。 拡張 EMU コメントは、UTF-16、リトル エンディアン、null 終端文字列の形式で提供されます。 新しい DataReader を作成し、 DataReader.FromBuffer を呼び出してキュー データを読み取ります。 リーダーの UnicodeEncoding プロパティを Utf16LE に設定して、正しい形式でデータを読み取ります。 ReadString を呼び出してデータを読み取り、Data フィールドの長さの半分を指定します。各文字のサイズは 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 を使用してメディア コンテンツを再生します。 詳細については、「 Adaptive Streaming」を参照してください。 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 ヘルパー メソッドで、MediaPlaybackItemTimedMetadataTracks コレクションにインデックスを付けることで、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 イベントのハンドラーで、MediaCueEventArgsCue プロパティに含まれるデータ キューをDataCueにキャストします。 キューの DataCueData プロパティが null ではないことを確認します。 拡張 EMU コメントは、トランスポート ストリームの未加工バイト形式で提供されます ( ID3 を参照)。 新しい DataReader を作成し、 DataReader.FromBuffer を呼び出してキュー データを読み取ります。 この例では、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 を使用してメディア コンテンツを再生します。 詳細については、「 Adaptive Streaming」を参照してください。 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 ボックス メタデータ イベントに登録すると、MediaPlayerElement 内で再生するために、MediaItemMediaPlayer に割り当てられます。

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

RegisterMetadataHandlerForEmsgCues ヘルパー メソッドで、MediaPlaybackItemTimedMetadataTracks コレクションにインデックスを付けることで、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 イベントのハンドラーで、MediaCueEventArgsCue プロパティに含まれるデータ キューを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 を作成して、 DataReader.FromBuffer を呼び出してキュー データを読み取り、 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}");
            }
        }
    }
}