Skip to main content
Version: 11.13.0

Google DAI

Load a Google DAI stream and inspect its scheduled ad breaks, events, and content time.

Tip: open your browser's developer console and use the player and THEOplayer variables to interact with this demo.

Stream configuration

Stream type
Stream format
Advanced options

Ad tag parameters

KeyValueAction
No ad tag parameters.
Generated source
player.source = {
"sources": {
"type": "application/x-mpegurl",
"ssai": {
"integration": "google-dai",
"availabilityType": "vod",
"apiKey": "",
"contentSourceID": "2548831",
"videoID": "tears-of-steel"
}
}
};

Stream and content time

Stream time—
Content time—

Scheduled ad breaks

OffsetMax durationAds
No scheduled breaks reported yet.

Events

Clock / player timeEventDetails
No events reported yet.

The stream type and the ssai configuration identify the Google DAI stream. VOD uses a content source ID and video ID; live streams use an asset key.

function loadVOD() {
player.autoplay = true;
player.source = {
sources: {
type: 'application/x-mpegurl',
ssai: {
integration: 'google-dai',
availabilityType: 'vod',
apiKey: '',
contentSourceID: '2548831',
videoID: 'tears-of-steel',
adTagParameters: {
description_url: window.location.href,
},
},
},
};
}

function loadLive() {
player.autoplay = true;
player.source = {
sources: {
type: 'application/x-mpegurl',
ssai: {
integration: 'google-dai',
availabilityType: 'live',
apiKey: '',
assetKey: 'c-rArva4ShKVIAkNfy6HUQ',
},
},
};
}

Listen to player.ads events to track ad playback. Ad blockers can block the Google IMA DAI SDK.

player.ads.addEventListener('adbegin', (event) => {
console.log(event.ad.id, event.ad.duration, event.adBreak.timeOffset);
});

See the Google DAI guide and the GoogleDAIConfiguration API.

Code
demo.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Google DAI</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet" />
<style>
html,
body {
margin: 0;
padding: 0;
}

theoplayer-default-ui:not(:defined) {
display: inline-block;
box-sizing: border-box;
}

theoplayer-default-ui {
width: 100%;
aspect-ratio: 16 / 9;
font-family: 'Noto Sans', sans-serif;
background: #000;
}
</style>
<script src="https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js"></script>
<script src="https://cdn.theoplayer.com/dash/theoplayer/THEOplayer.js"></script>
<script nomodule src="https://unpkg.com/@theoplayer/web-ui@2/polyfills"></script>
<script async src="https://unpkg.com/@theoplayer/web-ui@2"></script>
</head>
<body>
<!--
libraryLocation: For demonstration purposes, we use the THEOplayer CDN.
For production use, host the library yourself and update this URL and the script above.

licenseUrl: Point this to your license file, or replace it with a license property.
-->
<theoplayer-default-ui
configuration='{"libraryLocation":"https://cdn.theoplayer.com/dash/theoplayer/","licenseUrl":"../../../../theoplayer-license.txt","mutedAutoplay":"all"}'
></theoplayer-default-ui>
<script src="../utils.js"></script>
<script>
const adEventNames = [
'addadbreak',
'removeadbreak',
'adbreakbegin',
'adbegin',
'adfirstquartile',
'admidpoint',
'adthirdquartile',
'adend',
'adbreakend',
'adimpression',
'adskip',
'aderror',
'adloaded',
'updateadbreak',
'adbreakchange',
'addad',
'updatead',
'adbuffering',
'adclicked',
];

const defaultConfiguration = {
format: 'hls',
availabilityType: 'vod',
contentSourceID: '2548831',
videoID: 'tears-of-steel',
apiKey: '',
};

onPlayerReady((player) => {
let events = [];
let adBreaks = [];
let previousAdBreaks = new Set();
let sourceToken = 0;

function describeAdBreak(adBreak) {
return {
timeOffset: adBreak.timeOffset,
maxDuration: adBreak.maxDuration,
adCount: adBreak.ads.length,
};
}

function reportState() {
sendToParent('dai-state', {
adBreaks: adBreaks.slice(),
events: events.slice(),
});
}

function reportTime() {
const streamTime = player.currentTime;
sendToParent('dai-time', {
streamTime,
contentTime: player.ads.dai.contentTimeForStreamTime(streamTime),
});
}

function logEvent(name, event) {
const details = [];
if (event.adBreak) {
const breakInfo = describeAdBreak(event.adBreak);
const existingIndex = adBreaks.findIndex((adBreak) => adBreak.timeOffset === breakInfo.timeOffset);
if (name === 'removeadbreak') {
if (existingIndex >= 0) adBreaks.splice(existingIndex, 1);
} else if ((name === 'addadbreak' || name === 'updateadbreak') && !previousAdBreaks.has(event.adBreak)) {
if (existingIndex >= 0) adBreaks[existingIndex] = breakInfo;
else adBreaks.push(breakInfo);
}
}
if (event.ad) {
if (event.ad.id) details.push(`Ad ${event.ad.id}`);
if (event.ad.duration !== undefined) details.push(`duration ${event.ad.duration.toFixed(1)} s`);
}
if (event.adBreak) {
details.push(`break at ${event.adBreak.timeOffset.toFixed(1)} s`);
}
const errorMessage = event.error?.message || event.error || event.message;
if (errorMessage) details.push(String(errorMessage));
events = [
{
wallClock: new Date().toLocaleTimeString(),
playerTime: player.currentTime,
event: name,
details: details.join(' · '),
},
...events,
].slice(0, 100);
reportState();
}

function sourceFor(configuration) {
const ssai = {
integration: 'google-dai',
availabilityType: configuration.availabilityType,
apiKey: configuration.apiKey,
};
if (configuration.authToken) ssai.authToken = configuration.authToken;
if (configuration.networkCode) ssai.networkCode = configuration.networkCode;
if (configuration.streamActivityMonitorID) ssai.streamActivityMonitorID = configuration.streamActivityMonitorID;
if (configuration.enableNonce) ssai.enableNonce = true;
if (configuration.adTagParameters && Object.keys(configuration.adTagParameters).length > 0) {
ssai.adTagParameters = configuration.adTagParameters;
}
if (configuration.availabilityType === 'vod') {
if (configuration.contentSourceID) ssai.contentSourceID = configuration.contentSourceID;
if (configuration.videoID) ssai.videoID = configuration.videoID;
} else if (configuration.assetKey) ssai.assetKey = configuration.assetKey;
return {
sources: {
type: configuration.format === 'dash' ? 'application/dash+xml' : 'application/x-mpegurl',
ssai,
},
};
}

function setConfiguration(configuration) {
previousAdBreaks = configuration.availabilityType === 'live' ? new Set(player.ads.scheduledAdBreaks) : new Set();
const token = ++sourceToken;
events = [];
adBreaks = [];
player.addEventListener(
'loadedmetadata',
() => {
if (token !== sourceToken) return;
adBreaks = player.ads.scheduledAdBreaks.filter((adBreak) => !previousAdBreaks.has(adBreak)).map(describeAdBreak);
reportState();
},
{ once: true }
);
player.source = sourceFor(configuration);
reportState();
}

player.ads.addEventListener(adEventNames, (event) => logEvent(event.type, event));
player.addEventListener('sourcechange', (event) => {
events = [];
adBreaks = [];
logEvent('sourcechange', event);
});
player.addEventListener('error', (event) => {
const error = player.errorObject;
logEvent('error', { ...event, message: error?.message || event.message || 'Player error' });
});
player.addEventListener('timeupdate', reportTime);

onParentMessage('load-dai', (message) => {
player.autoplay = true;
setConfiguration(message.config);
});
onParentMessage('clear-dai-log', () => {
events = [];
reportState();
});

setConfiguration(defaultConfiguration);
});
</script>
</body>
</html>