시간이 제한된 시스템 지원 메타데이터 큐
이 문서에서는 미디어 파일 또는 스트림에 포함될 수 있는 시간이 지정된 여러 형식의 메타데이터를 활용하는 방법을 설명합니다. UWP 앱은 이러한 메타데이터 큐가 발견될 때마다 재생하는 동안 미디어 파이프라인에 의해 발생하는 이벤트를 등록할 수 있습니다. DataCue 클래스를 사용하여 앱은 자체 사용자 지정 메타데이터 큐를 구현할 수 있지만 이 문서는 다음과 같은 미디어 파이프라인이 자동으로 검색하는 몇 가지 메타데이터 표준에 초점을 맞춥니다.
- VobSub 형식의 이미지 기반 자막
- 단어 범위, 문장 경계 및 SSML(Speech Synthesis Markup Language) 책갈피를 포함한 음성 큐
- 챕터 큐
- 확장된 M3U 주석
- ID3 태그
- 조각화된 mp4 emsg 상자
이 문서는 MediaSource, MediaPlaybackItem 및 TimedMetadataTrack 클래스를 포함하는 미디어 항목, 재생 목록 및 트랙 문서 및 앱에서 시간이 제한된 메타데이터를 사용하는 일반 가이드에서 설명한 개념을 기반으로 합니다.
기본 구현 단계는 이 문서에서 설명하는 모든 다른 종류의 시간이 제한된 메타데이터에 대해 동일합니다.
- MediaSource 및 MediaPlaybackItem을 만들어 콘텐츠를 재생합니다.
- 미디어 파이프라인에 의해 미디어 항목의 하위 트랙이 확인될 때 발생하는 MediaPlaybackItem.TimedMetadataTracksChanged 이벤트를 등록합니다.
- 사용하고자 하는 시간이 제한된 메타데이터 트랙에 대한 TimedMetadataTrack.CueEntered 및 TimedMetadataTrack.CueExited 이벤트를 등록합니다.
- CueEntered 이벤트 처리기에서 이벤트 인수에 전달된 메타데이터를 기반으로 UI를 업데이트합니다. 예를 들어, 현재 자막 텍스트를 제거하려면 CueExited 이벤트에서 UI를 다시 업데이트할 수 있습니다.
이 문서에서는 고유한 시나리오로 각 유형의 메타 데이터를 처리하지만 대부분 공유 코드를 사용하여 다른 유형의 메타데이터를 처리(또는 무시)할 수 있습니다. 프로세스의 다중 포인트에서 TimedMetadataTrack 개체의 TimedMetadataKind 속성을 확인할 수 있습니다. 따라서 예를 들어, TimedMetadataKind.ImageSubtitle 값을 지닌 메타 데이터 트랙에 대해 CueEntered 이벤트를 등록하도록 선택할 수 있지만 TimedMetadataKind.Speech 값을 지닌 트랙에 대해서는 불가능합니다. 또는 모든 메타데이터 트랙 형식을 등록한 다음, CueEntered 처리기 내에 있는 TimedMetadataKind 값을 확인하여 큐에 대한 응답으로 수행할 작업을 결정할 수 있습니다.
이미지 기반 자막
Windows 10 버전 1703부터 UWP 앱은 VobSub 형식의 외부 이미지 기반 자막을 지원할 수 있습니다. 이 기능을 사용하려면 먼저 이미지 자막이 표시될 미디어 콘텐츠에 대한 MediaSource 개체를 만듭니다. 다음으로 CreateFromUriWithIndex 또는 CreateFromStreamWithIndex를 호출하여 자막 이미지 데이터가 포함된 .sub 파일 및 자막의 타이밍 정보가 포함된 .idx 파일의 URI를 전달함으로써 TimedTextSource 개체를 만듭니다. TimedTextSource를 소스의 ExternalTimedTextSources 컬렉션에 추가하여 MediaSource에 추가합니다. 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 도우미 메서드를 사용합니다. 람다 식은 시스템이 MediaPlaybackItem과 관련된 메타데이터 트랙의 변경을 발견할 때 발생하는 TimedMetadataTracksChanged 이벤트에 대한 처리기를 구현하는 데 사용됩니다. 경우에 따라 메타데이터 트랙은 재생 항목이 처음 지정될 때 사용 가능하기 때문에 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의 큐 속성에 포함된 데이터 큐를 ImageCue에 캐스팅합니다. ImageCue의 SoftwareBitmap 속성은 자막 이미지의 SoftwareBitmap 표시를 포함합니다. SoftwareBitmapSource를 만들고 SetBitmapAsync를 호출하여 이미지를 XAML 이미지 컨트롤에 할당합니다. ImageCue의 범위 및 위치 속성은 자막 이미지의 크기와 위치에 대한 정보를 제공합니다.
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(Speech Synthesis Markup Language) 책갈피에 응답하는 이벤트를 수신하도록 등록할 수 있습니다. 이를 사용하여 SpeechSynthesizer 클래스로 생성되는 오디오 스트림을 재생하여 현재 재생되는 단어 또는 문자의 텍스트를 표시하는 등 이벤트에 따라 UI를 업데이트할 수 있습니다.
이 섹션에 표시된 예제는 클래스 멤버 변수를 사용하여 합성되고 재생될 텍스트 문자열을 저장합니다.
string inputText = "In the lake heading for the mountain, the flea swims";
SpeechSynthesizer 클래스의 새 인스턴스를 만듭니다. 신시사이저에 대한 IncludeWordBoundaryMetadata 및 IncludeSentenceBoundaryMetadata 옵션을 true로 설정하여 메타데이터가 생성된 미디어 스트림에 포함되도록 지정합니다. SynthesizeTextToStreamAsync를 호출하여 합성된 음성 및 해당 메타데이터를 포함하는 스트림을 생성합니다. 합성 스트림에서 MediaSource와 MediaPlaybackItem을 만듭니다.
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 도우미 메서드를 사용합니다. 람다 식은 시스템이 MediaPlaybackItem과 관련된 메타데이터 트랙의 변경을 발견할 때 발생하는 TimedMetadataTracksChanged 이벤트에 대한 처리기를 구현하는 데 사용됩니다. 경우에 따라 메타데이터 트랙은 재생 항목이 처음 지정될 때 사용 가능하기 때문에 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의 큐 속성에 포함된 데이터 큐를 SpeechCue에 캐스팅합니다. 음성 큐의 경우 메타데이터 트랙에 포함된 음성 큐의 유형은 레이블 속성을 통해 확인할 수 있습니다. 이 속성 값은 문장 경계에 대해 "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 도우미 메서드를 사용합니다. 람다 식은 시스템이 MediaPlaybackItem과 관련된 메타데이터 트랙의 변경을 발견할 때 발생하는 TimedMetadataTracksChanged 이벤트에 대한 처리기를 구현하는 데 사용됩니다. 경우에 따라 메타데이터 트랙은 재생 항목이 처음 지정될 때 사용 가능하기 때문에 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의 큐 속성에 포함된 데이터 큐를 ChapterCue에 캐스팅합니다. ChapterCue의 제목 속성은 재생에 막 도달한 챕터 제목을 포함합니다.
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을 인수로 나타냅니다. 어떤 트랙이라도 TimedMetadataKind.Chapter의 TimedMetadataTrack 값의 TimedMetadataKind 속성을 지니는지 확인하기 위해 TimedMetadataTracks 컬렉션이 검색됩니다. 챕터 트랙이 발견되면 메서드는 트랙의 큐 컬렉션을 루핑하여 미디어 플레이어의 재생 세션의 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 도우미 메서드를 사용합니다. 람다 식은 시스템이 MediaPlaybackItem과 관련된 메타데이터 트랙의 변경을 발견할 때 발생하는 TimedMetadataTracksChanged 이벤트에 대한 처리기를 구현하는 데 사용됩니다. 경우에 따라 메타데이터 트랙은 재생 항목이 처음 지정될 때 사용 가능하기 때문에 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 클래스의 인스턴스를 가져옵니다. 트랙이 M3U 주석을 나타내는 경우 "EXTM3U" 값을 갖는 메타데이터 트랙의 DispatchType 속성을 확인합니다. 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 형식으로, Little-Endian, null로 끝나는 문자열로 제공됩니다. DataReader.FromBuffer를 호출하여 새 DataReader를 만들어 큐 데이터를 읽을 수 있습니다. 리더의 UnicodeEncoding 속성을 Utf16LE로 설정하여 올바른 유형의 데이터를 읽을 수 있습니다. ReadString을 호출하여 데이터를 읽고 각 문자의 크기는 2바이트이기 때문에 데이터 필드의 길이를 반으로 지정하고 하나를 빼 후행 null 문자를 제거할 수 있습니다. 여기에서 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 앱은 HLS(Http 라이브 스트리밍) 콘텐츠 내의 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 도우미 메서드를 사용합니다. 람다 식은 시스템이 MediaPlaybackItem과 관련된 메타데이터 트랙의 변경을 발견할 때 발생하는 TimedMetadataTracksChanged 이벤트에 대한 처리기를 구현하는 데 사용됩니다. 경우에 따라 메타데이터 트랙은 재생 항목이 처음 지정될 때 사용 가능하기 때문에 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 태그를 나타내는 경우 "15260DFFFF49443320FF49443320000F" GUID 문자열을 포함하는 값을 지닌 메타데이터 트랙의 DispatchType 속성을 확인합니다. 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 도우미 메서드를 사용합니다. 람다 식은 시스템이 MediaPlaybackItem과 관련된 메타데이터 트랙의 변경을 발견할 때 발생하는 TimedMetadataTracksChanged 이벤트에 대한 처리기를 구현하는 데 사용됩니다. 경우에 따라 메타데이터 트랙은 재생 항목이 처음 지정될 때 사용 가능하기 때문에 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 클래스의 인스턴스를 가져옵니다. 트랙이 emsg 상자를 나타내는 경우 "emsg:mp4" 값을 갖는 메타데이터 트랙의 DispatchType 속성을 확인합니다. 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}");
}
}
}
}
관련 항목