Indicações de metadados programados com suporte do sistema

Este artigo descreve como tirar proveito de vários formatos de metadados cronometrados que podem ser incorporados em arquivos de mídia ou fluxos. Os aplicativos UWP podem se registrar para eventos que são gerados pelo pipeline de mídia durante a reprodução sempre que essas pistas de metadados são encontradas. Usando a classe DataCue, os aplicativos podem implementar suas próprias dicas de metadados personalizados, mas este artigo se concentra em vários padrões de metadados que são detectados automaticamente pelo pipeline de mídia, incluindo:

  • Legendas baseadas em imagem no formato VobSub
  • Indicações de fala, incluindo limites de palavras, limites de frases e marcadores SSML (Speech Synthesis Markup Language)
  • Dicas do capítulo
  • Comentários estendidos do M3U
  • Tags ID3
  • Caixas emsg mp4 fragmentadas

Este artigo baseia-se nos conceitos discutidos no artigo Itens de mídia, listas de reprodução e faixas, que inclui as noções básicas de trabalho com as classes MediaSource, MediaPlaybackItem e TimedMetadataTrack e orientações gerais para o uso de metadados cronometrados em seu aplicativo.

As etapas básicas de implementação são as mesmas para todos os diferentes tipos de metadados cronometrados descritos neste artigo:

  1. Crie um MediaSource e, em seguida, um MediaPlaybackItem para o conteúdo a ser reproduzido.
  2. Registre-se para o evento MediaPlaybackItem.TimedMetadataTracksChanged , que ocorre quando as subfaixas do item de mídia são resolvidas pelo pipeline de mídia.
  3. Registre-se nos eventos TimedMetadataTrack.CueEntered e TimedMetadataTrack.CueExited para as faixas de metadados cronometradas que você deseja usar.
  4. No manipulador de eventos CueEnter , atualize sua interface do usuário com base nos metadados passados nos args de eventos. Você pode atualizar a interface do usuário novamente, para remover o texto atual da legenda , por exemplo, no evento CueExited .

Neste artigo, a manipulação de cada tipo de metadados é mostrada como um cenário distinto, mas é possível manipular (ou ignorar) diferentes tipos de metadados usando principalmente código compartilhado. Você pode verificar a propriedade TimedMetadataKind do objeto TimedMetadataTrack em vários pontos do processo. Assim, por exemplo, você pode optar por registrar o evento CueEntered para trilhas de metadados que têm o valor TimedMetadataKind.ImageSubtitle, mas não para trilhas que têm o valor TimedMetadataKind.Speech. Ou, em vez disso, você pode registrar um manipulador para todos os tipos de controle de metadados e, em seguida, verificar o valor TimedMetadataKind dentro do manipulador CueEntered para determinar qual ação executar em resposta à indicação.

Legendas baseadas em imagem

A partir do Windows 10, versão 1703, os aplicativos UWP podem oferecer suporte a legendas externas baseadas em imagem no formato VobSub. Para usar esse recurso, primeiro crie um objeto MediaSource para o conteúdo de mídia para o qual as legendas de imagem serão exibidas. Em seguida, crie um objeto TimedTextSource chamando CreateFromUriWithIndex ou CreateFromStreamWithIndex, passando o Uri do arquivo .sub que contém os dados da imagem da legenda e o arquivo .idx que contém as informações de tempo para as legendas. Adicione o TimedTextSource ao MediaSource adicionando-o à coleção ExternalTimedTextSources da fonte. Crie um MediaPlaybackItem a partir do 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);

Registre-se para os eventos de metadados de legenda de imagem usando o objeto MediaPlaybackItem criado na etapa anterior. Este exemplo usa um método auxiliar, RegisterMetadataHandlerForImageSubtitles, para registrar os eventos. Uma expressão lambda é usada para implementar um manipulador para o evento TimedMetadataTracksChanged, que ocorre quando o sistema detecta uma alteração nas trilhas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as faixas de metadados podem estar disponíveis quando o item de reprodução é inicialmente resolvido, portanto, fora do manipulador TimedMetadataTracksChanged , também fazemos um loop pelas trilhas de metadados disponíveis e chamamos 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);
}

Depois de se registrar para os eventos de metadados de legenda de imagem, o MediaItem é atribuído a um MediaPlayer para reprodução em um MediaPlayerElement.

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

No método auxiliar RegisterMetadataHandlerForImageSubtitles, obtenha uma instância da classe TimedMetadataTrack indexando na coleção TimedMetadataTracks do MediaPlaybackItem. Registre-se para o evento CueEntered e o evento CueExited. Em seguida, você deve chamar SetPresentationMode na coleção TimedMetadataTracks do item de reprodução para instruir o sistema de que o aplicativo deseja receber eventos de sinalização de metadados para esse item de reprodução.

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

}

No manipulador do evento CueEntered, você pode verificar a propriedade TimedMetadataKind do objeto TimedMetadataTrack passado para o manipulador para ver se os metadados são para legendas de imagem. Isso será necessário se você estiver usando o mesmo manipulador de eventos de sinalização de dados para vários tipos de metadados. Se a faixa de metadados associada for do tipo TimedMetadataKind.ImageSubtitle, converta a indicação de dados contida na propriedade Cue do MediaCueEventArgs em um ImageCue. A propriedade SoftwareBitmap do ImageCue contém uma representação SoftwareBitmap da imagem de legenda. Crie um SoftwareBitmapSource e chame SetBitmapAsync para atribuir a imagem a um controle de imagem XAML. As propriedades Extent e Position do ImageCue fornecem informações sobre o tamanho e a posição da imagem da legenda.

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

Pistas de fala

A partir do Windows 10, versão 1703, os aplicativos UWP podem se registrar para receber eventos em resposta a limites de palavras, limites de frases e marcadores SSML (Speech Synthesis Markup Language) em mídia reproduzida. Isso permite que você reproduza fluxos de áudio gerados com a classe SpeechSynthesizer e atualize sua interface do usuário com base nesses eventos, como exibir o texto da palavra ou frase em reprodução no momento.

O exemplo mostrado nesta seção usa uma variável de membro de classe para armazenar uma cadeia de caracteres de texto que será sintetizada e reproduzida.

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

Crie uma nova instância da classe SpeechSynthesizer . Defina as opções IncludeWordBoundaryMetadata e IncludeSentenceBoundaryMetadata para o sintetizador como true para especificar que os metadados devem ser incluídos no fluxo de mídia gerado. Chame SynthesizeTextToStreamAsync para gerar um fluxo contendo a fala sintetizada e os metadados correspondentes. Crie um MediaSource e um MediaPlaybackItem a partir do fluxo sintetizado.

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

Registre-se para os eventos de metadados de fala usando o objeto MediaPlaybackItem . Este exemplo usa um método auxiliar, RegisterMetadataHandlerForSpeech, para registrar os eventos. Uma expressão lambda é usada para implementar um manipulador para o evento TimedMetadataTracksChanged, que ocorre quando o sistema detecta uma alteração nas trilhas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as faixas de metadados podem estar disponíveis quando o item de reprodução é inicialmente resolvido, portanto, fora do manipulador TimedMetadataTracksChanged , também fazemos um loop pelas trilhas de metadados disponíveis e chamamos 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);
}

Depois de se registrar para os eventos de metadados de fala, o MediaItem é atribuído a um MediaPlayer para reprodução dentro de um MediaPlayerElement.

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

No método auxiliar RegisterMetadataHandlerForSpeech, obtenha uma instância da classe TimedMetadataTrack indexando na coleção TimedMetadataTracks do MediaPlaybackItem. Registre-se para o evento CueEntered e o evento CueExited. Em seguida, você deve chamar SetPresentationMode na coleção TimedMetadataTracks do item de reprodução para instruir o sistema de que o aplicativo deseja receber eventos de sinalização de metadados para esse item de reprodução.

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

}

No manipulador do evento CueEntered, você pode verificar a propriedade TimedMetadataKind do objeto TimedMetadataTrack passado para o manipulador para ver se os metadados são fala. Isso será necessário se você estiver usando o mesmo manipulador de eventos de sinalização de dados para vários tipos de metadados. Se o controle de metadados associado for do tipo TimedMetadataKind.Speech, converta a sinalização de dados contida na propriedade Cue do MediaCueEventArgs em um SpeechCue. Para indicações de fala, o tipo de sinalização de fala incluído no controle de metadados é determinado verificando a propriedade Label . O valor dessa propriedade será "SpeechWord" para limites de palavras, "SpeechSentence" para limites de sentenças ou "SpeechBookmark" para marcadores SSML. Neste exemplo, verificamos o valor "SpeechWord" e, se esse valor for encontrado, as propriedades StartPositionInInput e EndPositionInInput do SpeechCue serão usadas para determinar o local dentro do texto de entrada da palavra que está sendo reproduzida no momento. Este exemplo simplesmente gera a saída de cada palavra para a saída de depuração.

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

Dicas do capítulo

A partir do Windows 10, versão 1703, os aplicativos UWP podem se registrar para obter dicas que correspondam a capítulos em um item de mídia. Para usar esse recurso, crie um objeto MediaSource para o conteúdo de mídia e, em seguida, crie um MediaPlaybackItem do MediaSource.

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

Registre-se para os eventos de metadados do capítulo usando o objeto MediaPlaybackItem criado na etapa anterior. Este exemplo usa um método auxiliar, RegisterMetadataHandlerForChapterCues, para registrar os eventos. Uma expressão lambda é usada para implementar um manipulador para o evento TimedMetadataTracksChanged, que ocorre quando o sistema detecta uma alteração nas trilhas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as faixas de metadados podem estar disponíveis quando o item de reprodução é inicialmente resolvido, portanto, fora do manipulador TimedMetadataTracksChanged , também fazemos um loop pelas trilhas de metadados disponíveis e chamamos 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);
}

Depois de se registrar para os eventos de metadados do capítulo, o MediaItem é atribuído a um MediaPlayer para reprodução dentro de um MediaPlayerElement.

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

No método auxiliar RegisterMetadataHandlerForChapterCues, obtenha uma instância da classe TimedMetadataTrack indexando na coleção TimedMetadataTracks do MediaPlaybackItem. Registre-se para o evento CueEntered e o evento CueExited. Em seguida, você deve chamar SetPresentationMode na coleção TimedMetadataTracks do item de reprodução para instruir o sistema de que o aplicativo deseja receber eventos de sinalização de metadados para esse item de reprodução.

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

No manipulador do evento CueEntered, você pode verificar a propriedade TimedMetadataKind do objeto TimedMetadataTrack passado para o manipulador para ver se os metadados são para indicações de capítulo. Isso será necessário se você estiver usando o mesmo manipulador de eventos de sinalização de dados para vários tipos de metadados. Se o controle de metadados associado for do tipo TimedMetadataKind.Chapter, converta a indicação de dados contida na propriedade Cue do MediaCueEventArgs em um ChapterCue. A propriedade Title do ChapterCue contém o título do capítulo que acabou de ser alcançado na reprodução.

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

Procure o próximo capítulo usando dicas de capítulo

Além de receber notificações quando o capítulo atual muda em um item de jogo, você também pode usar dicas de capítulo para buscar o próximo capítulo dentro de um item de jogo. O método de exemplo mostrado abaixo usa como argumentos um MediaPlayer e um MediaPlaybackItem que representam o item de mídia em reprodução no momento. A coleção TimedMetadataTracks é pesquisada para ver se alguma das faixas tem a propriedade TimedMetadataKind do valor TimedMetadataTrack de TimedMetadataKind.Chapter. Se uma faixa de capítulo for encontrada, o método percorrerá cada pista na coleção Cues da faixa para encontrar a primeira pista que tenha um StartTime maior do que a Posição atual da sessão de reprodução do media player. Quando a pista correta for encontrada, a posição da sessão de reprodução será atualizada e o título do capítulo será atualizado na interface do usuário.

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

Comentários estendidos do M3U

A partir do Windows 10, versão 1703, os aplicativos UWP podem se registrar para obter dicas que correspondam a comentários em um arquivo de manifesto M3U estendido. Este exemplo usa AdaptiveMediaSource para reproduzir o conteúdo de mídia. Para obter mais informações, consulte Streaming adaptável. Crie um AdaptiveMediaSource para o conteúdo chamando CreateFromUriAsync ou CreateFromStreamAsync. Crie um objeto MediaSource chamando CreateFromAdaptiveMediaSource e, em seguida, crie um MediaPlaybackItem do 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);

Registre-se para os eventos de metadados M3U usando o objeto MediaPlaybackItem criado na etapa anterior. Este exemplo usa um método auxiliar, RegisterMetadataHandlerForEXTM3UCues, para registrar os eventos. Uma expressão lambda é usada para implementar um manipulador para o evento TimedMetadataTracksChanged, que ocorre quando o sistema detecta uma alteração nas trilhas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as faixas de metadados podem estar disponíveis quando o item de reprodução é inicialmente resolvido, portanto, fora do manipulador TimedMetadataTracksChanged , também fazemos um loop pelas trilhas de metadados disponíveis e chamamos 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);
}

Depois de registrar para os eventos de metadados M3U, o MediaItem é atribuído a um MediaPlayer para reprodução dentro de um MediaPlayerElement.

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

No método auxiliar RegisterMetadataHandlerForEXTM3UCues, obtenha uma instância da classe TimedMetadataTrack indexando na coleção TimedMetadataTracks do MediaPlaybackItem. Verifique a propriedade DispatchType da faixa de metadados, que terá um valor de "EXTM3U" se a faixa representar comentários M3U. Registre-se para o evento CueEntered e o evento CueExited. Em seguida, você deve chamar SetPresentationMode na coleção TimedMetadataTracks do item de reprodução para instruir o sistema de que o aplicativo deseja receber eventos de sinalização de metadados para esse item de reprodução.

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

No manipulador do evento CueEntered, converta a sinalização de dados contida na propriedade Cue do MediaCueEventArgs em um DataCue. Verifique se o DataCue e a propriedade Data da sinalização não são nulos. Os comentários estendidos da UEM são fornecidos na forma de UTF-16, pequenas cadeias de caracteres terminadas nulas. Crie um novo DataReader para ler os dados de sinalização chamando DataReader.FromBuffer. Defina a propriedade UnicodeEncoding do leitor como Utf16LE para ler os dados no formato correto. Chame ReadString para ler os dados, especificando metade do comprimento do campo Dados, porque cada caractere tem dois bytes de tamanho, e subtraia um para remover o caractere nulo à direita. Neste exemplo, o comentário M3U é simplesmente gravado na saída de depuração.

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

Tags ID3

A partir do Windows 10, versão 1703, os aplicativos UWP podem se registrar para obter dicas que correspondem a marcas ID3 no conteúdo HLS (Http Live Streaming). Este exemplo usa AdaptiveMediaSource para reproduzir o conteúdo de mídia. Para obter mais informações, consulte Streaming adaptável. Crie um AdaptiveMediaSource para o conteúdo chamando CreateFromUriAsync ou CreateFromStreamAsync. Crie um objeto MediaSource chamando CreateFromAdaptiveMediaSource e, em seguida, crie um MediaPlaybackItem do 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);

Registre-se para os eventos de marca ID3 usando o objeto MediaPlaybackItem criado na etapa anterior. Este exemplo usa um método auxiliar, RegisterMetadataHandlerForID3Cues, para registrar os eventos. Uma expressão lambda é usada para implementar um manipulador para o evento TimedMetadataTracksChanged, que ocorre quando o sistema detecta uma alteração nas trilhas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as trilhas de metadados podem estar disponíveis quando o item de reprodução é inicialmente resolvido, portanto, fora do manipulador TimedMetadataTracksChanged , também fazemos um loop pelas trilhas de metadados disponíveis e chamamos 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);

Depois de se registrar para os eventos de metadados ID3, o MediaItem é atribuído a um MediaPlayer para reprodução dentro de um MediaPlayerElement.

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

No método auxiliar RegisterMetadataHandlerForID3Cues, obtenha uma instância da classe TimedMetadataTrack indexando na coleção TimedMetadataTracks do MediaPlaybackItem. Verifique a propriedade DispatchType da faixa de metadados, que terá um valor contendo a cadeia de caracteres GUID "15260DFFFF49443320FF49443320000F" se a faixa representar marcas ID3. Registre-se para o evento CueEntered e o evento CueExited. Em seguida, você deve chamar SetPresentationMode na coleção TimedMetadataTracks do item de reprodução para instruir o sistema de que o aplicativo deseja receber eventos de sinalização de metadados para esse item de reprodução.

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

No manipulador do evento CueEntered, converta a sinalização de dados contida na propriedade Cue do MediaCueEventArgs em um DataCue. Verifique se o DataCue e a propriedade Data da sinalização não são nulos. Os comentários alargados da UEM são fornecidos sob a forma de bytes brutos no fluxo de transporte (ver ID3). Crie um novo DataReader para ler os dados de sinalização chamando DataReader.FromBuffer. Neste exemplo, os valores de cabeçalho da marca ID3 são lidos dos dados de sinalização e gravados na saída de depuração.

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

Caixas emsg mp4 fragmentadas

A partir do Windows 10, versão 1703, os aplicativos UWP podem se registrar para obter dicas que correspondem a caixas emsg em fluxos mp4 fragmentados. Um exemplo de uso desse tipo de metadados é que os provedores de conteúdo sinalizem aos aplicativos cliente para reproduzir um anúncio durante a transmissão ao vivo de conteúdo. Este exemplo usa AdaptiveMediaSource para reproduzir o conteúdo de mídia. Para obter mais informações, consulte Streaming adaptável. Crie um AdaptiveMediaSource para o conteúdo chamando CreateFromUriAsync ou CreateFromStreamAsync. Crie um objeto MediaSource chamando CreateFromAdaptiveMediaSource e, em seguida, crie um MediaPlaybackItem do 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);

Registre-se para os eventos da caixa emsg usando o objeto MediaPlaybackItem criado na etapa anterior. Este exemplo usa um método auxiliar, RegisterMetadataHandlerForEmsgCues, para registrar os eventos. Uma expressão lambda é usada para implementar um manipulador para o evento TimedMetadataTracksChanged, que ocorre quando o sistema detecta uma alteração nas trilhas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as faixas de metadados podem estar disponíveis quando o item de reprodução é inicialmente resolvido, portanto, fora do manipulador TimedMetadataTracksChanged , também fazemos um loop pelas trilhas de metadados disponíveis e chamamos 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);

Depois de se registrar para os eventos de metadados da caixa emsg, o MediaItem é atribuído a um MediaPlayer para reprodução dentro de um MediaPlayerElement.

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

No método auxiliar RegisterMetadataHandlerForEmsgCues, obtenha uma instância da classe TimedMetadataTrack indexando na coleção TimedMetadataTracks do MediaPlaybackItem. Verifique a propriedade DispatchType da faixa de metadados, que terá um valor de "emsg:mp4" se a faixa representar caixas emsg. Registre-se para o evento CueEntered e o evento CueExited. Em seguida, você deve chamar SetPresentationMode na coleção TimedMetadataTracks do item de reprodução para instruir o sistema de que o aplicativo deseja receber eventos de sinalização de metadados para esse item de reprodução.

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

No manipulador do evento CueEntered, converta a sinalização de dados contida na propriedade Cue do MediaCueEventArgs em um DataCue. Verifique se o objeto DataCue não é nulo. As propriedades da caixa emsg são fornecidas pelo pipeline de mídia como propriedades personalizadas na coleção Properties do objeto DataCue. Este exemplo tenta extrair vários valores de propriedade diferentes usando o método TryGetValue . Se esse método retornar null, isso significa que o propery solicitado não está presente na caixa emsg, portanto, um valor padrão é definido em vez disso.

A próxima parte do exemplo ilustra o cenário em que a reprodução de anúncios é acionada, que é o caso quando a propriedade scheme_id_uri , obtida na etapa anterior, tem um valor de "urn:scte:scte35:2013:xml". Para obter mais informações, confira https://dashif.org/identifiers/event_schemes/. Observe que o padrão recomenda o envio desse emsg várias vezes para redundância, portanto, este exemplo mantém uma lista dos IDs do emsg que já foram processados e processa apenas novas mensagens. Crie um novo DataReader para ler os dados de sinalização chamando DataReader.FromBuffer e defina a codificação como UTF-8 definindo a propriedade UnicodeEncoding e, em seguida, leia os dados. Neste exemplo, a carga da mensagem é gravada na saída de depuração. Um aplicativo real usaria os dados de carga útil para agendar a reprodução de um anúncio.

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