Select platform: [HTML5](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/timed-metadata "View this page for the HTML5 platform docs.") [Roku](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/roku/timed-metadata "View this page for the Roku platform docs.")

<br />

The Interactive Media Ads (IMA) Dynamic Ad Insertion (DAI) SDK relies on
metadata information embedded in the stream's media segments (in-band metadata),
or in the streaming manifest file (in-manifest metadata) to track viewers'
positions and client-side ad events. Metadata is sent in different formats,
depending on the type of stream being played.

The video player receives timed metadata in batches. Depending on the player,
metadata can be surfaced at the scheduled time, or in batches. Each metadata
string has an associated presentation timestamp (PTS) for when it should be
triggered.

Your app is responsible for capturing metadata and forwarding it to the IMA DAI SDK. The SDK offers the following methods to pass this information:

[onTimedMetadata](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/reference/js/StreamManager#onTimedMetadata)

:   This method forwards metadata strings that are ready to be processed to the
    SDK. It takes a single argument:

    - `metadata`: an object containing a key of `TXXX` with an associated string value that is prefixed by `google_`.

[processMetadata](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/reference/js/StreamManager#processMetadata)

:   This method schedules metadata strings to be processed by the SDK after the
    specified PTS. It takes the following arguments:

    - `type`: a string containing the type of event being processed. Accepted values are `ID3` for HLS or `urn:google:dai:2018` for DASH
    - `data`: either a string value prefixed by `google_` or a byte array that follows this format `ID3:u\0004u\000u\000u\0000TXXXu\0004u\000u\000u\0000google_xxxxxxxx`.
    - `timestamp`: the timestamp in seconds when data should be processed.

Each stream type supported by the IMA DAI SDK uses a unique form of timed
metadata, as described in the following sections.

## HLS MPEG2TS streams

Linear DAI HLS streams using the MPEG2TS segments pass timed metadata to the
video player through in-band ID3 tags. These ID3 tags are embedded within the
MPEG2TS segments and are given the TXXX field name (for custom user-defined text
content).

### Playback in Safari

Safari processes ID3 tags automatically, as a hidden track, so cuechange events
fire at the correct time to process each piece of metadata. It's alright to pass
all metadata to the IMA DAI SDK, regardless of content or type. Irrelevant
metadata is filtered out automatically.

Here's an example:

    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 provides ID3 tags in batches through the `FRAG_PARSING_METADATA` event,
as an array of samples. HLS.js doesn't translate the ID3 data from byte arrays
to strings and doesn't offset events to their corresponding PTS. It isn't
necessary to decode the sample data from byte array to string, or to filter out
irrelevant ID3 tags, as the IMA DAI SDK performs this decoding and filtering
automatically.

Here's an example:

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

## HLS CMAF streams

Linear DAI HLS streams using the Common Media Application Framework (CMAF) pass
timed metadata through in-band eMSGv1 boxes following the [ID3 through
CMAF](https://aomediacodec.github.io/id3-emsg/) standard. These eMSG boxes are
embedded at the beginning of each media segment, with each ID3 eMSG containing
a PTS relative to the last discontinuity in the stream.

As of the 1.2.0 release of HLS.js, both of our suggested players pass ID3
through CMAF to the user as if they were ID3 tags. For this reason, the
following examples are the same as for HLS MPEG2TS streams. However, this might
not be the case with all players, so implementing support for HLS CMAF
streams could require unique code to parse ID3 through eMSG.

### Playback in Safari

Safari treats ID3 through eMSG metadata as pseudo ID3 events, providing them in
batches, automatically, as a hidden track, such that *cuechange* events are
fired at the correct time to process each piece of metadata. It is alright to
pass all metadata to the IMA DAI SDK, whether relevant to timing or not. Any
non-DAI-related metadata are filtered out automatically.

Here's an example:

    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

As of version 1.2.0, HLS.js treats ID3 through eMSG metadata as pseudo ID3
events, providing them in batches, through the `FRAG_PARSING_METADATA` event,
as an array of samples. HLS.js does not translate the ID3 data from byte arrays
to strings and does not offset events to their corresponding PTS. It isn't
necessary to decode the sample data from byte array to string, as the IMA DAI SDK performs this decoding automatically.

Here's an example:

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

## DASH streams

Linear DAI DASH streams pass metadata as manifest events in an event stream with
the custom `schemeIdUri` value `urn:google:dai:2018`. Each event in these
streams contains a text payload, and the PTS.

### DASH.js

Dash.js provides custom event handlers named after the schemeIdUri value of each
event stream. These custom handlers fire in batches, leaving it up to you to
process the PTS value to properly time the event. The IMA DAI SDK can handle
this for you, with the streamManager method, [`processMetadata()`](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/reference/js/StreamManager#processMetadata).

Here's an example:

    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

> [!NOTE]
> **Note:** If you're using Shaka Player's integrated DAI support, you don't need to manually process timed metadata. This advice applies to advanced users who are implementing the IMA DAI SDK manually with Shaka player.

Shaka Player surfaces events as a part of their `timelineregionenter` event. Due
to a formatting incompatibility with Shaka Player, the metadata value must be
retrieved raw, through the detail property
`eventNode.attributes['messageData']`.

Here's an example:

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

## Pod serving

For Pod serving, there are different configurations for passing on timed
metadata depending on the following criteria:

- Live or VOD stream type
- HLS or DASH stream format
- The type of player being used
- The type of DAI backend being used

### HLS stream format (Live and VOD streams, HLS.js player)

If you are using an [HLS.js](https://github.com/video-dev/hls.js) player, listen to
the HLS.js `FRAG_PARSING_METADATA` event to get ID3 metadata and pass it to the
SDK with [`StreamManager.processMetadata()`](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/reference/js/StreamManager#processMetadata).

To automatically play the video after everything is loaded and ready, listen to
the HLS.js `MANIFEST_PARSED` event to trigger playback.

    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 streams format, Live and VOD stream type)

If you are using a [DASH.js](https://github.com/Dash-Industry-Forum/dash.js) player,
you have to use different strings to listen for ID3 metadata for Live or VOD
streams:

- Livestreams: `'https://developer.apple.com/streaming/emsg-id3'`
- VOD streams: `'urn:google:dai:2018'`

Pass the ID3 metadata to the SDK with [`StreamManager.processMetadata()`](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/reference/js/StreamManager#processMetadata).

To automatically show the video controls after everything is loaded and ready,
listen to the DASH.js `MANIFEST_LOADED` event.

    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 with livestreams (DASH streams format)

If you are using [Shaka player](https://github.com/shaka-project/shaka-player) for
livestream playback, use the string `'emsg'` to listen for metadata events.
Then, use the event message data in your call to [`StreamManager.onTimedMetadata()`](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/reference/js/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 with VOD streams (DASH streams format)

If you are using [Shaka player](https://github.com/shaka-project/shaka-player) for
VOD stream playback, use the string `'timelineregionenter'` to listen for
metadata events. Then, use the event message data in your call to
[`StreamManager.processMetadata()`](https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/reference/js/StreamManager#processMetadata)
with the 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);
           }
    }