Skip to content

Commit

Permalink
feat(Ads): Add support for overlay interstitials (or non-linear ads) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad authored Nov 25, 2024
1 parent e03dfc6 commit 70257ff
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 9 deletions.
1 change: 1 addition & 0 deletions build/conformance.textproto
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ requirement: {
"use \"left\" and \"top\" instead."
# The built-in set and map polyfills trigger this ban somehow.
whitelist_regexp: "synthetic:es6/"
whitelist_regexp: "lib/ads/interstitial_ad_manager.js"
}

# Disallow fdescribe.
Expand Down
50 changes: 50 additions & 0 deletions docs/tutorials/ad_monetization.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ adManager.addCustomInterstitial({
pre: false,
post: false,
timelineRange: false,
loop: false,
overlay: null,
});
```

Expand Down Expand Up @@ -144,11 +146,59 @@ player.addEventListener('timelineregionadded', (e) => {
pre: false,
post: false,
timelineRange: player.isLive(), // If true, the ad will appear as a range on the timeline.
loop: false,
overlay: null
});
});
```


##### Custom Overlay Interstitials

Example:

```js
const adManager = player.getAdManager();
const video = document.getElementById('video');
const ui = video['ui'];
// If you're using a non-UI build, this is the div you'll need to create
// for your layout. The ad manager will clear this div, when it unloads, so
// don't pass in a div that contains non-ad elements.
const container = video.ui.getControls().getClientSideAdContainer();
adManager.initInterstitial(container, player, video);
adManager.addCustomInterstitial({
id: null,
startTime: 10,
endTime: null,
uri: 'YOUR_URL',
isSkippable: true,
skipOffset: 10,
canJump: false,
resumeOffset: null,
playoutLimit: null,
once: true,
pre: false,
post: false,
timelineRange: false,
loop: false,
overlay: { // Show interstitial in upper right quadrant
viewport: {
x: 1920, // Pixels
y: 1080, // Pixels
},
topLeft: {
x: 960, // Pixels
y: 0, // Pixels
},
size: {
x: 960, // Pixels
y: 540, // Pixels
},
},
});
```


##### VAST/VMAP (playback without tracking)

Example:
Expand Down
33 changes: 32 additions & 1 deletion externs/shaka/ads.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ shaka.extern.AdCuePoint;
* once: boolean,
* pre: boolean,
* post: boolean,
* timelineRange: boolean
* timelineRange: boolean,
* loop: boolean,
* overlay: ?shaka.extern.AdInterstitialOverlay
* }}
*
* @description
Expand Down Expand Up @@ -116,10 +118,39 @@ shaka.extern.AdCuePoint;
* @property {boolean} timelineRange
* Indicates whether the interstitial should be presented in a timeline UI
* as a single point or as a range.
* @property {boolean} loop
* Indicates that the interstitials should play in loop.
* Only applies if the interstitials is an overlay.
* Only supported when using multiple video elements for interstitials.
* @property {?shaka.extern.AdInterstitialOverlay} overlay
* Indicates the characteristics of the overlay
* Only supported when using multiple video elements for interstitials.
* @exportDoc
*/
shaka.extern.AdInterstitial;


/**
* @typedef {{
* viewport: {x: number, y: number},
* topLeft: {x: number, y: number},
* size: {x: number, y: number}
* }}
*
* @description
* Contains the ad interstitial overlay info.
*
* @property {{x: number, y: number}} viewport
* The viewport in pixels.
* @property {{x: number, y: number}} topLeft
* The topLeft in pixels.
* @property {{x: number, y: number}} size
* The size in pixels.
* @exportDoc
*/
shaka.extern.AdInterstitialOverlay;


/**
* An object that's responsible for all the ad-related logic
* in the player.
Expand Down
2 changes: 2 additions & 0 deletions lib/ads/ad_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ shaka.ads.Utils = class {
pre: currentTime == null,
post: currentTime == Infinity,
timelineRange: false,
loop: false,
overlay: null,
});
break;
}
Expand Down
8 changes: 6 additions & 2 deletions lib/ads/interstitial_ad.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ shaka.ads.InterstitialAd = class {
* @param {number} sequenceLength
* @param {number} adPosition
* @param {boolean} isUsingAnotherMediaElement
* @param {?shaka.extern.AdInterstitialOverlay} overlay
*/
constructor(video, isSkippable, skipOffset, skipFor, onSkip,
sequenceLength, adPosition, isUsingAnotherMediaElement) {
sequenceLength, adPosition, isUsingAnotherMediaElement, overlay) {
/** @private {HTMLMediaElement} */
this.video_ = video;

Expand All @@ -47,6 +48,9 @@ shaka.ads.InterstitialAd = class {

/** @private {boolean} */
this.isUsingAnotherMediaElement_ = isUsingAnotherMediaElement;

/** @private {?shaka.extern.AdInterstitialOverlay} */
this.overlay_ = overlay;
}

/**
Expand Down Expand Up @@ -201,7 +205,7 @@ shaka.ads.InterstitialAd = class {
* @export
*/
isLinear() {
return true;
return this.overlay_ == null;
}

/**
Expand Down
41 changes: 35 additions & 6 deletions lib/ads/interstitial_ad_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,13 @@ shaka.ads.InterstitialAdManager = class {
this.playingAd_ = false;
this.lastTime_ = null;
this.lastPlayedAd_ = null;
if (!this.usingBaseVideo_) {
this.video_.loop = false;
this.video_.style.height = '';
this.video_.style.left = '';
this.video_.style.top = '';
this.video_.style.width = '';
}
}

/** @override */
Expand Down Expand Up @@ -336,6 +343,8 @@ shaka.ads.InterstitialAdManager = class {
pre: false,
post: false,
timelineRange: isReplace && !isInsert,
loop: false,
overlay: null,
};
this.addInterstitials([interstitial]);
}
Expand Down Expand Up @@ -548,10 +557,26 @@ shaka.ads.InterstitialAdManager = class {
await detachBasePlayerPromise;
}
if (!this.usingBaseVideo_) {
this.baseVideo_.pause();
if (interstitial.resumeOffset != null &&
interstitial.resumeOffset != 0) {
this.baseVideo_.currentTime += interstitial.resumeOffset;
if (interstitial.overlay) {
this.video_.loop = interstitial.loop;
const viewport = interstitial.overlay.viewport;
const topLeft = interstitial.overlay.topLeft;
const size = interstitial.overlay.size;
this.video_.style.height = (size.y / viewport.y * 100) + '%';
this.video_.style.left = (topLeft.x / viewport.x * 100) + '%';
this.video_.style.top = (topLeft.y / viewport.y * 100) + '%';
this.video_.style.width = (size.x / viewport.x * 100) + '%';
} else {
this.baseVideo_.pause();
if (interstitial.resumeOffset != null &&
interstitial.resumeOffset != 0) {
this.baseVideo_.currentTime += interstitial.resumeOffset;
}
this.video_.loop = false;
this.video_.style.height = '';
this.video_.style.left = '';
this.video_.style.top = '';
this.video_.style.width = '';
}
}

Expand All @@ -560,7 +585,7 @@ shaka.ads.InterstitialAdManager = class {
let playoutLimitTimer = null;

const updateBaseVideoTime = () => {
if (!this.usingBaseVideo_) {
if (!this.usingBaseVideo_ && !interstitial.overlay) {
if (interstitial.resumeOffset == null) {
if (interstitial.timelineRange && interstitial.endTime &&
interstitial.endTime != Infinity) {
Expand Down Expand Up @@ -658,7 +683,7 @@ shaka.ads.InterstitialAdManager = class {
const ad = new shaka.ads.InterstitialAd(this.video_,
interstitial.isSkippable, interstitial.skipOffset,
interstitial.skipFor, onSkip, sequenceLength, adPosition,
!this.usingBaseVideo_);
!this.usingBaseVideo_, interstitial.overlay);
if (!this.usingBaseVideo_) {
ad.setMuted(this.baseVideo_.muted);
ad.setVolume(this.baseVideo_.volume);
Expand Down Expand Up @@ -897,6 +922,8 @@ shaka.ads.InterstitialAdManager = class {
pre,
post,
timelineRange,
loop: false,
overlay: null,
});
} else if (assetList) {
const uri = /** @type {string} */(assetList.data);
Expand Down Expand Up @@ -947,6 +974,8 @@ shaka.ads.InterstitialAdManager = class {
pre,
post,
timelineRange,
loop: false,
overlay: null,
});
}
}
Expand Down

0 comments on commit 70257ff

Please sign in to comment.