Поддерживаемые системой синхронизированные подсказки метаданных

В этой статье описывается, как воспользоваться несколькими форматами метаданных времени, которые могут быть внедрены в файлы мультимедиа или потоки. Приложения UWP могут регистрировать события, создаваемые конвейером мультимедиа во время воспроизведения при обнаружении этих подсказок метаданных. С помощью класса DataCue приложения могут реализовать собственные пользовательские подсказки метаданных, но в этой статье рассматриваются несколько стандартов метаданных, которые автоматически обнаруживаются конвейером мультимедиа, включая:

  • Субтитры на основе изображений в формате VobSub
  • Подсказки речи, включая границы слов, границы предложения и закладки языка разметки синтеза речи (SSML)
  • Подсказки глав
  • Расширенные комментарии M3U
  • Теги ID3
  • Фрагментированные ящики mp4 emsg

В этой статье рассматриваются основные понятия, описанные в статье "Элементы мультимедиа", списки воспроизведения и треки, которые включают основы работы с MediaSource, MediaPlaybackItem и TimedMetadataTrack классов и общих рекомендаций по использованию метаданных времени в приложении.

Основные шаги реализации одинаковы для всех различных типов метаданных времени, описанных в этой статье:

  1. Создайте MediaSource, а затем MediaPlaybackItem для воспроизведения содержимого.
  2. Зарегистрируйте событие MediaPlaybackItem.TimedMetadataTracksChanged , которое возникает в качестве вложенных треков элемента мультимедиа, разрешается конвейером мультимедиа.
  3. Зарегистрируйте события TimedMetadataTrack.CueEntered и TimedMetadataTrack.CueExited для отслеживаемых метаданных, которые вы хотите использовать.
  4. В обработчике событий CueEntered обновите пользовательский интерфейс на основе метаданных, переданных в событиях args. Вы можете снова обновить пользовательский интерфейс, чтобы удалить текущий текст субтитров, например, в событии CueExited .

В этой статье обработка каждого типа метаданных отображается как отдельный сценарий, но можно обрабатывать различные типы метаданных (или игнорировать), используя в основном общий код. Свойство TimedMetadataKind объекта TimedMetadataTrack можно проверить в нескольких точках процесса. Например, можно зарегистрировать событие CueEntered для отслеживания метаданных с значением TimedMetadataKind.ImageSubtitle, но не для треков с значением TimedMetadataKind.Speech. Кроме того, можно зарегистрировать обработчик для всех типов отслеживания метаданных, а затем проверить значение TimedMetadataKind в обработчике CueEntered , чтобы определить, какие действия следует предпринять в ответ на подсказку.

Субтитры на основе изображений

Начиная с Windows 10 версии 1703, приложения UWP могут поддерживать внешние субтитры на основе изображений в формате VobSub. Чтобы использовать эту функцию, сначала создайте объект MediaSource для содержимого мультимедиа, для которого будут отображаться субтитры изображений. Затем создайте объект TimedTextSource, вызвав CreateFromUriWithIndex или CreateFromStreamWithIndex, передав URI дочернего файла, содержащего данные изображения субтитров и IDX-файл, содержащий сведения о времени для субтитров. Добавьте TimedTextSource в MediaSource, добавив его в коллекцию ExternalTimedTextSources источника. Создайте MediaPlaybackItem из MediaSource.

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 назначается MediaPlayer для воспроизведения в MediaPlayerElement.

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

В вспомогательном методе RegisterMetadataHandlerForImageSubtitles получите экземпляр класса TimedMetadataTrack путем индексирования в коллекцию TimedMetadataTracks mediaPlaybackItem. Зарегистрируйте событие CueEntered и событие CueExited. Затем необходимо вызвать SetPresentationMode в коллекции TimedMetadataTracks элемента воспроизведения, чтобы указать системе, что приложение хочет получать события подсказки метаданных для этого элемента воспроизведения.

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 можно проверить правильность объекта TimedMetadataKind объекта TimedMetadataTrack, переданного в обработчик, чтобы узнать, являются ли метаданные субтитрами изображений. Это необходимо, если вы используете один и тот же обработчик событий cue для нескольких типов метаданных. Если связанная трасса метаданных имеет тип TimedMetadataKind.ImageSubtitle, приведите подсказку данных, содержащуюся в свойстве Cue MediaCueEventArgs, в ImageCue. Свойство SoftwareBitmap объекта ImageCue содержит представление 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) в воспроизведения мультимедиа. Это позволяет воспроизводить звуковые потоки, созданные с помощью класса SpeechSynthesizer , и обновлять пользовательский интерфейс на основе этих событий, таких как отображение текста текущего воспроизведения слова или предложения.

В примере, приведенном в этом разделе, используется переменная члена класса для хранения текстовой строки, которая будет синтезирована и воспроизведена.

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

Создайте новый экземпляр класса SpeechSynthesizer . Задайте параметры IncludeWordBoundaryMetadata и IncludeSentenceBoundaryMetadata для синтезатора значение true, чтобы указать, что метаданные должны быть включены в созданный поток мультимедиа. Вызов синтезаTextToStreamAsync для создания потока, содержащего синтезированную речь и соответствующие метаданные. Создайте 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 для регистрации событий. Лямбда-выражение используется для реализации обработчика события 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 назначается MediaPlayer для воспроизведения в MediaPlayerElement.

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

В вспомогательном методе RegisterMetadataHandlerForSpeech получите экземпляр класса TimedMetadataTrack путем индексирования в коллекцию TimedMetadataTracks объекта MediaPlaybackItem. Зарегистрируйте событие CueEntered и событие CueExited. Затем необходимо вызвать SetPresentationMode в коллекции TimedMetadataTracks элемента воспроизведения, чтобы указать системе, что приложение хочет получать события подсказки метаданных для этого элемента воспроизведения.

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 можно проверить правильность объекта TimedMetadataKind объекта TimedMetadataTrack, переданного в обработчик, чтобы узнать, является ли метаданные речью. Это необходимо, если вы используете один и тот же обработчик событий cue для нескольких типов метаданных. Если связанная дорожка метаданных имеет тип TimedMetadataKind.Speech, приведите подсказку данных, содержащуюся в свойстве Cue MediaCueEventArgs, в SpeechCue. Для подсказок речи тип подсказки речи, включенный в трек метаданных, определяется путем проверки свойства Label . Значением этого свойства будет "SpeechWord" для границ слов, "SpeechSentence" для границ предложения или "SpeechBookmark" для закладок SSML. В этом примере мы проверяем значение SpeechWord и если это значение найдено, свойства StartPositionInput и EndPositionInput в SpeechCue используются для определения расположения в входном тексте слова, который в настоящее время воспроизводится обратно. В этом примере просто выводится каждое слово в выходные данные отладки.

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 для содержимого мультимедиа, а затем создайте MediaPlaybackItem из MediaSource.

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 назначается MediaPlayer для воспроизведения в MediaPlayerElement.

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

В вспомогательном методе RegisterMetadataHandlerForChapterCues получите экземпляр класса TimedMetadataTrack путем индексирования в коллекцию TimedMetadataTracks объекта MediaPlaybackItem. Зарегистрируйте событие CueEntered и событие CueExited. Затем необходимо вызвать SetPresentationMode в коллекции TimedMetadataTracks элемента воспроизведения, чтобы указать системе, что приложение хочет получать события подсказки метаданных для этого элемента воспроизведения.

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 можно проверить правильность объекта TimedMetadataKind объекта TimedMetadataTrack, переданного в обработчик, чтобы узнать, являются ли метаданные для подсказок главы. Это необходимо, если вы используете один и тот же обработчик событий cue для нескольких типов метаданных. Если связанная дорожка метаданных имеет тип TimedMetadataKind.Chapter, приведите подсказку данных, содержащуюся в свойстве Cue MediaCueEventArgs, в ГлавуCue. Свойство Title Главы содержит название главы, которая только что была достигнута в воспроизведении.

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 трека, чтобы найти первый сигнал, имеющий начальное время больше текущего положения сеанса воспроизведения проигрывателя мультимедиа. После обнаружения правильного подсказки положение сеанса воспроизведения обновляется, а заголовок главы обновляется в пользовательском интерфейсе.

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 . Дополнительные сведения см. в разделе "Адаптивная потоковая передача". Создайте AdaptiveMediaSource для содержимого, вызвав CreateFromUriAsync или CreateFromStreamAsync. Создайте объект MediaSource, вызвав CreateFromAdaptiveMediaSource и создав MediaPlaybackItem из MediaSource.

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);

Зарегистрируйтесь для событий метаданных M3U с помощью объекта MediaPlaybackItem , созданного на предыдущем шаге. В этом примере используется вспомогательный метод 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 назначается MediaPlayer для воспроизведения в MediaPlayerElement.

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

В вспомогательном методе RegisterMetadataHandlerForEXTM3UCues получите экземпляр класса TimedMetadataTrack путем индексирования в коллекцию TimedMetadataTracks объекта MediaPlaybackItem. Проверьте свойство DispatchType отслеживания метаданных, которое будет иметь значение EXTM3U, если трек представляет комментарии M3U. Зарегистрируйте событие CueEntered и событие CueExited. Затем необходимо вызвать SetPresentationMode в коллекции TimedMetadataTracks элемента воспроизведения, чтобы указать системе, что приложение хочет получать события подсказки метаданных для этого элемента воспроизведения.

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 присвойте подсказку данных, содержащуюся в свойстве Cue MediaCueEventArgs, к DataCue. Убедитесь, что dataCue и свойство Data подсказки не имеют значения NULL. Расширенные комментарии EMU предоставляются в формате UTF-16, маленький эндиан, завершаемые строки null. Создайте dataReader для чтения данных подсказки путем вызова DataReader.FromBuffer. Задайте свойству UnicodeEncoding средства чтения значение Utf16LE, чтобы считывать данные в правильном формате. Вызов ReadString для чтения данных, указав половину длины поля данных , так как каждый символ составляет два байта в размере и вычитает один, чтобы удалить конечный пустой символ. В этом примере комментарий 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 могут регистрироваться для подсказок, соответствующих тегам ID3 в содержимом Http Live Streaming (HLS). В этом примере для воспроизведения содержимого мультимедиа используется AdaptiveMediaSource . Дополнительные сведения см. в разделе "Адаптивная потоковая передача". Создайте AdaptiveMediaSource для содержимого, вызвав CreateFromUriAsync или CreateFromStreamAsync. Создайте объект MediaSource, вызвав CreateFromAdaptiveMediaSource и создав MediaPlaybackItem из MediaSource.

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 с помощью объекта MediaPlaybackItem , созданного на предыдущем шаге. В этом примере используется вспомогательный метод 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 назначается MediaPlayer для воспроизведения в MediaPlayerElement.

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

В вспомогательном методе RegisterMetadataHandlerForID3Cues получите экземпляр класса TimedMetadataTrack путем индексирования в коллекцию TimedMetadataTracks объекта MediaPlaybackItem. Проверьте свойство DispatchType для отслеживания метаданных, которое будет иметь значение, содержащее строку GUID "15260DFFFF4943320FF49443320000F", если трек представляет теги ID3. Зарегистрируйте событие CueEntered и событие CueExited. Затем необходимо вызвать SetPresentationMode в коллекции TimedMetadataTracks элемента воспроизведения, чтобы указать системе, что приложение хочет получать события подсказки метаданных для этого элемента воспроизведения.

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 присвойте подсказку данных, содержащуюся в свойстве Cue MediaCueEventArgs, к DataCue. Убедитесь, что dataCue и свойство Data подсказки не имеют значения NULL. Расширенные комментарии EMU предоставляются в виде необработанных байтов в потоке транспорта (см . идентификатор 3). Создайте 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 могут регистрироваться для подсказок, соответствующих полям emsg в фрагментированных потоках mp4. Пример использования этих метаданных заключается в том, чтобы поставщики контента сигнализируют клиентским приложениям воспроизводить рекламу во время потоковой передачи содержимого. В этом примере для воспроизведения содержимого мультимедиа используется AdaptiveMediaSource . Дополнительные сведения см. в разделе "Адаптивная потоковая передача". Создайте AdaptiveMediaSource для содержимого, вызвав CreateFromUriAsync или CreateFromStreamAsync. Создайте объект MediaSource, вызвав CreateFromAdaptiveMediaSource и создав MediaPlaybackItem из MediaSource.

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 box с помощью объекта MediaPlaybackItem , созданного на предыдущем шаге. В этом примере используется вспомогательный метод 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 назначается MediaPlayer для воспроизведения в MediaPlayerElement.

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

В вспомогательном методе RegisterMetadataHandlerForEmsgCues получите экземпляр класса TimedMetadataTrack путем индексирования в коллекцию TimedMetadataTracks объекта MediaPlaybackItem. Проверьте свойство DispatchType трека метаданных, которое будет иметь значение emsg:mp4, если трек представляет поля emsg. Зарегистрируйте событие CueEntered и событие CueExited. Затем необходимо вызвать SetPresentationMode в коллекции TimedMetadataTracks элемента воспроизведения, чтобы указать системе, что приложение хочет получать события подсказки метаданных для этого элемента воспроизведения.

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 присвойте подсказку данных, содержащуюся в свойстве Cue MediaCueEventArgs, к DataCue. Убедитесь, что объект DataCue не имеет значения NULL. Правильность поля emsg предоставляется конвейером мультимедиа в качестве настраиваемых свойств в коллекции свойств объекта DataCue. В этом примере выполняется попытка извлечь несколько различных значений свойств с помощью метода TryGetValue. Если этот метод возвращает значение NULL, это означает, что запрошенное правильное значение отсутствует в поле emsg, поэтому вместо этого устанавливается значение по умолчанию.

В следующей части примера показан сценарий, в котором активируется воспроизведение рекламы, что является случаем, когда свойство scheme_id_uri , полученное на предыдущем шаге, имеет значение urn:scte:scte:scte35:2013:xml. Дополнительные сведения см. в статье https://dashif.org/identifiers/event_schemes/. Обратите внимание, что стандарт рекомендует отправлять этот emsg несколько раз для избыточности, поэтому в этом примере содержится список идентификаторов emsg, которые уже обработаны и обрабатываются только новые сообщения. Создайте dataReader для чтения данных подсказки путем вызова DataReader.FromBuffer и задайте для кодировки UTF-8, задав свойство ЮникодEncoding, а затем считывает данные. В этом примере полезные данные сообщения записываются в выходные данные отладки. Реальное приложение будет использовать полезные данные для планирования воспроизведения рекламы.

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}");
            }
        }
    }
}