Skip to content

Commit

Permalink
fix(Ads): Apply X-PLAYOUT-LIMIT to entire interstitial (#7804)
Browse files Browse the repository at this point in the history
Fixes #7782
  • Loading branch information
avelad authored Dec 24, 2024
1 parent e07beac commit e40341c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 23 deletions.
3 changes: 3 additions & 0 deletions docs/tutorials/ad_monetization.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const container = video.ui.getControls().getClientSideAdContainer();
adManager.initInterstitial(container, player, video);
adManager.addCustomInterstitial({
id: null,
groupId: null,
startTime: 10,
endTime: null,
uri: 'YOUR_URL',
Expand Down Expand Up @@ -135,6 +136,7 @@ player.addEventListener('timelineregionadded', (e) => {
}
adManager.addCustomInterstitial({
id: event.id,
groupId: null,
startTime: event.startTime,
endTime: event.endTime,
uri: 'YOUR_URL',
Expand Down Expand Up @@ -171,6 +173,7 @@ const container = video.ui.getControls().getClientSideAdContainer();
adManager.initInterstitial(container, player, video);
adManager.addCustomInterstitial({
id: null,
groupId: null,
startTime: 10,
endTime: null,
uri: 'YOUR_URL',
Expand Down
3 changes: 3 additions & 0 deletions externs/shaka/ads.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ shaka.extern.AdCuePoint;
/**
* @typedef {{
* id: ?string,
* groupId: ?string,
* startTime: number,
* endTime: ?number,
* uri: string,
Expand All @@ -83,6 +84,8 @@ shaka.extern.AdCuePoint;
*
* @property {?string} id
* The id of the interstitial.
* @property {?string} groupId
* The group id of the interstitial.
* @property {number} startTime
* The start time of the interstitial.
* @property {?number} endTime
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 @@ -101,6 +101,7 @@ shaka.ads.Utils = class {
}
interstitials.push({
id: null,
groupId: null,
startTime: startTime,
endTime: null,
uri: adUrl,
Expand Down Expand Up @@ -167,6 +168,7 @@ shaka.ads.Utils = class {
}
interstitials.push({
id: null,
groupId: null,
startTime: startTime,
endTime: null,
uri: adUrl,
Expand Down
53 changes: 31 additions & 22 deletions lib/ads/interstitial_ad_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ shaka.ads.InterstitialAdManager = class {
/** @private {?shaka.util.Timer} */
this.playoutLimitTimer_ = null;

/** @private {?function()} */
this.lastOnSkip_ = null;

this.eventManager_.listen(this.baseVideo_, 'timeupdate', () => {
if (this.playingAd_ || this.lastTime_ ||
this.basePlayer_.isRemotePlayback()) {
Expand Down Expand Up @@ -349,6 +352,7 @@ shaka.ads.InterstitialAdManager = class {
/** @type {!shaka.extern.AdInterstitial} */
const interstitial = {
id: region.id,
groupId: null,
startTime: region.startTime,
endTime: region.endTime,
uri: alternativeMPDUri,
Expand Down Expand Up @@ -454,6 +458,7 @@ shaka.ads.InterstitialAdManager = class {
/** @type {!shaka.extern.AdInterstitial} */
const interstitial = {
id: region.id,
groupId: null,
startTime: region.startTime,
endTime: region.endTime,
uri,
Expand Down Expand Up @@ -909,16 +914,26 @@ shaka.ads.InterstitialAdManager = class {
}
};

const basicTask = async () => {
const basicTask = async (isSkip) => {
updateBaseVideoTime();
if (this.playoutLimitTimer_) {
this.playoutLimitTimer_.stop();
this.playoutLimitTimer_ = null;
}
// Optimization to avoid returning to main content when there is another
// interstitial below.
const nextCurrentInterstitial = this.getCurrentInterstitial_(
let nextCurrentInterstitial = this.getCurrentInterstitial_(
interstitial.pre, adPosition - oncePlayed);
if (isSkip && interstitial.groupId) {
while (nextCurrentInterstitial &&
nextCurrentInterstitial.groupId == interstitial.groupId) {
adPosition++;
nextCurrentInterstitial = this.getCurrentInterstitial_(
interstitial.pre, adPosition - oncePlayed);
}
}
if (this.playoutLimitTimer_ && (!interstitial.groupId ||
(nextCurrentInterstitial &&
nextCurrentInterstitial.groupId != interstitial.groupId))) {
this.playoutLimitTimer_.stop();
this.playoutLimitTimer_ = null;
}
if (!nextCurrentInterstitial || nextCurrentInterstitial.overlay) {
if (interstitial.post) {
this.lastTime_ = null;
Expand Down Expand Up @@ -973,29 +988,29 @@ shaka.ads.InterstitialAdManager = class {
unloadingInterstitial = true;
this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.AD_ERROR,
(new Map()).set('originalEvent', e)));
await basicTask();
await basicTask(/* isSkip= */ false);
};
const complete = async () => {
if (unloadingInterstitial) {
return;
}
unloadingInterstitial = true;
await basicTask();
await basicTask(/* isSkip= */ false);
this.onEvent_(
new shaka.util.FakeEvent(shaka.ads.Utils.AD_COMPLETE));
};
const onSkip = async () => {
this.lastOnSkip_ = async () => {
if (unloadingInterstitial) {
return;
}
unloadingInterstitial = true;
this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.AD_SKIPPED));
await basicTask();
await basicTask(/* isSkip= */ true);
};

const ad = new shaka.ads.InterstitialAd(this.video_,
interstitial.isSkippable, interstitial.skipOffset,
interstitial.skipFor, onSkip, sequenceLength, adPosition,
interstitial.skipFor, this.lastOnSkip_, sequenceLength, adPosition,
!this.usingBaseVideo_, interstitial.overlay);
if (!this.usingBaseVideo_) {
ad.setMuted(this.baseVideo_.muted);
Expand Down Expand Up @@ -1075,9 +1090,9 @@ shaka.ads.InterstitialAdManager = class {
this.player_.configure('playRangeEnd', duration);
}
}
if (interstitial.playoutLimit) {
if (interstitial.playoutLimit && !this.playoutLimitTimer_) {
this.playoutLimitTimer_ = new shaka.util.Timer(() => {
ad.skip();
this.lastOnSkip_();
}).tickAfter(interstitial.playoutLimit);
this.player_.configure('playRangeEnd', interstitial.playoutLimit);
}
Expand All @@ -1100,14 +1115,6 @@ shaka.ads.InterstitialAdManager = class {
/* startTime= */ null,
interstitial.mimeType || undefined);
}
if (interstitial.playoutLimit) {
if (this.playoutLimitTimer_) {
this.playoutLimitTimer_.stop();
}
this.playoutLimitTimer_ = new shaka.util.Timer(() => {
ad.skip();
}).tickAfter(interstitial.playoutLimit);
}
const loadTime = (Date.now() - startTime) / 1000;
this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.ADS_LOADED,
(new Map()).set('loadTime', loadTime)));
Expand Down Expand Up @@ -1228,6 +1235,7 @@ shaka.ads.InterstitialAdManager = class {
}
interstitialsAd.push({
id,
groupId: null,
startTime,
endTime,
uri,
Expand Down Expand Up @@ -1281,7 +1289,8 @@ shaka.ads.InterstitialAdManager = class {
const asset = dataAsJson['ASSETS'][i];
if (asset['URI']) {
interstitialsAd.push({
id: id + '_asset_' + i,
id: id + '_shaka_asset_' + i,
groupId: id,
startTime,
endTime,
uri: asset['URI'],
Expand Down
36 changes: 35 additions & 1 deletion test/ads/interstitial_ad_manager_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'TEST',
groupId: null,
startTime: 0,
endTime: null,
uri: 'test.m3u8',
Expand Down Expand Up @@ -380,8 +382,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'TEST',
groupId: null,
startTime: 0,
endTime: null,
uri: 'test.m3u8',
Expand Down Expand Up @@ -425,8 +429,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'TEST',
groupId: null,
startTime: 0,
endTime: null,
uri: 'test.m3u8',
Expand Down Expand Up @@ -470,8 +476,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'TEST',
groupId: null,
startTime: 0,
endTime: null,
uri: 'test.m3u8',
Expand Down Expand Up @@ -515,8 +523,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'TEST',
groupId: null,
startTime: 0,
endTime: null,
uri: 'test.m3u8',
Expand Down Expand Up @@ -560,8 +570,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'TEST',
groupId: null,
startTime: 0,
endTime: null,
uri: 'test.m3u8',
Expand Down Expand Up @@ -605,8 +617,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'TEST',
groupId: null,
startTime: 0,
endTime: null,
uri: 'test.m3u8',
Expand Down Expand Up @@ -650,8 +664,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'TEST',
groupId: null,
startTime: 100,
endTime: 130,
uri: 'test.m3u8',
Expand Down Expand Up @@ -705,8 +721,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'TEST_asset_0',
id: 'TEST_shaka_asset_0',
groupId: 'TEST',
startTime: 0,
endTime: null,
uri: 'ad.m3u8',
Expand Down Expand Up @@ -846,8 +864,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: 'OVERLAY',
groupId: null,
startTime: 0,
endTime: 1,
uri: 'test.mpd',
Expand Down Expand Up @@ -884,14 +904,17 @@ describe('Interstitial Ad manager', () => {

describe('custom', () => {
it('basic interstitial support', async () => {
/** @type {!shaka.extern.AdInterstitial} */
const interstitial = {
id: null,
groupId: null,
startTime: 10,
endTime: null,
uri: 'test.mp4',
mimeType: null,
isSkippable: true,
skipOffset: 10,
skipFor: null,
canJump: false,
resumeOffset: null,
playoutLimit: null,
Expand Down Expand Up @@ -919,9 +942,11 @@ describe('Interstitial Ad manager', () => {
});

it('supports multiple interstitials', async () => {
/** @type {!Array.<!shaka.extern.AdInterstitial>} */
const interstitials = [
{
id: null,
groupId: null,
startTime: 0,
endTime: null,
uri: 'test.mp4',
Expand All @@ -941,6 +966,7 @@ describe('Interstitial Ad manager', () => {
},
{
id: null,
groupId: null,
startTime: 10,
endTime: null,
uri: 'test.mp4',
Expand Down Expand Up @@ -980,8 +1006,10 @@ describe('Interstitial Ad manager', () => {
});

it('ignore duplicate interstitial', async () => {
/** @type {!shaka.extern.AdInterstitial} */
const interstitial = {
id: null,
groupId: null,
startTime: 10,
endTime: null,
uri: 'test.mp4',
Expand Down Expand Up @@ -1018,8 +1046,10 @@ describe('Interstitial Ad manager', () => {

it('ignore invalid interstitial', async () => {
// It is not valid because it does not have an interstitial URL
/** @type {!shaka.extern.AdInterstitial} */
const interstitial = {
id: null,
groupId: null,
startTime: 10,
endTime: null,
uri: '',
Expand Down Expand Up @@ -1116,8 +1146,10 @@ describe('Interstitial Ad manager', () => {

const interstitials = interstitialAdManager.getInterstitials();
expect(interstitials.length).toBe(1);
/** @type {!shaka.extern.AdInterstitial} */
const expectedInterstitial = {
id: null,
groupId: null,
startTime: 0,
endTime: null,
uri: 'test.png',
Expand Down Expand Up @@ -1394,8 +1426,10 @@ describe('Interstitial Ad manager', () => {
});

it('don\'t dispatch cue points changed if it is an overlay', async () => {
/** @type {!shaka.extern.AdInterstitial} */
const interstitial = {
id: null,
groupId: null,
startTime: 10,
endTime: null,
uri: 'test.mp4',
Expand Down

0 comments on commit e40341c

Please sign in to comment.