Cómo controlar metadatos temporizados en transmisiones de DAI lineales

El SDK de inserción de anuncios dinámicos (DAI) de anuncios multimedia interactivos (IMA) se basa en la información de metadatos incorporada en los segmentos multimedia de la transmisión (metadatos en banda) o en el archivo de manifiesto de transmisión (metadatos en manifiesto) para hacer un seguimiento de las posiciones de los espectadores y los eventos de anuncios del cliente. Los metadatos se envían en diferentes formatos, según el tipo de transmisión que se reproduce.

El reproductor de video recibe metadatos temporizados en lotes. Según el reproductor, los metadatos se pueden mostrar a la hora programada o en lotes. Cada cadena de metadatos tiene una marca de tiempo de presentación (PTS) asociada para el momento en que debe activarse.

Tu app es responsable de capturar metadatos y reenviarlos al SDK de DAI de IMA. El SDK ofrece los siguientes métodos para pasar esta información:

onTimedMetadata

Este método reenvía al SDK las strings de metadatos que están listas para procesarse. Se necesita un solo argumento:

  • metadata: Es un objeto que contiene una clave de TXXX y un valor de string asociado con el prefijo google_.
processMetadata

Este método programa las strings de metadatos para que el SDK las procese después del PTS especificado. Toma los siguientes argumentos:

  • type: Es una cadena que contiene el tipo de evento que se procesa. Los valores aceptados son ID3 para HLS o urn:google:dai:2018 para DASH.
  • data: Es un valor de string con el prefijo google_ o un array de bytes que se decodifica en esa string.
  • timestamp: Es la marca de tiempo en segundos de la que se deben procesar los datos.

Cada tipo de transmisión compatible con el SDK de IMA de DAI usa una forma única de metadatos temporizados, como se describe en las siguientes secciones.

Transmisiones HLS MPEG2TS

Las transmisiones HLS de DAI lineales que usan segmentos MPEG2TS pasan metadatos temporizados al reproductor de video mediante etiquetas ID3 dentro de la banda. Estas etiquetas ID3 están incorporadas en los segmentos MPEG2TS y reciben el nombre de campo TXXX (para el contenido de texto personalizado definido por el usuario).

Reproducción en Safari

Safari procesa las etiquetas ID3 automáticamente, como un segmento oculto, por lo que los eventos de cambio de inserción se activan en el momento correcto para procesar cada metadato. Está bien pasar todos los metadatos al SDK de DAI de IMA, independientemente del contenido o el tipo. Los metadatos irrelevantes se filtran automáticamente.

Por ejemplo:

videoElement.textTracks.addEventListener('addtrack', (e) => {
  const track = e.track;
  if (track.kind === 'metadata') {
    track.mode = 'hidden';
    track.addEventListener('cuechange', () => {
      for (const cue of track.activeCues) {
        const metadata = {};
        metadata[cue.value.key] = cue.value.data;
        streamManager.onTimedMetadata(metadata);
      }
    });
  }
});
...

HLS.js

HLS.js proporciona etiquetas ID3 en lotes a través del evento FRAG_PARSING_METADATA, como un array de muestras. HLS.js no traduce los datos del ID3 de arrays de bytes a strings ni desplaza los eventos a su PTS correspondiente. No es necesario decodificar los datos de muestra de un array de bytes a una cadena, ni filtrar etiquetas de ID3 irrelevantes, ya que el SDK de DAI de IMA realiza esta decodificación y filtrado automáticamente.

Por ejemplo:

hls.on(Hls.Events.FRAG_PARSING_METADATA, (e, data) => {
  if (streamManager && data) {
    data.samples.forEach((sample) => {
      streamManager.processMetadata('ID3', sample.data, sample.pts);
    });
  }
});
...

Transmisiones HLS CMAF

Las transmisiones HLS de DAI lineales que usan el Common Media Application Framework (CMAF) pasan metadatos temporizados a través de cuadros eMSGv1 dentro de la banda después del estándar ID3 a CMAF. Estos cuadros eMSG están incorporados al comienzo de cada segmento de medios, y cada eMSG ID3 contiene un PTS relacionado con la última discontinuidad de la transmisión.

A partir de la versión 1.2.0 de HLS.js, ambos reproductores sugeridos pasan el ID3 a través de CMAF al usuario como si fueran etiquetas ID3. Por este motivo, los siguientes ejemplos son los mismos que para las transmisiones HLS MPEG2TS. Sin embargo, este podría no ser el caso con todos los jugadores, por lo que la implementación de la compatibilidad con transmisiones HLS CMAF podría requerir un código único para analizar ID3 a través de eMSG.

Reproducción en Safari

Safari trata el ID3 mediante los metadatos de eMSG como seudo eventos ID3, lo que los proporciona en lotes, automáticamente, como una pista oculta. De esta manera, los eventos cuechange se activan en el momento correcto para procesar cada metadato. Está bien pasar todos los metadatos al SDK de DAI de IMA, ya sea que sea relevante para la sincronización o no. Todos los metadatos no relacionados con la DAI se filtran automáticamente.

Por ejemplo:

videoElement.textTracks.addEventListener('addtrack', (e) => {
  const track = e.track;
  if (track.kind === 'metadata') {
    track.mode = 'hidden';
    track.addEventListener('cuechange', () => {
      for (const cue of track.activeCues) {
        const metadata = {};
        metadata[cue.value.key] = cue.value.data;
        streamManager.onTimedMetadata(metadata);
      }
    });
  }
});
...

HLS.js

A partir de la versión 1.2.0, HLS.js trata a ID3 a través de metadatos eMSG como seudo eventos ID3, y los proporciona en lotes, a través del evento FRAG_PARSING_METADATA, como un arreglo de muestras. HLS.js no traduce los datos del ID3 de arrays de bytes a strings y no compensa los eventos a su PTS correspondiente. No es necesario decodificar los datos de muestra de un array de bytes a una cadena, ya que el SDK de IMA de DAI realiza esta decodificación automáticamente.

Por ejemplo:

hls.on(Hls.Events.FRAG_PARSING_METADATA, (e, data) => {
  if (streamManager && data) {
    data.samples.forEach((sample) => {
      streamManager.processMetadata('ID3', sample.data, sample.pts);
    });
  }
});
...

Transmisiones DASH

Las transmisiones de DASH de DAI lineales pasan metadatos como eventos de manifiesto en una transmisión de eventos con el valor schemeIdUri personalizado urn:google:dai:2018. Cada evento de estas transmisiones contiene una carga útil de texto y el PTS.

DASH.js

Dash.js proporciona controladores de eventos personalizados con el nombre del valor schemeIdUri de cada transmisión de eventos. Estos controladores personalizados se activan por lotes, lo que te permite procesar el valor PTS para cronometrar correctamente el evento. El SDK de IMA de DAI puede controlar esto por ti con el método streamManager, processMetadata().

Por ejemplo:

const dash = dashjs.MediaPlayer().create();
dash.on('urn:google:dai:2018', (payload) => {
  const mediaId = payload.event.messageData;
  const pts = payload.event.calculatedPresentationTime;
  streamManager.processMetadata('urn:google:dai:2018', mediaId, pts);
});
...

Shaka Player

Shaka Player muestra eventos como parte de su evento timelineregionenter. Debido a una incompatibilidad de formato con Shaka Player, el valor de los metadatos debe recuperarse sin formato mediante la propiedad de detalles eventElement.attributes['messageData'].value.

Por ejemplo:

player.addEventListener('timelineregionenter', function(event) {
  const detail = event.detail;
  if ( detail.eventElement.attributes &&
       detail.eventElement.attributes['messageData'] &&
       detail.eventElement.attributes['messageData'].value) {
    const mediaId = detail.eventElement.attributes['messageData'].value;
    const pts = detail.startTime;
    streamManager.processMetadata("urn:google:dai:2018", mediaId, pts);
  }
});
...

Publicación de grupos de anuncios

En la entrega de pods, existen diferentes configuraciones para pasar metadatos temporizados en función de los siguientes criterios:

  • Tipo de transmisión en vivo o VOD
  • Formato de transmisión HLS o DASH
  • El tipo de reproductor utilizado
  • El tipo de backend de DAI que se usa

Formato de transmisión HLS (transmisiones en vivo y VOD, reproductor HLS.js)

Si usas un reproductor HLS.js, escucha el evento FRAG_PARSING_METADATA de HLS.js para obtener los metadatos del ID3 y pasarlos al SDK con StreamManager.processMetadata().

Para reproducir automáticamente el video después de que todo esté cargado y listo, escucha el evento MANIFEST_PARSED de HLS.js para activar la reproducción.

function loadStream(streamID) {
  hls.loadSource(url);
  hls.attachMedia(videoElement);
  
  // Timed metadata is passed HLS stream events to the streamManager.
  hls.on(Hls.Events.FRAG_PARSING_METADATA, parseID3Events);
  hls.on(Hls.Events.MANIFEST_PARSED, startPlayback);
}

function parseID3Events(event, data) {
  if (streamManager && data) {
    // For each ID3 tag in the metadata, pass in the type - ID3, the
    // tag data (a byte array), and the presentation timestamp (PTS).
    data.samples.forEach((sample) => {
      streamManager.processMetadata('ID3', sample.data, sample.pts);
    });
  }
}

function startPlayback() {
  console.log('Video Play');
  videoElement.play();
}

DASH.js (formato de transmisiones de DASH, tipo de transmisión en vivo y VOD)

Si usas un reproductor DASH.js, debes usar strings diferentes a fin de escuchar los metadatos de ID3 para las transmisiones en vivo o de VOD:

  • Transmisiones en vivo: 'https://developer.apple.com/streaming/emsg-id3'
  • Transmisiones de VOD: 'urn:google:dai:2018'

Pasa los metadatos de ID3 al SDK con StreamManager.processMetadata().

Para mostrar automáticamente los controles de video después de que todo esté cargado y listo, escucha el evento MANIFEST_LOADED de DASH.js.

const googleLiveSchema = 'https://developer.apple.com/streaming/emsg-id3';
const googleVodSchema = 'urn:google:dai:2018';
dashPlayer.on(googleLiveSchema, processMetadata);
dashPlayer.on(googleVodSchema, processMetadata);
dashPlayer.on(dashjs.MediaPlayer.events.MANIFEST_LOADED, loadlistener);

function processMetadata(metadataEvent) {
  const messageData = metadataEvent.event.messageData;
  const timestamp = metadataEvent.event.calculatedPresentationTime;

  // Use StreamManager.processMetadata() if your video player provides raw
  // ID3 tags, as with dash.js.
  streamManager.processMetadata('ID3', messageData, timestamp);
}

function loadlistener() {
  showControls();

  // This listener must be removed, otherwise it triggers as addional
  // manifests are loaded. The manifest is loaded once for the content,
  // but additional manifests are loaded for upcoming ad breaks.
  dashPlayer.off(dashjs.MediaPlayer.events.MANIFEST_LOADED, loadlistener);
}

Shaka Player con transmisiones en vivo (formato de transmisiones DASH)

Si usas el Shaka player para la reproducción de transmisiones en vivo, usa la string 'emsg' a fin de escuchar eventos de metadatos. Luego, usa los datos del mensaje de evento en la llamada a StreamManager.onTimedMetadata().

shakaPlayer.addEventListener('emsg', (event) => onEmsgEvent(event));

function onEmsgEvent(metadataEvent) {
  // Use StreamManager.onTimedMetadata() if your video player provides
  // processed metadata, as with Shaka player livestreams.
  streamManager.onTimedMetadata({'TXXX': metadataEvent.detail.messageData});
}

Reproductor Shaka con transmisiones de VOD (formato de transmisiones DASH)

Si usas el Shaka Player para la reproducción de transmisión de VOD, usa la string 'timelineregionenter' para escuchar eventos de metadatos. Luego, usa los datos del mensaje de evento en la llamada a StreamManager.processMetadata() con la string 'urn:google:dai:2018'.

shakaPlayer.addEventListener('timelineregionenter', (event) => onTimelineEvent(event));

function onTimelineEvent(metadataEvent) {
  const detail = metadataEvent.detail;
  if ( detail.eventElement.attributes &&
       detail.eventElement.attributes['messageData'] &&
       detail.eventElement.attributes['messageData'].value ) {
        const mediaId = detail.eventElement.attributes['messageData'].value;
        const pts = detail.startTime;
        // Use StreamManager.processMetadata() if your video player provides raw
        // ID3 tags, as with Shaka player VOD streams.
        streamManager.processMetadata('urn:google:dai:2018', mediaId, pts);
       }
}