Indicaciones de metadatos temporizados admitidos por el sistema

En este artículo se describe cómo aprovechar varios formatos de metadatos con tiempo que se pueden incrustar en archivos multimedia o secuencias. Las aplicaciones para UWP pueden registrarse para los eventos que genera la canalización multimedia durante la reproducción cada vez que se encuentran estas indicaciones de metadatos. Con la clase DataCue , las aplicaciones pueden implementar sus propias indicaciones de metadatos personalizadas, pero este artículo se centra en varios estándares de metadatos detectados automáticamente por la canalización multimedia, entre los que se incluyen:

  • Subtítulos basados en imágenes en formato VobSub
  • Indicaciones de voz, incluidos los límites de palabras, los límites de oraciones y los marcadores del lenguaje de marcado de síntesis de voz (SSML)
  • Indicaciones del capítulo
  • Comentarios extendidos de M3U
  • Etiquetas ID3
  • Cuadros mp4 emsg fragmentados

Este artículo se basa en los conceptos descritos en el artículo Elementos multimedia, listas de reproducción y pistas, que incluye los conceptos básicos de trabajar con las clases MediaSource, MediaPlaybackItem y TimedMetadataTrack e instrucciones generales para usar metadatos con tiempo en la aplicación.

Los pasos de implementación básicos son los mismos para todos los distintos tipos de metadatos con tiempo descritos en este artículo:

  1. Cree un objeto MediaSource y, a continuación, un objeto MediaPlaybackItem para que se reproduzca el contenido.
  2. Regístrese para el evento MediaPlaybackItem.TimedMetadataTracksChanged , que se produce a medida que la canalización multimedia resuelve las subrutas del elemento multimedia.
  3. Regístrese para los eventos TimedMetadataTrack.CueEntered y TimedMetadataTrack.CueExited para las pistas de metadatos con tiempo que desea usar.
  4. En el controlador de eventos CueEntered , actualice la interfaz de usuario en función de los metadatos pasados en los argumentos del evento. Puede volver a actualizar la interfaz de usuario para quitar el texto del subtítulo actual, por ejemplo, en el evento CueExited .

En este artículo, el control de cada tipo de metadatos se muestra como un escenario distinto, pero es posible controlar (o omitir) distintos tipos de metadatos mediante código compartido principalmente. Puede comprobar la propiedad TimedMetadataKind del objeto TimedMetadataTrack en varios puntos del proceso. Por ejemplo, puede optar por registrarse para el evento CueEntered para las pistas de metadatos que tienen el valor TimedMetadataKind.ImageSubtitle, pero no para pistas que tengan el valor TimedMetadataKind.Speech. O, en su lugar, podría registrar un controlador para todos los tipos de seguimiento de metadatos y, a continuación, comprobar el valor TimedMetadataKind dentro del controlador CueEntered para determinar qué acción realizar en respuesta a la indicación.

Subtítulos basados en imágenes

A partir de Windows 10, versión 1703, las aplicaciones para UWP pueden admitir subtítulos externos basados en imágenes en formato VobSub. Para usar esta característica, cree primero un objeto MediaSource para el contenido multimedia para el que se mostrarán subtítulos de imagen. A continuación, cree un objeto TimedTextSource llamando a CreateFromUriWithIndex o CreateFromStreamWithIndex, pasando el URI del archivo .sub que contiene los datos de la imagen de subtítulo y el archivo .idx que contiene la información de tiempo de los subtítulos. Agregue TimedTextSource a MediaSource agregándolo a la colección ExternalTimedTextSources del origen. Cree un objeto MediaPlaybackItem desde 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);

Regístrese para los eventos de metadatos de subtítulos de imagen mediante el objeto MediaPlaybackItem creado en el paso anterior. En este ejemplo se usa un método auxiliar, RegisterMetadataHandlerForImageSubtitles, para registrarse para los eventos. Se usa una expresión lambda para implementar un controlador para el evento TimedMetadataTracksChanged , que se produce cuando el sistema detecta un cambio en las pistas de metadatos asociadas a un objeto MediaPlaybackItem. En algunos casos, las pistas de metadatos pueden estar disponibles cuando el elemento de reproducción se resuelve inicialmente, por lo que fuera del controlador TimedMetadataTracksChanged , también recorremos las pistas de metadatos disponibles y llamamos a 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);
}

Después de registrarse para los eventos de metadatos de subtítulos de imagen, el objeto MediaItem se asigna a un Objeto MediaPlayer para su reproducción dentro de un objeto MediaPlayerElement.

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

En el método auxiliar RegisterMetadataHandlerForImageSubtitles, obtenga una instancia de la clase TimedMetadataTrack indizando en la colección TimedMetadataTracks del objeto MediaPlaybackItem. Regístrese para el evento CueEntered y el evento CueExited. A continuación, debes llamar a SetPresentationMode en la colección TimedMetadataTracks del elemento de reproducción para indicar al sistema que la aplicación quiere recibir eventos de indicación de metadatos para este elemento de reproducción.

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

}

En el controlador del evento CueEntered, puede comprobar la propiedad TimedMetadataKind del objeto TimedMetadataTrack pasado al controlador para ver si los metadatos son para subtítulos de imagen. Esto es necesario si usa el mismo controlador de eventos de indicación de datos para varios tipos de metadatos. Si la pista de metadatos asociada es de tipo TimedMetadataKind.ImageSubtitle, convierta la indicación de datos contenida en la propiedad Cue de MediaCueEventArgs en un ImageCue. La propiedad SoftwareBitmap de ImageCue contiene una representación SoftwareBitmap de la imagen de subtítulo. Crea un SoftwareBitmapSource y llama a SetBitmapAsync para asignar la imagen a un control Imagen XAML. Las propiedades Extent y Position de ImageCue proporcionan información sobre el tamaño y la posición de la imagen del subtítulo.

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

Indicaciones de voz

A partir de Windows 10, versión 1703, las aplicaciones para UWP se pueden registrar para recibir eventos en respuesta a los límites de palabras, los límites de oraciones y los marcadores del lenguaje de marcado de síntesis de voz (SSML) en medios reproducidos. Esto le permite reproducir secuencias de audio generadas con la clase SpeechSynthesizer y actualizar la interfaz de usuario en función de estos eventos, como mostrar el texto de la palabra o oración que se está reproduciendo actualmente.

En el ejemplo que se muestra en esta sección se usa una variable miembro de clase para almacenar una cadena de texto que se sintetizará y reproducirá.

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

Cree una nueva instancia de la clase SpeechSynthesizer . Establezca las opciones IncludeWordBoundaryMetadata e IncludeSentenceBoundaryMetadata del sintetizador en true para especificar que los metadatos deben incluirse en la secuencia de medios generada. Llame a SynthesizeTextToStreamAsync para generar una secuencia que contenga la voz sintetizada y los metadatos correspondientes. Cree un objeto MediaSource y un objeto MediaPlaybackItem a partir de la secuencia sintetizada.

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

Regístrese para los eventos de metadatos de voz mediante el objeto MediaPlaybackItem . En este ejemplo se usa un método auxiliar, RegisterMetadataHandlerForSpeech, para registrarse para los eventos. Se usa una expresión lambda para implementar un controlador para el evento TimedMetadataTracksChanged , que se produce cuando el sistema detecta un cambio en las pistas de metadatos asociadas a un objeto MediaPlaybackItem. En algunos casos, las pistas de metadatos pueden estar disponibles cuando el elemento de reproducción se resuelve inicialmente, por lo que fuera del controlador TimedMetadataTracksChanged , también recorremos las pistas de metadatos disponibles y llamamos a 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);
}

Después de registrarse para los eventos de metadatos de voz, el objeto MediaItem se asigna a mediaPlayer para su reproducción dentro de un objeto MediaPlayerElement.

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

En el método auxiliar RegisterMetadataHandlerForSpeech, obtenga una instancia de la clase TimedMetadataTrack indizando en la colección TimedMetadataTracks del objeto MediaPlaybackItem. Regístrese para el evento CueEntered y el evento CueExited. A continuación, debes llamar a SetPresentationMode en la colección TimedMetadataTracks del elemento de reproducción para indicar al sistema que la aplicación quiere recibir eventos de indicación de metadatos para este elemento de reproducción.

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

}

En el controlador del evento CueEntered, puede comprobar la propiedad TimedMetadataKind del objeto TimedMetadataTrack pasado al controlador para ver si los metadatos son voz. Esto es necesario si usa el mismo controlador de eventos de indicación de datos para varios tipos de metadatos. Si la pista de metadatos asociada es de tipo TimedMetadataKind.Speech, convierta la indicación de datos contenida en la propiedad Cue de MediaCueEventArgs a speechCue. Para las indicaciones de voz, el tipo de indicación de voz incluida en la pista de metadatos se determina comprobando la propiedad Label . El valor de esta propiedad será "SpeechWord" para los límites de palabras, "SpeechSentence" para los límites de oración o "SpeechBookmark" para los marcadores SSML. En este ejemplo, comprobamos el valor "SpeechWord" y, si se encuentra este valor, las propiedades StartPositionInput y EndPositionInput de SpeechCue se usan para determinar la ubicación dentro del texto de entrada de la palabra que se está reproduciéndose actualmente. Este ejemplo simplemente genera cada palabra en la salida de depuración.

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

Indicaciones del capítulo

A partir de Windows 10, versión 1703, las aplicaciones para UWP pueden registrarse para obtener indicaciones que corresponden a capítulos dentro de un elemento multimedia. Para usar esta característica, cree un objeto MediaSource para el contenido multimedia y, a continuación, cree un objeto MediaPlaybackItem desde MediaSource.

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

Regístrese para los eventos de metadatos del capítulo mediante el objeto MediaPlaybackItem creado en el paso anterior. En este ejemplo se usa un método auxiliar RegisterMetadataHandlerForChapterCues para registrarse para los eventos. Se usa una expresión lambda para implementar un controlador para el evento TimedMetadataTracksChanged , que se produce cuando el sistema detecta un cambio en las pistas de metadatos asociadas a un objeto MediaPlaybackItem. En algunos casos, las pistas de metadatos pueden estar disponibles cuando el elemento de reproducción se resuelve inicialmente, por lo que fuera del controlador TimedMetadataTracksChanged , también recorremos las pistas de metadatos disponibles y llamamos a 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);
}

Después de registrarse para los eventos de metadatos del capítulo, el objeto MediaItem se asigna a un Objeto MediaPlayer para su reproducción dentro de un objeto MediaPlayerElement.

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

En el método auxiliar RegisterMetadataHandlerForChapterCues, obtenga una instancia de la clase TimedMetadataTrack indizando en la colección TimedMetadataTracks del objeto MediaPlaybackItem. Regístrese para el evento CueEntered y el evento CueExited. A continuación, debes llamar a SetPresentationMode en la colección TimedMetadataTracks del elemento de reproducción para indicar al sistema que la aplicación quiere recibir eventos de indicación de metadatos para este elemento de reproducción.

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

En el controlador del evento CueEntered, puede comprobar la propiedad TimedMetadataKind del objeto TimedMetadataTrack pasado al controlador para ver si los metadatos son indicaciones de capítulo. Esto es necesario si usa el mismo controlador de eventos de indicación de datos para varios tipos de metadatos. Si la pista de metadatos asociada es de tipo TimedMetadataKind.Chapter, convierta la indicación de datos contenida en la propiedad Cue de MediaCueEventArgs en un ChapterCue. La propiedad Title de ChapterCue contiene el título del capítulo que acaba de alcanzarse en la reproducción.

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

Busque el siguiente capítulo con indicaciones de capítulo

Además de recibir notificaciones cuando el capítulo actual cambia en un elemento de reproducción, también puede usar indicaciones de capítulo para buscar el siguiente capítulo dentro de un elemento de reproducción. El método de ejemplo que se muestra a continuación toma como argumentos un Objeto MediaPlayer y un objeto MediaPlaybackItem que representa el elemento multimedia que se está reproduciendo actualmente. Se busca en la colección TimedMetadataTracks para ver si alguna de las pistas tiene el valor TimedMetadataKind del valor TimedMetadataTrack de TimedMetadataKind.Chapter. Si se encuentra una pista de capítulo, el método recorre en bucle cada indicación de la colección Cues de la pista para encontrar la primera indicación que tiene un StartTime mayor que la posición actual de la sesión de reproducción del reproductor multimedia. Una vez que se encuentra la indicación correcta, la posición de la sesión de reproducción se actualiza y el título del capítulo se actualiza en la interfaz de usuario.

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

Comentarios extendidos de M3U

A partir de Windows 10, versión 1703, las aplicaciones para UWP pueden registrarse para obtener indicaciones que corresponden a comentarios dentro de un archivo de manifiesto M3U extendido. En este ejemplo se usa AdaptiveMediaSource para reproducir el contenido multimedia. Para obtener más información, consulte Streaming adaptable. Cree un AdaptiveMediaSource para el contenido llamando a CreateFromUriAsync o CreateFromStreamAsync. Cree un objeto MediaSource llamando a CreateFromAdaptiveMediaSource y, a continuación, cree un objeto MediaPlaybackItem desde 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);

Regístrese para los eventos de metadatos de M3U mediante el objeto MediaPlaybackItem creado en el paso anterior. En este ejemplo se usa un método auxiliar, RegisterMetadataHandlerForEXTM3UCues, para registrarse para los eventos. Se usa una expresión lambda para implementar un controlador para el evento TimedMetadataTracksChanged , que se produce cuando el sistema detecta un cambio en las pistas de metadatos asociadas a un objeto MediaPlaybackItem. En algunos casos, las pistas de metadatos pueden estar disponibles cuando el elemento de reproducción se resuelve inicialmente, por lo que fuera del controlador TimedMetadataTracksChanged , también recorremos las pistas de metadatos disponibles y llamamos a 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);
}

Después de registrarse para los eventos de metadatos de M3U, el objeto MediaItem se asigna a mediaPlayer para su reproducción dentro de un objeto MediaPlayerElement.

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

En el método auxiliar RegisterMetadataHandlerForEXTM3UCues, obtenga una instancia de la clase TimedMetadataTrack indizando en la colección TimedMetadataTracks del objeto MediaPlaybackItem. Compruebe la propiedad DispatchType de la pista de metadatos, que tendrá un valor de "EXTM3U" si la pista representa comentarios M3U. Regístrese para el evento CueEntered y el evento CueExited. A continuación, debes llamar a SetPresentationMode en la colección TimedMetadataTracks del elemento de reproducción para indicar al sistema que la aplicación quiere recibir eventos de indicación de metadatos para este elemento de reproducción.

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

En el controlador del evento CueEntered, convierta la indicación de datos contenida en la propiedad Cue de MediaCueEventArgs en un dataCue. Asegúrese de que dataCue y la propiedad Data de la indicación no son null. Los comentarios extendidos de EMU se proporcionan en forma de cadenas terminadas en UTF-16, little endian y null. Cree un dataReader para leer los datos de la indicación mediante una llamada a DataReader.FromBuffer. Establezca la propiedad UnicodeEncoding del lector en Utf16LE para leer los datos en el formato correcto. Llame a ReadString para leer los datos, especificando la mitad de la longitud del campo Datos, porque cada carácter tiene dos bytes de tamaño y resta uno para quitar el carácter nulo final. En este ejemplo, el comentario M3U se escribe simplemente en la salida de depuración.

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

Etiquetas ID3

A partir de Windows 10, versión 1703, las aplicaciones para UWP pueden registrarse para obtener indicaciones que corresponden a etiquetas ID3 dentro del contenido de Http Live Streaming (HLS). En este ejemplo se usa AdaptiveMediaSource para reproducir el contenido multimedia. Para obtener más información, consulte Streaming adaptable. Cree un AdaptiveMediaSource para el contenido llamando a CreateFromUriAsync o CreateFromStreamAsync. Cree un objeto MediaSource llamando a CreateFromAdaptiveMediaSource y, a continuación, cree un objeto MediaPlaybackItem desde 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);

Regístrese para los eventos de etiqueta ID3 mediante el objeto MediaPlaybackItem creado en el paso anterior. En este ejemplo se usa un método auxiliar RegisterMetadataHandlerForID3Cues para registrarse para los eventos. Se usa una expresión lambda para implementar un controlador para el evento TimedMetadataTracksChanged , que se produce cuando el sistema detecta un cambio en las pistas de metadatos asociadas a un objeto MediaPlaybackItem. En algunos casos, las pistas de metadatos pueden estar disponibles cuando el elemento de reproducción se resuelve inicialmente, por lo que fuera del controlador TimedMetadataTracksChanged , también recorremos las pistas de metadatos disponibles y llamamos a 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);

Después de registrarse para los eventos de metadatos ID3, el objeto MediaItem se asigna a un Objeto MediaPlayer para su reproducción en un objeto MediaPlayerElement.

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

En el método auxiliar RegisterMetadataHandlerForID3Cues, obtenga una instancia de la clase TimedMetadataTrack indizando en la colección TimedMetadataTracks del objeto MediaPlaybackItem. Compruebe la propiedad DispatchType de la pista de metadatos, que tendrá un valor que contiene la cadena GUID "15260DFFFF4944320FF49443320000F" si la pista representa etiquetas ID3. Regístrese para el evento CueEntered y el evento CueExited. A continuación, debes llamar a SetPresentationMode en la colección TimedMetadataTracks del elemento de reproducción para indicar al sistema que la aplicación quiere recibir eventos de indicación de metadatos para este elemento de reproducción.

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

En el controlador del evento CueEntered, convierta la indicación de datos contenida en la propiedad Cue de MediaCueEventArgs en un dataCue. Asegúrese de que dataCue y la propiedad Data de la indicación no son null. Los comentarios extendidos de EMU se proporcionan en forma de bytes sin procesar en el flujo de transporte (consulte ID3). Cree un dataReader para leer los datos de la indicación mediante una llamada a DataReader.FromBuffer. En este ejemplo, los valores de encabezado de la etiqueta ID3 se leen de los datos de indicación y se escriben en la salida de depuración.

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

Cuadros mp4 emsg fragmentados

A partir de Windows 10, versión 1703, las aplicaciones para UWP pueden registrarse para obtener indicaciones que corresponden a cuadros emsg dentro de secuencias mp4 fragmentadas. Un ejemplo de uso de este tipo de metadatos es para que los proveedores de contenido señalen a las aplicaciones cliente para reproducir un anuncio durante el contenido de streaming en vivo. En este ejemplo se usa AdaptiveMediaSource para reproducir el contenido multimedia. Para obtener más información, consulte Streaming adaptable. Cree un AdaptiveMediaSource para el contenido llamando a CreateFromUriAsync o CreateFromStreamAsync. Cree un objeto MediaSource llamando a CreateFromAdaptiveMediaSource y, a continuación, cree un objeto MediaPlaybackItem desde 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);

Regístrese para los eventos del cuadro emsg mediante el objeto MediaPlaybackItem creado en el paso anterior. En este ejemplo se usa un método auxiliar, RegisterMetadataHandlerForEmsgCues, para registrarse para los eventos. Se usa una expresión lambda para implementar un controlador para el evento TimedMetadataTracksChanged , que se produce cuando el sistema detecta un cambio en las pistas de metadatos asociadas a un objeto MediaPlaybackItem. En algunos casos, las pistas de metadatos pueden estar disponibles cuando el elemento de reproducción se resuelve inicialmente, por lo que fuera del controlador TimedMetadataTracksChanged , también recorremos las pistas de metadatos disponibles y llamamos a 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);

Después de registrarse para los eventos de metadatos del cuadro emsg, el objeto MediaItem se asigna a mediaPlayer para su reproducción dentro de un objeto MediaPlayerElement.

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

En el método auxiliar RegisterMetadataHandlerForEmsgCues, obtenga una instancia de la clase TimedMetadataTrack indizando en la colección TimedMetadataTracks del objeto MediaPlaybackItem. Compruebe la propiedad DispatchType de la pista de metadatos, que tendrá un valor de "emsg:mp4" si la pista representa cuadros emsg. Regístrese para el evento CueEntered y el evento CueExited. A continuación, debes llamar a SetPresentationMode en la colección TimedMetadataTracks del elemento de reproducción para indicar al sistema que la aplicación quiere recibir eventos de indicación de metadatos para este elemento de reproducción.

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

En el controlador del evento CueEntered, convierta la indicación de datos contenida en la propiedad Cue de MediaCueEventArgs en un dataCue. Asegúrese de que el objeto DataCue no sea NULL. La canalización multimedia proporciona las propiedades adecuadas del cuadro emsg como propiedades personalizadas en la colección Properties del objeto DataCue. En este ejemplo se intenta extraer varios valores de propiedad diferentes mediante el método TryGetValue. Si este método devuelve null, significa que la propiedad solicitada no está presente en el cuadro emsg, por lo que se establece un valor predeterminado en su lugar.

La siguiente parte del ejemplo muestra el escenario en el que se desencadena la reproducción de anuncios, que es el caso cuando la propiedad scheme_id_uri , obtenida en el paso anterior, tiene un valor de "urn:scte:scte:scte35:2013:xml". Para obtener más información, vea https://dashif.org/identifiers/event_schemes/. Tenga en cuenta que el estándar recomienda enviar este mensaje varias veces para la redundancia, por lo que en este ejemplo se mantiene una lista de los identificadores emsg que ya se han procesado y solo se procesan nuevos mensajes. Cree un dataReader nuevo para leer los datos de indicación llamando a DataReader.FromBuffer y establezca la codificación en UTF-8 estableciendo la propiedad UnicodeEncoding y, a continuación, lea los datos. En este ejemplo, la carga del mensaje se escribe en la salida de depuración. Una aplicación real usaría los datos de carga útil para programar la reproducción de un anuncio.

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