Skip to content

Commit

Permalink
feat(Ads): Add support for images and iframes in interstitials (#7712)
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad authored Dec 4, 2024
1 parent cd78811 commit 8db876f
Show file tree
Hide file tree
Showing 3 changed files with 427 additions and 34 deletions.
1 change: 1 addition & 0 deletions build/types/ads
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
+../../lib/ads/client_side_ad_manager.js
+../../lib/ads/interstitial_ad.js
+../../lib/ads/interstitial_ad_manager.js
+../../lib/ads/interstitial_static_ad.js
+../../lib/ads/media_tailor_ad.js
+../../lib/ads/media_tailor_ad_manager.js
+../../lib/ads/server_side_ad.js
Expand Down
164 changes: 130 additions & 34 deletions lib/ads/interstitial_ad_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ goog.provide('shaka.ads.InterstitialAdManager');
goog.require('goog.asserts');
goog.require('shaka.Player');
goog.require('shaka.ads.InterstitialAd');
goog.require('shaka.ads.InterstitialStaticAd');
goog.require('shaka.ads.Utils');
goog.require('shaka.log');
goog.require('shaka.media.PreloadManager');
Expand Down Expand Up @@ -658,20 +659,42 @@ shaka.ads.InterstitialAdManager = class {
this.basePlayer_.getConfiguration().streaming.retryParameters);
} catch (error) {}
}
if (interstitialMimeType) {
if (interstitialMimeType.startsWith('image/')) {
shaka.log.alwaysWarn('Unsupported image interstitial', interstitial);
if (!interstitial.overlay) {
return;

this.determineIfUsingBaseVideo_();
goog.asserts.assert(this.video_, 'Must have video');

if (!this.video_.parentElement && this.adContainer_) {
this.adContainer_.appendChild(this.video_);
}

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;
}
// TODO implement support for image in overlay
return;
} else if (interstitialMimeType === 'text/html') {
shaka.log.alwaysWarn('Unsupported HTML interstitial', interstitial);
return Math.abs(i.startTime - interstitial.startTime) < 0.001;
}).length;
}

if (interstitial.once) {
oncePlayed++;
this.interstitials_.delete(interstitial);
if (!interstitial.overlay) {
this.cuepointsChanged_();
}
}

if (interstitialMimeType) {
if (interstitialMimeType.startsWith('image/') ||
interstitialMimeType === 'text/html') {
if (!interstitial.overlay) {
shaka.log.alwaysWarn('Unsupported interstitial', interstitial);
return;
}
// TODO implement support for HTML in overlay
this.setupStaticAd_(interstitial, sequenceLength, adPosition,
oncePlayed);
return;
}
}
Expand All @@ -684,39 +707,111 @@ shaka.ads.InterstitialAdManager = class {
* @param {shaka.extern.AdInterstitial} interstitial
* @param {number} sequenceLength
* @param {number} adPosition
* @param {number} initialTime the clock time the ad started at
* @param {number} oncePlayed
* @private
*/
async setupVideoAd_(interstitial, sequenceLength, adPosition, initialTime,
oncePlayed) {
this.determineIfUsingBaseVideo_();
goog.asserts.assert(this.video_, 'Must have video');
setupStaticAd_(interstitial, sequenceLength, adPosition, oncePlayed) {
let duration = interstitial.playoutLimit;
if (interstitial.endTime) {
duration = interstitial.endTime - interstitial.startTime;
}

const startTime = Date.now();
const overlay = interstitial.overlay;
goog.asserts.assert(overlay, 'Must have overlay');

if (!this.video_.parentElement && this.adContainer_) {
this.adContainer_.appendChild(this.video_);
const tagName = interstitial.mimeType == 'text/html' ? 'iframe' : 'img';

const htmlElement = /** @type {!(HTMLImageElement|HTMLIFrameElement)} */ (
document.createElement(tagName));
htmlElement.style.objectFit = 'contain';
htmlElement.style.position = 'absolute';

const basicTask = () => {
if (this.playoutLimitTimer_) {
this.playoutLimitTimer_.stop();
this.playoutLimitTimer_ = null;
}
this.adContainer_.removeChild(htmlElement);
this.onEvent_(
new shaka.util.FakeEvent(shaka.ads.Utils.AD_STOPPED));
const nextCurrentInterstitial = this.getCurrentInterstitial_(
interstitial.pre, adPosition - oncePlayed);
if (nextCurrentInterstitial) {
this.adEventManager_.removeAll();
this.setupAd_(nextCurrentInterstitial, sequenceLength,
++adPosition, /* initialTime= */ Date.now(), oncePlayed);
} else {
this.playingAd_ = false;
}
};

const ad = new shaka.ads.InterstitialStaticAd(sequenceLength, adPosition,
interstitial.overlay == null);

this.onEvent_(
new shaka.util.FakeEvent(shaka.ads.Utils.AD_STARTED,
(new Map()).set('ad', ad)));

if (tagName == 'iframe') {
htmlElement.src = interstitial.uri;
} else {
htmlElement.src = interstitial.uri;
htmlElement.onerror = (e) => {
this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.AD_ERROR,
(new Map()).set('originalEvent', e)));
basicTask();
};
}

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;
const viewport = overlay.viewport;
const topLeft = overlay.topLeft;
const size = overlay.size;
// Special case for VAST non-linear ads
if (viewport.x == 0 && viewport.y == 0) {
htmlElement.width = interstitial.overlay.size.x;
htmlElement.height = interstitial.overlay.size.y;
htmlElement.style.bottom = '10%';
htmlElement.style.left = '0';
htmlElement.style.right = '0';
htmlElement.style.width = '100%';
if (!interstitial.overlay.size.y && tagName == 'iframe') {
htmlElement.style.height = 'auto';
}
} else {
htmlElement.style.height = (size.y / viewport.y * 100) + '%';
htmlElement.style.left = (topLeft.x / viewport.x * 100) + '%';
htmlElement.style.top = (topLeft.y / viewport.y * 100) + '%';
htmlElement.style.width = (size.x / viewport.x * 100) + '%';
htmlElement.style.height = 'auto';
}
this.adContainer_.appendChild(htmlElement);

if (interstitial.once) {
oncePlayed++;
this.interstitials_.delete(interstitial);
if (!interstitial.overlay) {
this.cuepointsChanged_();
if (duration) {
if (this.playoutLimitTimer_) {
this.playoutLimitTimer_.stop();
}
this.playoutLimitTimer_ = new shaka.util.Timer(() => {
this.onEvent_(
new shaka.util.FakeEvent(shaka.ads.Utils.AD_COMPLETE));
basicTask();
}).tickAfter(duration);
}
}


/**
* @param {shaka.extern.AdInterstitial} interstitial
* @param {number} sequenceLength
* @param {number} adPosition
* @param {number} initialTime the clock time the ad started at
* @param {number} oncePlayed
* @private
*/
async setupVideoAd_(interstitial, sequenceLength, adPosition, initialTime,
oncePlayed) {
goog.asserts.assert(this.video_, 'Must have video');
const startTime = Date.now();

this.playingAd_ = true;

if (this.usingBaseVideo_ && adPosition == 1) {
Expand Down Expand Up @@ -830,8 +925,9 @@ shaka.ads.InterstitialAdManager = class {
} else {
this.cuepointsChanged_();
}
this.determineIfUsingBaseVideo_();
} else {
}
this.determineIfUsingBaseVideo_();
if (nextCurrentInterstitial) {
this.onEvent_(
new shaka.util.FakeEvent(shaka.ads.Utils.AD_STOPPED));
this.adEventManager_.removeAll();
Expand Down
Loading

0 comments on commit 8db876f

Please sign in to comment.