Processar metadados cronometrados em streams da DAI linear

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

O player de vídeo recebe metadados com marcação de tempo 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 (PTS, na sigla em inglês) associado para quando ela precisa 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 as strings de metadados que estão prontas para serem processadas no SDK. É necessário 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 contendo 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 com marcação de tempo, conforme descrito nas seções a seguir.

Streams HLS MPEG2TS

Os streams HLS da DAI lineares que usam os segmentos MPEG2TS transmitem metadados com tempo determinado para o 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. Portanto, os eventos cuechange são 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, independentemente do conteúdo ou tipo. Metadados irrelevantes são filtrados automaticamente.

Confira 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 de 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.

Confira 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 da DAI lineares que usam o Common Media Application Framework (CMAF) transmitem metadados com marcação de tempo por meio de caixas eMSGv1 na banda, seguindo o padrão ID3 pelo 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 fluxos HLS MPEG2TS. No entanto, esse pode não ser o caso com todos os jogadores. Portanto, a implementação do suporte a streams do CMAF de HLS pode exigir um código exclusivo para analisar o ID3 por eMSG.

Reprodução no Safari

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

Confira 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 de ID3 de matrizes de bytes em strings e não desloca os eventos para o PTS correspondente. Não é necessário decodificar os dados de amostra da matriz de bytes para a string porque o SDK de DAI do IMA realiza essa decodificação automaticamente.

Confira 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 lineares DAI DASH transmitem metadados como eventos de manifesto em um fluxo 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

A Dash.js fornece manipuladores de eventos personalizados com os nomes do valor schemeIdUri de cada fluxo de eventos. Esses gerenciadores personalizados são acionados em lotes, deixando que você processe o valor do PTS para cronometrar corretamente o evento. O SDK de DAI do IMA pode gerenciar isso para você com o método streamManager, processMetadata().

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

Jogador 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 extraído, usando a propriedade de detalhes eventElement.attributes['messageData'].value.

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

Disponibilização de conjuntos

Para a disponibilização de pods, há diferentes configurações 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 em uso

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, ouça o evento MANIFEST_PARSED do 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 stream ao vivo e VOD)

Se você estiver usando um player DASH.js, use strings diferentes para detectar metadados ID3 em 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 para o SDK com StreamManager.processMetadata().

Para mostrar automaticamente os controles de vídeo 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 streams DASH)

Se você estiver usando o player Shaka (link em inglês) para a reprodução da transmissão ao vivo, use a string 'emsg' para detectar eventos de metadados. Em seguida, use os dados da mensagem de 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 Shaka (em inglês) para a reprodução de streams de VOD, use a string 'timelineregionenter' para detectar eventos de metadados. Em seguida, use os dados da mensagem de 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);
       }
}