Segnali di metadati temporizzati supportati dal sistema

Questo articolo descrive come sfruttare diversi formati di metadati temporizzati incorporabili in file multimediali o flussi. Le app UWP possono registrarsi per gli eventi generati dalla pipeline multimediale durante la riproduzione ogni volta che vengono rilevati questi segnali di metadati. Usando la classe DataCue, le app possono implementare i propri segnali di metadati personalizzati; questo articolo si concentra però su diversi standard di metadati rilevati automaticamente dalla pipeline multimediale, tra cui:

  • Sottotitoli basati su immagini in formato VobSub
  • Segnali vocali, inclusi i limiti delle parole, i limiti delle frasi e i segnalibri SSML (Speech Synthesis Markup Language)
  • Segnali di capitolo
  • Commenti M3U estesi
  • Tag ID3
  • Caselle emsg mp4 frammentate

Questo articolo si basa sui concetti illustrati nell'articolo Elementi multimediali, playlist e tracce, include le nozioni di base sull'uso delle classi MediaSource, MediaPlaybackItem e TimedMetadataTrack e le indicazioni generali per l'uso dei metadati temporali nell'app.

I passaggi di implementazione di base sono gli stessi per tutti i diversi tipi di metadati temporizzati descritti in questo articolo:

  1. Creare un oggetto MediaSource e quindi un oggetto MediaPlaybackItem per la riproduzione del contenuto.
  2. Eseguire la registrazione per l'evento MediaPlaybackItem.TimedMetadataTracksChanged, che si verifica quando le tracce secondarie dell'elemento multimediale vengono risolte dalla pipeline multimediale.
  3. Effettuare la registrazione per gli eventi TimedMetadataTrack.CueEntered e TimedMetadataTrack.CueExited per le tracce di metadati temporizzati che si desidera usare.
  4. Nel gestore eventi CueEntered aggiornare l'interfaccia utente in base ai metadati passati negli argomenti dell'evento. È possibile aggiornare di nuovo l'interfaccia utente per rimuovere il testo del sottotitolo corrente, ad esempio nell'evento CueExited.

In questo articolo la gestione di ogni tipo di metadati viene illustrata come uno scenario distinto, ma è possibile gestire (o ignorare) tipi diversi di metadati usando principalmente codice condiviso. È possibile controllare la proprietà TimedMetadataKind dell'oggetto TimedMetadataTrack in più punti del processo. Ad esempio, è possibile scegliere di registrarsi per l'evento CueEntered per le tracce di metadati con il valore TimedMetadataKind.ImageSubtitle, ma non per le tracce con il valore TimedMetadataKind.Speech. In alternativa, è possibile registrare un gestore per tutti i tipi di traccia dei metadati e quindi controllare il valore TimedMetadataKind all'interno del gestore CueEntered per determinare quale azione eseguire in risposta al segnale.

Sottotitoli basati su immagini

A partire da Windows 10 versione 1703, le app UWP possono supportare sottotitoli esterni basati su immagini in formato VobSub. Per usare questa funzionalità, creare prima di tutto un oggetto MediaSource per il contenuto multimediale per il quale verranno visualizzati i sottotitoli immagine. Creare quindi un oggetto TimedTextSource chiamando CreateFromUriWithIndex o CreateFromStreamWithIndex, passando l'URI del file con estensione sub contenente i dati dell'immagine del sottotitolo e il file con estensione idx contenente le informazioni di intervallo per i sottotitoli. Aggiungere TimedTextSource a MediaSource aggiungendolo all'insieme ExternalTimedTextSources dell'origine. Creare un oggetto MediaPlaybackItem da 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);

Eseguire la registrazione per gli eventi dei metadati del sottotitolo immagine usando l'oggetto MediaPlaybackItem creato nel passaggio precedente. Questo esempio usa un metodo helper, RegisterMetadataHandlerForImageSubtitles, per eseguire la registrazione per gli eventi. Un'espressione lambda viene usata per implementare un gestore per l'evento TimedMetadataTracksChanged, che si verifica quando il sistema rileva una modifica nelle tracce di metadati associate a un oggetto MediaPlaybackItem. In alcuni casi, le tracce di metadati possono essere disponibili quando l'elemento di riproduzione viene inizialmente risolto, quindi all'esterno del gestore TimedMetadataTracksChanged, viene eseguito anche il ciclo delle tracce di metadati disponibili e viene chiamato 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);
}

Dopo la registrazione per gli eventi dei metadati dei sottotitoli immagine, l'oggetto MediaItem viene assegnato a un MediaPlayer per la riproduzione all'interno di MediaPlayerElement.

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

Nel metodo helper RegisterMetadataHandlerForImageSubtitles ottenere un'istanza della classe TimedMetadataTrack eseguendo l'indicizzazione nell'insieme TimedMetadataTracks dell'oggetto MediaPlaybackItem. Eseguire la registrazione per l'evento CueEntered e l'evento CueExited. Chiamare quindi SetPresentationMode nella raccolta TimedMetadataTracks dell'elemento di riproduzione per indicare al sistema che l'app vuole ricevere eventi di segnale di metadati per questo elemento di riproduzione.

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

}

Nel gestore per l'evento CueEntered è possibile controllare la proprietà TimedMetadataKind dell'oggetto TimedMetadataTrack passato nel gestore per verificare se i metadati sono relativi ai sottotitoli immagine. Questa operazione è necessaria se si usa lo stesso gestore eventi dei segnali dati per più tipi di metadati. Se la traccia dei metadati associata è di tipo TimedMetadataKind.ImageSubtitle, eseguire il cast del segnale di dati contenuto nella proprietà Cue di MediaCueEventArgs a un oggetto ImageCue. La proprietà SoftwareBitmap di ImageCue contiene una rappresentazione SoftwareBitmap dell'immagine del sottotitolo. Creare un SoftwareBitmapSource e chiamare SetBitmapAsync per assegnare l'immagine a un controllo Image XAML. Le proprietà Extent e Position di ImageCue forniscono informazioni sulle dimensioni e sulla posizione dell'immagine del sottotitolo.

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

Segnali vocali

A partire da Windows 10, versione 1703, le app UWP possono registrarsi per ricevere eventi in risposta ai limiti delle parole, ai limiti delle frasi e ai segnalibri SSML (Speech Synthesis Markup Language) nei supporti riprodotti. In questo modo è possibile riprodurre flussi audio generati con la classe SpeechSynthesizer e aggiornare l'interfaccia utente in base a questi eventi, ad esempio la visualizzazione del testo della parola o della frase attualmente in riproduzione.

L'esempio illustrato in questa sezione usa una variabile membro della classe per archiviare una stringa di testo che verrà sintetizzata e riprodotta.

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

Creare una nuova istanza della classe serializerOptions. Impostare le opzioni IncludeWordBoundaryMetadata e IncludeSentenceBoundaryMetadata per il sintetizzatore su true per specificare che i metadati devono essere inclusi nel flusso multimediale generato. Chiamare SynthesizeTextToStreamAsync per generare un flusso contenente il parlato sintetizzato e i metadati corrispondenti. Creare un oggetto MediaSource e un oggetto MediaPlaybackItem dal flusso sintetizzato.

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

Eseguire la registrazione per gli eventi dei metadati vocali usando l'oggetto MediaPlaybackItem. Questo esempio usa un metodo helper, RegisterMetadataHandlerForSpeech, per eseguire la registrazione per gli eventi. Un'espressione lambda viene usata per implementare un gestore per l'evento TimedMetadataTracksChanged, che si verifica quando il sistema rileva una modifica nelle tracce di metadati associate a un oggetto MediaPlaybackItem. In alcuni casi, le tracce di metadati possono essere disponibili quando l'elemento di riproduzione viene inizialmente risolto, quindi all'esterno del gestore TimedMetadataTracksChanged, viene eseguito anche il ciclo delle tracce di metadati disponibili e viene chiamato 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);
}

Dopo la registrazione per gli eventi dei metadati vocali, l'oggetto MediaItem viene assegnato a un MediaPlayer per la riproduzione all'interno di MediaPlayerElement.

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

Nel metodo helper RegisterMetadataHandlerForSpeech ottenere un'istanza della classe TimedMetadataTrack eseguendo l'indicizzazione nell'insieme TimedMetadataTracks dell'oggetto MediaPlaybackItem. Eseguire la registrazione per l'evento CueEntered e l'evento CueExited. Chiamare quindi SetPresentationMode nella raccolta TimedMetadataTracks dell'elemento di riproduzione per indicare al sistema che l'app vuole ricevere eventi di segnale di metadati per questo elemento di riproduzione.

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

}

Nel gestore per l'evento CueEntered è possibile controllare la proprietà TimedMetadataKind dell'oggetto TimedMetadataTrack passato nel gestore per verificare se i metadati sono relativi alla voce. Questa operazione è necessaria se si usa lo stesso gestore eventi dei segnali dati per più tipi di metadati. Se la traccia dei metadati associata è di tipo TimedMetadataKind.ImageSubtitle, eseguire il cast del segnale di dati contenuto nella proprietà Cue di MediaCueEventArgs a un oggetto SpeechCue. Per i segnali vocali, il tipo di segnale vocale incluso nella traccia dei metadati è determinato controllando la proprietà Label. Il valore di questa proprietà sarà "SpeechWord" per i limiti delle parole, "SpeechSentence" per i limiti delle frasi o "SpeechBookmark" per i segnalibri SSML. In questo esempio viene verificata la presenza del valore "SpeechWord" e, se questo valore viene trovato, vengono usate le proprietà StartPositionInInput e EndPositionInput di SpeechCue per determinare la posizione all'interno del testo di input della parola attualmente riprodotta. In questo esempio viene semplicemente restituita ogni parola all'output di debug.

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

Segnali di capitolo

A partire da Windows 10, versione 1703, le app UWP possono registrarsi per segnali che corrispondono ai capitoli all'interno di un elemento multimediale. Per usare questa funzionalità, creare un oggetto MediaSource per il contenuto multimediale e quindi creare un oggetto MediaPlaybackItem da MediaSource.

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

Eseguire la registrazione per gli eventi dei metadati di capitolo usando l'oggetto MediaPlaybackItem creato nel passaggio precedente. Questo esempio usa un metodo helper, RegisterMetadataHandlerForChapterCues, per eseguire la registrazione per gli eventi. Un'espressione lambda viene usata per implementare un gestore per l'evento TimedMetadataTracksChanged, che si verifica quando il sistema rileva una modifica nelle tracce di metadati associate a un oggetto MediaPlaybackItem. In alcuni casi, le tracce di metadati possono essere disponibili quando l'elemento di riproduzione viene inizialmente risolto, quindi all'esterno del gestore TimedMetadataTracksChanged, viene eseguito anche il ciclo delle tracce di metadati disponibili e viene chiamato 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);
}

Dopo la registrazione per gli eventi di capitolo, l'oggetto MediaItem viene assegnato a un MediaPlayer per la riproduzione all'interno di MediaPlayerElement.

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

Nel metodo helper RegisterMetadataHandlerForChapterCues ottenere un'istanza della classe TimedMetadataTrack eseguendo l'indicizzazione nell'insieme TimedMetadataTracks dell'oggetto MediaPlaybackItem. Eseguire la registrazione per l'evento CueEntered e l'evento CueExited. Chiamare quindi SetPresentationMode nella raccolta TimedMetadataTracks dell'elemento di riproduzione per indicare al sistema che l'app vuole ricevere eventi di segnale di metadati per questo elemento di riproduzione.

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

Nel gestore per l'evento CueEntered è possibile controllare la proprietà TimedMetadataKind dell'oggetto TimedMetadataTrack passato al gestore per verificare se i metadati sono relativi ai segnali del capitolo. Questa operazione è necessaria se si usa lo stesso gestore eventi dei segnali dati per più tipi di metadati. Se la traccia dei metadati associata è di tipo TimedMetadataKind.ImageSubtitle, eseguire il cast del segnale di dati contenuto nella proprietà Cue di MediaCueEventArgs a un oggetto ChapterCue. La proprietà Title di ChapterCue contiene il titolo del capitolo appena raggiunto nella riproduzione.

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

Cercare il capitolo successivo usando i segnali di capitolo

Oltre a ricevere notifiche quando il capitolo corrente cambia in un elemento di riproduzione, è anche possibile usare i segnali di capitolo per cercare il capitolo successivo all'interno di un elemento in riproduzione. Il metodo di esempio illustrato di seguito accetta come argomenti un MediaPlayer e un oggetto MediaPlaybackItem che rappresenta l'elemento multimediale attualmente in riproduzione. Viene eseguita la ricerca nell'insieme TimedMetadataTracks per verificare se una delle tracce contiene TimedMetadataKind in modo appropriato del valore TimedMetadataTrack di TimedMetadataKind.Chapter. Se viene trovata una traccia del capitolo, il metodo scorre ogni segnale nella raccolta Cues della traccia per trovare il primo segnale con un valore StartTime maggiore della posizione corrente della sessione di riproduzione del lettore multimediale. Dopo aver trovato il segnale corretto, la posizione della sessione di riproduzione viene aggiornata e il titolo del capitolo viene aggiornato nell'interfaccia utente.

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

Commenti M3U estesi

A partire da Windows 10, versione 1703, le app UWP possono registrarsi per segnali che corrispondono ai commenti all'interno di un file manifesto M3U esteso. Questo esempio usa AdaptiveMediaSource per riprodurre il contenuto multimediale. Per altre informazioni, vedere Flussi adattivi. Creare un oggetto AdaptiveMediaSource per il contenuto chiamando CreateFromUriAsync o CreateFromStreamAsync. Creare un oggetto MediaSource chiamando CreateFromAdaptiveMediaSource e quindi creare un oggetto MediaPlaybackItem da 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);

Eseguire la registrazione per gli eventi dei metadati M3U usando l'oggetto MediaPlaybackItem creato nel passaggio precedente. Questo esempio usa un metodo helper, RegisterMetadataHandlerForEXTM3UCues, per eseguire la registrazione per gli eventi. Un'espressione lambda viene usata per implementare un gestore per l'evento TimedMetadataTracksChanged, che si verifica quando il sistema rileva una modifica nelle tracce di metadati associate a un oggetto MediaPlaybackItem. In alcuni casi, le tracce di metadati possono essere disponibili quando l'elemento di riproduzione viene inizialmente risolto, quindi all'esterno del gestore TimedMetadataTracksChanged, viene eseguito anche il ciclo delle tracce di metadati disponibili e viene chiamato 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);
}

Dopo la registrazione per gli eventi M3U, l'oggetto MediaItem viene assegnato a un MediaPlayer per la riproduzione all'interno di MediaPlayerElement.

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

Nel metodo helper RegisterMetadataHandlerForEXTM3UCuess ottenere un'istanza della classe TimedMetadataTrack eseguendo l'indicizzazione nell'insieme TimedMetadataTracks dell'oggetto MediaPlaybackItem. Controllare la proprietà DispatchType della traccia dei metadati, che avrà il valore "EXTM3U" se la traccia rappresenta i commenti M3U. Eseguire la registrazione per l'evento CueEntered e l'evento CueExited. Chiamare quindi SetPresentationMode nella raccolta TimedMetadataTracks dell'elemento di riproduzione per indicare al sistema che l'app vuole ricevere eventi di segnale di metadati per questo elemento di riproduzione.

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

Nel gestore per l'evento CueEntered eseguire il cast del segnale di dati contenuto nella proprietà Cue di MediaCueEventArgs a un Oggetto DataCue. Verificare che DataCue e la proprietà Data del segnale non siano Null. I commenti EMU estesi vengono forniti sotto forma di stringhe con terminazione UTF-16, little endian e null. Creare un nuovo DataReader per leggere i dati dei segnali chiamando DataReader.FromBuffer. Impostare la proprietà UnicodeEncoding del lettore su Utf16LE per leggere i dati nel formato corretto. Chiamare ReadString per leggere i dati, specificando la metà della lunghezza del campo Data, perché ogni carattere è di due byte di dimensione e ne sottrae uno per rimuovere il carattere Null finale. In questo esempio, il commento M3U viene semplicemente scritto nell'output di debug.

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

Tag ID3

A partire da Windows 10, versione 1703, le app UWP possono registrarsi per segnali che corrispondono ai tag ID3 all'interno del contenuto HLS (Http Live Streaming). Questo esempio usa AdaptiveMediaSource per riprodurre il contenuto multimediale. Per altre informazioni, vedere Flussi adattivi. Creare un oggetto AdaptiveMediaSource per il contenuto chiamando CreateFromUriAsync o CreateFromStreamAsync. Creare un oggetto MediaSource chiamando CreateFromAdaptiveMediaSource e quindi creare un oggetto MediaPlaybackItem da 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);

Eseguire la registrazione per gli eventi dei metadati tag ID3 usando l'oggetto MediaPlaybackItem creato nel passaggio precedente. Questo esempio usa un metodo helper, RegisterMetadataHandlerForID3Cues, per eseguire la registrazione per gli eventi. Un'espressione lambda viene usata per implementare un gestore per l'evento TimedMetadataTracksChanged, che si verifica quando il sistema rileva una modifica nelle tracce di metadati associate a un oggetto MediaPlaybackItem. In alcuni casi, le tracce di metadati possono essere disponibili quando l'elemento di riproduzione viene inizialmente risolto, quindi all'esterno del gestore TimedMetadataTracksChanged, viene eseguito anche il ciclo delle tracce di metadati disponibili e viene chiamato 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);

Dopo la registrazione per gli eventi ID3 l'oggetto MediaItem viene assegnato a un MediaPlayer per la riproduzione all'interno di MediaPlayerElement.

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

Nel metodo helper RegisterMetadataHandlerForID3Cues ottenere un'istanza della classe TimedMetadataTrack eseguendo l'indicizzazione nell'insieme TimedMetadataTracks dell'oggetto MediaPlaybackItem. Controllare la proprietà DispatchType della traccia dei metadati, che avrà un valore contenente la stringa GUID "15260DFFFFFF9443320FF494433320000F" se la traccia rappresenta i tag ID3. Eseguire la registrazione per l'evento CueEntered e l'evento CueExited. Chiamare quindi SetPresentationMode nella raccolta TimedMetadataTracks dell'elemento di riproduzione per indicare al sistema che l'app vuole ricevere eventi di segnale di metadati per questo elemento di riproduzione.

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

Nel gestore per l'evento CueEntered eseguire il cast del segnale di dati contenuto nella proprietà Cue di MediaCueEventArgs a un Oggetto DataCue. Verificare che DataCue e la proprietà Data del segnale non siano Null. I commenti EMU estesi vengono forniti nel formato byte non elaborati nel flusso di trasporto (vedere ID3). Creare un nuovo DataReader per leggere i dati dei segnali chiamando DataReader.FromBuffer. In questo esempio, i valori dell'intestazione del tag ID3 vengono letti dai dati dei segnali e scritti nell'output di debug.

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

Caselle emsg mp4 frammentate

A partire da Windows 10, versione 1703, le app UWP possono registrarsi per segnali che corrispondono a caselle emsg all'interno di flussi mp4 frammentati. Un esempio di utilizzo di questo tipo di metadati è destinato ai provider di contenuti per segnalare alle applicazioni client di riprodurre un annuncio durante il contenuto in streaming live. Questo esempio usa AdaptiveMediaSource per riprodurre il contenuto multimediale. Per altre informazioni, vedere Flussi adattivi. Creare un oggetto AdaptiveMediaSource per il contenuto chiamando CreateFromUriAsync o CreateFromStreamAsync. Creare un oggetto MediaSource chiamando CreateFromAdaptiveMediaSource e quindi creare un oggetto MediaPlaybackItem da 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);

Eseguire la registrazione per gli eventi delle caselle emsg usando l'oggetto MediaPlaybackItem creato nel passaggio precedente. Questo esempio usa un metodo helper, RegisterMetadataHandlerForEmsgCues, per eseguire la registrazione per gli eventi. Un'espressione lambda viene usata per implementare un gestore per l'evento TimedMetadataTracksChanged, che si verifica quando il sistema rileva una modifica nelle tracce di metadati associate a un oggetto MediaPlaybackItem. In alcuni casi, le tracce di metadati possono essere disponibili quando l'elemento di riproduzione viene inizialmente risolto, quindi all'esterno del gestore TimedMetadataTracksChanged, viene eseguito anche il ciclo delle tracce di metadati disponibili e viene chiamato 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);

Dopo la registrazione per le caselle emsg, l'oggetto MediaItem viene assegnato a un MediaPlayer per la riproduzione all'interno di MediaPlayerElement.

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

Nel metodo helper RegisterMetadataHandlerForEmsgCues ottenere un'istanza della classe TimedMetadataTrack eseguendo l'indicizzazione nell'insieme TimedMetadataTracks dell'oggetto MediaPlaybackItem. Controllare la proprietà DispatchType della traccia dei metadati, che avrà il valore "emsg:mp4" se la traccia rappresenta caselle emsg. Eseguire la registrazione per l'evento CueEntered e l'evento CueExited. Chiamare quindi SetPresentationMode nella raccolta TimedMetadataTracks dell'elemento di riproduzione per indicare al sistema che l'app vuole ricevere eventi di segnale di metadati per questo elemento di riproduzione.

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

Nel gestore per l'evento CueEntered eseguire il cast del segnale di dati contenuto nella proprietà Cue di MediaCueEventArgs a un Oggetto DataCue. Verificare che l'oggetto DataCue non sia Null. Le proprietà della casella emsg vengono fornite dalla pipeline multimediale come proprietà personalizzate nell'insieme Properties dell'oggetto DataCue. Questo esempio tenta di estrarre diversi valori di proprietà usando il metodo TryGetValue. Se questo metodo restituisce Null, significa che la corretta richiesta non è presente nella casella emsg, quindi viene impostato un valore predefinito.

Nella parte successiva dell'esempio viene illustrato lo scenario in cui viene attivata la riproduzione degli annunci, ovvero il caso in cui la proprietà scheme_id_uri, ottenuta nel passaggio precedente, ha il valore "urn:scte:scte:scte35:2013:xml". Per ulteriori informazioni, vedere https://dashif.org/identifiers/event_schemes/. Si noti che lo standard consiglia di inviare questo emsg più volte per la ridondanza, pertanto questo esempio mantiene un elenco degli ID di emsg già elaborati ed elabora solo nuovi messaggi. Creare un nuovo DataReader per leggere i dati dei segnali chiamando DataReader.FromBuffer e impostando la codifica su UTF-8 impostando la proprietà UnicodeEncoding, quindi leggere i dati. In questo esempio il payload del messaggio viene scritto nell'output di debug. Un'app reale userebbe i dati del payload per pianificare la riproduzione di un annuncio.

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