處理線性 DAI 串流中的定時中繼資料

互動式媒體廣告 (IMA) 動態廣告插播 (DAI) SDK 需要使用內嵌於串流媒體區隔 (頻內中繼資料) 或串流資訊清單檔案 (資訊清單中繼資料) 中的中繼資料資訊,來追蹤觀眾的位置和用戶端廣告事件。視播放的串流類型而定,中繼資料會以不同格式傳送。

影片播放器會分批收到定時中繼資料。視玩家而定,中繼資料可在排定的時間或以批次方式顯示。每個中繼資料字串都有相關聯的顯示時間戳記 (PTS),指出應觸發的時間。

應用程式負責擷取中繼資料,並轉送至 IMA DAI SDK。SDK 提供下列方法傳遞這項資訊:

onTimedMetadata

此方法會將已準備好處理的中繼資料字串轉送至 SDK。這會使用單一引數:

  • metadata:包含 TXXX 金鑰的物件,與以 google_ 為開頭的相關字串值。
processMetadata

這個方法會安排在指定 PTS 後,由 SDK 處理中繼資料字串。該函式採用的是下列引數:

  • type:包含要處理事件類型的字串。可接受的值為 HLS 的 ID3,或 DASH 的 urn:google:dai:2018
  • data:以 google_ 開頭的字串值,或解碼為這類字串的位元組陣列。
  • timestamp:系統應處理資料時的時間戳記 (以秒為單位)。

IMA DAI SDK 支援的每種串流類型都使用格式專屬的定時中繼資料,如以下各節所述。

HLS MPEG2TS 串流

使用 MPEG2TS 區段的線性 DAI HLS 串流,透過頻內 ID3 標記將有時間的中繼資料傳送至影片播放器。這些 ID3 標記會嵌入 MPEG2TS 區隔中,並為 TXXX 欄位名稱 (適用於自訂使用者定義的文字內容)。

在 Safari 中播放

Safari 會自動以隱藏測試群組的形式處理 ID3 標記,因此建議在正確的時間觸發每個中繼資料。無論內容或類型為何,您都可以將所有中繼資料傳送至 IMA DAI SDK。系統會自動篩除不相關的中繼資料。

範例如下:

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 會透過 FRAG_PARSING_METADATA 事件,以批次形式提供 ID3 標記。HLS.js 不會將 ID3 資料從位元組陣列轉譯為字串,也不會將事件偏移至對應的 PTS。IMA DAI SDK 會自動執行解碼及篩選,因此您不需要將樣本資料從位元組陣列解碼為字串,或篩除不相關的 ID3 代碼。

範例如下:

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

HTTP 即時串流 CMAF 串流

使用通用媒體應用程式架構 (CMAF) 的線性 DAI HLS 串流,透過 ID3 到 CMAF 標準,透過頻內 eMSGv1 方塊傳遞定時中繼資料。這些 eMSG 方塊會嵌入每個媒體區段的開頭,每個 ID3 eMSG 皆包含相對於串流中最後一個停止性的 PTS。

自 HLS.js 1.2.0 版本起,我們建議的玩家透過 CMAF 將 ID3 傳送給使用者,就像是 ID3 標記一樣。因此,下列範例與 HLS MPEG2TS 串流相同。不過,並非所有播放器都會採用這種做法,因此實作 HLS CMAF 串流的支援功能時,可能需要使用專屬程式碼透過 eMSG 剖析 ID3。

在 Safari 中播放

Safari 會將 ID3 透過 eMSG 中繼資料視為虛擬 ID3 事件,並自動以隱藏軌跡的形式提供這些事件,也就是說,系統會在正確的時間點觸發 cuechange 事件,以處理每則中繼資料。無論是否與時間有關,都可以將所有中繼資料傳遞至 IMA DAI SDK。系統會自動篩除任何與 DAI 相關的中繼資料。

範例如下:

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

自 1.2.0 版起,HLS.js 會將 ID3 視為虛擬 ID3 事件,並透過 FRAG_PARSING_METADATA 事件當做樣本陣列,分批提供。HLS.js 不會將 ID3 資料從位元組陣列轉換為字串,也不會將事件偏移至對應的 PTS。由於 IMA DAI SDK 會自動執行解碼,因此您不需要將位元組陣列的範例資料解碼為字串。

範例如下:

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

DASH 串流

線性 DAI DASH 會透過自訂 schemeIdUriurn:google:dai:2018 的事件串流,將中繼資料做為資訊清單事件傳遞。這些串流中的每個事件都包含文字酬載和 PTS。

DASH.js

Dash.js 提供的自訂事件處理常式是以每個事件串流的 schemeIdUri 值命名。這些自訂處理常式會分批觸發,讓您可以自行處理 PTS 值,以便為事件正確設定時間。IMA DAI SDK 可以使用 streamManager 方法 processMetadata() 代為處理。

範例如下:

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 播放器

Shaka Player 會在其 timelineregionenter 事件中顯示事件。由於格式與 Shaka Player 不相容,因此您必須透過詳細資料屬性 eventElement.attributes['messageData'].value 擷取原始中繼資料值。

範例如下:

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

廣告連播放送

對於 Pod 放送,根據下列條件傳送定時中繼資料有不同設定:

  • 直播或 VOD 串流類型
  • HLS 或 DASH 串流格式
  • 使用的玩家類型
  • 目前使用的 DAI 後端類型

HLS 串流格式 (直播和 VOD 串流、HLS.js 播放器)

如果您使用 HLS.js 播放器,可以監聽 HLS.js FRAG_PARSING_METADATA 事件來取得 ID3 中繼資料,並使用 StreamManager.processMetadata() 將其傳送至 SDK。

如要在所有內容載入完成並準備就緒後自動播放影片,請監聽 HLS.js MANIFEST_PARSED 事件以觸發播放。

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 (DASH 串流格式、直播和 VOD 串流類型)

如果使用 DASH.js 播放器,就必須使用不同的字串來監聽直播或 VOD 串流的 ID3 中繼資料:

  • 直播:'https://developer.apple.com/streaming/emsg-id3'
  • VOD 串流量:'urn:google:dai:2018'

使用 StreamManager.processMetadata() 將 ID3 中繼資料傳遞至 SDK。

如要在元素載入完畢並準備就緒後自動顯示影片控制項,請監聽 DASH.js MANIFEST_LOADED 事件。

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 (DASH 串流格式)

如果您要使用 Shaka Player 進行即時串流播放,請使用 'emsg' 字串來監聽中繼資料事件。然後在對 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});
}

採用 VOD 串流的 Shaka Player (DASH 串流格式)

如果您在 VOD 串流播放中使用 Shaka Player,請使用 'timelineregionenter' 字串來監聽中繼資料事件。然後在呼叫 StreamManager.processMetadata() 時,使用 '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);
       }
}