Partilhar via


Indicações de metadados programados com suporte do sistema

Este artigo descreve como aproveitar vários formatos de metadados cronometrados que podem ser inseridos em arquivos de mídia ou fluxos. Os aplicativos UWP podem se registrar para eventos gerados pelo pipeline de mídia durante a reprodução sempre que essas indicações de metadados forem encontradas. Usando a classe DataCue , os aplicativos podem implementar suas próprias indicações de metadados personalizadas, 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
  • Dicas de fala, incluindo limites de palavras, limites de frases e marcadores SSML (Speech Synthesis Markup Language)
  • Dicas de capítulo
  • Comentários estendidos do M3U
  • Tags ID3
  • Caixas mp4 emsg fragmentadas

Este artigo se baseia nos conceitos discutidos no artigo Itens de mídia, listas de reprodução e faixas, que inclui as noções básicas de como trabalhar com as classes MediaSource, MediaPlaybackItem e TimedMetadataTrack e diretrizes gerais para usar 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 para os eventos TimedMetadataTrack.CueEntered e TimedMetadataTrack.CueExited para as faixas de metadados cronometradas que você deseja usar.
  4. No manipulador de eventos CueEntered , atualize sua interface do usuário com base nos metadados passados nos argumentos de evento. Você pode atualizar a interface do usuário novamente, para remover o texto da legenda atual, 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 se registrar para o evento CueEntered para faixas de metadados que têm o valor TimedMetadataKind.ImageSubtitle, mas não para faixas que têm o valor TimedMetadataKind.Speech. Ou, em vez disso, você pode registrar um manipulador para todos os tipos de faixa de metadados e, em seguida, verificar o valor TimedMetadataKind dentro do manipulador CueEntered para determinar qual ação tomar em resposta à indicação.

Legendas baseadas em imagem

A partir do Windows 10, versão 1703, os aplicativos UWP podem dar suporte a legendas externas baseadas em imagens no formato VobSub. Para usar esse recurso, primeiro crie um objeto MediaSource para o conteúdo de mídia para o qual as legendas das imagens 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 origem. Crie um MediaPlaybackItem 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 se registrar para 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 faixas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as faixas de metadados podem estar disponíveis quando o item de reprodução é resolvido inicialmente, portanto, fora do manipulador TimedMetadataTracksChanged , também percorremos as faixas 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 indicaçã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 indicaçã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 da 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);
            });
        }
    }
}

Dicas 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 indicadores SSML (Speech Synthesis Markup Language) na 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 execuçã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 que contém a fala sintetizada e os metadados correspondentes. Crie um MediaSource e um MediaPlaybackItem 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 se registrar para 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 faixas 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 percorremos as faixas 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 em 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 indicaçã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 indicação de dados para vários tipos de metadados. Se a faixa de metadados associada for do tipo TimedMetadataKind.Speech, converta a indicação de dados contida na propriedade Cue do MediaCueEventArgs em um SpeechCue. Para indicações de fala, o tipo de indicação de fala incluído na faixa de metadados é determinado verificando a propriedade Label . O valor dessa propriedade será "SpeechWord" para limites de palavras, "SpeechSentence" para limites de frases ou "SpeechBookmark" para indicadores 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 a localização dentro do texto de entrada da palavra que está sendo reproduzida no momento. Este exemplo simplesmente gera 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 de capítulo

A partir do Windows 10, versão 1703, os aplicativos UWP podem se registrar para indicações que correspondem 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 de capítulo usando o objeto MediaPlaybackItem criado na etapa anterior. Este exemplo usa um método auxiliar, RegisterMetadataHandlerForChapterCues, para se registrar para 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 faixas 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 percorremos as faixas 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 de capítulo, 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 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 indicaçã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 indicação de dados para vários tipos de metadados. Se a faixa de metadados associada 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 acessado 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 propriedade TimedMetadataKind do valor TimedMetadataTrack de TimedMetadataKind.Chapter. Se uma faixa de capítulo for encontrada, o método percorrerá cada indicação na coleção Cues da faixa para localizar a primeira indicação que tenha um StartTime maior que a posição atual da sessão de reprodução do reprodutor de mídia. Depois que a indicação correta é encontrada, a posição da sessão de reprodução é atualizada e o título do capítulo é 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 indicações que correspondem 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 se registrar para 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 faixas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as faixas de metadados podem estar disponíveis quando o item de reprodução é resolvido inicialmente, portanto, fora do manipulador TimedMetadataTracksChanged , também percorremos as faixas 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 se registrar para os eventos de metadados M3U, 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 RegisterMetadataHandlerForEXTM3UCues, obtenha uma instância da classe TimedMetadataTrack indexando-a 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 indicaçã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 indicação de dados contida na propriedade Cue do MediaCueEventArgs em um DataCue. Verifique se o DataCue e a propriedade Data da indicação não são nulos. Os comentários estendidos da UEM são fornecidos na forma de cadeias de caracteres UTF-16, little endian, terminadas em nulo. 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, pois 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 indicações 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 se registrar para 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 faixas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as faixas de metadados podem estar disponíveis quando o item de reprodução é resolvido inicialmente, portanto, fora do manipulador TimedMetadataTracksChanged , também percorremos as faixas 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 em 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 que contém 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 indicaçã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 indicação de dados contida na propriedade Cue do MediaCueEventArgs em um DataCue. Verifique se o DataCue e a propriedade Data da indicação não são nulos. Os comentários estendidos da EMU são fornecidos na forma de bytes brutos no fluxo de transporte (consulte ID3). Crie um novo DataReader para ler os dados de sinalização chamando DataReader.FromBuffer. Neste exemplo, os valores de cabeçalho da tag 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 mp4 emsg fragmentadas

A partir do Windows 10, versão 1703, os aplicativos UWP podem se registrar para indicações que correspondem a caixas emsg em fluxos mp4 fragmentados. Um exemplo de uso desse tipo de metadados é que os provedores de conteúdo sinalizem aplicativos cliente para reproduzir um anúncio durante o conteúdo de transmissão ao vivo. 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 se registrar para 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 faixas de metadados associadas a um MediaPlaybackItem. Em alguns casos, as faixas de metadados podem estar disponíveis quando o item de reprodução é resolvido inicialmente, portanto, fora do manipulador TimedMetadataTracksChanged , também percorremos as faixas 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 em 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 indicaçã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 indicaçã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, significa que a propriedade solicitada não está presente na caixa emsg, portanto, um valor padrão é definido.

A próxima parte do exemplo ilustra o cenário em que a reprodução do anúncio é disparada, 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, consulte https://dashif.org/identifiers/event_schemes/. Observe que o padrão recomenda enviar esse 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 indicaçã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}");
            }
        }
    }
}