Ad Playback Delegate
What you learn
This article is intended for front-end developers integrating DIVA Player with DIVA Back Office as the video streaming source. By the end of this article, your application controls client-side ads (VAST/VMAP) when a VOD playback resumes from a saved position, and receives a callback for every ad break that starts and ends on Web and WebTV.
adPlaybackDelegate is available from DIVA Web and WebTV 5.12.6.
Before starting
- Your video setup is working and you have a
<videoId>and basic instantiation code for your platform. - Client-side ads are configured: the
googleIMAlib is loaded (see Libs) and the video has a VAST/VMAP ad tag. - The playback is a VOD. The delegate is ignored for Live and DVR.
- To resume a video, you know the resume position of the user. See Resume Point API to track it.
How it works
adPlaybackDelegate is an optional object in the player configuration. Every method is optional. When no delegate is set, ads behave as before.
| Method | When DIVA Player calls it | What you do with it |
|---|---|---|
shouldSkipAdsBeforeStart(videoId, startPosition) | Once per VOD playback, before the ad schedule is requested. | Return true to skip every ad break before the resume position. Return false to play the latest one. |
onAdScheduleLoaded(videoId, cuePoints) | When the ad schedule is loaded, before content starts. | Read the ad break positions, for example to mark them on your own UI. |
onAdBreakStarted(videoId, cue) | When the first ad of an ad break starts. | Track the ad break start. |
onAdBreakEnded(videoId, cue, outcome) | When an ad break that started ends. | Track the result, for example to remember which ad breaks the user watched. |
Times
All times are content seconds:
- The video trim-in is removed.
0is the start of the content. - The pre-roll cue point is
0. - The post-roll is out of scope. DIVA Player never reports it.
Resume rule
shouldSkipAdsBeforeStart receives startPosition, the resume position in seconds, from deepLinkType: "relative" and deepLinkValue. DIVA Player then applies one of two rules to the ad breaks scheduled at or before startPosition:
| Answer | Result |
|---|---|
true | Every ad break at or before startPosition is skipped. Content starts directly at startPosition. |
false (default) | Only the latest ad break at or before startPosition plays. The ones before it are skipped. Content then resumes at startPosition. |
Ad breaks after startPosition always play as scheduled.
Example: the schedule has a pre-roll (0) and mid-rolls at 300 and 900. The user resumes at 600.
true: no ad plays at start. The mid-roll at900plays later.false: the mid-roll at300plays, then content resumes at600. The mid-roll at900plays later.
shouldSkipAdsBeforeStartcan return abooleanor aPromise<boolean>.- DIVA Player waits for the answer for 2 seconds at most (
AD_PLAYBACK_DELEGATE_TIMEOUT_MS = 2000). A late answer, a rejected promise or a thrown error is treated asfalse. - When
startPositionis0, the playback is not a resume: ads behave as normal, whatever the answer. - With
deepLinkType: "absolute", DIVA Player does not callshouldSkipAdsBeforeStart. - DIVA Player calls it when a video starts (first play or video switch). It does not call it again when the player recovers from an error: the first answer is kept.
Ad break outcomes
onAdBreakEnded receives an outcome of type AdBreakOutcome:
| Outcome | Meaning |
|---|---|
"completed" | Every ad of the ad break played until the end, and content resumed. |
"skipped" | The user skipped an ad of the ad break. |
"abandoned" | The ad break was left part-way: exit, player teardown, or playback stopped before it ended. |
"failed" | The ad break ended because of an ad error. |
onAdBreakEnded is called only for an ad break that started (onAdBreakStarted was called for the same cue).
Keep the callbacks fast and safe. DIVA Player catches and logs an error thrown by a callback, and playback continues.
Integration steps
Step 1 — Write the delegate
import type { AdPlaybackDelegate } from "diva-web-types";
const adPlaybackDelegate: AdPlaybackDelegate = {
shouldSkipAdsBeforeStart: async (videoId, startPosition) => {
// Example: skip ads before the resume position when the user already watched them
return await myAdHistory.hasWatchedAdsBefore(videoId, startPosition);
},
onAdScheduleLoaded: (videoId, cuePoints) => {
console.log("Ad schedule:", videoId, cuePoints);
},
onAdBreakStarted: (videoId, cue) => {
console.log("Ad break started:", videoId, cue);
},
onAdBreakEnded: (videoId, cue, outcome) => {
console.log("Ad break ended:", videoId, cue, outcome);
if (outcome === "completed") {
myAdHistory.saveWatchedAdBreak(videoId, cue);
}
},
};
myAdHistory is your own storage. Replace it with your logic.
Step 2 — Add the delegate and the resume position to the configuration
Set adPlaybackDelegate in the configuration. Set deepLinkType: "relative" and deepLinkValue (milliseconds) to start from the resume position.
- RW:
deepLinkValueis an array, one value for each video of the configuration. - WebTV:
deepLinkValueis a string.
Full code samples
Write the Basic instantiation code. Extend it with adPlaybackDelegate as below.
- RW
- WebTV
import {
BoAdapterWebComponentProps,
DivaWebBoAdapter,
} from "@deltatre-vxp/diva-sdk/diva-web-bo-adapter";
import "@deltatre-vxp/diva-sdk/diva-web-sdk/index.min.css";
import type { AdPlaybackDelegate } from "diva-web-types";
const videoId = "<videoId>";
const resumePositionMs = 600000; // 10 minutes, read from your resume point storage
const libs = {
mux: "https://cdnjs.cloudflare.com/ajax/libs/mux.js/6.2.0/mux.min.js",
shaka: "https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.11.2/shaka-player.compiled.js",
hlsJs: "https://cdn.jsdelivr.net/npm/hls.js@1.5.7",
googleIMA: "https://imasdk.googleapis.com/js/sdkloader/ima3.js",
googleDAI: false,
googleCast: false,
threeJs: false,
};
const adPlaybackDelegate: AdPlaybackDelegate = {
shouldSkipAdsBeforeStart: (videoId, startPosition) => {
console.log("Resume:", videoId, startPosition);
return true;
},
onAdScheduleLoaded: (videoId, cuePoints) => console.log("Ad schedule:", videoId, cuePoints),
onAdBreakStarted: (videoId, cue) => console.log("Ad break started:", videoId, cue),
onAdBreakEnded: (videoId, cue, outcome) => console.log("Ad break ended:", videoId, cue, outcome),
};
const config = {
videoId,
libs,
deepLinkType: "relative",
deepLinkValue: [resumePositionMs],
// When no delegate is set, DIVA keeps its default client-side ad behaviour:
// no resume rule is applied and no ad break events are reported.
adPlaybackDelegate,
};
function App() {
const props: BoAdapterWebComponentProps = {
settingsUrl: "<settings URL>",
languageCode: "en-US",
languageDictionary: "en-US",
onDivaBoAdapterError: console.error,
config,
};
return <DivaWebBoAdapter {...props} />;
}
export default App;
import { DivaWebTvBoAdapter } from "@deltatre-vxp/diva-webtv-bo-adapter";
import type { AdPlaybackDelegate, BoAdapterWebTvProps } from "diva-web-types";
const videoId = "<videoId>";
const resumePositionMs = 600000; // 10 minutes, read from your resume point storage
const libs = {
mux: "https://cdnjs.cloudflare.com/ajax/libs/mux.js/6.2.0/mux.min.js",
shaka: "https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.11.2/shaka-player.compiled.js",
hlsJs: "https://cdn.jsdelivr.net/npm/hls.js@1.5.7",
googleIMA: "https://imasdk.googleapis.com/js/sdkloader/ima3.js",
googleDAI: false,
googleCast: false,
threeJs: false,
};
const adPlaybackDelegate: AdPlaybackDelegate = {
shouldSkipAdsBeforeStart: (videoId, startPosition) => {
console.log("Resume:", videoId, startPosition);
return true;
},
onAdScheduleLoaded: (videoId, cuePoints) => console.log("Ad schedule:", videoId, cuePoints),
onAdBreakStarted: (videoId, cue) => console.log("Ad break started:", videoId, cue),
onAdBreakEnded: (videoId, cue, outcome) => console.log("Ad break ended:", videoId, cue, outcome),
};
const config = {
videoId,
libs,
deepLinkType: "relative",
deepLinkValue: String(resumePositionMs),
// When no delegate is set, DIVA keeps its default client-side ad behaviour:
// no resume rule is applied and no ad break events are reported.
adPlaybackDelegate,
};
function App() {
const props: BoAdapterWebTvProps = {
settingsUrl: "<settings URL>",
languageCode: "en-US",
languageDictionary: "en-US",
onDivaBoAdapterError: console.error,
config,
};
return <DivaWebTvBoAdapter {...props} />;
}
export default App;
Reference
export const AdBreakOutcome = {
COMPLETED: "completed",
SKIPPED: "skipped",
ABANDONED: "abandoned",
FAILED: "failed",
} as const;
export type AdBreakOutcome = (typeof AdBreakOutcome)[keyof typeof AdBreakOutcome];
export interface AdPlaybackDelegate {
shouldSkipAdsBeforeStart?: (videoId: string, startPosition: number) => boolean | Promise<boolean>;
onAdScheduleLoaded?: (videoId: string, cuePoints: number[]) => void;
onAdBreakStarted?: (videoId: string, cue: number) => void;
onAdBreakEnded?: (videoId: string, cue: number, outcome: AdBreakOutcome) => void;
}
export const AD_PLAYBACK_DELEGATE_TIMEOUT_MS = 2000;
| Parameter | Type | Description |
|---|---|---|
videoId | string | Id of the video that plays. |
startPosition | number | Resume position in content seconds. |
cuePoints | number[] | Ad break positions in content seconds. 0 is the pre-roll. The post-roll is not included. |
cue | number | Ad break position in content seconds. |
outcome | AdBreakOutcome | "completed", "skipped", "abandoned" or "failed". |