Processar metadados cronometrados em streams da DAI linear

O SDK da Inserção de anúncios dinâmicos (DAI) do Interactive Media Ads (IMA) depende de informações de metadados incorporadas aos segmentos de mídia do stream (metadados em banda) ou ao arquivo de manifesto de streaming (metadados no manifesto) para rastrear as posições dos espectadores e os eventos de anúncios do lado do cliente. Os metadados são enviados em formatos diferentes, dependendo do tipo de transmissão em exibição.

O player de vídeo recebe metadados cronometrados em lotes. Dependendo do player, os metadados podem ser exibidos no horário programado ou em lotes. Cada string de metadados tem um carimbo de data/hora de apresentação associado (PTS, na sigla em inglês) para quando ela deve ser acionada.

Seu app é responsável por capturar metadados e encaminhá-los ao SDK de DAI do IMA. O SDK oferece os seguintes métodos para transmitir essas informações:

onTimedMetadata

Esse método encaminha strings de metadados prontas para serem processadas ao SDK. Ele usa um único argumento:

  • metadata: um objeto que contém uma chave de TXXX com um valor de string associado prefixado por google_.
processMetadata

Esse método programa strings de metadados para serem processadas pelo SDK após o PTS especificado. Ele usa os seguintes argumentos:

  • type: uma string que contém o tipo de evento que está sendo processado. Os valores aceitos são ID3 para HLS ou urn:google:dai:2018 para DASH
  • data: um valor de string com o prefixo google_ ou uma matriz de bytes que decodifica para essa string.
  • timestamp: o carimbo de data/hora em segundos em que os dados precisam ser processados.

Cada tipo de stream compatível com o SDK de DAI do IMA usa uma forma exclusiva de metadados cronometrados, conforme descrito nas seções a seguir.

Streams HLS MPEG2TS

Os streams HLS de DAI linear que usam segmentos MPEG2TS transmitem metadados cronometrados ao player de vídeo por meio de tags ID3 na banda. Essas tags ID3 são incorporadas aos segmentos MPEG2TS e recebem o nome do campo TXXX (para conteúdo de texto personalizado definido pelo usuário).

Reprodução no Safari

O Safari processa tags ID3 automaticamente, como uma faixa oculta. Assim, os eventos cuechange são acionados no momento correto para processar cada parte dos metadados. Não há problema em transmitir todos os metadados ao SDK de DAI do IMA, independentemente do conteúdo ou tipo. Metadados irrelevantes são filtrados automaticamente.

Veja um exemplo:

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

O HLS.js fornece tags ID3 em lotes por meio do evento FRAG_PARSING_METADATA, como uma matriz de amostras. O HLS.js não converte os dados ID3 de matrizes de bytes em strings e não desloca eventos para o PTS correspondente. Não é necessário decodificar os dados de amostra da matriz de bytes para a string nem filtrar tags ID3 irrelevantes, já que o SDK de DAI do IMA executa essa decodificação e filtragem automaticamente.

Veja um exemplo:

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

Streams HLS CMAF

Os streams HLS lineares da DAI que usam o Common Media Application Framework (CMAF) transmitem metadados cronometrados por caixas eMSGv1 na banda seguindo o padrão ID3 a CMAF. Essas caixas de eMSG são incorporadas no início de cada segmento de mídia, e cada eMSG ID3 contém um PTS relativo à última descontinuidade no stream.

Na versão 1.2.0 do HLS.js, nossos dois players sugeridos transmitem o ID3 pelo CMAF ao usuário como se fossem tags ID3. Por esse motivo, os exemplos a seguir são os mesmos dos streams HLS MPEG2TS. No entanto, talvez esse não seja o caso com todos os players. Portanto, a implementação do suporte a streams do CMAF de HLS pode exigir um código exclusivo para analisar o ID3 por meio do eMSG.

Reprodução no Safari

O Safari trata o ID3 por meio dos metadados eMSG como eventos pseudoID3, fornecendo-os em lotes automaticamente como uma faixa oculta, de modo que os eventos cuechange sejam disparados no momento correto para processar cada parte dos metadados. Não há problema em transmitir todos os metadados ao SDK de DAI do IMA, sejam eles relevantes ao tempo ou não. Todos os metadados não relacionados à DAI são filtrados automaticamente.

Veja um exemplo:

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 da versão 1.2.0, o HLS.js trata o ID3 por meio de metadados eMSG como eventos pseudoID3, fornecendo-os em lotes, por meio do evento FRAG_PARSING_METADATA, como uma matriz de amostras. O HLS.js não converte os dados ID3 de matrizes de bytes em strings e não desloca eventos para o PTS correspondente. Não é necessário decodificar os dados de amostra da matriz de bytes para a string, já que o SDK de DAI do IMA executa essa decodificação automaticamente.

Veja um exemplo:

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

streams DASH

Os streams DASH de DAI lineares transmitem metadados como eventos de manifesto em um stream de eventos com o valor schemeIdUri personalizado urn:google:dai:2018. Cada evento nesses streams contém um payload de texto e o PTS.

DASH.js

O Dash.js fornece manipuladores de eventos personalizados nomeados de acordo com o valor schemeIdUri de cada fluxo de eventos. Esses gerenciadores personalizados são disparados em lotes, deixando para você processar o valor do PTS para cronometrar o evento corretamente. O SDK de DAI do IMA pode resolver isso usando o método streamManager, processMetadata().

Veja um exemplo:

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

Tocador de shaka

Shaka Player mostra eventos como parte do evento timelineregionenter. Devido a uma incompatibilidade de formatação com o Shaka Player, o valor dos metadados precisa ser recuperado por meio da propriedade de detalhes eventElement.attributes['messageData'].value.

Veja um exemplo:

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

Veiculação de conjuntos

Para a disponibilização de pods, há configurações diferentes para transmitir metadados cronometrados, dependendo dos seguintes critérios:

  • Tipo de transmissão ao vivo ou VOD
  • Formato de transmissão HLS ou DASH
  • O tipo de player usado
  • O tipo de back-end da DAI sendo usado

Formato da transmissão HLS (transmissões ao vivo e VOD, player HLS.js)

Se você estiver usando um player HLS.js, detecte o evento FRAG_PARSING_METADATA do HLS.js para receber metadados ID3 e transmiti-los ao SDK com StreamManager.processMetadata().

Para abrir o vídeo automaticamente depois que tudo estiver carregado e pronto, detecte o evento MANIFEST_PARSED de HLS.js para acionar a reprodução.

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 transmissões DASH, tipo de transmissão ao vivo e VOD)

Se você estiver usando um player DASH.js, será necessário usar strings diferentes para ouvir metadados ID3 de transmissões ao vivo ou VOD:

  • Transmissões ao vivo: 'https://developer.apple.com/streaming/emsg-id3'
  • Streams de VOD: 'urn:google:dai:2018'

Transmita os metadados ID3 ao SDK com StreamManager.processMetadata().

Para mostrar os controles de vídeo automaticamente depois que tudo estiver carregado e pronto, ouça o evento MANIFEST_LOADED do 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 com transmissões ao vivo (formato de streaming DASH)

Se você estiver usando o player Shaca para reprodução da transmissão ao vivo, use a string 'emsg' para detectar eventos de metadados. Em seguida, use os dados das mensagens do evento na chamada para 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 com streams VOD (formato de streams DASH)

Se você estiver usando o player Shaca para reprodução de streaming de VOD, use a string 'timelineregionenter' para detectar eventos de metadados. Em seguida, use os dados da mensagem do evento na chamada para StreamManager.processMetadata() com a 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);
       }
}