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 de contenido multimedia de la transmisión (metadatos en banda) o en el archivo de manifiesto de transmisión (metadatos en el manifiesto) para hacer un seguimiento de las posiciones de los usuarios 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 de tiempo en lotes. Según el reproductor, los metadatos se pueden mostrar a la hora programada o por lotes. Cada cadena de metadatos tiene una marca de tiempo de presentación (PTS) asociada para indicar cuándo se debe activar.

Tu app es responsable de capturar los 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 cadenas de metadatos que están listas para procesarse al SDK. Toma un solo argumento:

  • metadata: Un objeto que contiene una clave de TXXX con un valor de cadena asociado que tiene el prefijo google_.
processMetadata

Este método programa cadenas de metadatos que el SDK procesará después de la PTS especificada. Toma los siguientes argumentos:

  • type: Es una cadena que contiene el tipo de evento que se está procesando. Los valores aceptados son ID3 para HLS o urn:google:dai:2018 para DASH.
  • data: Un valor de cadena con el prefijo google_ o un array de bytes que sigue este formato ID3:u\0004u\000u\000u\0000TXXXu\0004u\000u\000u\0000google_xxxxxxxx.
  • timestamp: Es la marca de tiempo en segundos en la que se deben procesar los datos.

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

Transmisiones HLS MPEG2TS

Las transmisiones de HLS de DAI lineal que usan los segmentos MPEG2TS pasan metadatos de tiempo al reproductor de video a través de etiquetas ID3 en banda. Estas etiquetas ID3 se incorporan en los segmentos MPEG2TS y se les asigna 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 una pista oculta, de modo que los eventos de cambio de pista se activen 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 de ID3 de los arrays de bytes a cadenas ni compensa los eventos a sus PTS correspondientes. No es necesario decodificar los datos de muestra del array de bytes a la cadena ni filtrar las etiquetas ID3 irrelevantes, ya que el SDK de IMA DAI 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 de CMAF HLS

Las transmisiones de HLS de DAI lineal que usan el Common Media Application Framework (CMAF) pasan los metadatos sincronizados a través de cuadros eMSGv1 en banda siguiendo el estándar ID3 a través de CMAF. Estos cuadros de eMSG se incorporan al comienzo de cada segmento multimedia, y cada eMSG ID3 contiene un PTS en relación con la última discontinuidad en la transmisión.

A partir de la versión 1.2.0 de HLS.js, ambos reproductores sugeridos pasan 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, es posible que no sea así con todos los reproductores, por lo que implementar la compatibilidad con las transmisiones de CMAF de HLS podría requerir un código único para analizar ID3 a través de eMSG.

Reproducción en Safari

Safari trata los metadatos ID3 a través de eMSG como eventos de ID3 simulados y los proporciona en lotes, automáticamente, como una pista oculta, de modo que los eventos cuechange se activen en el momento correcto para procesar cada metadato. Está bien pasar todos los metadatos al SDK de IMA DAI, ya sea que sean relevantes para el tiempo o no. Los metadatos que no estén 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 los metadatos de ID3 a través de eMSG como eventos de ID3 ficticios y los proporciona en lotes, a través del evento FRAG_PARSING_METADATA, como un array de muestras. HLS.js no traduce los datos de ID3 de los arrays de bytes a cadenas ni compensa los eventos a sus PTS correspondientes. No es necesario decodificar los datos de muestra del array de bytes a la cadena, ya que el SDK de DAI de IMA 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 de DASH

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

DASH.js

Dash.js proporciona controladores de eventos personalizados que se nombran según el valor de schemeIdUri de cada flujo de eventos. Estos controladores personalizados se activan por lotes, por lo que debes procesar el valor de PTS para programar correctamente el evento. El SDK de DAI de IMA 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 se debe recuperar sin procesar a través de la propiedad de detalles eventNode.attributes['messageData'].

Por ejemplo:

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

Publicación de grupos de anuncios

Para la publicación de pods, existen diferentes configuraciones para pasar metadatos programados según los siguientes criterios:

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

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

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

Para reproducir el video automáticamente 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 DASH, tipo de transmisión en vivo y VOD)

Si usas un reproductor DASH.js, debes usar cadenas diferentes para escuchar metadatos ID3 para transmisiones en vivo o VOD:

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

Pasa los metadatos 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 reproductor Shaka para la reproducción de transmisiones en vivo, usa la cadena 'emsg' para detectar eventos de metadatos. Luego, usa los datos del mensaje del evento en tu 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});
}

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

Si usas el reproductor Shaka para la reproducción de transmisiones de VOD, usa la cadena 'timelineregionenter' para escuchar eventos de metadatos. Luego, usa los datos del mensaje del evento en tu llamada a StreamManager.processMetadata() con la cadena '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);
       }
}