Skip to content

Commit

Permalink
feat(Ads): Add CUE PRE and POST support in Interstitials
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad committed Jun 11, 2024
1 parent c72493a commit ae43d1f
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 30 deletions.
52 changes: 42 additions & 10 deletions lib/ads/interstitial_ad_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ shaka.ads.InterstitialAdManager = class {
if (this.playingAd_) {
return;
}
const needPreRoll = this.lastTime_ == null;
this.lastTime_ = this.baseVideo_.currentTime;
const currentInterstitial = this.getCurrentIntestitial_();
const currentInterstitial = this.getCurrentIntestitial_(needPreRoll);
if (currentInterstitial) {
this.setupAd_(currentInterstitial, /* sequenceLength= */ 1,
/* adPosition= */ 1, /* initialTime= */ Date.now());
Expand Down Expand Up @@ -252,24 +253,35 @@ shaka.ads.InterstitialAdManager = class {


/**
* @param {boolean} needPreRoll
* @param {number=} numberToSkip
* @return {?shaka.ads.InterstitialAdManager.Interstitial}
* @private
*/
getCurrentIntestitial_(numberToSkip = 0) {
getCurrentIntestitial_(needPreRoll, numberToSkip = 0) {
let skipped = 0;
let currentInterstitial = null;
if (this.interstitials_.size && this.lastTime_ != null) {
const isEnded = this.baseVideo_.ended;
const interstitials = Array.from(this.interstitials_).sort((a, b) => {
return b.startTime - a.startTime;
});
const roundDecimals = (number) => {
return Math.round(number * 1000) / 1000;
};
for (const interstitial of interstitials) {
const difference =
this.lastTime_ - roundDecimals(interstitial.startTime);
if (difference > 0 && (difference <= 1 || !interstitial.canJump)) {
let isValid = false;
if (needPreRoll) {
isValid = interstitial.pre;
} else if (isEnded) {
isValid = interstitial.post;
} else if (!interstitial.pre && !interstitial.post) {
const difference =
this.lastTime_ - roundDecimals(interstitial.startTime);
isValid = difference > 0 &&
(difference <= 1 || !interstitial.canJump);
}
if (isValid) {
if (skipped == numberToSkip) {
currentInterstitial = interstitial;
break;
Expand Down Expand Up @@ -300,6 +312,11 @@ shaka.ads.InterstitialAdManager = class {

if (adPosition == 1 && sequenceLength == 1) {
sequenceLength = Array.from(this.interstitials_).filter((i) => {
if (interstitial.pre) {
return i.pre == interstitial.pre;
} else if (interstitial.post) {
return i.post == interstitial.post;
}
return Math.abs(i.startTime - interstitial.startTime) < 0.001;
}).length;
}
Expand Down Expand Up @@ -332,7 +349,8 @@ shaka.ads.InterstitialAdManager = class {
}
if (!this.usingBaseVideo_) {
this.baseVideo_.pause();
if (interstitial.resumeOffset != null) {
if (interstitial.resumeOffset != null &&
interstitial.resumeOffset != 0) {
this.baseVideo_.currentTime += interstitial.resumeOffset;
}
}
Expand Down Expand Up @@ -360,8 +378,8 @@ shaka.ads.InterstitialAdManager = class {
'Should be an number!');
// Optimization to avoid returning to main content when there is another
// interstitial below.
const nextCurrentInterstitial =
this.getCurrentIntestitial_(adPosition - oncePlayed);
const nextCurrentInterstitial = this.getCurrentIntestitial_(
interstitial.pre, adPosition - oncePlayed);
if (nextCurrentInterstitial) {
this.onEvent_(
new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STOPPED));
Expand Down Expand Up @@ -389,7 +407,9 @@ shaka.ads.InterstitialAdManager = class {
this.playingAd_ = false;
if (!this.usingBaseVideo_) {
updateBaseVideoTime();
this.baseVideo_.play();
if (!this.baseVideo_.ended) {
this.baseVideo_.play();
}
}
}
};
Expand Down Expand Up @@ -540,10 +560,14 @@ shaka.ads.InterstitialAdManager = class {
}
}
let once = false;
let pre = false;
let post = false;
const cue = interstitial.values.find((v) => v.key == 'CUE');
if (cue) {
const data = /** @type {string} */(cue.data);
once = data.includes('ONCE');
pre = data.includes('PRE');
post = data.includes('POST');
}
if (assetUri) {
const uri = /** @type {string} */(assetUri.data);
Expand All @@ -559,6 +583,8 @@ shaka.ads.InterstitialAdManager = class {
resumeOffset,
playoutLimit,
once,
pre,
post,
});
} else if (assetList) {
const uri = /** @type {string} */(assetList.data);
Expand Down Expand Up @@ -587,6 +613,8 @@ shaka.ads.InterstitialAdManager = class {
resumeOffset,
playoutLimit,
once,
pre,
post,
});
}
}
Expand Down Expand Up @@ -643,7 +671,9 @@ shaka.ads.InterstitialAdManager.Asset;
* canJump: boolean,
* resumeOffset: ?number,
* playoutLimit: ?number,
* once: boolean
* once: boolean,
* pre: boolean,
* post: boolean
* }}
*
* @property {number} startTime
Expand All @@ -654,5 +684,7 @@ shaka.ads.InterstitialAdManager.Asset;
* @property {?number} resumeOffset
* @property {?number} playoutLimit
* @property {boolean} once
* @property {boolean} pre
* @property {boolean} post
*/
shaka.ads.InterstitialAdManager.Interstitial;
11 changes: 10 additions & 1 deletion lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -776,15 +776,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
/** @private {HTMLMediaElement} */
this.preloadDueAdManagerVideo_ = null;

/** @private {boolean} */
this.preloadDueAdManagerVideoEnded_ = false;

/** @private {shaka.util.Timer} */
this.preloadDueAdManagerTimer_ = new shaka.util.Timer(async () => {
if (this.preloadDueAdManager_) {
goog.asserts.assert(this.preloadDueAdManagerVideo_, 'Must have video');
await this.attach(
this.preloadDueAdManagerVideo_, /* initializeMediaSource= */ true);
await this.load(this.preloadDueAdManager_);
this.preloadDueAdManagerVideo_.play();
if (!this.preloadDueAdManagerVideoEnded_) {
this.preloadDueAdManagerVideo_.play();
} else {
this.preloadDueAdManagerVideo_.pause();
}
this.preloadDueAdManager_ = null;
this.preloadDueAdManagerVideoEnded_ = false;
}
});

Expand All @@ -799,6 +807,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
this.preloadDueAdManagerTimer_.stop();
if (!this.preloadDueAdManager_) {
this.preloadDueAdManagerVideo_ = this.video_;
this.preloadDueAdManagerVideoEnded_ = this.video_.ended;
const saveLivePosition = /** @type {boolean} */(
e['saveLivePosition']) || false;
this.preloadDueAdManager_ = await this.detachAndSavePreload(
Expand Down
25 changes: 8 additions & 17 deletions test/ads_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ describe('Ads', () => {
document.body.appendChild(video);
adContainer =
/** @type {!HTMLElement} */ (document.createElement('div'));
adContainer.style.position = 'relative';
adContainer.style.width = '600px';
adContainer.style.height = '400px';
document.body.appendChild(adContainer);
compiledShaka =
await shaka.test.Loader.loadShaka(getClientArg('uncompiled'));
Expand Down Expand Up @@ -192,18 +195,14 @@ describe('Ads', () => {
await video.play();
expect(player.isLive()).toBe(false);

// Wait for the video to start playback. If it takes longer than 10
// seconds, fail the test.
await waiter.waitForMovementOrFailOnTimeout(video, 10);

// Wait a maximum of 10 seconds before the ad starts playing.
await waiter.timeoutAfter(10)
.waitForEvent(adManager, shaka.ads.AdManager.AD_STARTED);
await waiter.timeoutAfter(20)
.waitForEvent(adManager, shaka.ads.AdManager.AD_STOPPED);

await shaka.test.Util.delay(1);
expect(video.currentTime).toBeLessThanOrEqual(6);
expect(video.currentTime).toBeLessThanOrEqual(3);

// Wait a maximum of 10 seconds before the ad starts playing.
await waiter.timeoutAfter(10)
Expand All @@ -212,7 +211,7 @@ describe('Ads', () => {
.waitForEvent(adManager, shaka.ads.AdManager.AD_STOPPED);

await shaka.test.Util.delay(1);
expect(video.currentTime).toBeGreaterThan(12);
expect(video.currentTime).toBeGreaterThan(8);

// Play for 10 seconds, but stop early if the video ends. If it takes
// longer than 30 seconds, fail the test.
Expand All @@ -230,18 +229,14 @@ describe('Ads', () => {
await video.play();
expect(player.isLive()).toBe(false);

// Wait for the video to start playback. If it takes longer than 10
// seconds, fail the test.
await waiter.waitForMovementOrFailOnTimeout(video, 10);

// Wait a maximum of 10 seconds before the ad starts playing.
await waiter.timeoutAfter(10)
.waitForEvent(adManager, shaka.ads.AdManager.AD_STARTED);
await waiter.timeoutAfter(20)
.waitForEvent(adManager, shaka.ads.AdManager.AD_STOPPED);

await shaka.test.Util.delay(1);
expect(video.currentTime).toBeLessThanOrEqual(6);
expect(video.currentTime).toBeLessThanOrEqual(3);

// Wait a maximum of 10 seconds before the ad starts playing.
await waiter.timeoutAfter(10)
Expand All @@ -250,7 +245,7 @@ describe('Ads', () => {
.waitForEvent(adManager, shaka.ads.AdManager.AD_STOPPED);

await shaka.test.Util.delay(1);
expect(video.currentTime).toBeGreaterThan(12);
expect(video.currentTime).toBeGreaterThan(8);

// Play for 10 seconds, but stop early if the video ends. If it takes
// longer than 30 seconds, fail the test.
Expand All @@ -266,18 +261,14 @@ describe('Ads', () => {
await video.play();
expect(player.isLive()).toBe(false);

// Wait for the video to start playback. If it takes longer than 10
// seconds, fail the test.
await waiter.waitForMovementOrFailOnTimeout(video, 10);

// Wait a maximum of 10 seconds before the ad starts playing.
await waiter.timeoutAfter(10)
.waitForEvent(adManager, shaka.ads.AdManager.AD_STARTED);
await waiter.timeoutAfter(20)
.waitForEvent(adManager, shaka.ads.AdManager.AD_STOPPED);

await shaka.test.Util.delay(1);
expect(video.currentTime).toBeLessThanOrEqual(5);
expect(video.currentTime).toBeLessThanOrEqual(3);

// Wait a maximum of 10 seconds before the ad starts playing.
await waiter.timeoutAfter(10)
Expand Down
4 changes: 2 additions & 2 deletions test/test/assets/hls-interstitial/main.m3u8
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ s4.mp4
#EXTINF:4.000,
s5.mp4
#EXT-X-ENDLIST
#EXT-X-DATERANGE:ID="0",CLASS="com.apple.hls.interstitial",CUE="ONCE",START-DATE="2000-01-01T00:00:02Z",X-ASSET-URI="ad.m3u8",X-RESTRICT="JUMP",X-RESUME-OFFSET=0.0
#EXT-X-DATERANGE:ID="1",CLASS="com.apple.hls.interstitial",CUE="ONCE",START-DATE="2000-01-01T00:00:08Z",X-ASSET-URI="ad.m3u8",X-RESTRICT="SKIP,JUMP",X-RESUME-OFFSET=6.0,X-PLAYOUT-LIMIT=15.0
#EXT-X-DATERANGE:ID="0",CLASS="com.apple.hls.interstitial",CUE="PRE",START-DATE="2000-01-01T00:00:02Z",X-ASSET-URI="ad.m3u8",X-RESTRICT="JUMP",X-RESUME-OFFSET=0.0
#EXT-X-DATERANGE:ID="1",CLASS="com.apple.hls.interstitial",CUE="ONCE",START-DATE="2000-01-01T00:00:04Z",X-ASSET-URI="ad.m3u8",X-RESTRICT="SKIP,JUMP",X-RESUME-OFFSET=6.0,X-PLAYOUT-LIMIT=15.0

0 comments on commit ae43d1f

Please sign in to comment.