aboutsummaryrefslogtreecommitdiffstats
path: root/src/js/plugins/ads.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/plugins/ads.js')
-rw-r--r--src/js/plugins/ads.js1120
1 files changed, 560 insertions, 560 deletions
diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js
index 9f1088fa..1a52ebce 100644
--- a/src/js/plugins/ads.js
+++ b/src/js/plugins/ads.js
@@ -16,629 +16,629 @@ import { formatTime } from '../utils/time';
import { buildUrlParams } from '../utils/urls';
const destroy = instance => {
- // Destroy our adsManager
- if (instance.manager) {
- instance.manager.destroy();
- }
+ // Destroy our adsManager
+ if (instance.manager) {
+ instance.manager.destroy();
+ }
- // Destroy our adsManager
- if (instance.elements.displayContainer) {
- instance.elements.displayContainer.destroy();
- }
+ // Destroy our adsManager
+ if (instance.elements.displayContainer) {
+ instance.elements.displayContainer.destroy();
+ }
- instance.elements.container.remove();
+ instance.elements.container.remove();
};
class Ads {
- /**
- * Ads constructor.
- * @param {Object} player
- * @return {Ads}
- */
- constructor(player) {
- this.player = player;
- this.config = player.config.ads;
- this.playing = false;
- this.initialized = false;
- this.elements = {
- container: null,
- displayContainer: null,
- };
- this.manager = null;
- this.loader = null;
- this.cuePoints = null;
- this.events = {};
- this.safetyTimer = null;
- this.countdownTimer = null;
-
- // Setup a promise to resolve when the IMA manager is ready
- this.managerPromise = new Promise((resolve, reject) => {
- // The ad is loaded and ready
- this.on('loaded', resolve);
-
- // Ads failed
- this.on('error', reject);
- });
-
- this.load();
+ /**
+ * Ads constructor.
+ * @param {Object} player
+ * @return {Ads}
+ */
+ constructor(player) {
+ this.player = player;
+ this.config = player.config.ads;
+ this.playing = false;
+ this.initialized = false;
+ this.elements = {
+ container: null,
+ displayContainer: null,
+ };
+ this.manager = null;
+ this.loader = null;
+ this.cuePoints = null;
+ this.events = {};
+ this.safetyTimer = null;
+ this.countdownTimer = null;
+
+ // Setup a promise to resolve when the IMA manager is ready
+ this.managerPromise = new Promise((resolve, reject) => {
+ // The ad is loaded and ready
+ this.on('loaded', resolve);
+
+ // Ads failed
+ this.on('error', reject);
+ });
+
+ this.load();
+ }
+
+ get enabled() {
+ const { config } = this;
+
+ return (
+ this.player.isHTML5 &&
+ this.player.isVideo &&
+ config.enabled &&
+ (!is.empty(config.publisherId) || is.url(config.tagUrl))
+ );
+ }
+
+ /**
+ * Load the IMA SDK
+ */
+ load() {
+ if (!this.enabled) {
+ return;
}
- get enabled() {
- const { config } = this;
-
- return (
- this.player.isHTML5 &&
- this.player.isVideo &&
- config.enabled &&
- (!is.empty(config.publisherId) || is.url(config.tagUrl))
- );
- }
-
- /**
- * Load the IMA SDK
- */
- load() {
- if (!this.enabled) {
- return;
- }
-
- // Check if the Google IMA3 SDK is loaded or load it ourselves
- if (!is.object(window.google) || !is.object(window.google.ima)) {
- loadScript(this.player.config.urls.googleIMA.sdk)
- .then(() => {
- this.ready();
- })
- .catch(() => {
- // Script failed to load or is blocked
- this.trigger('error', new Error('Google IMA SDK failed to load'));
- });
- } else {
- this.ready();
- }
- }
-
- /**
- * Get the ads instance ready
- */
- ready() {
- // Double check we're enabled
- if (!this.enabled) {
- destroy(this);
- }
-
- // Start ticking our safety timer. If the whole advertisement
- // thing doesn't resolve within our set time; we bail
- this.startSafetyTimer(12000, 'ready()');
-
- // Clear the safety timer
- this.managerPromise.then(() => {
- this.clearSafetyTimer('onAdsManagerLoaded()');
+ // Check if the Google IMA3 SDK is loaded or load it ourselves
+ if (!is.object(window.google) || !is.object(window.google.ima)) {
+ loadScript(this.player.config.urls.googleIMA.sdk)
+ .then(() => {
+ this.ready();
+ })
+ .catch(() => {
+ // Script failed to load or is blocked
+ this.trigger('error', new Error('Google IMA SDK failed to load'));
});
-
- // Set listeners on the Plyr instance
- this.listeners();
-
- // Setup the IMA SDK
- this.setupIMA();
+ } else {
+ this.ready();
}
-
- // Build the tag URL
- get tagUrl() {
- const { config } = this;
-
- if (is.url(config.tagUrl)) {
- return config.tagUrl;
- }
-
- const params = {
- AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
- AV_CHANNELID: '5a0458dc28a06145e4519d21',
- AV_URL: window.location.hostname,
- cb: Date.now(),
- AV_WIDTH: 640,
- AV_HEIGHT: 480,
- AV_CDIM2: config.publisherId,
- };
-
- const base = 'https://go.aniview.com/api/adserver6/vast/';
-
- return `${base}?${buildUrlParams(params)}`;
+ }
+
+ /**
+ * Get the ads instance ready
+ */
+ ready() {
+ // Double check we're enabled
+ if (!this.enabled) {
+ destroy(this);
}
- /**
- * In order for the SDK to display ads for our video, we need to tell it where to put them,
- * so here we define our ad container. This div is set up to render on top of the video player.
- * Using the code below, we tell the SDK to render ads within that div. We also provide a
- * handle to the content video player - the SDK will poll the current time of our player to
- * properly place mid-rolls. After we create the ad display container, we initialize it. On
- * mobile devices, this initialization is done as the result of a user action.
- */
- setupIMA() {
- // Create the container for our advertisements
- this.elements.container = createElement('div', {
- class: this.player.config.classNames.ads,
- });
-
- this.player.elements.container.appendChild(this.elements.container);
-
- // So we can run VPAID2
- google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED);
-
- // Set language
- google.ima.settings.setLocale(this.player.config.ads.language);
+ // Start ticking our safety timer. If the whole advertisement
+ // thing doesn't resolve within our set time; we bail
+ this.startSafetyTimer(12000, 'ready()');
- // Set playback for iOS10+
- google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline);
+ // Clear the safety timer
+ this.managerPromise.then(() => {
+ this.clearSafetyTimer('onAdsManagerLoaded()');
+ });
- // We assume the adContainer is the video container of the plyr element that will house the ads
- this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media);
+ // Set listeners on the Plyr instance
+ this.listeners();
- // Create ads loader
- this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
+ // Setup the IMA SDK
+ this.setupIMA();
+ }
- // Listen and respond to ads loaded and error events
- this.loader.addEventListener(
- google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
- event => this.onAdsManagerLoaded(event),
- false,
- );
- this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
+ // Build the tag URL
+ get tagUrl() {
+ const { config } = this;
- // Request video ads to be pre-loaded
- this.requestAds();
+ if (is.url(config.tagUrl)) {
+ return config.tagUrl;
}
- /**
- * Request advertisements
- */
- requestAds() {
- const { container } = this.player.elements;
-
- try {
- // Request video ads
- const request = new google.ima.AdsRequest();
- request.adTagUrl = this.tagUrl;
-
- // Specify the linear and nonlinear slot sizes. This helps the SDK
- // to select the correct creative if multiple are returned
- request.linearAdSlotWidth = container.offsetWidth;
- request.linearAdSlotHeight = container.offsetHeight;
- request.nonLinearAdSlotWidth = container.offsetWidth;
- request.nonLinearAdSlotHeight = container.offsetHeight;
-
- // We only overlay ads as we only support video.
- request.forceNonLinearFullSlot = false;
-
- // Mute based on current state
- request.setAdWillPlayMuted(!this.player.muted);
-
- this.loader.requestAds(request);
- } catch (e) {
- this.onAdError(e);
- }
+ const params = {
+ AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
+ AV_CHANNELID: '5a0458dc28a06145e4519d21',
+ AV_URL: window.location.hostname,
+ cb: Date.now(),
+ AV_WIDTH: 640,
+ AV_HEIGHT: 480,
+ AV_CDIM2: config.publisherId,
+ };
+
+ const base = 'https://go.aniview.com/api/adserver6/vast/';
+
+ return `${base}?${buildUrlParams(params)}`;
+ }
+
+ /**
+ * In order for the SDK to display ads for our video, we need to tell it where to put them,
+ * so here we define our ad container. This div is set up to render on top of the video player.
+ * Using the code below, we tell the SDK to render ads within that div. We also provide a
+ * handle to the content video player - the SDK will poll the current time of our player to
+ * properly place mid-rolls. After we create the ad display container, we initialize it. On
+ * mobile devices, this initialization is done as the result of a user action.
+ */
+ setupIMA() {
+ // Create the container for our advertisements
+ this.elements.container = createElement('div', {
+ class: this.player.config.classNames.ads,
+ });
+
+ this.player.elements.container.appendChild(this.elements.container);
+
+ // So we can run VPAID2
+ google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED);
+
+ // Set language
+ google.ima.settings.setLocale(this.player.config.ads.language);
+
+ // Set playback for iOS10+
+ google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline);
+
+ // We assume the adContainer is the video container of the plyr element that will house the ads
+ this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media);
+
+ // Create ads loader
+ this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
+
+ // Listen and respond to ads loaded and error events
+ this.loader.addEventListener(
+ google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
+ event => this.onAdsManagerLoaded(event),
+ false,
+ );
+ this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
+
+ // Request video ads to be pre-loaded
+ this.requestAds();
+ }
+
+ /**
+ * Request advertisements
+ */
+ requestAds() {
+ const { container } = this.player.elements;
+
+ try {
+ // Request video ads
+ const request = new google.ima.AdsRequest();
+ request.adTagUrl = this.tagUrl;
+
+ // Specify the linear and nonlinear slot sizes. This helps the SDK
+ // to select the correct creative if multiple are returned
+ request.linearAdSlotWidth = container.offsetWidth;
+ request.linearAdSlotHeight = container.offsetHeight;
+ request.nonLinearAdSlotWidth = container.offsetWidth;
+ request.nonLinearAdSlotHeight = container.offsetHeight;
+
+ // We only overlay ads as we only support video.
+ request.forceNonLinearFullSlot = false;
+
+ // Mute based on current state
+ request.setAdWillPlayMuted(!this.player.muted);
+
+ this.loader.requestAds(request);
+ } catch (e) {
+ this.onAdError(e);
+ }
+ }
+
+ /**
+ * Update the ad countdown
+ * @param {Boolean} start
+ */
+ pollCountdown(start = false) {
+ if (!start) {
+ clearInterval(this.countdownTimer);
+ this.elements.container.removeAttribute('data-badge-text');
+ return;
}
- /**
- * Update the ad countdown
- * @param {Boolean} start
- */
- pollCountdown(start = false) {
- if (!start) {
- clearInterval(this.countdownTimer);
- this.elements.container.removeAttribute('data-badge-text');
- return;
- }
-
- const update = () => {
- const time = formatTime(Math.max(this.manager.getRemainingTime(), 0));
- const label = `${i18n.get('advertisement', this.player.config)} - ${time}`;
- this.elements.container.setAttribute('data-badge-text', label);
- };
-
- this.countdownTimer = setInterval(update, 100);
+ const update = () => {
+ const time = formatTime(Math.max(this.manager.getRemainingTime(), 0));
+ const label = `${i18n.get('advertisement', this.player.config)} - ${time}`;
+ this.elements.container.setAttribute('data-badge-text', label);
+ };
+
+ this.countdownTimer = setInterval(update, 100);
+ }
+
+ /**
+ * This method is called whenever the ads are ready inside the AdDisplayContainer
+ * @param {Event} adsManagerLoadedEvent
+ */
+ onAdsManagerLoaded(event) {
+ // Load could occur after a source change (race condition)
+ if (!this.enabled) {
+ return;
}
- /**
- * This method is called whenever the ads are ready inside the AdDisplayContainer
- * @param {Event} adsManagerLoadedEvent
- */
- onAdsManagerLoaded(event) {
- // Load could occur after a source change (race condition)
- if (!this.enabled) {
- return;
- }
+ // Get the ads manager
+ const settings = new google.ima.AdsRenderingSettings();
- // Get the ads manager
- const settings = new google.ima.AdsRenderingSettings();
+ // Tell the SDK to save and restore content video state on our behalf
+ settings.restoreCustomPlaybackStateOnAdBreakComplete = true;
+ settings.enablePreloading = true;
- // Tell the SDK to save and restore content video state on our behalf
- settings.restoreCustomPlaybackStateOnAdBreakComplete = true;
- settings.enablePreloading = true;
+ // The SDK is polling currentTime on the contentPlayback. And needs a duration
+ // so it can determine when to start the mid- and post-roll
+ this.manager = event.getAdsManager(this.player, settings);
- // The SDK is polling currentTime on the contentPlayback. And needs a duration
- // so it can determine when to start the mid- and post-roll
- this.manager = event.getAdsManager(this.player, settings);
+ // Get the cue points for any mid-rolls by filtering out the pre- and post-roll
+ this.cuePoints = this.manager.getCuePoints();
- // Get the cue points for any mid-rolls by filtering out the pre- and post-roll
- this.cuePoints = this.manager.getCuePoints();
+ // Add listeners to the required events
+ // Advertisement error events
+ this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
- // Add listeners to the required events
- // Advertisement error events
- this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
+ // Advertisement regular events
+ Object.keys(google.ima.AdEvent.Type).forEach(type => {
+ this.manager.addEventListener(google.ima.AdEvent.Type[type], e => this.onAdEvent(e));
+ });
- // Advertisement regular events
- Object.keys(google.ima.AdEvent.Type).forEach(type => {
- this.manager.addEventListener(google.ima.AdEvent.Type[type], e => this.onAdEvent(e));
- });
+ // Resolve our adsManager
+ this.trigger('loaded');
+ }
- // Resolve our adsManager
- this.trigger('loaded');
- }
+ addCuePoints() {
+ // Add advertisement cue's within the time line if available
+ if (!is.empty(this.cuePoints)) {
+ this.cuePoints.forEach(cuePoint => {
+ if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
+ const seekElement = this.player.elements.progress;
- addCuePoints() {
- // Add advertisement cue's within the time line if available
- if (!is.empty(this.cuePoints)) {
- this.cuePoints.forEach(cuePoint => {
- if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
- const seekElement = this.player.elements.progress;
-
- if (is.element(seekElement)) {
- const cuePercentage = (100 / this.player.duration) * cuePoint;
- const cue = createElement('span', {
- class: this.player.config.classNames.cues,
- });
-
- cue.style.left = `${cuePercentage.toString()}%`;
- seekElement.appendChild(cue);
- }
- }
+ if (is.element(seekElement)) {
+ const cuePercentage = (100 / this.player.duration) * cuePoint;
+ const cue = createElement('span', {
+ class: this.player.config.classNames.cues,
});
- }
- }
- /**
- * This is where all the event handling takes place. Retrieve the ad from the event. Some
- * events (e.g. ALL_ADS_COMPLETED) don't have the ad object associated
- * https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type
- * @param {Event} event
- */
- onAdEvent(event) {
- const { container } = this.player.elements;
- // Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED)
- // don't have ad object associated
- const ad = event.getAd();
- const adData = event.getAdData();
-
- // Proxy event
- const dispatchEvent = type => {
- triggerEvent.call(this.player, this.player.media, `ads${type.replace(/_/g, '').toLowerCase()}`);
- };
-
- // Bubble the event
- dispatchEvent(event.type);
-
- switch (event.type) {
- case google.ima.AdEvent.Type.LOADED:
- // This is the first event sent for an ad - it is possible to determine whether the
- // ad is a video ad or an overlay
- this.trigger('loaded');
-
- // Start countdown
- this.pollCountdown(true);
-
- if (!ad.isLinear()) {
- // Position AdDisplayContainer correctly for overlay
- ad.width = container.offsetWidth;
- ad.height = container.offsetHeight;
- }
-
- // console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex());
- // console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset());
-
- break;
-
- case google.ima.AdEvent.Type.STARTED:
- // Set volume to match player
- this.manager.setVolume(this.player.volume);
-
- break;
-
- case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
- // All ads for the current videos are done. We can now request new advertisements
- // in case the video is re-played
-
- // TODO: Example for what happens when a next video in a playlist would be loaded.
- // So here we load a new video when all ads are done.
- // Then we load new ads within a new adsManager. When the video
- // Is started - after - the ads are loaded, then we get ads.
- // You can also easily test cancelling and reloading by running
- // player.ads.cancel() and player.ads.play from the console I guess.
- // this.player.source = {
- // type: 'video',
- // title: 'View From A Blue Moon',
- // sources: [{
- // src:
- // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4', type:
- // 'video/mp4', }], poster:
- // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg', tracks:
- // [ { kind: 'captions', label: 'English', srclang: 'en', src:
- // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
- // default: true, }, { kind: 'captions', label: 'French', srclang: 'fr', src:
- // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt', }, ],
- // };
-
- // TODO: So there is still this thing where a video should only be allowed to start
- // playing when the IMA SDK is ready or has failed
-
- if (this.player.ended) {
- this.loadAds();
- } else {
- // The SDK won't allow new ads to be called without receiving a contentComplete()
- this.loader.contentComplete();
- }
-
- break;
-
- case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
- // This event indicates the ad has started - the video player can adjust the UI,
- // for example display a pause button and remaining time. Fired when content should
- // be paused. This usually happens right before an ad is about to cover the content
-
- this.pauseContent();
-
- break;
-
- case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED:
- // This event indicates the ad has finished - the video player can perform
- // appropriate UI actions, such as removing the timer for remaining time detection.
- // Fired when content should be resumed. This usually happens when an ad finishes
- // or collapses
-
- this.pollCountdown();
-
- this.resumeContent();
-
- break;
-
- case google.ima.AdEvent.Type.LOG:
- if (adData.adError) {
- this.player.debug.warn(`Non-fatal ad error: ${adData.adError.getMessage()}`);
- }
-
- break;
-
- default:
- break;
+ cue.style.left = `${cuePercentage.toString()}%`;
+ seekElement.appendChild(cue);
+ }
}
+ });
}
+ }
+
+ /**
+ * This is where all the event handling takes place. Retrieve the ad from the event. Some
+ * events (e.g. ALL_ADS_COMPLETED) don't have the ad object associated
+ * https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type
+ * @param {Event} event
+ */
+ onAdEvent(event) {
+ const { container } = this.player.elements;
+ // Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED)
+ // don't have ad object associated
+ const ad = event.getAd();
+ const adData = event.getAdData();
+
+ // Proxy event
+ const dispatchEvent = type => {
+ triggerEvent.call(this.player, this.player.media, `ads${type.replace(/_/g, '').toLowerCase()}`);
+ };
+
+ // Bubble the event
+ dispatchEvent(event.type);
+
+ switch (event.type) {
+ case google.ima.AdEvent.Type.LOADED:
+ // This is the first event sent for an ad - it is possible to determine whether the
+ // ad is a video ad or an overlay
+ this.trigger('loaded');
- /**
- * Any ad error handling comes through here
- * @param {Event} event
- */
- onAdError(event) {
- this.cancel();
- this.player.debug.warn('Ads error', event);
- }
-
- /**
- * Setup hooks for Plyr and window events. This ensures
- * the mid- and post-roll launch at the correct time. And
- * resize the advertisement when the player resizes
- */
- listeners() {
- const { container } = this.player.elements;
- let time;
-
- this.player.on('canplay', () => {
- this.addCuePoints();
- });
-
- this.player.on('ended', () => {
- this.loader.contentComplete();
- });
-
- this.player.on('timeupdate', () => {
- time = this.player.currentTime;
- });
+ // Start countdown
+ this.pollCountdown(true);
- this.player.on('seeked', () => {
- const seekedTime = this.player.currentTime;
+ if (!ad.isLinear()) {
+ // Position AdDisplayContainer correctly for overlay
+ ad.width = container.offsetWidth;
+ ad.height = container.offsetHeight;
+ }
- if (is.empty(this.cuePoints)) {
- return;
- }
+ // console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex());
+ // console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset());
+
+ break;
+
+ case google.ima.AdEvent.Type.STARTED:
+ // Set volume to match player
+ this.manager.setVolume(this.player.volume);
+
+ break;
+
+ case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
+ // All ads for the current videos are done. We can now request new advertisements
+ // in case the video is re-played
+
+ // TODO: Example for what happens when a next video in a playlist would be loaded.
+ // So here we load a new video when all ads are done.
+ // Then we load new ads within a new adsManager. When the video
+ // Is started - after - the ads are loaded, then we get ads.
+ // You can also easily test cancelling and reloading by running
+ // player.ads.cancel() and player.ads.play from the console I guess.
+ // this.player.source = {
+ // type: 'video',
+ // title: 'View From A Blue Moon',
+ // sources: [{
+ // src:
+ // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4', type:
+ // 'video/mp4', }], poster:
+ // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg', tracks:
+ // [ { kind: 'captions', label: 'English', srclang: 'en', src:
+ // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
+ // default: true, }, { kind: 'captions', label: 'French', srclang: 'fr', src:
+ // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt', }, ],
+ // };
+
+ // TODO: So there is still this thing where a video should only be allowed to start
+ // playing when the IMA SDK is ready or has failed
+
+ if (this.player.ended) {
+ this.loadAds();
+ } else {
+ // The SDK won't allow new ads to be called without receiving a contentComplete()
+ this.loader.contentComplete();
+ }
- this.cuePoints.forEach((cuePoint, index) => {
- if (time < cuePoint && cuePoint < seekedTime) {
- this.manager.discardAdBreak();
- this.cuePoints.splice(index, 1);
- }
- });
- });
+ break;
- // Listen to the resizing of the window. And resize ad accordingly
- // TODO: eventually implement ResizeObserver
- window.addEventListener('resize', () => {
- if (this.manager) {
- this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
- }
- });
- }
+ case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
+ // This event indicates the ad has started - the video player can adjust the UI,
+ // for example display a pause button and remaining time. Fired when content should
+ // be paused. This usually happens right before an ad is about to cover the content
- /**
- * Initialize the adsManager and start playing advertisements
- */
- play() {
- const { container } = this.player.elements;
+ this.pauseContent();
- if (!this.managerPromise) {
- this.resumeContent();
- }
+ break;
- // Play the requested advertisement whenever the adsManager is ready
- this.managerPromise
- .then(() => {
- // Set volume to match player
- this.manager.setVolume(this.player.volume);
-
- // Initialize the container. Must be done via a user action on mobile devices
- this.elements.displayContainer.initialize();
-
- try {
- if (!this.initialized) {
- // Initialize the ads manager. Ad rules playlist will start at this time
- this.manager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
-
- // Call play to start showing the ad. Single video and overlay ads will
- // start at this time; the call will be ignored for ad rules
- this.manager.start();
- }
-
- this.initialized = true;
- } catch (adError) {
- // An error may be thrown if there was a problem with the
- // VAST response
- this.onAdError(adError);
- }
- })
- .catch(() => {});
- }
+ case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED:
+ // This event indicates the ad has finished - the video player can perform
+ // appropriate UI actions, such as removing the timer for remaining time detection.
+ // Fired when content should be resumed. This usually happens when an ad finishes
+ // or collapses
- /**
- * Resume our video
- */
- resumeContent() {
- // Hide the advertisement container
- this.elements.container.style.zIndex = '';
+ this.pollCountdown();
- // Ad is stopped
- this.playing = false;
+ this.resumeContent();
- // Play video
- silencePromise(this.player.media.play());
- }
+ break;
- /**
- * Pause our video
- */
- pauseContent() {
- // Show the advertisement container
- this.elements.container.style.zIndex = 3;
+ case google.ima.AdEvent.Type.LOG:
+ if (adData.adError) {
+ this.player.debug.warn(`Non-fatal ad error: ${adData.adError.getMessage()}`);
+ }
- // Ad is playing
- this.playing = true;
+ break;
- // Pause our video.
- this.player.media.pause();
+ default:
+ break;
}
-
- /**
- * Destroy the adsManager so we can grab new ads after this. If we don't then we're not
- * allowed to call new ads based on google policies, as they interpret this as an accidental
- * video requests. https://developers.google.com/interactive-
- * media-ads/docs/sdks/android/faq#8
- */
- cancel() {
- // Pause our video
- if (this.initialized) {
- this.resumeContent();
+ }
+
+ /**
+ * Any ad error handling comes through here
+ * @param {Event} event
+ */
+ onAdError(event) {
+ this.cancel();
+ this.player.debug.warn('Ads error', event);
+ }
+
+ /**
+ * Setup hooks for Plyr and window events. This ensures
+ * the mid- and post-roll launch at the correct time. And
+ * resize the advertisement when the player resizes
+ */
+ listeners() {
+ const { container } = this.player.elements;
+ let time;
+
+ this.player.on('canplay', () => {
+ this.addCuePoints();
+ });
+
+ this.player.on('ended', () => {
+ this.loader.contentComplete();
+ });
+
+ this.player.on('timeupdate', () => {
+ time = this.player.currentTime;
+ });
+
+ this.player.on('seeked', () => {
+ const seekedTime = this.player.currentTime;
+
+ if (is.empty(this.cuePoints)) {
+ return;
+ }
+
+ this.cuePoints.forEach((cuePoint, index) => {
+ if (time < cuePoint && cuePoint < seekedTime) {
+ this.manager.discardAdBreak();
+ this.cuePoints.splice(index, 1);
}
-
- // Tell our instance that we're done for now
- this.trigger('error');
-
- // Re-create our adsManager
- this.loadAds();
+ });
+ });
+
+ // Listen to the resizing of the window. And resize ad accordingly
+ // TODO: eventually implement ResizeObserver
+ window.addEventListener('resize', () => {
+ if (this.manager) {
+ this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
+ }
+ });
+ }
+
+ /**
+ * Initialize the adsManager and start playing advertisements
+ */
+ play() {
+ const { container } = this.player.elements;
+
+ if (!this.managerPromise) {
+ this.resumeContent();
}
- /**
- * Re-create our adsManager
- */
- loadAds() {
- // Tell our adsManager to go bye bye
- this.managerPromise
- .then(() => {
- // Destroy our adsManager
- if (this.manager) {
- this.manager.destroy();
- }
-
- // Re-set our adsManager promises
- this.managerPromise = new Promise(resolve => {
- this.on('loaded', resolve);
- this.player.debug.log(this.manager);
- });
- // Now that the manager has been destroyed set it to also be un-initialized
- this.initialized = false;
-
- // Now request some new advertisements
- this.requestAds();
- })
- .catch(() => {});
- }
+ // Play the requested advertisement whenever the adsManager is ready
+ this.managerPromise
+ .then(() => {
+ // Set volume to match player
+ this.manager.setVolume(this.player.volume);
- /**
- * Handles callbacks after an ad event was invoked
- * @param {String} event - Event type
- */
- trigger(event, ...args) {
- const handlers = this.events[event];
-
- if (is.array(handlers)) {
- handlers.forEach(handler => {
- if (is.function(handler)) {
- handler.apply(this, args);
- }
- });
+ // Initialize the container. Must be done via a user action on mobile devices
+ this.elements.displayContainer.initialize();
+
+ try {
+ if (!this.initialized) {
+ // Initialize the ads manager. Ad rules playlist will start at this time
+ this.manager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
+
+ // Call play to start showing the ad. Single video and overlay ads will
+ // start at this time; the call will be ignored for ad rules
+ this.manager.start();
+ }
+
+ this.initialized = true;
+ } catch (adError) {
+ // An error may be thrown if there was a problem with the
+ // VAST response
+ this.onAdError(adError);
}
+ })
+ .catch(() => {});
+ }
+
+ /**
+ * Resume our video
+ */
+ resumeContent() {
+ // Hide the advertisement container
+ this.elements.container.style.zIndex = '';
+
+ // Ad is stopped
+ this.playing = false;
+
+ // Play video
+ silencePromise(this.player.media.play());
+ }
+
+ /**
+ * Pause our video
+ */
+ pauseContent() {
+ // Show the advertisement container
+ this.elements.container.style.zIndex = 3;
+
+ // Ad is playing
+ this.playing = true;
+
+ // Pause our video.
+ this.player.media.pause();
+ }
+
+ /**
+ * Destroy the adsManager so we can grab new ads after this. If we don't then we're not
+ * allowed to call new ads based on google policies, as they interpret this as an accidental
+ * video requests. https://developers.google.com/interactive-
+ * media-ads/docs/sdks/android/faq#8
+ */
+ cancel() {
+ // Pause our video
+ if (this.initialized) {
+ this.resumeContent();
}
- /**
- * Add event listeners
- * @param {String} event - Event type
- * @param {Function} callback - Callback for when event occurs
- * @return {Ads}
- */
- on(event, callback) {
- if (!is.array(this.events[event])) {
- this.events[event] = [];
+ // Tell our instance that we're done for now
+ this.trigger('error');
+
+ // Re-create our adsManager
+ this.loadAds();
+ }
+
+ /**
+ * Re-create our adsManager
+ */
+ loadAds() {
+ // Tell our adsManager to go bye bye
+ this.managerPromise
+ .then(() => {
+ // Destroy our adsManager
+ if (this.manager) {
+ this.manager.destroy();
}
- this.events[event].push(callback);
+ // Re-set our adsManager promises
+ this.managerPromise = new Promise(resolve => {
+ this.on('loaded', resolve);
+ this.player.debug.log(this.manager);
+ });
+ // Now that the manager has been destroyed set it to also be un-initialized
+ this.initialized = false;
- return this;
+ // Now request some new advertisements
+ this.requestAds();
+ })
+ .catch(() => {});
+ }
+
+ /**
+ * Handles callbacks after an ad event was invoked
+ * @param {String} event - Event type
+ */
+ trigger(event, ...args) {
+ const handlers = this.events[event];
+
+ if (is.array(handlers)) {
+ handlers.forEach(handler => {
+ if (is.function(handler)) {
+ handler.apply(this, args);
+ }
+ });
}
-
- /**
- * Setup a safety timer for when the ad network doesn't respond for whatever reason.
- * The advertisement has 12 seconds to get its things together. We stop this timer when the
- * advertisement is playing, or when a user action is required to start, then we clear the
- * timer on ad ready
- * @param {Number} time
- * @param {String} from
- */
- startSafetyTimer(time, from) {
- this.player.debug.log(`Safety timer invoked from: ${from}`);
-
- this.safetyTimer = setTimeout(() => {
- this.cancel();
- this.clearSafetyTimer('startSafetyTimer()');
- }, time);
+ }
+
+ /**
+ * Add event listeners
+ * @param {String} event - Event type
+ * @param {Function} callback - Callback for when event occurs
+ * @return {Ads}
+ */
+ on(event, callback) {
+ if (!is.array(this.events[event])) {
+ this.events[event] = [];
}
- /**
- * Clear our safety timer(s)
- * @param {String} from
- */
- clearSafetyTimer(from) {
- if (!is.nullOrUndefined(this.safetyTimer)) {
- this.player.debug.log(`Safety timer cleared from: ${from}`);
-
- clearTimeout(this.safetyTimer);
- this.safetyTimer = null;
- }
+ this.events[event].push(callback);
+
+ return this;
+ }
+
+ /**
+ * Setup a safety timer for when the ad network doesn't respond for whatever reason.
+ * The advertisement has 12 seconds to get its things together. We stop this timer when the
+ * advertisement is playing, or when a user action is required to start, then we clear the
+ * timer on ad ready
+ * @param {Number} time
+ * @param {String} from
+ */
+ startSafetyTimer(time, from) {
+ this.player.debug.log(`Safety timer invoked from: ${from}`);
+
+ this.safetyTimer = setTimeout(() => {
+ this.cancel();
+ this.clearSafetyTimer('startSafetyTimer()');
+ }, time);
+ }
+
+ /**
+ * Clear our safety timer(s)
+ * @param {String} from
+ */
+ clearSafetyTimer(from) {
+ if (!is.nullOrUndefined(this.safetyTimer)) {
+ this.player.debug.log(`Safety timer cleared from: ${from}`);
+
+ clearTimeout(this.safetyTimer);
+ this.safetyTimer = null;
}
+ }
}
export default Ads;