Indicateurs de métadonnées synchronisées pris en charge par le système

Cet article explique comment tirer parti de plusieurs formats de métadonnées chronométrées qui peuvent être incorporées dans des fichiers multimédias ou des flux. Les applications UWP peuvent s’inscrire aux événements déclenchés par le pipeline multimédia lors de la lecture chaque fois que ces indicateurs de métadonnées sont rencontrés. À l’aide de la classe DataCue , les applications peuvent implémenter leurs propres indicateurs de métadonnées personnalisés, mais cet article se concentre sur plusieurs normes de métadonnées détectées automatiquement par le pipeline multimédia, notamment :

  • Sous-titres basés sur des images au format VobSub
  • Indicateurs vocaux, y compris les limites de mots, les limites de phrase et les signets SSML (Speech Synthesis Markup Language)
  • Indicateurs de chapitre
  • Commentaires M3U étendus
  • Balises ID3
  • Boîtes mp4 emsg fragmentées

Cet article s’appuie sur les concepts abordés dans l’article Éléments multimédias, playlists et pistes, qui inclut les principes de base de l’utilisation des classes MediaSource, MediaPlaybackItem et TimedMetadataTrack, ainsi que des conseils généraux sur l’utilisation de métadonnées chronométrées dans votre application.

Les étapes d’implémentation de base sont les mêmes pour tous les types de métadonnées chronométrées décrites dans cet article :

  1. Créez un MediaSource, puis un MediaPlaybackItem pour le contenu à lire.
  2. Inscrivez-vous à l’événement MediaPlaybackItem.TimedMetadataTracksChanged , qui se produit en tant que sous-pistes de l’élément multimédia sont résolus par le pipeline multimédia.
  3. Inscrivez-vous aux événements TimedMetadataTrack.CueEntered et TimedMetadataTrack.CueExited pour les pistes de métadonnées chronométrées que vous souhaitez utiliser.
  4. Dans le gestionnaire d’événements CueEntered , mettez à jour votre interface utilisateur en fonction des métadonnées transmises dans les arguments d’événement. Vous pouvez à nouveau mettre à jour l’interface utilisateur pour supprimer le texte du sous-titre actuel, par exemple, dans l’événement CueExited .

Dans cet article, la gestion de chaque type de métadonnées s’affiche sous la forme d’un scénario distinct, mais il est possible de gérer (ou ignorer) différents types de métadonnées à l’aide principalement de code partagé. Vous pouvez vérifier la propriété TimedMetadataKind de l’objet TimedMetadataTrack à plusieurs points du processus. Par exemple, vous pouvez choisir de vous inscrire à l’événement CueEntered pour les pistes de métadonnées qui ont la valeur TimedMetadataKind.ImageSubtitle, mais pas pour les pistes qui ont la valeur TimedMetadataKind.Speech. Vous pouvez également inscrire un gestionnaire pour tous les types de piste de métadonnées, puis vérifier la valeur TimedMetadataKind dans le gestionnaire CueEntered pour déterminer quelle action effectuer en réponse à l’indicateur.

Sous-titres basés sur des images

À compter de Windows 10, version 1703, les applications UWP peuvent prendre en charge les sous-titres basés sur des images externes au format VobSub. Pour utiliser cette fonctionnalité, créez d’abord un objet MediaSource pour le contenu multimédia pour lequel les sous-titres d’image seront affichés. Ensuite, créez un objet TimedTextSource en appelant CreateFromUriWithIndex ou CreateFromStreamWithIndex, en passant l’URI du fichier .sub contenant les données d’image de sous-titre et le fichier .idx contenant les informations de minutage des sous-titres. Ajoutez TimedTextSource à MediaSource en l’ajoutant à la collection ExternalTimedTextSources de la source. Créez un MediaPlaybackItem à partir de 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);

Inscrivez-vous aux événements de métadonnées de sous-titre d’image à l’aide de l’objet MediaPlaybackItem créé à l’étape précédente. Cet exemple utilise une méthode d’assistance, RegisterMetadataHandlerForImageSubtitles, pour s’inscrire aux événements. Une expression lambda est utilisée pour implémenter un gestionnaire pour l’événement TimedMetadataTracksChanged , qui se produit lorsque le système détecte une modification dans les pistes de métadonnées associées à un MediaPlaybackItem. Dans certains cas, les pistes de métadonnées peuvent être disponibles lorsque l’élément de lecture est initialement résolu. Par conséquent, en dehors du gestionnaire TimedMetadataTracksChanged , nous effectuons également une boucle dans les pistes de métadonnées disponibles et appelons 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);
}

Après s’être inscrit pour les événements de métadonnées de sous-titre d’image, MediaItem est affecté à un MediaPlayer pour la lecture au sein d’un MediaPlayerElement.

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

Dans la méthode d’assistance RegisterMetadataHandlerForImageSubtitles , obtenez une instance de la classe TimedMetadataTrack en indexant dans la collection TimedMetadataTracks de MediaPlaybackItem. Inscrivez-vous à l’événement CueEntered et à l’événement CueExited. Ensuite, vous devez appeler SetPresentationMode sur la collection TimedMetadataTracks de l’élément de lecture pour indiquer au système que l’application souhaite recevoir des événements de indicateurs de métadonnées pour cet élément de lecture.

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

}

Dans le gestionnaire de l’événement CueEntered, vous pouvez vérifier le bon fonctionnement de l’objet TimedMetadataKind de l’objet TimedMetadataTrack passé dans le gestionnaire pour voir si les métadonnées concernent les sous-titres d’image. Cela est nécessaire si vous utilisez le même gestionnaire d’événements de cue de données pour plusieurs types de métadonnées. Si la piste de métadonnées associée est de type TimedMetadataKind.ImageSubtitle, castez l’indicateur de données contenu dans la propriété Cue de MediaCueEventArgs en imageCue. La propriété SoftwareBitmap de l’ImageCuecontient une représentation SoftwareBitmap de l’image de sous-titre. Créez un SoftwareBitmapSource et appelez SetBitmapAsync pour affecter l’image à un contrôle Image XAML. Les propriétés Étendue et Position de l’ImageCue fournissent des informations sur la taille et la position de l’image de sous-titre.

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

Signaux vocaux

À compter de Windows 10, version 1703, les applications UWP peuvent s’inscrire pour recevoir des événements en réponse aux limites de mots, aux limites de phrases et aux signets SSML (Speech Synthesis Markup Language) dans les médias lus. Cela vous permet de lire des flux audio générés avec la classe SpeechSynthesizer et de mettre à jour votre interface utilisateur en fonction de ces événements, tels que l’affichage du texte du mot ou de la phrase en cours de lecture.

L’exemple présenté dans cette section utilise une variable membre de classe pour stocker une chaîne de texte qui sera synthétisée et lue.

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

Créez une instance de la classe SpeechSynthesizer . Définissez les options IncludeWordBoundaryMetadata et IncludeSentenceBoundaryMetadata pour que le synthétiseur ait la valeur true pour spécifier que les métadonnées doivent être incluses dans le flux multimédia généré. AppelezSynthTextToStreamAsync pour générer un flux contenant la voix synthétisée et les métadonnées correspondantes. Créez un MediaSource et un MediaPlaybackItem à partir du flux synthétisé.

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

Inscrivez-vous aux événements de métadonnées vocales à l’aide de l’objet MediaPlaybackItem . Cet exemple utilise une méthode d’assistance, RegisterMetadataHandlerForSpeech, pour s’inscrire aux événements. Une expression lambda est utilisée pour implémenter un gestionnaire pour l’événement TimedMetadataTracksChanged , qui se produit lorsque le système détecte une modification dans les pistes de métadonnées associées à un MediaPlaybackItem. Dans certains cas, les pistes de métadonnées peuvent être disponibles lorsque l’élément de lecture est initialement résolu. Par conséquent, en dehors du gestionnaire TimedMetadataTracksChanged , nous effectuons également une boucle dans les pistes de métadonnées disponibles et appelons 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);
}

Après s’être inscrit aux événements de métadonnées vocales, MediaItem est affecté à un MediaPlayer pour la lecture dans un MediaPlayerElement.

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

Dans la méthode d’assistance RegisterMetadataHandlerForSpeech , obtenez une instance de la classe TimedMetadataTrack en indexant dans la collection TimedMetadataTracks de MediaPlaybackItem. Inscrivez-vous à l’événement CueEntered et à l’événement CueExited. Ensuite, vous devez appeler SetPresentationMode sur la collection TimedMetadataTracks de l’élément de lecture pour indiquer au système que l’application souhaite recevoir des événements de indicateurs de métadonnées pour cet élément de lecture.

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

}

Dans le gestionnaire de l’événement CueEntered, vous pouvez vérifier le bon fonctionnement de l’objet TimedMetadataKind de l’objet TimedMetadataTrack passé dans le gestionnaire pour voir si les métadonnées sont vocales. Cela est nécessaire si vous utilisez le même gestionnaire d’événements de cue de données pour plusieurs types de métadonnées. Si la piste de métadonnées associée est de type TimedMetadataKind.Speech, cassez l’indicateur de données contenu dans la propriété Cue du MediaCueEventArgs en speechCue. Pour les signaux vocaux, le type d’indicateur vocal inclus dans la piste de métadonnées est déterminé en vérifiant la propriété Label . La valeur de cette propriété est « SpeechWord » pour les limites de mots, « SpeechSentence » pour les limites de phrases ou « SpeechBookmark » pour les signets SSML. Dans cet exemple, nous vérifions la valeur « SpeechWord » et, si cette valeur est trouvée, les propriétés StartPositionInInput et EndPositionInInput de SpeechCue sont utilisées pour déterminer l’emplacement dans le texte d’entrée du mot en cours de lecture. Cet exemple génère simplement chaque mot vers la sortie de débogage.

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

Indicateurs de chapitre

À compter de Windows 10, version 1703, les applications UWP peuvent s’inscrire à des signaux correspondant aux chapitres d’un élément multimédia. Pour utiliser cette fonctionnalité, créez un objet MediaSource pour le contenu multimédia, puis créez un MediaPlaybackItem à partir de MediaSource.

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

Inscrivez-vous aux événements de métadonnées de chapitre à l’aide de l’objet MediaPlaybackItem créé à l’étape précédente. Cet exemple utilise une méthode d’assistance, RegisterMetadataHandlerForChapterCues, pour s’inscrire aux événements. Une expression lambda est utilisée pour implémenter un gestionnaire pour l’événement TimedMetadataTracksChanged , qui se produit lorsque le système détecte une modification dans les pistes de métadonnées associées à un MediaPlaybackItem. Dans certains cas, les pistes de métadonnées peuvent être disponibles lorsque l’élément de lecture est initialement résolu. Par conséquent, en dehors du gestionnaire TimedMetadataTracksChanged , nous allons également parcourir les pistes de métadonnées disponibles et appeler 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);
}

Après l’inscription aux événements de métadonnées de chapitre, MediaItem est affecté à un MediaPlayer pour la lecture au sein d’un MediaPlayerElement.

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

Dans la méthode d’assistance RegisterMetadataHandlerForChapterCues , obtenez une instance de la classe TimedMetadataTrack en indexant dans la collection TimedMetadataTracks de MediaPlaybackItem. Inscrivez-vous à l’événement CueEntered et à l’événement CueExited. Ensuite, vous devez appeler SetPresentationMode sur la collection TimedMetadataTracks de l’élément de lecture pour indiquer au système que l’application souhaite recevoir des événements de indicateurs de métadonnées pour cet élément de lecture.

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

Dans le gestionnaire de l’événement CueEntered, vous pouvez vérifier le bon fonctionnement de l’objet TimedMetadataKind de l’objet TimedMetadataTrack passé dans le gestionnaire pour voir si les métadonnées sont destinées aux indicateurs de chapitre. Cela est nécessaire si vous utilisez le même gestionnaire d’événements de cue de données pour plusieurs types de métadonnées. Si la piste de métadonnées associée est de type TimedMetadataKind.Chapter, cassez l’indicateur de données contenu dans la propriété Cue du MediaCueEventArgs en Un ChapterCue. La propriété Title du ChapterCue contient le titre du chapitre qui vient d’être atteint en lecture.

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

Recherchez le chapitre suivant à l’aide de repères de chapitre

En plus de recevoir des notifications lorsque le chapitre actuel change dans un élément de lecture, vous pouvez également utiliser des indications de chapitre pour rechercher le chapitre suivant dans un élément de lecture. L’exemple de méthode ci-dessous prend en tant qu’arguments un MediaPlayer et un MediaPlaybackItem représentant l’élément multimédia en cours de lecture. La collection TimedMetadataTracks est recherchée pour voir si l’une des pistes a TimedMetadataKind appropriée de la valeur TimedMetadataTrack de TimedMetadataKind.Chapter. Si une piste de chapitre est trouvée, la méthode effectue une boucle à travers chaque indicateur de la collection Cues de la piste pour trouver le premier indicateur qui a une valeur StartTime supérieure à la position actuelle de la session de lecture du lecteur multimédia. Une fois l’indicateur correct trouvé, la position de la session de lecture est mise à jour et le titre du chapitre est mis à jour dans l’interface utilisateur.

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

Commentaires M3U étendus

À compter de Windows 10, version 1703, les applications UWP peuvent s’inscrire pour obtenir des indications qui correspondent aux commentaires dans un fichier manifeste M3U étendu. Cet exemple utilise AdaptiveMediaSource pour lire le contenu multimédia. Pour plus d’informations, consultez Adaptive Streaming. Créez une AdaptiveMediaSource pour le contenu en appelant CreateFromUriAsync ou CreateFromStreamAsync. Créez un objet MediaSource en appelant CreateFromAdaptiveMediaSource, puis créez un MediaPlaybackItem à partir de 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);

Inscrivez-vous aux événements de métadonnées M3U à l’aide de l’objet MediaPlaybackItem créé à l’étape précédente. Cet exemple utilise une méthode d’assistance, RegisterMetadataHandlerForEXTM3UCues, pour s’inscrire aux événements. Une expression lambda est utilisée pour implémenter un gestionnaire pour l’événement TimedMetadataTracksChanged , qui se produit lorsque le système détecte une modification dans les pistes de métadonnées associées à un MediaPlaybackItem. Dans certains cas, les pistes de métadonnées peuvent être disponibles lorsque l’élément de lecture est initialement résolu. Par conséquent, en dehors du gestionnaire TimedMetadataTracksChanged , nous allons également parcourir les pistes de métadonnées disponibles et appeler 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);
}

Après l’inscription des événements de métadonnées M3U, MediaItem est affecté à un MediaPlayer pour la lecture dans un MediaPlayerElement.

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

Dans la méthode d’assistance RegisterMetadataHandlerForEXTM3UCues , obtenez une instance de la classe TimedMetadataTrack en indexant dans la collection TimedMetadataTracks de MediaPlaybackItem. Vérifiez la propriété DispatchType de la piste de métadonnées, qui aura la valeur « EXTM3U » si la piste représente des commentaires M3U. Inscrivez-vous à l’événement CueEntered et à l’événement CueExited. Ensuite, vous devez appeler SetPresentationMode sur la collection TimedMetadataTracks de l’élément de lecture pour indiquer au système que l’application souhaite recevoir des événements de indicateurs de métadonnées pour cet élément de lecture.

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

Dans le gestionnaire de l’événement CueEntered, cassez l’indicateur de données contenu dans la propriété Cue du MediaCueEventArgs en dataCue. Vérifiez que DataCue et la propriété Données de l’indicateur ne sont pas null. Les commentaires UEM étendus sont fournis sous la forme de chaînes UTF-16, little endian, null terminated strings. Créez un DataReader pour lire les données de l’indicateur en appelant DataReader.FromBuffer. Définissez la propriété UnicodeEncoding du lecteur sur Utf16LE pour lire les données au format correct. Appelez ReadString pour lire les données, en spécifiant la moitié de la longueur du champ Données , car chaque caractère est de deux octets de taille et soustrait un pour supprimer le caractère null de fin. Dans cet exemple, le commentaire M3U est simplement écrit dans la sortie de débogage.

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

Balises ID3

À compter de Windows 10, version 1703, les applications UWP peuvent s’inscrire à des signaux correspondant à des balises ID3 dans le contenu HLS (Http Live Streaming). Cet exemple utilise AdaptiveMediaSource pour lire le contenu multimédia. Pour plus d’informations, consultez Adaptive Streaming. Créez une AdaptiveMediaSource pour le contenu en appelant CreateFromUriAsync ou CreateFromStreamAsync. Créez un objet MediaSource en appelant CreateFromAdaptiveMediaSource, puis créez un MediaPlaybackItem à partir de 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);

Inscrivez-vous aux événements de balise ID3 à l’aide de l’objet MediaPlaybackItem créé à l’étape précédente. Cet exemple utilise une méthode d’assistance, RegisterMetadataHandlerForID3Cues, pour s’inscrire aux événements. Une expression lambda est utilisée pour implémenter un gestionnaire pour l’événement TimedMetadataTracksChanged , qui se produit lorsque le système détecte une modification dans les pistes de métadonnées associées à un MediaPlaybackItem. Dans certains cas, les pistes de métadonnées peuvent être disponibles lorsque l’élément de lecture est initialement résolu. Par conséquent, en dehors du gestionnaire TimedMetadataTracksChanged , nous effectuons également une boucle dans les pistes de métadonnées disponibles et appelons 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);

Après l’inscription pour les événements de métadonnées ID3, MediaItem est affecté à un MediaPlayer pour la lecture dans un MediaPlayerElement.

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

Dans la méthode d’assistance RegisterMetadataHandlerForID3Cues , obtenez une instance de la classe TimedMetadataTrack en indexant dans la collection TimedMetadataTracks de MediaPlaybackItem. Vérifiez la propriété DispatchType de la piste de métadonnées, qui aura une valeur contenant la chaîne GUID « 15260DFFFF4943320FF49443320000F » si la piste représente des balises ID3. Inscrivez-vous à l’événement CueEntered et à l’événement CueExited. Ensuite, vous devez appeler SetPresentationMode sur la collection TimedMetadataTracks de l’élément de lecture pour indiquer au système que l’application souhaite recevoir des événements de indicateurs de métadonnées pour cet élément de lecture.

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

Dans le gestionnaire de l’événement CueEntered, cassez l’indicateur de données contenu dans la propriété Cue du MediaCueEventArgs en dataCue. Vérifiez que DataCue et la propriété Données de l’indicateur ne sont pas null. Les commentaires UEM étendus sont fournis sous forme d’octets bruts dans le flux de transport (voir ID3). Créez un DataReader pour lire les données de l’indicateur en appelant DataReader.FromBuffer. Dans cet exemple, les valeurs d’en-tête de la balise ID3 sont lues à partir des données de l’indicateur et écrites dans la sortie de débogage.

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

Boîtes mp4 emsg fragmentées

À compter de Windows 10, version 1703, les applications UWP peuvent s’inscrire à des signaux correspondant aux zones emsg dans des flux mp4 fragmentés. Un exemple d’utilisation de ce type de métadonnées est destiné aux fournisseurs de contenu pour signaler aux applications clientes de lire une publicité pendant le contenu de streaming en direct. Cet exemple utilise AdaptiveMediaSource pour lire le contenu multimédia. Pour plus d’informations, consultez Adaptive Streaming. Créez une AdaptiveMediaSource pour le contenu en appelant CreateFromUriAsync ou CreateFromStreamAsync. Créez un objet MediaSource en appelant CreateFromAdaptiveMediaSource, puis créez un MediaPlaybackItem à partir de 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);

Inscrivez-vous aux événements de zone emsg à l’aide de l’objet MediaPlaybackItem créé à l’étape précédente. Cet exemple utilise une méthode d’assistance, RegisterMetadataHandlerForEmsgCues, pour s’inscrire aux événements. Une expression lambda est utilisée pour implémenter un gestionnaire pour l’événement TimedMetadataTracksChanged , qui se produit lorsque le système détecte une modification dans les pistes de métadonnées associées à un MediaPlaybackItem. Dans certains cas, les pistes de métadonnées peuvent être disponibles lorsque l’élément de lecture est initialement résolu. Par conséquent, en dehors du gestionnaire TimedMetadataTracksChanged , nous effectuons également une boucle dans les pistes de métadonnées disponibles et appelons 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);

Après l’inscription pour les événements de métadonnées de zone emsg, MediaItem est affecté à un MediaPlayer pour la lecture au sein d’un MediaPlayerElement.

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

Dans la méthode d’assistance RegisterMetadataHandlerForEmsgCues , obtenez une instance de la classe TimedMetadataTrack en indexant dans la collection TimedMetadataTracks de MediaPlaybackItem. Cochez la propriété DispatchType de la piste de métadonnées, qui aura la valeur « emsg:mp4 » si la piste représente des zones emsg. Inscrivez-vous à l’événement CueEntered et à l’événement CueExited. Ensuite, vous devez appeler SetPresentationMode sur la collection TimedMetadataTracks de l’élément de lecture pour indiquer au système que l’application souhaite recevoir des événements de indicateurs de métadonnées pour cet élément de lecture.

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

Dans le gestionnaire de l’événement CueEntered, cassez l’indicateur de données contenu dans la propriété Cue du MediaCueEventArgs en dataCue. Vérifiez que l’objet DataCue n’est pas null. Les propriétés appropriées de la zone emsg sont fournies par le pipeline multimédia en tant que propriétés personnalisées dans la collection Properties de l’objet DataCue. Cet exemple tente d’extraire plusieurs valeurs de propriété différentes à l’aide de la méthode TryGetValue. Si cette méthode retourne la valeur Null, cela signifie que la valeur appropriée demandée n’est pas présente dans la zone emsg. Par conséquent, une valeur par défaut est définie à la place.

La partie suivante de l’exemple illustre le scénario dans lequel la lecture publicitaire est déclenchée, qui est le cas lorsque la propriété scheme_id_uri , obtenue à l’étape précédente, a la valeur « urn:scte:scte35:2013:xml ». Pour plus d’informations, consultez https://dashif.org/identifiers/event_schemes/. Notez que la norme recommande d’envoyer ce emsg plusieurs fois pour la redondance. Cet exemple conserve donc une liste des ID emsg qui ont déjà été traités et traite uniquement les nouveaux messages. Créez un DataReader pour lire les données de l’indicateur en appelant DataReader.FromBuffer et définissez l’encodage sur UTF-8 en définissant la propriété UnicodeEncoding, puis en lisant les données. Dans cet exemple, la charge utile du message est écrite dans la sortie de débogage. Une application réelle utilise les données de charge utile pour planifier la lecture d’une publicité.

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