Skip to content

Commit 9e1f4e7

Browse files
authored
feat(Ads): Allow the use of custom interstitials ads (#6991)
1 parent f4f9b05 commit 9e1f4e7

File tree

7 files changed

+157
-89
lines changed

7 files changed

+157
-89
lines changed

externs/shaka/ads.js

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,58 @@ shaka.extern.AdsStats;
5757
shaka.extern.AdCuePoint;
5858

5959

60+
/**
61+
* @typedef {{
62+
* startTime: number,
63+
* endTime: ?number,
64+
* uri: string,
65+
* isSkippable: boolean,
66+
* canJump: boolean,
67+
* resumeOffset: ?number,
68+
* playoutLimit: ?number,
69+
* once: boolean,
70+
* pre: boolean,
71+
* post: boolean,
72+
* timelineRange: boolean
73+
* }}
74+
*
75+
* @description
76+
* Contains the ad interstitial info.
77+
*
78+
* @property {number} startTime
79+
* The start time of the interstitial.
80+
* @property {?number} endTime
81+
* The end time of the interstitial.
82+
* @property {string} uri
83+
* The uri of the interstitial, can be any type that
84+
* ShakaPlayer supports (either in MSE or src=)
85+
* @property {boolean} isSkippable
86+
* Indicate if the interstitial is skippable.
87+
* @property {boolean} canJump
88+
* Indicate if the interstitial is jumpable.
89+
* @property {?number} resumeOffset
90+
* Indicates where the primary playback will resume after the interstitial
91+
* plays. It is expressed as a time lag from when interstitial playback was
92+
* scheduled on the primary player's timeline. For live ad replacement it
93+
* must be null.
94+
* @property {?number} playoutLimit
95+
* Indicate a limit for the playout time of the entire interstitial.
96+
* @property {boolean} once
97+
* Indicates that the interstitial should only be played once.
98+
* @property {boolean} pre
99+
* Indicates that an action is to be triggered before playback of the
100+
* primary asset begins, regardless of where playback begins in the primary
101+
* asset.
102+
* @property {boolean} post
103+
* Indicates that an action is to be triggered after the primary asset has
104+
* been played to its end without error.
105+
* @property {boolean} timelineRange
106+
* Indicates whether the interstitial should be presented in a timeline UI
107+
* as a single point or as a range.
108+
*/
109+
shaka.extern.AdInterstitial;
110+
111+
60112
/**
61113
* An object that's responsible for all the ad-related logic
62114
* in the player.
@@ -187,9 +239,14 @@ shaka.extern.IAdManager = class extends EventTarget {
187239
/**
188240
* @param {!shaka.Player} basePlayer
189241
* @param {!HTMLMediaElement} baseVideo
190-
* @param {shaka.extern.Interstitial} interstitial
242+
* @param {shaka.extern.HLSInterstitial} interstitial
243+
*/
244+
onHLSInterstitialMetadata(basePlayer, baseVideo, interstitial) {}
245+
246+
/**
247+
* @param {shaka.extern.AdInterstitial} interstitial
191248
*/
192-
onInterstitialMetadata(basePlayer, baseVideo, interstitial) {}
249+
addCustomInterstitial(interstitial) {}
193250
};
194251

195252

externs/shaka/player.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ shaka.extern.MetadataFrame;
596596
* @property {!Array.<shaka.extern.MetadataFrame>} values
597597
* @exportDoc
598598
*/
599-
shaka.extern.Interstitial;
599+
shaka.extern.HLSInterstitial;
600600

601601

602602
/**

lib/ads/ad_manager.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -823,7 +823,7 @@ shaka.ads.AdManager = class extends shaka.util.FakeEventTarget {
823823
* @override
824824
* @export
825825
*/
826-
onInterstitialMetadata(basePlayer, baseVideo, interstitial) {
826+
onHLSInterstitialMetadata(basePlayer, baseVideo, interstitial) {
827827
if (!this.interstitialAdManager_) {
828828
this.initInterstitial(/* adContainer= */ null, basePlayer, baseVideo);
829829
}
@@ -832,6 +832,20 @@ shaka.ads.AdManager = class extends shaka.util.FakeEventTarget {
832832
}
833833
}
834834

835+
/**
836+
* @override
837+
* @export
838+
*/
839+
addCustomInterstitial(interstitial) {
840+
if (!this.interstitialAdManager_) {
841+
throw new shaka.util.Error(
842+
shaka.util.Error.Severity.RECOVERABLE,
843+
shaka.util.Error.Category.ADS,
844+
shaka.util.Error.Code.INTERSTITIAL_AD_MANAGER_NOT_INITIALIZED);
845+
}
846+
this.interstitialAdManager_.addInterstitials([interstitial]);
847+
}
848+
835849
/**
836850
* @param {!shaka.util.FakeEvent} event
837851
* @private

lib/ads/interstitial_ad_manager.js

Lines changed: 67 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ shaka.ads.InterstitialAdManager = class {
6161
/** @private {!Set.<string>} */
6262
this.interstitialIds_ = new Set();
6363

64-
/** @private {!Set.<shaka.ads.InterstitialAdManager.Interstitial>} */
64+
/** @private {!Set.<shaka.extern.AdInterstitial>} */
6565
this.interstitials_ = new Set();
6666

6767
/**
68-
* @private {!Map.<shaka.ads.InterstitialAdManager.Interstitial,
68+
* @private {!Map.<shaka.extern.AdInterstitial,
6969
* Promise<?shaka.media.PreloadManager>>}
7070
*/
7171
this.preloadManagerInterstitials_ = new Map();
@@ -204,48 +204,63 @@ shaka.ads.InterstitialAdManager = class {
204204

205205

206206
/**
207-
* @param {shaka.extern.Interstitial} interstitial
207+
* @param {shaka.extern.HLSInterstitial} hlsInterstitial
208208
*/
209-
async addMetadata(interstitial) {
209+
async addMetadata(hlsInterstitial) {
210210
if (this.basePlayer_.getLoadMode() == shaka.Player.LoadMode.SRC_EQUALS &&
211211
this.usingBaseVideo_) {
212212
shaka.log.alwaysWarn(
213-
'Unsupported interstitial when using single media element',
214-
interstitial);
213+
'Unsupported HLS interstitial when using single media element',
214+
hlsInterstitial);
215215
return;
216216
}
217217
this.updatePlayerConfig_();
218+
const adInterstitials = await this.getInterstitialsInfo_(hlsInterstitial);
219+
if (adInterstitials.length) {
220+
this.addInterstitials(adInterstitials);
221+
} else {
222+
shaka.log.alwaysWarn('Unsupported HLS interstitial', hlsInterstitial);
223+
}
224+
}
225+
226+
227+
/**
228+
* @param {!Array.<shaka.extern.AdInterstitial>} interstitials
229+
*/
230+
addInterstitials(interstitials) {
231+
if (this.basePlayer_.getLoadMode() == shaka.Player.LoadMode.SRC_EQUALS &&
232+
this.usingBaseVideo_) {
233+
shaka.log.alwaysWarn(
234+
'Unsupported interstitial when using single media element',
235+
interstitials);
236+
return;
237+
}
218238
let cuepointsChanged = false;
219-
const interstitialsAd = await this.getInterstitialsInfo_(interstitial);
220-
if (interstitialsAd.length) {
221-
for (const interstitialAd of interstitialsAd) {
222-
const interstitialId = JSON.stringify(interstitialAd);
223-
if (this.interstitialIds_.has(interstitialId)) {
224-
continue;
225-
}
226-
cuepointsChanged = true;
227-
this.interstitialIds_.add(interstitialId);
228-
this.interstitials_.add(interstitialAd);
229-
let shouldPreload = false;
230-
if (interstitial.pre && this.lastTime_ == null) {
231-
shouldPreload = true;
232-
} else if (interstitialAd.startTime == 0 && !interstitialAd.canJump) {
239+
for (const interstitial of interstitials) {
240+
const interstitialId = JSON.stringify(interstitial);
241+
if (this.interstitialIds_.has(interstitialId)) {
242+
continue;
243+
}
244+
cuepointsChanged = true;
245+
this.interstitialIds_.add(interstitialId);
246+
this.interstitials_.add(interstitial);
247+
let shouldPreload = false;
248+
if (interstitial.pre && this.lastTime_ == null) {
249+
shouldPreload = true;
250+
} else if (interstitial.startTime == 0 && !interstitial.canJump) {
251+
shouldPreload = true;
252+
} else if (this.lastTime_ != null) {
253+
const difference = interstitial.startTime - this.lastTime_;
254+
if (difference > 0 && difference <= 10) {
233255
shouldPreload = true;
234-
} else if (this.lastTime_ != null) {
235-
const difference = interstitial.startTime - this.lastTime_;
236-
if (difference > 0 && difference <= 10) {
237-
shouldPreload = true;
238-
}
239256
}
240-
if (shouldPreload) {
241-
if (!this.preloadManagerInterstitials_.has(interstitialAd)) {
242-
this.preloadManagerInterstitials_.set(
243-
interstitialAd, this.player_.preload(interstitialAd.uri));
244-
}
257+
}
258+
if (shouldPreload) {
259+
if (!this.preloadManagerInterstitials_.has(interstitial)) {
260+
this.preloadManagerInterstitials_.set(
261+
interstitial, this.player_.preload(interstitial.uri));
245262
}
246263
}
247-
} else {
248-
shaka.log.alwaysWarn('Unsupported interstitial', interstitial);
249264
}
250265
if (cuepointsChanged) {
251266
this.cuepointsChanged_();
@@ -274,7 +289,7 @@ shaka.ads.InterstitialAdManager = class {
274289
/**
275290
* @param {boolean} needPreRoll
276291
* @param {number=} numberToSkip
277-
* @return {?shaka.ads.InterstitialAdManager.Interstitial}
292+
* @return {?shaka.extern.AdInterstitial}
278293
* @private
279294
*/
280295
getCurrentInterstitial_(needPreRoll, numberToSkip = 0) {
@@ -314,7 +329,7 @@ shaka.ads.InterstitialAdManager = class {
314329

315330

316331
/**
317-
* @param {shaka.ads.InterstitialAdManager.Interstitial} interstitial
332+
* @param {shaka.extern.AdInterstitial} interstitial
318333
* @param {number} sequenceLength
319334
* @param {number} adPosition
320335
* @param {number} initialTime the clock time the ad started at
@@ -554,21 +569,22 @@ shaka.ads.InterstitialAdManager = class {
554569

555570

556571
/**
557-
* @param {shaka.extern.Interstitial} interstitial
558-
* @return {!Promise.<!Array.<shaka.ads.InterstitialAdManager.Interstitial>>}
572+
* @param {shaka.extern.HLSInterstitial} hlsInterstitial
573+
* @return {!Promise.<!Array.<shaka.extern.AdInterstitial>>}
559574
* @private
560575
*/
561-
async getInterstitialsInfo_(interstitial) {
576+
async getInterstitialsInfo_(hlsInterstitial) {
562577
const interstitialsAd = [];
563-
if (!interstitial) {
578+
if (!hlsInterstitial) {
564579
return interstitialsAd;
565580
}
566-
const assetUri = interstitial.values.find((v) => v.key == 'X-ASSET-URI');
567-
const assetList = interstitial.values.find((v) => v.key == 'X-ASSET-LIST');
581+
const assetUri = hlsInterstitial.values.find((v) => v.key == 'X-ASSET-URI');
582+
const assetList =
583+
hlsInterstitial.values.find((v) => v.key == 'X-ASSET-LIST');
568584
if (!assetUri && !assetList) {
569585
return interstitialsAd;
570586
}
571-
const restrict = interstitial.values.find((v) => v.key == 'X-RESTRICT');
587+
const restrict = hlsInterstitial.values.find((v) => v.key == 'X-RESTRICT');
572588
let isSkippable = true;
573589
let canJump = true;
574590
if (restrict && restrict.data) {
@@ -577,7 +593,8 @@ shaka.ads.InterstitialAdManager = class {
577593
canJump = !data.includes('JUMP');
578594
}
579595
let resumeOffset = null;
580-
const resume = interstitial.values.find((v) => v.key == 'X-RESUME-OFFSET');
596+
const resume =
597+
hlsInterstitial.values.find((v) => v.key == 'X-RESUME-OFFSET');
581598
if (resume) {
582599
const resumeOffsetString = /** @type {string} */(resume.data);
583600
resumeOffset = parseFloat(resumeOffsetString);
@@ -586,7 +603,8 @@ shaka.ads.InterstitialAdManager = class {
586603
}
587604
}
588605
let playoutLimit = null;
589-
const playout = interstitial.values.find((v) => v.key == 'X-PLAYOUT-LIMIT');
606+
const playout =
607+
hlsInterstitial.values.find((v) => v.key == 'X-PLAYOUT-LIMIT');
590608
if (playout) {
591609
const playoutLimitString = /** @type {string} */(playout.data);
592610
playoutLimit = parseFloat(playoutLimitString);
@@ -597,7 +615,7 @@ shaka.ads.InterstitialAdManager = class {
597615
let once = false;
598616
let pre = false;
599617
let post = false;
600-
const cue = interstitial.values.find((v) => v.key == 'CUE');
618+
const cue = hlsInterstitial.values.find((v) => v.key == 'CUE');
601619
if (cue) {
602620
const data = /** @type {string} */(cue.data);
603621
once = data.includes('ONCE');
@@ -606,7 +624,7 @@ shaka.ads.InterstitialAdManager = class {
606624
}
607625
let timelineRange = true;
608626
const timelineOccupies =
609-
interstitial.values.find((v) => v.key == 'X-TIMELINE-OCCUPIES');
627+
hlsInterstitial.values.find((v) => v.key == 'X-TIMELINE-OCCUPIES');
610628
if (timelineOccupies) {
611629
const data = /** @type {string} */(timelineOccupies.data);
612630
timelineRange = data.includes('RANGE');
@@ -619,8 +637,8 @@ shaka.ads.InterstitialAdManager = class {
619637
return interstitialsAd;
620638
}
621639
interstitialsAd.push({
622-
startTime: interstitial.startTime,
623-
endTime: interstitial.endTime,
640+
startTime: hlsInterstitial.startTime,
641+
endTime: hlsInterstitial.endTime,
624642
uri,
625643
isSkippable,
626644
canJump,
@@ -650,8 +668,8 @@ shaka.ads.InterstitialAdManager = class {
650668
for (const asset of dataAsJson.ASSETS) {
651669
if (asset.URI) {
652670
interstitialsAd.push({
653-
startTime: interstitial.startTime,
654-
endTime: interstitial.endTime,
671+
startTime: hlsInterstitial.startTime,
672+
endTime: hlsInterstitial.endTime,
655673
uri: asset.URI,
656674
isSkippable,
657675
canJump,
@@ -741,33 +759,3 @@ shaka.ads.InterstitialAdManager.AssetsList;
741759
* @property {string} URI
742760
*/
743761
shaka.ads.InterstitialAdManager.Asset;
744-
745-
746-
/**
747-
* @typedef {{
748-
* startTime: number,
749-
* endTime: ?number,
750-
* uri: string,
751-
* isSkippable: boolean,
752-
* canJump: boolean,
753-
* resumeOffset: ?number,
754-
* playoutLimit: ?number,
755-
* once: boolean,
756-
* pre: boolean,
757-
* post: boolean,
758-
* timelineRange: boolean
759-
* }}
760-
*
761-
* @property {number} startTime
762-
* @property {?number} endTime
763-
* @property {string} uri
764-
* @property {boolean} isSkippable
765-
* @property {boolean} canJump
766-
* @property {?number} resumeOffset
767-
* @property {?number} playoutLimit
768-
* @property {boolean} once
769-
* @property {boolean} pre
770-
* @property {boolean} post
771-
* @property {boolean} timelineRange
772-
*/
773-
shaka.ads.InterstitialAdManager.Interstitial;

0 commit comments

Comments
 (0)