From 025fc1090b0e7d78569ea010360f04d8b8decfa9 Mon Sep 17 00:00:00 2001 From: ferdiemmen Date: Sat, 13 Jan 2018 23:02:59 +0100 Subject: Implementing ads plugin --- src/js/plugins/ads.js | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 src/js/plugins/ads.js (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js new file mode 100644 index 00000000..5f19f6c7 --- /dev/null +++ b/src/js/plugins/ads.js @@ -0,0 +1,329 @@ +import utils from '../utils'; + +// Events are different on various devices. We det the correct events, based on userAgent. +const getStartEvents = () => { + let startEvents = ['click']; + + // For mobile users the start event will be one of + // touchstart, touchend and touchmove. + if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/Android/i)) { + startEvents = [ + 'touchstart', + 'touchend', + 'touchmove', + ]; + } + return startEvents; +}; + +export default class Ads { + constructor(player) { + + // Check if an adTagUrl is provided. + if (!player.config.ads.adTagUrl) { + return this; + } + + // Check if the Google IMA3 SDK is loaded. + if (!utils.is.object(window.google)) { + utils.loadScript(player.config.urls.googleIMA.api); + } + + this.player = player; + this.startEvents = getStartEvents(); + this.adDisplayContainer = null; + this.adDisplayElement = null; + this.adsManager = null; + this.adsLoader = null; + this.adCuePoints = null; + this.currentAd = null; + this.events = {}; + this.videoElement = document.createElement('video'); + + // Setup the ad display container. + this.setupAdDisplayContainer(); + + // Setup the IMA SDK. + this.setupIMA(); + + // Set listeners on the Plyr instance. + // this.setupListeners(); + } + + setupIMA() { + const { container } = this.player.elements; + + // Create ads loader. + this.adsLoader = new window.google.ima.AdsLoader(this.adDisplayContainer, this.videoElement); + + // Tell the adsLoader we are handling ad breaks manually. + this.adsLoader.getSettings().setAutoPlayAdBreaks(false); + + // Listen and respond to ads loaded and error events. + this.adsLoader.addEventListener(window.google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, adEvent => this.onAdsManagerLoaded(adEvent), false); + this.adsLoader.addEventListener(window.google.ima.AdErrorEvent.Type.AD_ERROR, adError => this.onAdError(adError), false); + + // Request video ads. + const adsRequest = new window.google.ima.AdsRequest(); + adsRequest.adTagUrl = this.player.config.ads.adTagUrl; + + // Specify the linear and nonlinear slot sizes. This helps the SDK to + // select the correct creative if multiple are returned. + adsRequest.linearAdSlotWidth = container.offsetWidth; + adsRequest.linearAdSlotHeight = container.offsetHeight; + adsRequest.nonLinearAdSlotWidth = container.offsetWidth; + adsRequest.nonLinearAdSlotHeight = container.offsetHeight; + + this.adsLoader.requestAds(adsRequest); + } + + onAdsManagerLoaded(adsManagerLoadedEvent) { + const { videoElement } = this; + + // Get the ads manager. + const adsRenderingSettings = new window.google.ima.AdsRenderingSettings(); + adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; + adsRenderingSettings.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.adsManager = adsManagerLoadedEvent.getAdsManager(videoElement, adsRenderingSettings); + + // Get the cue points for any mid-rolls by filtering out the pre- and post-roll. + this.adsCuePoints = this.adsManager.getCuePoints().filter(x => x > 0 && x !== -1); + + // Add listeners to the required events. + this.adsManager.addEventListener(window.google.ima.AdErrorEvent.Type.AD_ERROR, adError => this.onAdError(adError)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, adEvent => this.onAdEvent(adEvent)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, adEvent => this.onAdEvent(adEvent)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.ALL_ADS_COMPLETED, adEvent => this.onAdEvent(adEvent)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.AD_BREAK_READY, adEvent => this.onAdEvent(adEvent)); + + // Listen to any additional events, if necessary. + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.LOADED, adEvent => this.onAdEvent(adEvent)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.STARTED, adEvent => this.onAdEvent(adEvent)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.COMPLETE, adEvent => this.onAdEvent(adEvent)); + } + + onAdEvent(adEvent) { + 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 = adEvent.getAd(); + + // Set the currently played ad. This information could be used by callback + // events. + this.currentAd = ad; + + // let intervalTimer; + + switch (adEvent.type) { + case window.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. + + // Show the ad display element. + this.adDisplayElement.style.display = 'block'; + + this.handleEventListeners('LOADED'); + + if (!ad.isLinear()) { + // Position AdDisplayContainer correctly for overlay. + ad.width = container.offsetWidth; + ad.height = container.offsetHeight; + } + break; + case window.google.ima.AdEvent.Type.STARTED: + // This event indicates the ad has started - the video player + // can adjust the UI, for example display a pause button and + // remaining time. + + this.player.pause(); + this.handleEventListeners('STARTED'); + + // if (ad.isLinear()) { + // For a linear ad, a timer can be started to poll for + // the remaining time. + // intervalTimer = setInterval( + // () => { + // let remainingTime = this.adsManager.getRemainingTime(); + // console.log(remainingTime); + // }, + // 300); // every 300ms + // } + break; + case window.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED: + this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); + break; + case window.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: + this.handleEventListeners('CONTENT_RESUME_REQUESTED'); + break; + case window.google.ima.AdEvent.Type.AD_BREAK_READY: + // This event indicates that a mid-roll ad is ready to start. + // We pause the player and tell the adsManager to start playing the ad. + this.player.pause(); + this.adsManager.start(); + this.handleEventListeners('AD_BREAK_READY'); + break; + case window.google.ima.AdEvent.Type.COMPLETE: + // This event indicates the ad has finished - the video player + // can perform appropriate UI actions, such as removing the timer for + // remaining time detection. + // clearInterval(intervalTimer); + this.handleEventListeners('COMPLETE'); + + this.adDisplayElement.style.display = 'none'; + if (this.player.currentTime < this.player.duration) { + this.player.play(); + } + break; + case window.google.ima.AdEvent.Type.ALL_ADS_COMPLETED: + this.handleEventListeners('ALL_ADS_COMPLETED'); + break; + default: + break; + } + } + + onAdError(adErrorEvent) { + + // Handle the error logging. + this.adDisplayElement.remove(); + + if (this.adsManager) { + this.adsManager.destroy(); + } + + if (this.player.debug) { + throw new Error(adErrorEvent); + } + } + + setupAdDisplayContainer() { + const { container } = this.player.elements; + + // We assume the adContainer is the video container of the plyr element + // that will house the ads. + this.adDisplayContainer = new window.google.ima.AdDisplayContainer(container); + + this.adDisplayElement = container.firstChild; + + // The AdDisplayContainer call from google IMA sets the style attribute + // by default. We remove the inline style and set it through the stylesheet. + this.adDisplayElement.removeAttribute('style'); + + // Set class name on the adDisplayContainer element. + this.adDisplayElement.setAttribute('class', 'plyr__ads'); + + // Play ads when clicked. + this.setOnClickHandler(this.adDisplayElement, this.playAds); + } + + playAds() { + const { container } = this.player.elements; + + // Initialize the container. Must be done via a user action on mobile devices. + this.adDisplayContainer.initialize(); + + try { + // Initialize the ads manager. Ad rules playlist will start at this time. + this.adsManager.init(container.offsetWidth, container.offsetHeight, window.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.adsManager.start(); + } catch (adError) { + // An error may be thrown if there was a problem with the VAST response. + this.player.play(); + this.adDisplayElement.remove(); + + if (this.player.debug) { + throw new Error(adError); + } + } + } + + /** + * 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. + */ + setupListeners() { + const { container } = this.player.elements; + let time; + + // Add listeners to the required events. + this.player.on('ended', () => { + this.adsLoader.contentComplete(); + }); + + this.player.on('timeupdate', event => { + const { currentTime } = event.detail.plyr; + this.videoElement.currentTime = Math.ceil(currentTime); + }); + + this.player.on('seeking', event => { + time = event.detail.plyr.currentTime; + return time; + }); + + this.player.on('seeked', event => { + const seekedTime = event.detail.plyr.currentTime; + + for (let i = 0; i < this.adsCuePoints.length; i += 1) { + const cuePoint = this.adsCuePoints[i]; + if (time < cuePoint && cuePoint < seekedTime) { + this.adsManager.discardAdBreak(); + this.adsCuePoints.splice(i, 1); + } + } + }); + + // Listen to the resizing of the window. And resize ad accordingly. + window.addEventListener('resize', () => { + this.adsManager.resize(container.offsetWidth, container.offsetHeight, window.google.ima.ViewMode.NORMAL); + }); + } + + /** + * Handles callbacks after an ad event was invoked. + */ + handleEventListeners(event) { + if (typeof this.events[event] !== 'undefined') { + this.events[event].call(this); + } + } + + /** + * Set start event listener on a DOM element and triggers the + * callback when clicked. + * @param {HtmlElment} element - The element on which to set the listener + * @param {Function} callback - The callback which will be invoked once triggered. + */ + + setOnClickHandler(element, callback) { + for (let i = 0; i < this.startEvents.length; i += 1) { + const startEvent = this.startEvents[i]; + element.addEventListener( + startEvent, + event => { + if ((event.type === 'touchend' && startEvent === 'touchend') || event.type === 'click') { + callback.call(this); + } + }, + { once: true }, + ); + } + } + + /** + * Add event listeners + * @param {string} event - Event type + * @param {function} callback - Callback for when event occurs + */ + on(event, callback) { + this.events[event] = callback; + return this; + } +} -- cgit v1.2.3 From bbdf225d7b25c16d293108a9cf813c521f4d44eb Mon Sep 17 00:00:00 2001 From: ferdiemmen Date: Sun, 14 Jan 2018 08:21:35 +0100 Subject: Fix loading google ima sdk --- src/js/plugins/ads.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 5f19f6c7..f83948af 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -18,6 +18,7 @@ const getStartEvents = () => { export default class Ads { constructor(player) { + this.player = player; // Check if an adTagUrl is provided. if (!player.config.ads.adTagUrl) { @@ -26,10 +27,15 @@ export default class Ads { // Check if the Google IMA3 SDK is loaded. if (!utils.is.object(window.google)) { - utils.loadScript(player.config.urls.googleIMA.api); + utils.loadScript(player.config.urls.googleIMA.api, () => { + this.ready(this); + }); + } else { + this.ready(); } + } - this.player = player; + ready() { this.startEvents = getStartEvents(); this.adDisplayContainer = null; this.adDisplayElement = null; @@ -47,7 +53,7 @@ export default class Ads { this.setupIMA(); // Set listeners on the Plyr instance. - // this.setupListeners(); + this.setupListeners(); } setupIMA() { -- cgit v1.2.3 From 22e8892993b995f50c15d49558081e1271ac6bca Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 14 Jan 2018 23:15:33 +1100 Subject: Code tweaks --- src/js/plugins/ads.js | 63 ++++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 28 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index f83948af..3ee45b38 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -2,33 +2,35 @@ import utils from '../utils'; // Events are different on various devices. We det the correct events, based on userAgent. const getStartEvents = () => { - let startEvents = ['click']; + let events = ['click']; + // TODO: Detecting touch is tricky, we should look at other ways? // For mobile users the start event will be one of // touchstart, touchend and touchmove. if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/Android/i)) { - startEvents = [ + events = [ 'touchstart', 'touchend', 'touchmove', ]; } - return startEvents; + + return events; }; export default class Ads { constructor(player) { this.player = player; - // Check if an adTagUrl is provided. - if (!player.config.ads.adTagUrl) { + // Check if a tag URL is provided. + if (!utils.is.url(player.config.ads.tagUrl)) { return this; } - // Check if the Google IMA3 SDK is loaded. + // Check if the Google IMA3 SDK is loaded if (!utils.is.object(window.google)) { utils.loadScript(player.config.urls.googleIMA.api, () => { - this.ready(this); + this.ready(); }); } else { this.ready(); @@ -66,12 +68,12 @@ export default class Ads { this.adsLoader.getSettings().setAutoPlayAdBreaks(false); // Listen and respond to ads loaded and error events. - this.adsLoader.addEventListener(window.google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, adEvent => this.onAdsManagerLoaded(adEvent), false); - this.adsLoader.addEventListener(window.google.ima.AdErrorEvent.Type.AD_ERROR, adError => this.onAdError(adError), false); + this.adsLoader.addEventListener(window.google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false); + this.adsLoader.addEventListener(window.google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false); // Request video ads. const adsRequest = new window.google.ima.AdsRequest(); - adsRequest.adTagUrl = this.player.config.ads.adTagUrl; + adsRequest.adTagUrl = this.player.config.ads.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. @@ -87,28 +89,28 @@ export default class Ads { const { videoElement } = this; // Get the ads manager. - const adsRenderingSettings = new window.google.ima.AdsRenderingSettings(); - adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; - adsRenderingSettings.enablePreloading = true; + const settings = new window.google.ima.AdsRenderingSettings(); + 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.adsManager = adsManagerLoadedEvent.getAdsManager(videoElement, adsRenderingSettings); + this.adsManager = adsManagerLoadedEvent.getAdsManager(videoElement, settings); // Get the cue points for any mid-rolls by filtering out the pre- and post-roll. this.adsCuePoints = this.adsManager.getCuePoints().filter(x => x > 0 && x !== -1); // Add listeners to the required events. - this.adsManager.addEventListener(window.google.ima.AdErrorEvent.Type.AD_ERROR, adError => this.onAdError(adError)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, adEvent => this.onAdEvent(adEvent)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, adEvent => this.onAdEvent(adEvent)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.ALL_ADS_COMPLETED, adEvent => this.onAdEvent(adEvent)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.AD_BREAK_READY, adEvent => this.onAdEvent(adEvent)); + this.adsManager.addEventListener(window.google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.ALL_ADS_COMPLETED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.AD_BREAK_READY, event => this.onAdEvent(event)); // Listen to any additional events, if necessary. - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.LOADED, adEvent => this.onAdEvent(adEvent)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.STARTED, adEvent => this.onAdEvent(adEvent)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.COMPLETE, adEvent => this.onAdEvent(adEvent)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.LOADED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.STARTED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(window.google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); } onAdEvent(adEvent) { @@ -140,6 +142,7 @@ export default class Ads { ad.height = container.offsetHeight; } break; + case window.google.ima.AdEvent.Type.STARTED: // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and @@ -159,12 +162,15 @@ export default class Ads { // 300); // every 300ms // } break; + case window.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED: this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); break; + case window.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: this.handleEventListeners('CONTENT_RESUME_REQUESTED'); break; + case window.google.ima.AdEvent.Type.AD_BREAK_READY: // This event indicates that a mid-roll ad is ready to start. // We pause the player and tell the adsManager to start playing the ad. @@ -172,6 +178,7 @@ export default class Ads { this.adsManager.start(); this.handleEventListeners('AD_BREAK_READY'); break; + case window.google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for @@ -184,16 +191,17 @@ export default class Ads { this.player.play(); } break; + case window.google.ima.AdEvent.Type.ALL_ADS_COMPLETED: this.handleEventListeners('ALL_ADS_COMPLETED'); break; + default: break; } } onAdError(adErrorEvent) { - // Handle the error logging. this.adDisplayElement.remove(); @@ -220,7 +228,7 @@ export default class Ads { this.adDisplayElement.removeAttribute('style'); // Set class name on the adDisplayContainer element. - this.adDisplayElement.setAttribute('class', 'plyr__ads'); + this.adDisplayElement.setAttribute('class', this.player.config.classNames.ads); // Play ads when clicked. this.setOnClickHandler(this.adDisplayElement, this.playAds); @@ -277,13 +285,12 @@ export default class Ads { this.player.on('seeked', event => { const seekedTime = event.detail.plyr.currentTime; - for (let i = 0; i < this.adsCuePoints.length; i += 1) { - const cuePoint = this.adsCuePoints[i]; + this.adsCuePoints.forEach((cuePoint, index) => { if (time < cuePoint && cuePoint < seekedTime) { this.adsManager.discardAdBreak(); - this.adsCuePoints.splice(i, 1); + this.adsCuePoints.splice(index, 1); } - } + }); }); // Listen to the resizing of the window. And resize ad accordingly. -- cgit v1.2.3 From 8064405dbc44e24a5547aa79dbd7968926ec62a1 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 14 Jan 2018 23:33:18 +1100 Subject: Tweaks --- src/js/plugins/ads.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 3ee45b38..06b4eee5 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -113,12 +113,12 @@ export default class Ads { this.adsManager.addEventListener(window.google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); } - onAdEvent(adEvent) { + 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 = adEvent.getAd(); + const ad = event.getAd(); // Set the currently played ad. This information could be used by callback // events. @@ -126,7 +126,7 @@ export default class Ads { // let intervalTimer; - switch (adEvent.type) { + switch (event.type) { case window.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. @@ -311,8 +311,8 @@ export default class Ads { /** * Set start event listener on a DOM element and triggers the * callback when clicked. - * @param {HtmlElment} element - The element on which to set the listener - * @param {Function} callback - The callback which will be invoked once triggered. + * @param {element} element - The element on which to set the listener + * @param {function} callback - The callback which will be invoked once triggered. */ setOnClickHandler(element, callback) { -- cgit v1.2.3 From 4b0005c28eaab460be1b4f5f22fa5d1f3ffae522 Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Mon, 15 Jan 2018 14:47:34 +0100 Subject: Added promises, missing events, new ad tag and additional logging. --- src/js/plugins/ads.js | 281 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 213 insertions(+), 68 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 06b4eee5..956fbb6e 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -1,6 +1,6 @@ import utils from '../utils'; -// Events are different on various devices. We det the correct events, based on userAgent. +// Events are different on various devices. We set the correct events, based on userAgent. const getStartEvents = () => { let events = ['click']; @@ -38,16 +38,43 @@ export default class Ads { } ready() { + this.time = Date.now(); this.startEvents = getStartEvents(); this.adDisplayContainer = null; this.adDisplayElement = null; this.adsManager = null; this.adsLoader = null; - this.adCuePoints = null; + this.adsCuePoints = null; this.currentAd = null; this.events = {}; + this.safetyTimer = null; this.videoElement = document.createElement('video'); + // Setup a simple promise to resolve if the IMA loader is ready. + this.adsLoaderResolve = () => {}; + this.adsLoaderPromise = new Promise((resolve) => { + this.adsLoaderResolve = resolve; + }); + this.adsLoaderPromise.then(() => { + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsLoader resolved!`, this.adsLoader); + }); + + // Setup a promise to resolve if the IMA manager is ready. + this.adsManagerResolve = () => {}; + this.adsManagerPromise = new Promise((resolve) => { + // Resolve our promise. + this.adsManagerResolve = resolve; + }); + this.adsManagerPromise.then(() => { + // Clear the safety timer. + this.clearSafetyTimer('onAdsManagerLoaded()'); + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsManager resolved!`, this.adsManager); + }); + + // Start ticking our safety timer. If the whole advertisement + // thing doesn't resolve within our set time; we bail. + this.startSafetyTimer(12000, 'ready()'); + // Setup the ad display container. this.setupAdDisplayContainer(); @@ -83,6 +110,8 @@ export default class Ads { adsRequest.nonLinearAdSlotHeight = container.offsetHeight; this.adsLoader.requestAds(adsRequest); + + this.adsLoaderResolve(); } onAdsManagerLoaded(adsManagerLoadedEvent) { @@ -111,6 +140,9 @@ export default class Ads { this.adsManager.addEventListener(window.google.ima.AdEvent.Type.LOADED, event => this.onAdEvent(event)); this.adsManager.addEventListener(window.google.ima.AdEvent.Type.STARTED, event => this.onAdEvent(event)); this.adsManager.addEventListener(window.google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); + + // Resolve our adsManager. + this.adsManagerResolve(); } onAdEvent(event) { @@ -127,9 +159,58 @@ export default class Ads { // let intervalTimer; switch (event.type) { - case window.google.ima.AdEvent.Type.LOADED: + + case google.ima.AdEvent.Type.AD_BREAK_READY: + // This event indicates that a mid-roll ad is ready to start. + // We pause the player and tell the adsManager to start playing the ad. + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_BREAK_READY |`, 'Fired when an ad rule or a VMAP ad break would have played if autoPlayAdBreaks is false.'); + + this.player.pause(); + + this.adsManager.start(); + + this.handleEventListeners('AD_BREAK_READY'); + break; + case google.ima.AdEvent.Type.AD_METADATA: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_METADATA |`, 'Fired when an ads list is loaded.'); + break; + case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] ALL_ADS_COMPLETED |`, 'Fired when the ads manager is done playing all the ads.'); + this.handleEventListeners('ALL_ADS_COMPLETED'); + break; + case google.ima.AdEvent.Type.CLICK: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CLICK |`, 'Fired when the ad is clicked.'); + break; + case google.ima.AdEvent.Type.COMPLETE: + // This event indicates the ad has finished - the video player + // can perform appropriate UI actions, such as removing the timer for + // remaining time detection. + // clearInterval(intervalTimer); + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] COMPLETE |`, 'Fired when the ad completes playing.'); + + this.handleEventListeners('COMPLETE'); + + this.adDisplayElement.style.display = 'none'; + + if (this.player.currentTime < this.player.duration) { + this.player.play(); + } + break; + case window.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_PAUSE_REQUESTED |`, 'Fired when content should be paused. This usually happens right before an ad is about to cover the content.'); + + this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); + break; + + case window.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_RESUME_REQUESTED |`, 'Fired when content should be resumed. This usually happens when an ad finishes or collapses.'); + + this.handleEventListeners('CONTENT_RESUME_REQUESTED'); + break; + 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.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] LOADED |`, event.getAd().getContentType()); // Show the ad display element. this.adDisplayElement.style.display = 'block'; @@ -141,59 +222,60 @@ export default class Ads { ad.width = container.offsetWidth; ad.height = container.offsetHeight; } - break; - case window.google.ima.AdEvent.Type.STARTED: + // console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex()); + // console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset()); + break; + case google.ima.AdEvent.Type.STARTED: // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] STARTED |`, 'Fired when the ad starts playing.'); this.player.pause(); this.handleEventListeners('STARTED'); - - // if (ad.isLinear()) { - // For a linear ad, a timer can be started to poll for - // the remaining time. - // intervalTimer = setInterval( - // () => { - // let remainingTime = this.adsManager.getRemainingTime(); - // console.log(remainingTime); - // }, - // 300); // every 300ms - // } break; - - case window.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED: - this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); + case google.ima.AdEvent.Type.DURATION_CHANGE: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] DURATION_CHANGE |`, 'Fired when the ad\'s duration changes.'); break; - - case window.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: - this.handleEventListeners('CONTENT_RESUME_REQUESTED'); + case google.ima.AdEvent.Type.FIRST_QUARTILE: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] FIRST_QUARTILE |`, 'Fired when the ad playhead crosses first quartile.'); break; - - case window.google.ima.AdEvent.Type.AD_BREAK_READY: - // This event indicates that a mid-roll ad is ready to start. - // We pause the player and tell the adsManager to start playing the ad. - this.player.pause(); - this.adsManager.start(); - this.handleEventListeners('AD_BREAK_READY'); + case google.ima.AdEvent.Type.IMPRESSION: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] IMPRESSION |`, 'Fired when the impression URL has been pinged.'); break; - - case window.google.ima.AdEvent.Type.COMPLETE: - // This event indicates the ad has finished - the video player - // can perform appropriate UI actions, such as removing the timer for - // remaining time detection. - // clearInterval(intervalTimer); - this.handleEventListeners('COMPLETE'); - - this.adDisplayElement.style.display = 'none'; - if (this.player.currentTime < this.player.duration) { - this.player.play(); - } + case google.ima.AdEvent.Type.INTERACTION: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] INTERACTION |`, 'Fired when an ad triggers the interaction callback. Ad interactions contain an interaction ID string in the ad data.'); break; - - case window.google.ima.AdEvent.Type.ALL_ADS_COMPLETED: - this.handleEventListeners('ALL_ADS_COMPLETED'); + case google.ima.AdEvent.Type.LINEAR_CHANGED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] LINEAR_CHANGED |`, 'Fired when the displayed ad changes from linear to nonlinear, or vice versa.'); + break; + case google.ima.AdEvent.Type.MIDPOINT: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] MIDPOINT |`, 'Fired when the ad playhead crosses midpoint.'); + break; + case google.ima.AdEvent.Type.PAUSED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] PAUSED |`, 'Fired when the ad is paused.'); + break; + case google.ima.AdEvent.Type.RESUMED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] RESUMED |`, 'Fired when the ad is resumed.'); + break; + case google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] SKIPPABLE_STATE_CHANGED |`, 'Fired when the displayed ads skippable state is changed.'); + break; + case google.ima.AdEvent.Type.SKIPPED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] SKIPPED |`, 'Fired when the ad is skipped by the user.'); + break; + case google.ima.AdEvent.Type.THIRD_QUARTILE: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] THIRD_QUARTILE |`, 'Fired when the ad playhead crosses third quartile.'); + break; + case google.ima.AdEvent.Type.USER_CLOSE: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] USER_CLOSE |`, 'Fired when the ad is closed by the user.'); + break; + case google.ima.AdEvent.Type.VOLUME_CHANGED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] VOLUME_CHANGED |`, 'Fired when the ad volume has changed.'); + break; + case google.ima.AdEvent.Type.VOLUME_MUTED: + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] VOLUME_MUTED |`, 'Fired when the ad volume has been muted.'); break; default: @@ -202,21 +284,44 @@ export default class Ads { } onAdError(adErrorEvent) { - // Handle the error logging. - this.adDisplayElement.remove(); - - if (this.adsManager) { - this.adsManager.destroy(); - } + this.cancel(); if (this.player.debug) { throw new Error(adErrorEvent); } } + /** + * 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() { + this.player.debug.warn(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Advertisement cancelled.'); + + // Todo: Removing the ad container might be problematic if we were to recreate the adsManager. Think of playlists. Every new video you need to request a new VAST xml and preload the advertisement. + this.adDisplayElement.remove(); + + // Tell our adsManager to go bye bye. + this.adsManagerPromise.then(() => { + if (this.adsManager) { + this.adsManager.destroy(); + } + }); + } + setupAdDisplayContainer() { const { container } = this.player.elements; + // So we can run VPAID2. + google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); + + // Set language. + // Todo: Could make a config option out of this locale value. + google.ima.settings.setLocale('en'); + // We assume the adContainer is the video container of the plyr element // that will house the ads. this.adDisplayContainer = new window.google.ima.AdDisplayContainer(container); @@ -230,32 +335,41 @@ export default class Ads { // Set class name on the adDisplayContainer element. this.adDisplayElement.setAttribute('class', this.player.config.classNames.ads); - // Play ads when clicked. - this.setOnClickHandler(this.adDisplayElement, this.playAds); + // Play ads when clicked. Wait until the adsManager and adsLoader + // are both resolved. + Promise.all([ + this.adsManagerPromise, + this.adsLoaderPromise, + ]).then(() => { + this.setOnClickHandler(this.adDisplayElement, this.playAds); + }); } playAds() { const { container } = this.player.elements; - // Initialize the container. Must be done via a user action on mobile devices. - this.adDisplayContainer.initialize(); + // Play the requested advertisement whenever the adsManager is ready. + this.adsManagerPromise.then(() => { + // Initialize the container. Must be done via a user action on mobile devices. + this.adDisplayContainer.initialize(); - try { - // Initialize the ads manager. Ad rules playlist will start at this time. - this.adsManager.init(container.offsetWidth, container.offsetHeight, window.google.ima.ViewMode.NORMAL); + try { + // Initialize the ads manager. Ad rules playlist will start at this time. + this.adsManager.init(container.offsetWidth, container.offsetHeight, window.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.adsManager.start(); - } catch (adError) { - // An error may be thrown if there was a problem with the VAST response. - this.player.play(); - this.adDisplayElement.remove(); + // 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.adsManager.start(); + } catch (adError) { + // An error may be thrown if there was a problem with the VAST response. + this.player.play(); + this.adDisplayElement.remove(); - if (this.player.debug) { - throw new Error(adError); + if (this.player.debug) { + throw new Error(adError); + } } - } + }); } /** @@ -314,7 +428,6 @@ export default class Ads { * @param {element} element - The element on which to set the listener * @param {function} callback - The callback which will be invoked once triggered. */ - setOnClickHandler(element, callback) { for (let i = 0; i < this.startEvents.length; i += 1) { const startEvent = this.startEvents[i]; @@ -339,4 +452,36 @@ export default class Ads { this.events[event] = callback; return this; } + + /** + * startSafetyTimer + * Setup a safety timer for when the ad network + * doesn't respond for whatever reason. The advertisement has 12 seconds + * to get its shit 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 + * @private + */ + startSafetyTimer(time, from) { + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, `Safety timer invoked timer from: ${from}`); + this.safetyTimer = window.setTimeout(() => { + this.cancel(); + this.clearSafetyTimer('startSafetyTimer()'); + }, time); + } + + /** + * clearSafetyTimer + * @param {String} from + * @private + */ + clearSafetyTimer(from) { + if (typeof this.safetyTimer !== 'undefined' && this.safetyTimer !== null) { + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, `Safety timer cleared timer from: ${from}`); + clearTimeout(this.safetyTimer); + this.safetyTimer = undefined; + } + } } -- cgit v1.2.3 From 1cef48d4f861c747573f12803f7f33bd4cf9c542 Mon Sep 17 00:00:00 2001 From: ferdiemmen Date: Mon, 15 Jan 2018 23:30:54 +0100 Subject: fix blocking play() if ads are set --- src/js/plugins/ads.js | 93 ++++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 49 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 956fbb6e..e1c188c4 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -41,14 +41,14 @@ export default class Ads { this.time = Date.now(); this.startEvents = getStartEvents(); this.adDisplayContainer = null; - this.adDisplayElement = null; + this.adsDisplayElement = null; this.adsManager = null; this.adsLoader = null; this.adsCuePoints = null; this.currentAd = null; this.events = {}; this.safetyTimer = null; - this.videoElement = document.createElement('video'); + this.playing = false; // Setup a simple promise to resolve if the IMA loader is ready. this.adsLoaderResolve = () => {}; @@ -89,17 +89,17 @@ export default class Ads { const { container } = this.player.elements; // Create ads loader. - this.adsLoader = new window.google.ima.AdsLoader(this.adDisplayContainer, this.videoElement); + this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer); // Tell the adsLoader we are handling ad breaks manually. this.adsLoader.getSettings().setAutoPlayAdBreaks(false); // Listen and respond to ads loaded and error events. - this.adsLoader.addEventListener(window.google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false); - this.adsLoader.addEventListener(window.google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false); + this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false); + this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false); // Request video ads. - const adsRequest = new window.google.ima.AdsRequest(); + const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = this.player.config.ads.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK to @@ -115,31 +115,32 @@ export default class Ads { } onAdsManagerLoaded(adsManagerLoadedEvent) { - const { videoElement } = this; // Get the ads manager. - const settings = new window.google.ima.AdsRenderingSettings(); - settings.restoreCustomPlaybackStateOnAdBreakComplete = true; + const settings = new google.ima.AdsRenderingSettings(); + + // Tell the SDK NOT to save and restore content video state on our behalf. + settings.restoreCustomPlaybackStateOnAdBreakComplete = false; 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.adsManager = adsManagerLoadedEvent.getAdsManager(videoElement, settings); + this.adsManager = adsManagerLoadedEvent.getAdsManager(this.player, settings); // Get the cue points for any mid-rolls by filtering out the pre- and post-roll. this.adsCuePoints = this.adsManager.getCuePoints().filter(x => x > 0 && x !== -1); // Add listeners to the required events. - this.adsManager.addEventListener(window.google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.ALL_ADS_COMPLETED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.AD_BREAK_READY, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_BREAK_READY, event => this.onAdEvent(event)); // Listen to any additional events, if necessary. - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.LOADED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.STARTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(window.google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); // Resolve our adsManager. this.adsManagerResolve(); @@ -164,12 +165,9 @@ export default class Ads { // This event indicates that a mid-roll ad is ready to start. // We pause the player and tell the adsManager to start playing the ad. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_BREAK_READY |`, 'Fired when an ad rule or a VMAP ad break would have played if autoPlayAdBreaks is false.'); - - this.player.pause(); - - this.adsManager.start(); - this.handleEventListeners('AD_BREAK_READY'); + this.adsManager.start(); + this.playing = true; break; case google.ima.AdEvent.Type.AD_METADATA: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_METADATA |`, 'Fired when an ads list is loaded.'); @@ -187,35 +185,33 @@ export default class Ads { // remaining time detection. // clearInterval(intervalTimer); this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] COMPLETE |`, 'Fired when the ad completes playing.'); - this.handleEventListeners('COMPLETE'); - this.adDisplayElement.style.display = 'none'; + this.adsDisplayElement.style.display = 'none'; + this.playing = false; if (this.player.currentTime < this.player.duration) { this.player.play(); } break; - case window.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED: + case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_PAUSE_REQUESTED |`, 'Fired when content should be paused. This usually happens right before an ad is about to cover the content.'); - + this.player.pause(); this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); break; - case window.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: + case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_RESUME_REQUESTED |`, 'Fired when content should be resumed. This usually happens when an ad finishes or collapses.'); - this.handleEventListeners('CONTENT_RESUME_REQUESTED'); break; 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.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] LOADED |`, event.getAd().getContentType()); + this.handleEventListeners('LOADED'); // Show the ad display element. - this.adDisplayElement.style.display = 'block'; - - this.handleEventListeners('LOADED'); + this.adsDisplayElement.style.display = 'block'; if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. @@ -231,8 +227,8 @@ export default class Ads { // can adjust the UI, for example display a pause button and // remaining time. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] STARTED |`, 'Fired when the ad starts playing.'); - this.player.pause(); + this.playing = true; this.handleEventListeners('STARTED'); break; case google.ima.AdEvent.Type.DURATION_CHANGE: @@ -302,7 +298,7 @@ export default class Ads { this.player.debug.warn(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Advertisement cancelled.'); // Todo: Removing the ad container might be problematic if we were to recreate the adsManager. Think of playlists. Every new video you need to request a new VAST xml and preload the advertisement. - this.adDisplayElement.remove(); + this.adsDisplayElement.remove(); // Tell our adsManager to go bye bye. this.adsManagerPromise.then(() => { @@ -324,16 +320,16 @@ export default class Ads { // We assume the adContainer is the video container of the plyr element // that will house the ads. - this.adDisplayContainer = new window.google.ima.AdDisplayContainer(container); + this.adDisplayContainer = new google.ima.AdDisplayContainer(container); - this.adDisplayElement = container.firstChild; + this.adsDisplayElement = container.firstChild; // The AdDisplayContainer call from google IMA sets the style attribute // by default. We remove the inline style and set it through the stylesheet. - this.adDisplayElement.removeAttribute('style'); + this.adsDisplayElement.removeAttribute('style'); // Set class name on the adDisplayContainer element. - this.adDisplayElement.setAttribute('class', this.player.config.classNames.ads); + this.adsDisplayElement.setAttribute('class', this.player.config.classNames.ads); // Play ads when clicked. Wait until the adsManager and adsLoader // are both resolved. @@ -341,7 +337,7 @@ export default class Ads { this.adsManagerPromise, this.adsLoaderPromise, ]).then(() => { - this.setOnClickHandler(this.adDisplayElement, this.playAds); + this.setOnClickHandler(this.adsDisplayElement, this.playAds); }); } @@ -355,7 +351,7 @@ export default class Ads { try { // Initialize the ads manager. Ad rules playlist will start at this time. - this.adsManager.init(container.offsetWidth, container.offsetHeight, window.google.ima.ViewMode.NORMAL); + this.adsManager.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. @@ -363,7 +359,7 @@ export default class Ads { } catch (adError) { // An error may be thrown if there was a problem with the VAST response. this.player.play(); - this.adDisplayElement.remove(); + this.adsDisplayElement.remove(); if (this.player.debug) { throw new Error(adError); @@ -372,6 +368,10 @@ export default class Ads { }); } + isPlaying() { + return this.playing; + } + /** * Setup hooks for Plyr and window events. This ensures * the mid- and post-roll launch at the correct time. And @@ -386,18 +386,13 @@ export default class Ads { this.adsLoader.contentComplete(); }); - this.player.on('timeupdate', event => { - const { currentTime } = event.detail.plyr; - this.videoElement.currentTime = Math.ceil(currentTime); - }); - this.player.on('seeking', event => { - time = event.detail.plyr.currentTime; + time = this.player.currentTime; return time; }); this.player.on('seeked', event => { - const seekedTime = event.detail.plyr.currentTime; + const seekedTime = this.player.currentTime; this.adsCuePoints.forEach((cuePoint, index) => { if (time < cuePoint && cuePoint < seekedTime) { @@ -409,7 +404,7 @@ export default class Ads { // Listen to the resizing of the window. And resize ad accordingly. window.addEventListener('resize', () => { - this.adsManager.resize(container.offsetWidth, container.offsetHeight, window.google.ima.ViewMode.NORMAL); + this.adsManager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); }); } -- cgit v1.2.3 From ec73d34bd3061c2b512dda97765e4f38db4619c3 Mon Sep 17 00:00:00 2001 From: ferdiemmen Date: Tue, 16 Jan 2018 23:06:40 +0100 Subject: Some tweaks --- src/js/plugins/ads.js | 162 +++++++++++++++++++++++++++++--------------------- 1 file changed, 93 insertions(+), 69 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index e1c188c4..091ee80d 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -18,9 +18,11 @@ const getStartEvents = () => { return events; }; -export default class Ads { +class Ads { constructor(player) { this.player = player; + this.isPlaying = false; + this.isInitialized = false; // Check if a tag URL is provided. if (!utils.is.url(player.config.ads.tagUrl)) { @@ -37,6 +39,22 @@ export default class Ads { } } + set playing(bool = false) { + this.isPlaying = bool; + } + + get playing() { + return this.isPlaying; + } + + set initialized(bool = false) { + this.isInitialized = bool; + } + + get initialized() { + return this.isInitialized; + } + ready() { this.time = Date.now(); this.startEvents = getStartEvents(); @@ -48,7 +66,6 @@ export default class Ads { this.currentAd = null; this.events = {}; this.safetyTimer = null; - this.playing = false; // Setup a simple promise to resolve if the IMA loader is ready. this.adsLoaderResolve = () => {}; @@ -91,9 +108,6 @@ export default class Ads { // Create ads loader. this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer); - // Tell the adsLoader we are handling ad breaks manually. - this.adsLoader.getSettings().setAutoPlayAdBreaks(false); - // Listen and respond to ads loaded and error events. this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false); this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false); @@ -119,8 +133,8 @@ export default class Ads { // Get the ads manager. const settings = new google.ima.AdsRenderingSettings(); - // Tell the SDK NOT to save and restore content video state on our behalf. - settings.restoreCustomPlaybackStateOnAdBreakComplete = false; + // 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 @@ -128,7 +142,7 @@ export default class Ads { this.adsManager = adsManagerLoadedEvent.getAdsManager(this.player, settings); // Get the cue points for any mid-rolls by filtering out the pre- and post-roll. - this.adsCuePoints = this.adsManager.getCuePoints().filter(x => x > 0 && x !== -1); + this.adsCuePoints = this.adsManager.getCuePoints(); // Add listeners to the required events. this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); @@ -165,9 +179,9 @@ export default class Ads { // This event indicates that a mid-roll ad is ready to start. // We pause the player and tell the adsManager to start playing the ad. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_BREAK_READY |`, 'Fired when an ad rule or a VMAP ad break would have played if autoPlayAdBreaks is false.'); - this.handleEventListeners('AD_BREAK_READY'); - this.adsManager.start(); - this.playing = true; + // this.handleEventListeners('AD_BREAK_READY'); + // this.playing = true; + // this.adsManager.start(); break; case google.ima.AdEvent.Type.AD_METADATA: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_METADATA |`, 'Fired when an ads list is loaded.'); @@ -186,9 +200,9 @@ export default class Ads { // clearInterval(intervalTimer); this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] COMPLETE |`, 'Fired when the ad completes playing.'); this.handleEventListeners('COMPLETE'); + this.playing = false; this.adsDisplayElement.style.display = 'none'; - this.playing = false; if (this.player.currentTime < this.player.duration) { this.player.play(); @@ -196,13 +210,16 @@ export default class Ads { break; case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_PAUSE_REQUESTED |`, 'Fired when content should be paused. This usually happens right before an ad is about to cover the content.'); - this.player.pause(); this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); + this.player.pause(); break; case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_RESUME_REQUESTED |`, 'Fired when content should be resumed. This usually happens when an ad finishes or collapses.'); this.handleEventListeners('CONTENT_RESUME_REQUESTED'); + if (this.player.currentTime < this.player.duration) { + this.player.play(); + } break; case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to @@ -287,29 +304,8 @@ export default class Ads { } } - /** - * 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() { - this.player.debug.warn(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Advertisement cancelled.'); - - // Todo: Removing the ad container might be problematic if we were to recreate the adsManager. Think of playlists. Every new video you need to request a new VAST xml and preload the advertisement. - this.adsDisplayElement.remove(); - - // Tell our adsManager to go bye bye. - this.adsManagerPromise.then(() => { - if (this.adsManager) { - this.adsManager.destroy(); - } - }); - } - setupAdDisplayContainer() { - const { container } = this.player.elements; + const { wrapper } = this.player.elements; // So we can run VPAID2. google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); @@ -320,9 +316,9 @@ export default class Ads { // We assume the adContainer is the video container of the plyr element // that will house the ads. - this.adDisplayContainer = new google.ima.AdDisplayContainer(container); + this.adDisplayContainer = new google.ima.AdDisplayContainer(wrapper); - this.adsDisplayElement = container.firstChild; + this.adsDisplayElement = wrapper.firstChild; // The AdDisplayContainer call from google IMA sets the style attribute // by default. We remove the inline style and set it through the stylesheet. @@ -337,41 +333,10 @@ export default class Ads { this.adsManagerPromise, this.adsLoaderPromise, ]).then(() => { - this.setOnClickHandler(this.adsDisplayElement, this.playAds); + this.setOnClickHandler(this.adsDisplayElement, this.play); }); } - playAds() { - const { container } = this.player.elements; - - // Play the requested advertisement whenever the adsManager is ready. - this.adsManagerPromise.then(() => { - // Initialize the container. Must be done via a user action on mobile devices. - this.adDisplayContainer.initialize(); - - try { - // Initialize the ads manager. Ad rules playlist will start at this time. - this.adsManager.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.adsManager.start(); - } catch (adError) { - // An error may be thrown if there was a problem with the VAST response. - this.player.play(); - this.adsDisplayElement.remove(); - - if (this.player.debug) { - throw new Error(adError); - } - } - }); - } - - isPlaying() { - return this.playing; - } - /** * Setup hooks for Plyr and window events. This ensures * the mid- and post-roll launch at the correct time. And @@ -408,6 +373,62 @@ export default class Ads { }); } + /** + * Initialize the adsManager and start playing advertisements. + */ + play() { + const { container } = this.player.elements; + + // Initialize the container. Must be done via a user action on mobile devices. + this.adDisplayContainer.initialize(); + + // Play the requested advertisement whenever the adsManager is ready. + this.adsManagerPromise.then(() => { + try { + if (!this.initialized) { + + // Initialize the ads manager. Ad rules playlist will start at this time. + this.adsManager.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.adsManager.start(); + } + + this.initialized = true; + } catch (adError) { + // An error may be thrown if there was a problem with the VAST response. + this.adsDisplayElement.remove(); + + if (this.player.debug) { + throw new Error(adError); + } + this.player.play(); + } + }); + } + + /** + * 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() { + this.player.debug.warn(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Advertisement cancelled.'); + + // Todo: Removing the ad container might be problematic if we were to recreate the adsManager. Think of playlists. Every new video you need to request a new VAST xml and preload the advertisement. + this.adsDisplayElement.remove(); + + // Tell our adsManager to go bye bye. + this.adsManagerPromise.then(() => { + if (this.adsManager) { + this.adsManager.destroy(); + } + }); + } + /** * Handles callbacks after an ad event was invoked. */ @@ -480,3 +501,6 @@ export default class Ads { } } } + +export default Ads; + -- cgit v1.2.3 From 8348f79742a40ac465affff7527e7c22dc34cb83 Mon Sep 17 00:00:00 2001 From: ferdiemmen Date: Wed, 17 Jan 2018 08:31:36 +0100 Subject: Fix loading/playing of the ads when there is no valid ads.tagUrl --- src/js/plugins/ads.js | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 091ee80d..ceb00ee4 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -21,8 +21,8 @@ const getStartEvents = () => { class Ads { constructor(player) { this.player = player; - this.isPlaying = false; - this.isInitialized = false; + this.playing = false; + this.initialized = false; // Check if a tag URL is provided. if (!utils.is.url(player.config.ads.tagUrl)) { @@ -39,22 +39,6 @@ class Ads { } } - set playing(bool = false) { - this.isPlaying = bool; - } - - get playing() { - return this.isPlaying; - } - - set initialized(bool = false) { - this.isInitialized = bool; - } - - get initialized() { - return this.isInitialized; - } - ready() { this.time = Date.now(); this.startEvents = getStartEvents(); @@ -298,10 +282,7 @@ class Ads { onAdError(adErrorEvent) { this.cancel(); - - if (this.player.debug) { - throw new Error(adErrorEvent); - } + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] ERROR |`, adErrorEvent); } setupAdDisplayContainer() { -- cgit v1.2.3 From 12a7a4142cd550e85a65e07d3a1cd8a5bc5d6ffa Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 17 Jan 2018 11:32:13 +0100 Subject: Moved the logic for pausing and playing the video to content pause/ resume IMA events to avoid flickering. Also used events for resolving the adsmanager and adsloader promises. --- src/js/plugins/ads.js | 64 ++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 37 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index ceb00ee4..87ce61a1 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -51,20 +51,20 @@ class Ads { this.events = {}; this.safetyTimer = null; + // Set listeners on the Plyr instance. + this.setupListeners(); + // Setup a simple promise to resolve if the IMA loader is ready. - this.adsLoaderResolve = () => {}; this.adsLoaderPromise = new Promise((resolve) => { - this.adsLoaderResolve = resolve; + this.on('ADS_LOADER_LOADED', () => resolve()); }); this.adsLoaderPromise.then(() => { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsLoader resolved!`, this.adsLoader); }); // Setup a promise to resolve if the IMA manager is ready. - this.adsManagerResolve = () => {}; this.adsManagerPromise = new Promise((resolve) => { - // Resolve our promise. - this.adsManagerResolve = resolve; + this.on('ADS_MANAGER_LOADED', () => resolve()); }); this.adsManagerPromise.then(() => { // Clear the safety timer. @@ -81,9 +81,6 @@ class Ads { // Setup the IMA SDK. this.setupIMA(); - - // Set listeners on the Plyr instance. - this.setupListeners(); } setupIMA() { @@ -109,7 +106,7 @@ class Ads { this.adsLoader.requestAds(adsRequest); - this.adsLoaderResolve(); + this.handleEventListeners('ADS_LOADER_LOADED'); } onAdsManagerLoaded(adsManagerLoadedEvent) { @@ -141,7 +138,7 @@ class Ads { this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); // Resolve our adsManager. - this.adsManagerResolve(); + this.handleEventListeners('ADS_MANAGER_LOADED'); } onAdEvent(event) { @@ -160,12 +157,7 @@ class Ads { switch (event.type) { case google.ima.AdEvent.Type.AD_BREAK_READY: - // This event indicates that a mid-roll ad is ready to start. - // We pause the player and tell the adsManager to start playing the ad. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_BREAK_READY |`, 'Fired when an ad rule or a VMAP ad break would have played if autoPlayAdBreaks is false.'); - // this.handleEventListeners('AD_BREAK_READY'); - // this.playing = true; - // this.adsManager.start(); break; case google.ima.AdEvent.Type.AD_METADATA: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_METADATA |`, 'Fired when an ads list is loaded.'); @@ -178,29 +170,36 @@ class Ads { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CLICK |`, 'Fired when the ad is clicked.'); break; case google.ima.AdEvent.Type.COMPLETE: - // This event indicates the ad has finished - the video player - // can perform appropriate UI actions, such as removing the timer for - // remaining time detection. - // clearInterval(intervalTimer); this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] COMPLETE |`, 'Fired when the ad completes playing.'); - this.handleEventListeners('COMPLETE'); - this.playing = false; - - this.adsDisplayElement.style.display = 'none'; - - if (this.player.currentTime < this.player.duration) { - this.player.play(); - } 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. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_PAUSE_REQUESTED |`, 'Fired when content should be paused. This usually happens right before an ad is about to cover the content.'); this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); + + // Show our advertiment container. + this.adsDisplayElement.style.display = 'block'; + + this.playing = true; + + // Pause our video. this.player.pause(); 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. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_RESUME_REQUESTED |`, 'Fired when content should be resumed. This usually happens when an ad finishes or collapses.'); this.handleEventListeners('CONTENT_RESUME_REQUESTED'); + + // Hide the advertisement container. + this.adsDisplayElement.style.display = 'none'; + + this.playing = false; + + // Play our video. if (this.player.currentTime < this.player.duration) { this.player.play(); } @@ -211,9 +210,6 @@ class Ads { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] LOADED |`, event.getAd().getContentType()); this.handleEventListeners('LOADED'); - // Show the ad display element. - this.adsDisplayElement.style.display = 'block'; - if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. ad.width = container.offsetWidth; @@ -224,13 +220,7 @@ class Ads { // console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset()); break; case google.ima.AdEvent.Type.STARTED: - // This event indicates the ad has started - the video player - // can adjust the UI, for example display a pause button and - // remaining time. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] STARTED |`, 'Fired when the ad starts playing.'); - this.player.pause(); - this.playing = true; - this.handleEventListeners('STARTED'); break; case google.ima.AdEvent.Type.DURATION_CHANGE: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] DURATION_CHANGE |`, 'Fired when the ad\'s duration changes.'); -- cgit v1.2.3 From 9e52296dc62c3efab3e00204ab2e561b712c8965 Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 17 Jan 2018 12:19:32 +0100 Subject: Moved the ads container to be outside of the video wrapper. This way we can easily move the ad in front or behind the video controls based on content resume or pause IMA events. --- src/js/plugins/ads.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 87ce61a1..48a5e4da 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -276,7 +276,11 @@ class Ads { } setupAdDisplayContainer() { - const { wrapper } = this.player.elements; + // Create the container for our advertisements. + const container = utils.createElement('div', { + class: this.player.config.classNames.ads, + }); + this.player.elements.container.appendChild(container); // So we can run VPAID2. google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); @@ -287,9 +291,9 @@ class Ads { // We assume the adContainer is the video container of the plyr element // that will house the ads. - this.adDisplayContainer = new google.ima.AdDisplayContainer(wrapper); + this.adDisplayContainer = new google.ima.AdDisplayContainer(container); - this.adsDisplayElement = wrapper.firstChild; + this.adsDisplayElement = container.firstChild; // The AdDisplayContainer call from google IMA sets the style attribute // by default. We remove the inline style and set it through the stylesheet. @@ -298,6 +302,14 @@ class Ads { // Set class name on the adDisplayContainer element. this.adsDisplayElement.setAttribute('class', this.player.config.classNames.ads); + // Make sure our advertisement container has the right z-index. + this.on('CONTENT_PAUSE_REQUESTED', () => { + container.style.zIndex = '3'; + }); + this.on('CONTENT_RESUME_REQUESTED', () => { + container.style.zIndex = '1'; + }); + // Play ads when clicked. Wait until the adsManager and adsLoader // are both resolved. Promise.all([ -- cgit v1.2.3 From d822f0c6bfefddfbce3a987014077de9b68db00a Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 17 Jan 2018 13:58:39 +0100 Subject: Adsmanager is now re/pre-loaded with new ads when the video is done or an ad error appears. Will make it possible to request ads when a new video is loaded. Added comments and missing events within the adsmanagerloader method. --- src/js/plugins/ads.js | 353 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 237 insertions(+), 116 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 48a5e4da..8e6d90a0 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -42,8 +42,8 @@ class Ads { ready() { this.time = Date.now(); this.startEvents = getStartEvents(); + this.adsContainer = null; this.adDisplayContainer = null; - this.adsDisplayElement = null; this.adsManager = null; this.adsLoader = null; this.adsCuePoints = null; @@ -54,61 +54,125 @@ class Ads { // Set listeners on the Plyr instance. this.setupListeners(); + // Start ticking our safety timer. If the whole advertisement + // thing doesn't resolve within our set time; we bail. + this.startSafetyTimer(12000, 'ready()'); + // Setup a simple promise to resolve if the IMA loader is ready. this.adsLoaderPromise = new Promise((resolve) => { this.on('ADS_LOADER_LOADED', () => resolve()); - }); - this.adsLoaderPromise.then(() => { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsLoader resolved!`, this.adsLoader); }); // Setup a promise to resolve if the IMA manager is ready. this.adsManagerPromise = new Promise((resolve) => { this.on('ADS_MANAGER_LOADED', () => resolve()); - }); - this.adsManagerPromise.then(() => { + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsManager resolved!`, this.adsManager); + // Clear the safety timer. this.clearSafetyTimer('onAdsManagerLoaded()'); - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsManager resolved!`, this.adsManager); }); - // Start ticking our safety timer. If the whole advertisement - // thing doesn't resolve within our set time; we bail. - this.startSafetyTimer(12000, 'ready()'); - - // Setup the ad display container. - this.setupAdDisplayContainer(); - // Setup the IMA SDK. this.setupIMA(); } + /** + * setupIMA + * 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() { - const { container } = this.player.elements; + // Create the container for our advertisements. + this.adsContainer = utils.createElement('div', { + class: this.player.config.classNames.ads, + }); + this.player.elements.container.appendChild(this.adsContainer); - // Create ads loader. - this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer); + // So we can run VPAID2. + google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); + + // Set language. + // Todo: Could make a config option out of this locale value. + google.ima.settings.setLocale('en'); - // Listen and respond to ads loaded and error events. - this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false); - this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false); + // We assume the adContainer is the video container of the plyr element + // that will house the ads. + this.adDisplayContainer = new google.ima.AdDisplayContainer(this.adsContainer); - // Request video ads. - const adsRequest = new google.ima.AdsRequest(); - adsRequest.adTagUrl = this.player.config.ads.tagUrl; + const adsDisplayElement = this.adsContainer.firstChild; - // Specify the linear and nonlinear slot sizes. This helps the SDK to - // select the correct creative if multiple are returned. - adsRequest.linearAdSlotWidth = container.offsetWidth; - adsRequest.linearAdSlotHeight = container.offsetHeight; - adsRequest.nonLinearAdSlotWidth = container.offsetWidth; - adsRequest.nonLinearAdSlotHeight = container.offsetHeight; + // The AdDisplayContainer call from google IMA sets the style attribute + // by default. We remove the inline style and set it through the stylesheet. + adsDisplayElement.removeAttribute('style'); - this.adsLoader.requestAds(adsRequest); + // Set class name on the adDisplayContainer element. + adsDisplayElement.setAttribute('class', this.player.config.classNames.ads); - this.handleEventListeners('ADS_LOADER_LOADED'); + // Play ads when clicked. Wait until the adsManager and adsLoader + // are both resolved. + Promise.all([ + this.adsManagerPromise, + this.adsLoaderPromise, + ]).then(() => { + this.setOnClickHandler(adsDisplayElement, this.play); + }); + + // Request video ads to be pre-loaded. + this.requestAds(); + } + + /** + * Request advertisements. + */ + requestAds() { + const { container } = this.player.elements; + + try { + // Create ads loader. + this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer); + + // Listen and respond to ads loaded and error events. + this.adsLoader.addEventListener( + google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, + event => this.onAdsManagerLoaded(event), false); + this.adsLoader.addEventListener( + google.ima.AdErrorEvent.Type.AD_ERROR, + error => this.onAdError(error), false); + + // Request video ads. + const adsRequest = new google.ima.AdsRequest(); + adsRequest.adTagUrl = this.player.config.ads.tagUrl; + + // Specify the linear and nonlinear slot sizes. This helps the SDK + // to select the correct creative if multiple are returned. + adsRequest.linearAdSlotWidth = container.offsetWidth; + adsRequest.linearAdSlotHeight = container.offsetHeight; + adsRequest.nonLinearAdSlotWidth = container.offsetWidth; + adsRequest.nonLinearAdSlotHeight = container.offsetHeight; + + // We only overlay ads as we only support video. + adsRequest.forceNonLinearFullSlot = false; + + this.adsLoader.requestAds(adsRequest); + + this.handleEventListeners('ADS_LOADER_LOADED'); + } catch (e) { + this.onAdError(e); + } } + /** + * This method is called whenever the ads are ready inside + * the AdDisplayContainer. + * @param {Event} adsManagerLoadedEvent + */ onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. @@ -126,21 +190,44 @@ class Ads { this.adsCuePoints = this.adsManager.getCuePoints(); // Add listeners to the required events. + // Advertisement error events. this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); + + // Advertisement regular events. + this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_BREAK_READY, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_METADATA, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.CLICK, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, event => this.onAdEvent(event)); this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_BREAK_READY, event => this.onAdEvent(event)); - - // Listen to any additional events, if necessary. this.adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, event => this.onAdEvent(event)); this.adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.DURATION_CHANGE, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.FIRST_QUARTILE, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.IMPRESSION, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.INTERACTION, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.LINEAR_CHANGED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.MIDPOINT, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.PAUSED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.RESUMED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.THIRD_QUARTILE, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.USER_CLOSE, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.VOLUME_CHANGED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.VOLUME_MUTED, event => this.onAdEvent(event)); // Resolve our adsManager. this.handleEventListeners('ADS_MANAGER_LOADED'); } + /** + * 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 ad + * object associated. + * @param {Event} event + */ onAdEvent(event) { const { container } = this.player.elements; @@ -163,8 +250,45 @@ class Ads { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_METADATA |`, 'Fired when an ads list is loaded.'); 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. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] ALL_ADS_COMPLETED |`, 'Fired when the ads manager is done playing all the ads.'); this.handleEventListeners('ALL_ADS_COMPLETED'); + + // 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. + + this.loadAds(); break; case google.ima.AdEvent.Type.CLICK: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CLICK |`, 'Fired when the ad is clicked.'); @@ -178,14 +302,7 @@ class Ads { // remaining time. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_PAUSE_REQUESTED |`, 'Fired when content should be paused. This usually happens right before an ad is about to cover the content.'); this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); - - // Show our advertiment container. - this.adsDisplayElement.style.display = 'block'; - - this.playing = true; - - // Pause our video. - this.player.pause(); + this.contentPause(); break; case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: // This event indicates the ad has finished - the video player @@ -193,16 +310,7 @@ class Ads { // remaining time detection. this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_RESUME_REQUESTED |`, 'Fired when content should be resumed. This usually happens when an ad finishes or collapses.'); this.handleEventListeners('CONTENT_RESUME_REQUESTED'); - - // Hide the advertisement container. - this.adsDisplayElement.style.display = 'none'; - - this.playing = false; - - // Play our video. - if (this.player.currentTime < this.player.duration) { - this.player.play(); - } + this.contentResume(); break; case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to @@ -270,56 +378,15 @@ class Ads { } } + /** + * Any ad error handling comes through here. + * @param {Event} adErrorEvent + */ onAdError(adErrorEvent) { this.cancel(); this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] ERROR |`, adErrorEvent); } - setupAdDisplayContainer() { - // Create the container for our advertisements. - const container = utils.createElement('div', { - class: this.player.config.classNames.ads, - }); - this.player.elements.container.appendChild(container); - - // So we can run VPAID2. - google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); - - // Set language. - // Todo: Could make a config option out of this locale value. - google.ima.settings.setLocale('en'); - - // We assume the adContainer is the video container of the plyr element - // that will house the ads. - this.adDisplayContainer = new google.ima.AdDisplayContainer(container); - - this.adsDisplayElement = container.firstChild; - - // The AdDisplayContainer call from google IMA sets the style attribute - // by default. We remove the inline style and set it through the stylesheet. - this.adsDisplayElement.removeAttribute('style'); - - // Set class name on the adDisplayContainer element. - this.adsDisplayElement.setAttribute('class', this.player.config.classNames.ads); - - // Make sure our advertisement container has the right z-index. - this.on('CONTENT_PAUSE_REQUESTED', () => { - container.style.zIndex = '3'; - }); - this.on('CONTENT_RESUME_REQUESTED', () => { - container.style.zIndex = '1'; - }); - - // Play ads when clicked. Wait until the adsManager and adsLoader - // are both resolved. - Promise.all([ - this.adsManagerPromise, - this.adsLoaderPromise, - ]).then(() => { - this.setOnClickHandler(this.adsDisplayElement, this.play); - }); - } - /** * Setup hooks for Plyr and window events. This ensures * the mid- and post-roll launch at the correct time. And @@ -380,17 +447,49 @@ class Ads { this.initialized = true; } catch (adError) { - // An error may be thrown if there was a problem with the VAST response. - this.adsDisplayElement.remove(); - - if (this.player.debug) { - throw new Error(adError); - } - this.player.play(); + // An error may be thrown if there was a problem with the + // VAST response. + this.onAdError(adError); } }); } + /** + * Resume our video. + */ + contentResume() { + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Resume video.'); + + // Hide our ad container. + this.adsContainer.style.display = 'none'; + this.adsContainer.style.zIndex = '1'; + + // Ad is stopped. + this.playing = false; + + // Play our video. + if (this.player.currentTime < this.player.duration) { + this.player.play(); + } + } + + /** + * Pause our video. + */ + contentPause() { + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Pause video.'); + + // Show our ad container. + this.adsContainer.style.display = 'block'; + this.adsContainer.style.zIndex = '3'; + + // Ad is playing. + this.playing = true; + + // Pause our video. + this.player.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 @@ -401,14 +500,40 @@ class Ads { cancel() { this.player.debug.warn(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Advertisement cancelled.'); - // Todo: Removing the ad container might be problematic if we were to recreate the adsManager. Think of playlists. Every new video you need to request a new VAST xml and preload the advertisement. - this.adsDisplayElement.remove(); + // Pause our video. + this.contentResume(); + + // Tell our instance that we're done for now. + this.handleEventListeners('ERROR'); + + // Re-create our adsManager. + this.loadAds(); + } + + /** + * Re-create our adsManager. + */ + loadAds() { + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Re-loading advertisements.'); // Tell our adsManager to go bye bye. this.adsManagerPromise.then(() => { + // Destroy our adsManager. if (this.adsManager) { this.adsManager.destroy(); } + + // Re-set our adsManager promises. + this.adsManagerPromise = new Promise((resolve) => { + this.on('ADS_MANAGER_LOADED', () => resolve()); + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsManager resolved!`, this.adsManager); + }); + + // Make sure we can re-call advertisements. + this.initialized = false; + + // Now request some new advertisements. + this.requestAds(); }); } @@ -453,15 +578,12 @@ class Ads { } /** - * startSafetyTimer - * Setup a safety timer for when the ad network - * doesn't respond for whatever reason. The advertisement has 12 seconds - * to get its shit 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. + * 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 - * @private */ startSafetyTimer(time, from) { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, `Safety timer invoked timer from: ${from}`); @@ -472,9 +594,8 @@ class Ads { } /** - * clearSafetyTimer + * Clear our safety timer(s). * @param {String} from - * @private */ clearSafetyTimer(from) { if (typeof this.safetyTimer !== 'undefined' && this.safetyTimer !== null) { -- cgit v1.2.3 From 3583165b30ac80afbd6235fc2febd1a5987d4f8b Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 17 Jan 2018 14:09:11 +0100 Subject: Removed logic related to starting the ad by clicking/ tapping the advertisement container. Ad is started by plyr play method. --- src/js/plugins/ads.js | 60 --------------------------------------------------- 1 file changed, 60 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 8e6d90a0..3b76fcc8 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -1,23 +1,5 @@ import utils from '../utils'; -// Events are different on various devices. We set the correct events, based on userAgent. -const getStartEvents = () => { - let events = ['click']; - - // TODO: Detecting touch is tricky, we should look at other ways? - // For mobile users the start event will be one of - // touchstart, touchend and touchmove. - if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/Android/i)) { - events = [ - 'touchstart', - 'touchend', - 'touchmove', - ]; - } - - return events; -}; - class Ads { constructor(player) { this.player = player; @@ -41,7 +23,6 @@ class Ads { ready() { this.time = Date.now(); - this.startEvents = getStartEvents(); this.adsContainer = null; this.adDisplayContainer = null; this.adsManager = null; @@ -106,24 +87,6 @@ class Ads { // that will house the ads. this.adDisplayContainer = new google.ima.AdDisplayContainer(this.adsContainer); - const adsDisplayElement = this.adsContainer.firstChild; - - // The AdDisplayContainer call from google IMA sets the style attribute - // by default. We remove the inline style and set it through the stylesheet. - adsDisplayElement.removeAttribute('style'); - - // Set class name on the adDisplayContainer element. - adsDisplayElement.setAttribute('class', this.player.config.classNames.ads); - - // Play ads when clicked. Wait until the adsManager and adsLoader - // are both resolved. - Promise.all([ - this.adsManagerPromise, - this.adsLoaderPromise, - ]).then(() => { - this.setOnClickHandler(adsDisplayElement, this.play); - }); - // Request video ads to be pre-loaded. this.requestAds(); } @@ -462,7 +425,6 @@ class Ads { // Hide our ad container. this.adsContainer.style.display = 'none'; - this.adsContainer.style.zIndex = '1'; // Ad is stopped. this.playing = false; @@ -481,7 +443,6 @@ class Ads { // Show our ad container. this.adsContainer.style.display = 'block'; - this.adsContainer.style.zIndex = '3'; // Ad is playing. this.playing = true; @@ -546,27 +507,6 @@ class Ads { } } - /** - * Set start event listener on a DOM element and triggers the - * callback when clicked. - * @param {element} element - The element on which to set the listener - * @param {function} callback - The callback which will be invoked once triggered. - */ - setOnClickHandler(element, callback) { - for (let i = 0; i < this.startEvents.length; i += 1) { - const startEvent = this.startEvents[i]; - element.addEventListener( - startEvent, - event => { - if ((event.type === 'touchend' && startEvent === 'touchend') || event.type === 'click') { - callback.call(this); - } - }, - { once: true }, - ); - } - } - /** * Add event listeners * @param {string} event - Event type -- cgit v1.2.3 From 1d1eb02bd734461bad8884c827e79c05d8f77dbd Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 17 Jan 2018 14:44:44 +0100 Subject: Added the logging of our main promises to their resolving callback. Otherwise they come up as null. --- src/js/plugins/ads.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 3b76fcc8..3738ed81 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -42,12 +42,16 @@ class Ads { // Setup a simple promise to resolve if the IMA loader is ready. this.adsLoaderPromise = new Promise((resolve) => { this.on('ADS_LOADER_LOADED', () => resolve()); + }); + this.adsLoaderPromise.then(() => { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsLoader resolved!`, this.adsLoader); }); // Setup a promise to resolve if the IMA manager is ready. this.adsManagerPromise = new Promise((resolve) => { this.on('ADS_MANAGER_LOADED', () => resolve()); + }); + this.adsManagerPromise.then(() => { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsManager resolved!`, this.adsManager); // Clear the safety timer. -- cgit v1.2.3 From 896ea7c689f25e5eed5ad6bcac0e9314cff825ed Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 17 Jan 2018 15:38:26 +0100 Subject: Added cue markings within the time line for when midrolls will be displayed. Removed unusued callback parameter. --- src/js/plugins/ads.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 3738ed81..1df56b7b 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -141,6 +141,7 @@ class Ads { * @param {Event} adsManagerLoadedEvent */ onAdsManagerLoaded(adsManagerLoadedEvent) { + const { container } = this.player.elements; // Get the ads manager. const settings = new google.ima.AdsRenderingSettings(); @@ -156,6 +157,18 @@ class Ads { // Get the cue points for any mid-rolls by filtering out the pre- and post-roll. this.adsCuePoints = this.adsManager.getCuePoints(); + // Add advertisement cue's within the time line if available. + this.adsCuePoints.forEach((cuePoint, index) => { + if (cuePoint !== 0 && cuePoint !== -1) { + const seekElement = this.player.elements.progress; + const cue = utils.createElement('span', { + class: this.player.config.classNames.cues, + }); + cue.style.left = cuePoint.toString() + 'px'; + seekElement.appendChild(cue); + } + }); + // Add listeners to the required events. // Advertisement error events. this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); @@ -368,12 +381,12 @@ class Ads { this.adsLoader.contentComplete(); }); - this.player.on('seeking', event => { + this.player.on('seeking', () => { time = this.player.currentTime; return time; }); - this.player.on('seeked', event => { + this.player.on('seeked', () => { const seekedTime = this.player.currentTime; this.adsCuePoints.forEach((cuePoint, index) => { -- cgit v1.2.3 From 0cb2f9588845dc7cf93c3bec45968100c2db8166 Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 17 Jan 2018 15:43:08 +0100 Subject: Removed an un-used variable. --- src/js/plugins/ads.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 1df56b7b..dcd236a9 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -141,8 +141,6 @@ class Ads { * @param {Event} adsManagerLoadedEvent */ onAdsManagerLoaded(adsManagerLoadedEvent) { - const { container } = this.player.elements; - // Get the ads manager. const settings = new google.ima.AdsRenderingSettings(); @@ -158,7 +156,7 @@ class Ads { this.adsCuePoints = this.adsManager.getCuePoints(); // Add advertisement cue's within the time line if available. - this.adsCuePoints.forEach((cuePoint, index) => { + this.adsCuePoints.forEach((cuePoint) => { if (cuePoint !== 0 && cuePoint !== -1) { const seekElement = this.player.elements.progress; const cue = utils.createElement('span', { -- cgit v1.2.3 From 31c816656267efa495d02e7f3429d35d91f9c648 Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 17 Jan 2018 15:57:10 +0100 Subject: Fixed string literal and position issue of the midroll cue inside the time line. Added a check for the progress element existence. --- src/js/plugins/ads.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index dcd236a9..0b12a8e7 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -159,11 +159,14 @@ class Ads { this.adsCuePoints.forEach((cuePoint) => { if (cuePoint !== 0 && cuePoint !== -1) { const seekElement = this.player.elements.progress; - const cue = utils.createElement('span', { - class: this.player.config.classNames.cues, - }); - cue.style.left = cuePoint.toString() + 'px'; - seekElement.appendChild(cue); + if(seekElement) { + const cuePercentage = 100 / this.player.duration * cuePoint; + const cue = utils.createElement('span', { + class: this.player.config.classNames.cues, + }); + cue.style.left = `${cuePercentage.toString()}%`; + seekElement.appendChild(cue); + } } }); -- cgit v1.2.3 From d87ada4f58c71299f38ed2764da04a77312faf93 Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Thu, 18 Jan 2018 12:26:53 +0100 Subject: Reformatted ads codebase and added/ changed comments. Also removed un-used events. --- src/js/plugins/ads.js | 283 +++++++++++++++++++++----------------------------- 1 file changed, 119 insertions(+), 164 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 0b12a8e7..bdd07d34 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -1,6 +1,14 @@ import utils from '../utils'; +/** + * Advertisements using Google IMA HTML5 SDK. + */ class Ads { + /** + * Ads constructor. + * @param {object} player + * @return {Ads} + */ constructor(player) { this.player = player; this.playing = false; @@ -21,6 +29,9 @@ class Ads { } } + /** + * Get the ads instance ready. + */ ready() { this.time = Date.now(); this.adsContainer = null; @@ -28,7 +39,6 @@ class Ads { this.adsManager = null; this.adsLoader = null; this.adsCuePoints = null; - this.currentAd = null; this.events = {}; this.safetyTimer = null; @@ -44,7 +54,7 @@ class Ads { this.on('ADS_LOADER_LOADED', () => resolve()); }); this.adsLoaderPromise.then(() => { - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsLoader resolved!`, this.adsLoader); + this.player.debug.log('Ads loader resolved!', this.adsLoader); }); // Setup a promise to resolve if the IMA manager is ready. @@ -52,7 +62,7 @@ class Ads { this.on('ADS_MANAGER_LOADED', () => resolve()); }); this.adsManagerPromise.then(() => { - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsManager resolved!`, this.adsManager); + this.player.debug.log('Ads manager resolved!', this.adsManager); // Clear the safety timer. this.clearSafetyTimer('onAdsManagerLoaded()'); @@ -63,15 +73,12 @@ class Ads { } /** - * setupIMA - * 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. + * 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. @@ -136,8 +143,7 @@ class Ads { } /** - * This method is called whenever the ads are ready inside - * the AdDisplayContainer. + * This method is called whenever the ads are ready inside the AdDisplayContainer. * @param {Event} adsManagerLoadedEvent */ onAdsManagerLoaded(adsManagerLoadedEvent) { @@ -159,7 +165,7 @@ class Ads { this.adsCuePoints.forEach((cuePoint) => { if (cuePoint !== 0 && cuePoint !== -1) { const seekElement = this.player.elements.progress; - if(seekElement) { + if (seekElement) { const cuePercentage = 100 / this.player.duration * cuePoint; const cue = utils.createElement('span', { class: this.player.config.classNames.cues, @@ -172,68 +178,80 @@ class Ads { // Add listeners to the required events. // Advertisement error events. - this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); + this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, + error => this.onAdError(error)); // Advertisement regular events. - this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_BREAK_READY, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_METADATA, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.CLICK, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.DURATION_CHANGE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.FIRST_QUARTILE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.IMPRESSION, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.INTERACTION, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.LINEAR_CHANGED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.MIDPOINT, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.PAUSED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.RESUMED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.THIRD_QUARTILE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.USER_CLOSE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.VOLUME_CHANGED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.VOLUME_MUTED, event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_BREAK_READY, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_METADATA, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.CLICK, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.DURATION_CHANGE, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.FIRST_QUARTILE, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.IMPRESSION, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.INTERACTION, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.LINEAR_CHANGED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.MIDPOINT, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.PAUSED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.RESUMED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.THIRD_QUARTILE, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.USER_CLOSE, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.VOLUME_CHANGED, + event => this.onAdEvent(event)); + this.adsManager.addEventListener(google.ima.AdEvent.Type.VOLUME_MUTED, + event => this.onAdEvent(event)); // Resolve our adsManager. this.handleEventListeners('ADS_MANAGER_LOADED'); } /** - * 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 ad - * object associated. + * 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. * @param {Event} event */ onAdEvent(event) { const { container } = this.player.elements; + // Listen for events if debugging. + this.player.debug.log(`ads event: ${event.type}`); + // Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED) // don't have ad object associated. const ad = event.getAd(); - // Set the currently played ad. This information could be used by callback - // events. - this.currentAd = ad; - - // let intervalTimer; - switch (event.type) { - - case google.ima.AdEvent.Type.AD_BREAK_READY: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_BREAK_READY |`, 'Fired when an ad rule or a VMAP ad break would have played if autoPlayAdBreaks is false.'); - break; - case google.ima.AdEvent.Type.AD_METADATA: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_METADATA |`, 'Fired when an ads list is loaded.'); - 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. - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] ALL_ADS_COMPLETED |`, 'Fired when the ads manager is done playing all the ads.'); + // All ads for the current videos are done. We can now request new advertisements + // in case the video is re-played. this.handleEventListeners('ALL_ADS_COMPLETED'); // Todo: Example for what happens when a next video in a playlist would be loaded. @@ -246,57 +264,39 @@ class Ads { // 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', - // }, - // ], + // 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. + // 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. this.loadAds(); break; - case google.ima.AdEvent.Type.CLICK: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CLICK |`, 'Fired when the ad is clicked.'); - break; - case google.ima.AdEvent.Type.COMPLETE: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] COMPLETE |`, 'Fired when the ad completes playing.'); - 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. - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_PAUSE_REQUESTED |`, 'Fired when content should be paused. This usually happens right before an ad is about to cover the content.'); + // 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.handleEventListeners('CONTENT_PAUSE_REQUESTED'); - this.contentPause(); + this.pause(); 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. - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_RESUME_REQUESTED |`, 'Fired when content should be resumed. This usually happens when an ad finishes or collapses.'); + // 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.handleEventListeners('CONTENT_RESUME_REQUESTED'); - this.contentResume(); + this.resume(); break; 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.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] LOADED |`, event.getAd().getContentType()); + // 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.handleEventListeners('LOADED'); if (!ad.isLinear()) { @@ -308,51 +308,6 @@ class Ads { // console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex()); // console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset()); break; - case google.ima.AdEvent.Type.STARTED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] STARTED |`, 'Fired when the ad starts playing.'); - break; - case google.ima.AdEvent.Type.DURATION_CHANGE: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] DURATION_CHANGE |`, 'Fired when the ad\'s duration changes.'); - break; - case google.ima.AdEvent.Type.FIRST_QUARTILE: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] FIRST_QUARTILE |`, 'Fired when the ad playhead crosses first quartile.'); - break; - case google.ima.AdEvent.Type.IMPRESSION: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] IMPRESSION |`, 'Fired when the impression URL has been pinged.'); - break; - case google.ima.AdEvent.Type.INTERACTION: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] INTERACTION |`, 'Fired when an ad triggers the interaction callback. Ad interactions contain an interaction ID string in the ad data.'); - break; - case google.ima.AdEvent.Type.LINEAR_CHANGED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] LINEAR_CHANGED |`, 'Fired when the displayed ad changes from linear to nonlinear, or vice versa.'); - break; - case google.ima.AdEvent.Type.MIDPOINT: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] MIDPOINT |`, 'Fired when the ad playhead crosses midpoint.'); - break; - case google.ima.AdEvent.Type.PAUSED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] PAUSED |`, 'Fired when the ad is paused.'); - break; - case google.ima.AdEvent.Type.RESUMED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] RESUMED |`, 'Fired when the ad is resumed.'); - break; - case google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] SKIPPABLE_STATE_CHANGED |`, 'Fired when the displayed ads skippable state is changed.'); - break; - case google.ima.AdEvent.Type.SKIPPED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] SKIPPED |`, 'Fired when the ad is skipped by the user.'); - break; - case google.ima.AdEvent.Type.THIRD_QUARTILE: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] THIRD_QUARTILE |`, 'Fired when the ad playhead crosses third quartile.'); - break; - case google.ima.AdEvent.Type.USER_CLOSE: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] USER_CLOSE |`, 'Fired when the ad is closed by the user.'); - break; - case google.ima.AdEvent.Type.VOLUME_CHANGED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] VOLUME_CHANGED |`, 'Fired when the ad volume has changed.'); - break; - case google.ima.AdEvent.Type.VOLUME_MUTED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] VOLUME_MUTED |`, 'Fired when the ad volume has been muted.'); - break; default: break; @@ -365,7 +320,7 @@ class Ads { */ onAdError(adErrorEvent) { this.cancel(); - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] ERROR |`, adErrorEvent); + this.player.debug.log('Ads error.', adErrorEvent); } /** @@ -400,7 +355,8 @@ class Ads { // Listen to the resizing of the window. And resize ad accordingly. window.addEventListener('resize', () => { - this.adsManager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); + this.adsManager.resize(container.offsetWidth, container.offsetHeight, + google.ima.ViewMode.NORMAL); }); } @@ -419,7 +375,8 @@ class Ads { if (!this.initialized) { // Initialize the ads manager. Ad rules playlist will start at this time. - this.adsManager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); + this.adsManager.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. @@ -438,8 +395,8 @@ class Ads { /** * Resume our video. */ - contentResume() { - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Resume video.'); + resume() { + this.player.debug.log('Resume video.'); // Hide our ad container. this.adsContainer.style.display = 'none'; @@ -456,8 +413,8 @@ class Ads { /** * Pause our video. */ - contentPause() { - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Pause video.'); + pause() { + this.player.debug.log('Pause video.'); // Show our ad container. this.adsContainer.style.display = 'block'; @@ -470,17 +427,16 @@ class Ads { } /** - * 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 + * 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() { - this.player.debug.warn(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Advertisement cancelled.'); + this.player.debug.warn('Ad cancelled.'); // Pause our video. - this.contentResume(); + this.resume(); // Tell our instance that we're done for now. this.handleEventListeners('ERROR'); @@ -493,8 +449,6 @@ class Ads { * Re-create our adsManager. */ loadAds() { - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, 'Re-loading advertisements.'); - // Tell our adsManager to go bye bye. this.adsManagerPromise.then(() => { // Destroy our adsManager. @@ -505,7 +459,7 @@ class Ads { // Re-set our adsManager promises. this.adsManagerPromise = new Promise((resolve) => { this.on('ADS_MANAGER_LOADED', () => resolve()); - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] adsManager resolved!`, this.adsManager); + this.player.debug.log(this.adsManager); }); // Make sure we can re-call advertisements. @@ -518,6 +472,7 @@ class Ads { /** * Handles callbacks after an ad event was invoked. + * @param {string} event - Event type */ handleEventListeners(event) { if (typeof this.events[event] !== 'undefined') { @@ -529,6 +484,7 @@ class Ads { * Add event listeners * @param {string} event - Event type * @param {function} callback - Callback for when event occurs + * @return {Ads} */ on(event, callback) { this.events[event] = callback; @@ -536,15 +492,15 @@ class Ads { } /** - * 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. + * 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(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, `Safety timer invoked timer from: ${from}`); + this.player.debug.log(`Safety timer invoked from: ${from}.`); this.safetyTimer = window.setTimeout(() => { this.cancel(); this.clearSafetyTimer('startSafetyTimer()'); @@ -557,7 +513,7 @@ class Ads { */ clearSafetyTimer(from) { if (typeof this.safetyTimer !== 'undefined' && this.safetyTimer !== null) { - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK]`, `Safety timer cleared timer from: ${from}`); + this.player.debug.log(`Safety timer cleared from: ${from}.`); clearTimeout(this.safetyTimer); this.safetyTimer = undefined; } @@ -565,4 +521,3 @@ class Ads { } export default Ads; - -- cgit v1.2.3 From 8af312fe3cfe55c5003b645f1fcfe23601fc435f Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Thu, 18 Jan 2018 12:31:10 +0100 Subject: Updated pause and resume content methods within Ads class. --- src/js/plugins/ads.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index bdd07d34..2e7b1e93 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -284,7 +284,7 @@ class Ads { // 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.handleEventListeners('CONTENT_PAUSE_REQUESTED'); - this.pause(); + this.pauseContent(); break; case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: // This event indicates the ad has finished - the video player can perform @@ -292,7 +292,7 @@ class Ads { // Fired when content should be resumed. This usually happens when an ad finishes // or collapses. this.handleEventListeners('CONTENT_RESUME_REQUESTED'); - this.resume(); + this.resumeContent(); break; case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to determine whether the @@ -395,7 +395,7 @@ class Ads { /** * Resume our video. */ - resume() { + resumeContent() { this.player.debug.log('Resume video.'); // Hide our ad container. @@ -413,7 +413,7 @@ class Ads { /** * Pause our video. */ - pause() { + pauseContent() { this.player.debug.log('Pause video.'); // Show our ad container. @@ -436,7 +436,7 @@ class Ads { this.player.debug.warn('Ad cancelled.'); // Pause our video. - this.resume(); + this.resumeContent(); // Tell our instance that we're done for now. this.handleEventListeners('ERROR'); -- cgit v1.2.3 From ed6048034b86a1de7a705daf28150b79c45def20 Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Thu, 18 Jan 2018 14:04:47 +0100 Subject: Noticed that Plyr stopped working when ads are blocked. --- src/js/plugins/ads.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 2e7b1e93..4e405962 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -33,7 +33,6 @@ class Ads { * Get the ads instance ready. */ ready() { - this.time = Date.now(); this.adsContainer = null; this.adDisplayContainer = null; this.adsManager = null; @@ -366,11 +365,15 @@ class Ads { play() { const { container } = this.player.elements; - // Initialize the container. Must be done via a user action on mobile devices. - this.adDisplayContainer.initialize(); + if (!this.adsManagerPromise) { + return; + } // Play the requested advertisement whenever the adsManager is ready. this.adsManagerPromise.then(() => { + // Initialize the container. Must be done via a user action on mobile devices. + this.adDisplayContainer.initialize(); + try { if (!this.initialized) { -- cgit v1.2.3 From 021f6c84605e4e3f2c979d88edd386e36b2a0393 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Fri, 19 Jan 2018 14:26:25 +1100 Subject: Comments and formatting --- src/js/plugins/ads.js | 53 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index ceb00ee4..1875dcc9 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -1,3 +1,11 @@ +// ========================================================================== +// Advertisment plugin +// Create an account with our ad partner, vi here: +// https://www.vi.ai/publisher-video-monetization/ +// ========================================================================== + +/* global google */ + import utils from '../utils'; // Events are different on various devices. We set the correct events, based on userAgent. @@ -53,7 +61,7 @@ class Ads { // Setup a simple promise to resolve if the IMA loader is ready. this.adsLoaderResolve = () => {}; - this.adsLoaderPromise = new Promise((resolve) => { + this.adsLoaderPromise = new Promise(resolve => { this.adsLoaderResolve = resolve; }); this.adsLoaderPromise.then(() => { @@ -62,7 +70,7 @@ class Ads { // Setup a promise to resolve if the IMA manager is ready. this.adsManagerResolve = () => {}; - this.adsManagerPromise = new Promise((resolve) => { + this.adsManagerPromise = new Promise(resolve => { // Resolve our promise. this.adsManagerResolve = resolve; }); @@ -113,7 +121,6 @@ class Ads { } onAdsManagerLoaded(adsManagerLoadedEvent) { - // Get the ads manager. const settings = new google.ima.AdsRenderingSettings(); @@ -158,11 +165,13 @@ class Ads { // let intervalTimer; switch (event.type) { - case google.ima.AdEvent.Type.AD_BREAK_READY: // This event indicates that a mid-roll ad is ready to start. // We pause the player and tell the adsManager to start playing the ad. - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_BREAK_READY |`, 'Fired when an ad rule or a VMAP ad break would have played if autoPlayAdBreaks is false.'); + this.player.debug.log( + `[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_BREAK_READY |`, + 'Fired when an ad rule or a VMAP ad break would have played if autoPlayAdBreaks is false.', + ); // this.handleEventListeners('AD_BREAK_READY'); // this.playing = true; // this.adsManager.start(); @@ -171,7 +180,10 @@ class Ads { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] AD_METADATA |`, 'Fired when an ads list is loaded.'); break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] ALL_ADS_COMPLETED |`, 'Fired when the ads manager is done playing all the ads.'); + this.player.debug.log( + `[${(Date.now() - this.time) / 1000}s][IMA SDK] ALL_ADS_COMPLETED |`, + 'Fired when the ads manager is done playing all the ads.', + ); this.handleEventListeners('ALL_ADS_COMPLETED'); break; case google.ima.AdEvent.Type.CLICK: @@ -193,13 +205,19 @@ class Ads { } break; case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_PAUSE_REQUESTED |`, 'Fired when content should be paused. This usually happens right before an ad is about to cover the content.'); + this.player.debug.log( + `[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_PAUSE_REQUESTED |`, + 'Fired when content should be paused. This usually happens right before an ad is about to cover the content.', + ); this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); this.player.pause(); break; case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_RESUME_REQUESTED |`, 'Fired when content should be resumed. This usually happens when an ad finishes or collapses.'); + this.player.debug.log( + `[${(Date.now() - this.time) / 1000}s][IMA SDK] CONTENT_RESUME_REQUESTED |`, + 'Fired when content should be resumed. This usually happens when an ad finishes or collapses.', + ); this.handleEventListeners('CONTENT_RESUME_REQUESTED'); if (this.player.currentTime < this.player.duration) { this.player.play(); @@ -233,7 +251,7 @@ class Ads { this.handleEventListeners('STARTED'); break; case google.ima.AdEvent.Type.DURATION_CHANGE: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] DURATION_CHANGE |`, 'Fired when the ad\'s duration changes.'); + this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] DURATION_CHANGE |`, "Fired when the ad's duration changes."); break; case google.ima.AdEvent.Type.FIRST_QUARTILE: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] FIRST_QUARTILE |`, 'Fired when the ad playhead crosses first quartile.'); @@ -242,10 +260,16 @@ class Ads { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] IMPRESSION |`, 'Fired when the impression URL has been pinged.'); break; case google.ima.AdEvent.Type.INTERACTION: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] INTERACTION |`, 'Fired when an ad triggers the interaction callback. Ad interactions contain an interaction ID string in the ad data.'); + this.player.debug.log( + `[${(Date.now() - this.time) / 1000}s][IMA SDK] INTERACTION |`, + 'Fired when an ad triggers the interaction callback. Ad interactions contain an interaction ID string in the ad data.', + ); break; case google.ima.AdEvent.Type.LINEAR_CHANGED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] LINEAR_CHANGED |`, 'Fired when the displayed ad changes from linear to nonlinear, or vice versa.'); + this.player.debug.log( + `[${(Date.now() - this.time) / 1000}s][IMA SDK] LINEAR_CHANGED |`, + 'Fired when the displayed ad changes from linear to nonlinear, or vice versa.', + ); break; case google.ima.AdEvent.Type.MIDPOINT: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] MIDPOINT |`, 'Fired when the ad playhead crosses midpoint.'); @@ -257,7 +281,10 @@ class Ads { this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] RESUMED |`, 'Fired when the ad is resumed.'); break; case google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED: - this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] SKIPPABLE_STATE_CHANGED |`, 'Fired when the displayed ads skippable state is changed.'); + this.player.debug.log( + `[${(Date.now() - this.time) / 1000}s][IMA SDK] SKIPPABLE_STATE_CHANGED |`, + 'Fired when the displayed ads skippable state is changed.', + ); break; case google.ima.AdEvent.Type.SKIPPED: this.player.debug.log(`[${(Date.now() - this.time) / 1000}s][IMA SDK] SKIPPED |`, 'Fired when the ad is skipped by the user.'); @@ -367,7 +394,6 @@ class Ads { this.adsManagerPromise.then(() => { try { if (!this.initialized) { - // Initialize the ads manager. Ad rules playlist will start at this time. this.adsManager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); @@ -484,4 +510,3 @@ class Ads { } export default Ads; - -- cgit v1.2.3 From 5fad152cbf5b3b1db6098b1a592fdb3e873725a5 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Fri, 19 Jan 2018 20:24:15 +1100 Subject: =?UTF-8?q?Formatting=20and=20=E2=80=9Cad=E2=80=9D=20badge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/plugins/ads.js | 169 +++++++++++++++++++++++--------------------------- 1 file changed, 77 insertions(+), 92 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 96e0ce61..2f3b0669 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -46,14 +46,14 @@ class Ads { this.events = {}; this.safetyTimer = null; - // Set listeners on the Plyr instance. - this.setupListeners(); + // Set listeners on the Plyr instance + this.listeners(); // Start ticking our safety timer. If the whole advertisement - // thing doesn't resolve within our set time; we bail. + // thing doesn't resolve within our set time; we bail this.startSafetyTimer(12000, 'ready()'); - // Setup a simple promise to resolve if the IMA loader is ready. + // Setup a simple promise to resolve if the IMA loader is ready this.adsLoaderPromise = new Promise(resolve => { this.on('ADS_LOADER_LOADED', () => resolve()); }); @@ -61,18 +61,18 @@ class Ads { this.player.debug.log('Ads loader resolved!', this.adsLoader); }); - // Setup a promise to resolve if the IMA manager is ready. + // Setup a promise to resolve if the IMA manager is ready this.adsManagerPromise = new Promise(resolve => { this.on('ADS_MANAGER_LOADED', () => resolve()); }); this.adsManagerPromise.then(() => { this.player.debug.log('Ads manager resolved!', this.adsManager); - // Clear the safety timer. + // Clear the safety timer this.clearSafetyTimer('onAdsManagerLoaded()'); }); - // Setup the IMA SDK. + // Setup the IMA SDK this.setupIMA(); } @@ -85,47 +85,49 @@ class Ads { * mobile devices, this initialization is done as the result of a user action. */ setupIMA() { - // Create the container for our advertisements. + // Create the container for our advertisements this.adsContainer = utils.createElement('div', { class: this.player.config.classNames.ads, + 'data-badge-text': this.player.config.i18n.advertisement, + hidden: '', }); this.player.elements.container.appendChild(this.adsContainer); - // So we can run VPAID2. + // So we can run VPAID2 google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); - // Set language. + // Set language // Todo: Could make a config option out of this locale value. google.ima.settings.setLocale('en'); // We assume the adContainer is the video container of the plyr element - // that will house the ads. + // that will house the ads this.adDisplayContainer = new google.ima.AdDisplayContainer(this.adsContainer); - // Request video ads to be pre-loaded. + // Request video ads to be pre-loaded this.requestAds(); } /** - * Request advertisements. + * Request advertisements */ requestAds() { const { container } = this.player.elements; try { - // Create ads loader. + // Create ads loader this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer); - // Listen and respond to ads loaded and error events. + // Listen and respond to ads loaded and error events this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false); this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false); - // Request video ads. + // Request video ads const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = this.player.config.ads.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK - // to select the correct creative if multiple are returned. + // to select the correct creative if multiple are returned adsRequest.linearAdSlotWidth = container.offsetWidth; adsRequest.linearAdSlotHeight = container.offsetHeight; adsRequest.nonLinearAdSlotWidth = container.offsetWidth; @@ -143,25 +145,25 @@ class Ads { } /** - * This method is called whenever the ads are ready inside the AdDisplayContainer. + * This method is called whenever the ads are ready inside the AdDisplayContainer * @param {Event} adsManagerLoadedEvent */ onAdsManagerLoaded(adsManagerLoadedEvent) { - // Get the ads manager. + // Get the ads manager const settings = new google.ima.AdsRenderingSettings(); - // Tell the SDK to save and restore content video state on our behalf. + // 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. + // so it can determine when to start the mid- and post-roll this.adsManager = adsManagerLoadedEvent.getAdsManager(this.player, settings); - // Get the cue points for any mid-rolls by filtering out the pre- and post-roll. + // Get the cue points for any mid-rolls by filtering out the pre- and post-roll this.adsCuePoints = this.adsManager.getCuePoints(); - // Add advertisement cue's within the time line if available. + // Add advertisement cue's within the time line if available this.adsCuePoints.forEach(cuePoint => { if (cuePoint !== 0 && cuePoint !== -1) { const seekElement = this.player.elements.progress; @@ -176,58 +178,38 @@ class Ads { } }); - // Add listeners to the required events. - // Advertisement error events. + // Add listeners to the required events + // Advertisement error events this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); - // Advertisement regular events. - this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_BREAK_READY, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_METADATA, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.CLICK, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.DURATION_CHANGE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.FIRST_QUARTILE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.IMPRESSION, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.INTERACTION, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.LINEAR_CHANGED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.MIDPOINT, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.PAUSED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.RESUMED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.THIRD_QUARTILE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.USER_CLOSE, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.VOLUME_CHANGED, event => this.onAdEvent(event)); - this.adsManager.addEventListener(google.ima.AdEvent.Type.VOLUME_MUTED, event => this.onAdEvent(event)); - - // Resolve our adsManager. + // Advertisement regular events + Object.keys(google.ima.AdEvent.Type).forEach(type => { + this.adsManager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event)); + }); + + // Resolve our adsManager this.handleEventListeners('ADS_MANAGER_LOADED'); } /** * 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. + * events (e.g. ALL_ADS_COMPLETED) don't have the ad object associated * @param {Event} event */ onAdEvent(event) { const { container } = this.player.elements; - // Listen for events if debugging. + // Listen for events if debugging this.player.debug.log(`Ads event: ${event.type}`); // Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED) - // don't have ad object associated. + // don't have ad object associated const ad = event.getAd(); switch (event.type) { 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. + // in case the video is re-played this.handleEventListeners('ALL_ADS_COMPLETED'); // Todo: Example for what happens when a next video in a playlist would be loaded. @@ -251,32 +233,35 @@ class Ads { // }; // 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. + // playing when the IMA SDK is ready or has failed this.loadAds(); 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. + // be paused. This usually happens right before an ad is about to cover the content this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); 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. + // or collapses this.handleEventListeners('CONTENT_RESUME_REQUESTED'); this.resumeContent(); break; + 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. + // ad is a video ad or an overlay this.handleEventListeners('LOADED'); if (!ad.isLinear()) { - // Position AdDisplayContainer correctly for overlay. + // Position AdDisplayContainer correctly for overlay ad.width = container.offsetWidth; ad.height = container.offsetHeight; } @@ -291,24 +276,24 @@ class Ads { } /** - * Any ad error handling comes through here. - * @param {Event} adErrorEvent + * Any ad error handling comes through here + * @param {Event} event */ - onAdError(adErrorEvent) { + onAdError(event) { this.cancel(); - this.player.debug.log('Ads error', adErrorEvent); + this.player.debug.log('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. + * resize the advertisement when the player resizes */ - setupListeners() { + listeners() { const { container } = this.player.elements; let time; - // Add listeners to the required events. + // Add listeners to the required events this.player.on('ended', () => { this.adsLoader.contentComplete(); }); @@ -329,14 +314,14 @@ class Ads { }); }); - // Listen to the resizing of the window. And resize ad accordingly. + // Listen to the resizing of the window. And resize ad accordingly window.addEventListener('resize', () => { this.adsManager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); }); } /** - * Initialize the adsManager and start playing advertisements. + * Initialize the adsManager and start playing advertisements */ play() { const { container } = this.player.elements; @@ -345,25 +330,25 @@ class Ads { return; } - // Play the requested advertisement whenever the adsManager is ready. + // Play the requested advertisement whenever the adsManager is ready this.adsManagerPromise.then(() => { - // Initialize the container. Must be done via a user action on mobile devices. + // Initialize the container. Must be done via a user action on mobile devices this.adDisplayContainer.initialize(); try { if (!this.initialized) { - // Initialize the ads manager. Ad rules playlist will start at this time. + // Initialize the ads manager. Ad rules playlist will start at this time this.adsManager.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. + // start at this time; the call will be ignored for ad rules this.adsManager.start(); } this.initialized = true; } catch (adError) { // An error may be thrown if there was a problem with the - // VAST response. + // VAST response this.onAdError(adError); } }); @@ -375,26 +360,26 @@ class Ads { resumeContent() { this.player.debug.log('Resume video'); - // Hide our ad container. - this.adsContainer.style.display = 'none'; + // Hide our ad container + utils.toggleHidden(this.adsContainer, true); - // Ad is stopped. + // Ad is stopped this.playing = false; - // Play our video. + // Play our video if (this.player.currentTime < this.player.duration) { this.player.play(); } } /** - * Pause our video. + * Pause our video */ pauseContent() { this.player.debug.log('Pause video'); // Show our ad container. - this.adsContainer.style.display = 'block'; + utils.toggleHidden(this.adsContainer, false); // Ad is playing. this.playing = true; @@ -412,43 +397,43 @@ class Ads { cancel() { this.player.debug.warn('Ad cancelled'); - // Pause our video. + // Pause our video this.resumeContent(); - // Tell our instance that we're done for now. + // Tell our instance that we're done for now this.handleEventListeners('ERROR'); - // Re-create our adsManager. + // Re-create our adsManager this.loadAds(); } /** - * Re-create our adsManager. + * Re-create our adsManager */ loadAds() { - // Tell our adsManager to go bye bye. + // Tell our adsManager to go bye bye this.adsManagerPromise.then(() => { - // Destroy our adsManager. + // Destroy our adsManager if (this.adsManager) { this.adsManager.destroy(); } - // Re-set our adsManager promises. + // Re-set our adsManager promises this.adsManagerPromise = new Promise(resolve => { this.on('ADS_MANAGER_LOADED', () => resolve()); this.player.debug.log(this.adsManager); }); - // Make sure we can re-call advertisements. + // Make sure we can re-call advertisements this.initialized = false; - // Now request some new advertisements. + // Now request some new advertisements this.requestAds(); }); } /** - * Handles callbacks after an ad event was invoked. + * Handles callbacks after an ad event was invoked * @param {string} event - Event type */ handleEventListeners(event) { @@ -472,7 +457,7 @@ class Ads { * 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. + * timer on ad ready * @param {Number} time * @param {String} from */ @@ -485,7 +470,7 @@ class Ads { } /** - * Clear our safety timer(s). + * Clear our safety timer(s) * @param {String} from */ clearSafetyTimer(from) { -- cgit v1.2.3 From 5671235fd93eac4554d2c522461df813b589a7f4 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 22 Jan 2018 23:15:10 +1100 Subject: Formatting, events and ad countdown added --- src/js/plugins/ads.js | 220 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 140 insertions(+), 80 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 2f3b0669..8c9baf71 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -38,13 +38,16 @@ class Ads { * Get the ads instance ready. */ ready() { - this.adsContainer = null; - this.adDisplayContainer = null; - this.adsManager = null; - this.adsLoader = null; - this.adsCuePoints = null; + this.elements = { + container: null, + displayContainer: null, + }; + this.manager = null; + this.loader = null; + this.cuePoints = null; this.events = {}; this.safetyTimer = null; + this.countdownTimer = null; // Set listeners on the Plyr instance this.listeners(); @@ -54,21 +57,17 @@ class Ads { this.startSafetyTimer(12000, 'ready()'); // Setup a simple promise to resolve if the IMA loader is ready - this.adsLoaderPromise = new Promise(resolve => { + this.loaderPromise = new Promise(resolve => { this.on('ADS_LOADER_LOADED', () => resolve()); }); - this.adsLoaderPromise.then(() => { - this.player.debug.log('Ads loader resolved!', this.adsLoader); - }); // Setup a promise to resolve if the IMA manager is ready - this.adsManagerPromise = new Promise(resolve => { + this.managerPromise = new Promise(resolve => { this.on('ADS_MANAGER_LOADED', () => resolve()); }); - this.adsManagerPromise.then(() => { - this.player.debug.log('Ads manager resolved!', this.adsManager); - // Clear the safety timer + // Clear the safety timer + this.managerPromise.then(() => { this.clearSafetyTimer('onAdsManagerLoaded()'); }); @@ -86,23 +85,21 @@ class Ads { */ setupIMA() { // Create the container for our advertisements - this.adsContainer = utils.createElement('div', { + this.elements.container = utils.createElement('div', { class: this.player.config.classNames.ads, - 'data-badge-text': this.player.config.i18n.advertisement, hidden: '', }); - this.player.elements.container.appendChild(this.adsContainer); + this.player.elements.container.appendChild(this.elements.container); // So we can run VPAID2 google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); // Set language - // Todo: Could make a config option out of this locale value. - google.ima.settings.setLocale('en'); + google.ima.settings.setLocale(this.player.config.ads.language); // We assume the adContainer is the video container of the plyr element // that will house the ads - this.adDisplayContainer = new google.ima.AdDisplayContainer(this.adsContainer); + this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container); // Request video ads to be pre-loaded this.requestAds(); @@ -116,27 +113,27 @@ class Ads { try { // Create ads loader - this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer); + this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events - this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false); - this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false); + 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 - const adsRequest = new google.ima.AdsRequest(); - adsRequest.adTagUrl = this.player.config.ads.tagUrl; + const request = new google.ima.AdsRequest(); + request.adTagUrl = this.player.config.ads.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK // to select the correct creative if multiple are returned - adsRequest.linearAdSlotWidth = container.offsetWidth; - adsRequest.linearAdSlotHeight = container.offsetHeight; - adsRequest.nonLinearAdSlotWidth = container.offsetWidth; - adsRequest.nonLinearAdSlotHeight = container.offsetHeight; + 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. - adsRequest.forceNonLinearFullSlot = false; + request.forceNonLinearFullSlot = false; - this.adsLoader.requestAds(adsRequest); + this.loader.requestAds(request); this.handleEventListeners('ADS_LOADER_LOADED'); } catch (e) { @@ -144,6 +141,25 @@ class Ads { } } + /** + * Update the ad countdown + * @param {boolean} start + */ + pollCountdown(start = false) { + if (!start) { + window.clearInterval(this.countdownTimer); + this.elements.container.removeAttribute('data-badge-text'); + } + + const update = () => { + const time = utils.formatTime(this.manager.getRemainingTime()); + const text = this.player.config.i18n.adCountdown.replace('{countdown}', time); + this.elements.container.setAttribute('data-badge-text', text); + }; + + this.countdownTimer = window.setInterval(update, 500); + } + /** * This method is called whenever the ads are ready inside the AdDisplayContainer * @param {Event} adsManagerLoadedEvent @@ -158,33 +174,42 @@ class Ads { // 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.adsManager = adsManagerLoadedEvent.getAdsManager(this.player, settings); + this.manager = adsManagerLoadedEvent.getAdsManager(this.player, settings); // Get the cue points for any mid-rolls by filtering out the pre- and post-roll - this.adsCuePoints = this.adsManager.getCuePoints(); + this.cuePoints = this.manager.getCuePoints(); // Add advertisement cue's within the time line if available - this.adsCuePoints.forEach(cuePoint => { + this.cuePoints.forEach(cuePoint => { if (cuePoint !== 0 && cuePoint !== -1) { const seekElement = this.player.elements.progress; + if (seekElement) { const cuePercentage = 100 / this.player.duration * cuePoint; const cue = utils.createElement('span', { class: this.player.config.classNames.cues, }); + cue.style.left = `${cuePercentage.toString()}%`; seekElement.appendChild(cue); } } }); + // Get skippable state + // TODO: Skip button + // this.manager.getAdSkippableState(); + + // Set volume to match player + this.manager.setVolume(this.player.volume); + // Add listeners to the required events // Advertisement error events - this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); + 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.adsManager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event)); + this.manager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event)); }); // Resolve our adsManager @@ -194,25 +219,52 @@ class Ads { /** * 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; - // Listen for events if debugging - this.player.debug.log(`Ads event: ${event.type}`); - // Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED) // don't have ad object associated const ad = event.getAd(); + // Proxy event + const dispatchEvent = type => { + utils.dispatchEvent.call(this.player, this.player.media, `ads${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.handleEventListeners('LOADED'); + + // Bubble event + dispatchEvent('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.ALL_ADS_COMPLETED: // All ads for the current videos are done. We can now request new advertisements // in case the video is re-played this.handleEventListeners('ALL_ADS_COMPLETED'); - // Todo: Example for what happens when a next video in a playlist would be loaded. + // Fire event + dispatchEvent('allcomplete'); + + // 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. @@ -232,7 +284,7 @@ class Ads { // '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 + // 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 this.loadAds(); @@ -243,7 +295,9 @@ class Ads { // 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.handleEventListeners('CONTENT_PAUSE_REQUESTED'); + dispatchEvent('contentpause'); this.pauseContent(); + break; case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: @@ -252,22 +306,31 @@ class Ads { // Fired when content should be resumed. This usually happens when an ad finishes // or collapses this.handleEventListeners('CONTENT_RESUME_REQUESTED'); + dispatchEvent('contentresume'); this.resumeContent(); break; - 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.handleEventListeners('LOADED'); + case google.ima.AdEvent.Type.STARTED: + dispatchEvent('started'); + break; - if (!ad.isLinear()) { - // Position AdDisplayContainer correctly for overlay - ad.width = container.offsetWidth; - ad.height = container.offsetHeight; - } + case google.ima.AdEvent.Type.MIDPOINT: + dispatchEvent('midpoint'); + break; - // console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex()); - // console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset()); + case google.ima.AdEvent.Type.COMPLETE: + dispatchEvent('complete'); + + // End countdown + this.pollCountdown(); + break; + + case google.ima.AdEvent.Type.IMPRESSION: + dispatchEvent('impression'); + break; + + case google.ima.AdEvent.Type.CLICK: + dispatchEvent('click'); break; default: @@ -295,7 +358,7 @@ class Ads { // Add listeners to the required events this.player.on('ended', () => { - this.adsLoader.contentComplete(); + this.loader.contentComplete(); }); this.player.on('seeking', () => { @@ -306,17 +369,18 @@ class Ads { this.player.on('seeked', () => { const seekedTime = this.player.currentTime; - this.adsCuePoints.forEach((cuePoint, index) => { + this.cuePoints.forEach((cuePoint, index) => { if (time < cuePoint && cuePoint < seekedTime) { - this.adsManager.discardAdBreak(); - this.adsCuePoints.splice(index, 1); + this.manager.discardAdBreak(); + this.cuePoints.splice(index, 1); } }); }); // Listen to the resizing of the window. And resize ad accordingly + // TODO: eventually implement ResizeObserver window.addEventListener('resize', () => { - this.adsManager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); + this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); }); } @@ -326,23 +390,23 @@ class Ads { play() { const { container } = this.player.elements; - if (!this.adsManagerPromise) { + if (!this.managerPromise) { return; } // Play the requested advertisement whenever the adsManager is ready - this.adsManagerPromise.then(() => { + this.managerPromise.then(() => { // Initialize the container. Must be done via a user action on mobile devices - this.adDisplayContainer.initialize(); + this.elements.displayContainer.initialize(); try { if (!this.initialized) { // Initialize the ads manager. Ad rules playlist will start at this time - this.adsManager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); + 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.adsManager.start(); + this.manager.start(); } this.initialized = true; @@ -358,10 +422,8 @@ class Ads { * Resume our video. */ resumeContent() { - this.player.debug.log('Resume video'); - // Hide our ad container - utils.toggleHidden(this.adsContainer, true); + utils.toggleHidden(this.elements.container, true); // Ad is stopped this.playing = false; @@ -376,10 +438,8 @@ class Ads { * Pause our video */ pauseContent() { - this.player.debug.log('Pause video'); - // Show our ad container. - utils.toggleHidden(this.adsContainer, false); + utils.toggleHidden(this.elements.container, false); // Ad is playing. this.playing = true; @@ -395,8 +455,6 @@ class Ads { * media-ads/docs/sdks/android/faq#8 */ cancel() { - this.player.debug.warn('Ad cancelled'); - // Pause our video this.resumeContent(); @@ -412,16 +470,16 @@ class Ads { */ loadAds() { // Tell our adsManager to go bye bye - this.adsManagerPromise.then(() => { + this.managerPromise.then(() => { // Destroy our adsManager - if (this.adsManager) { - this.adsManager.destroy(); + if (this.manager) { + this.manager.destroy(); } // Re-set our adsManager promises - this.adsManagerPromise = new Promise(resolve => { + this.managerPromise = new Promise(resolve => { this.on('ADS_MANAGER_LOADED', () => resolve()); - this.player.debug.log(this.adsManager); + this.player.debug.log(this.manager); }); // Make sure we can re-call advertisements @@ -437,7 +495,7 @@ class Ads { * @param {string} event - Event type */ handleEventListeners(event) { - if (typeof this.events[event] !== 'undefined') { + if (utils.is.function(this.events[event])) { this.events[event].call(this); } } @@ -458,11 +516,12 @@ class Ads { * 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 + * @param {number} time + * @param {string} from */ startSafetyTimer(time, from) { this.player.debug.log(`Safety timer invoked from: ${from}`); + this.safetyTimer = window.setTimeout(() => { this.cancel(); this.clearSafetyTimer('startSafetyTimer()'); @@ -471,13 +530,14 @@ class Ads { /** * Clear our safety timer(s) - * @param {String} from + * @param {string} from */ clearSafetyTimer(from) { - if (typeof this.safetyTimer !== 'undefined' && this.safetyTimer !== null) { + if (!utils.is.nullOrUndefined(this.safetyTimer)) { this.player.debug.log(`Safety timer cleared from: ${from}`); + clearTimeout(this.safetyTimer); - this.safetyTimer = undefined; + this.safetyTimer = null; } } } -- cgit v1.2.3 From ebf53d14b12c025b6c2f37a887a55e2bf7ab3b7d Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 22 Jan 2018 23:39:09 +1100 Subject: Small tweaks --- src/js/plugins/ads.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 8c9baf71..dd942b49 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -18,10 +18,11 @@ class Ads { this.player = player; this.playing = false; this.initialized = false; + this.enabled = utils.is.url(player.config.ads.tagUrl); // Check if a tag URL is provided. - if (!utils.is.url(player.config.ads.tagUrl)) { - return this; + if (!this.enabled) { + return; } // Check if the Google IMA3 SDK is loaded @@ -456,7 +457,9 @@ class Ads { */ cancel() { // Pause our video - this.resumeContent(); + if (this.initialized) { + this.resumeContent(); + } // Tell our instance that we're done for now this.handleEventListeners('ERROR'); -- cgit v1.2.3 From 3c8acb4e9e76d1b0ab59c86c18d3ef16efbbfe6f Mon Sep 17 00:00:00 2001 From: ferdiemmen Date: Mon, 22 Jan 2018 21:07:08 +0100 Subject: Fix bug where ad would play on every play action --- src/js/plugins/ads.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index dd942b49..e7940d18 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -485,9 +485,6 @@ class Ads { this.player.debug.log(this.manager); }); - // Make sure we can re-call advertisements - this.initialized = false; - // Now request some new advertisements this.requestAds(); }); -- cgit v1.2.3 From cc128e60888468a90dd73c90f05fc7ddbf104551 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Tue, 23 Jan 2018 10:31:21 +1100 Subject: Renamed property, UI tweak --- src/js/plugins/ads.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index e7940d18..5c828141 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -18,7 +18,7 @@ class Ads { this.player = player; this.playing = false; this.initialized = false; - this.enabled = utils.is.url(player.config.ads.tagUrl); + this.enabled = utils.is.url(player.config.ads.tag); // Check if a tag URL is provided. if (!this.enabled) { @@ -122,7 +122,7 @@ class Ads { // Request video ads const request = new google.ima.AdsRequest(); - request.adTagUrl = this.player.config.ads.tagUrl; + request.adTagUrl = this.player.config.ads.tag; // Specify the linear and nonlinear slot sizes. This helps the SDK // to select the correct creative if multiple are returned @@ -150,15 +150,15 @@ class Ads { if (!start) { window.clearInterval(this.countdownTimer); this.elements.container.removeAttribute('data-badge-text'); + return; } const update = () => { const time = utils.formatTime(this.manager.getRemainingTime()); - const text = this.player.config.i18n.adCountdown.replace('{countdown}', time); - this.elements.container.setAttribute('data-badge-text', text); + this.elements.container.setAttribute('data-badge-text', time); }; - this.countdownTimer = window.setInterval(update, 500); + this.countdownTimer = window.setInterval(update, 100); } /** @@ -296,7 +296,9 @@ class Ads { // 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.handleEventListeners('CONTENT_PAUSE_REQUESTED'); + dispatchEvent('contentpause'); + this.pauseContent(); break; @@ -307,8 +309,13 @@ class Ads { // Fired when content should be resumed. This usually happens when an ad finishes // or collapses this.handleEventListeners('CONTENT_RESUME_REQUESTED'); + dispatchEvent('contentresume'); + + this.pollCountdown(); + this.resumeContent(); + break; case google.ima.AdEvent.Type.STARTED: @@ -321,9 +328,6 @@ class Ads { case google.ima.AdEvent.Type.COMPLETE: dispatchEvent('complete'); - - // End countdown - this.pollCountdown(); break; case google.ima.AdEvent.Type.IMPRESSION: -- cgit v1.2.3 From b4e22e2e7b6d73bb58778125801618f499c59bcc Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Wed, 24 Jan 2018 09:06:20 +1100 Subject: =?UTF-8?q?Restored=20=E2=80=9CAd=E2=80=9D=20in=20label?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/plugins/ads.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 5c828141..1028d73e 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -155,7 +155,8 @@ class Ads { const update = () => { const time = utils.formatTime(this.manager.getRemainingTime()); - this.elements.container.setAttribute('data-badge-text', time); + const label = `${this.player.config.i18n.advertisment} - ${time}`; + this.elements.container.setAttribute('data-badge-text', label); }; this.countdownTimer = window.setInterval(update, 100); -- cgit v1.2.3 From 5e68f8c8ddd11070a2b2ee11339be8c2c37ed8a5 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Thu, 25 Jan 2018 22:41:30 +1100 Subject: Attempt to fix YouTube message error, added ads references, changes to bool --- src/js/plugins/ads.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 1028d73e..a71336bb 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -8,6 +8,22 @@ import utils from '../utils'; +// Build the default tag URL +const getTagUrl = () => { + const params = { + AV_PUBLISHERID: '58c25bb0073ef448b1087ad6', + AV_CHANNELID: '5a0458dc28a06145e4519d21', + AV_URL: '127.0.0.1:3000', + cb: 1, + AV_WIDTH: 640, + AV_HEIGHT: 480, + }; + + const base = 'https://go.aniview.com/api/adserver6/vast/'; + + return `${base}?${utils.buildUrlParams(params)}`; +}; + class Ads { /** * Ads constructor. @@ -16,9 +32,9 @@ class Ads { */ constructor(player) { this.player = player; + this.enabled = player.config.ads.enabled; this.playing = false; this.initialized = false; - this.enabled = utils.is.url(player.config.ads.tag); // Check if a tag URL is provided. if (!this.enabled) { @@ -122,7 +138,7 @@ class Ads { // Request video ads const request = new google.ima.AdsRequest(); - request.adTagUrl = this.player.config.ads.tag; + request.adTagUrl = getTagUrl(); // Specify the linear and nonlinear slot sizes. This helps the SDK // to select the correct creative if multiple are returned -- cgit v1.2.3 From c4eb4c97ac1f186e5054da298e4960cb498c6d01 Mon Sep 17 00:00:00 2001 From: ferdiemmen Date: Mon, 29 Jan 2018 22:40:08 +0100 Subject: fix(ads): Fixes media from playing when ads are blocked --- src/js/plugins/ads.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 5c828141..c59c4667 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -18,6 +18,7 @@ class Ads { this.player = player; this.playing = false; this.initialized = false; + this.blocked = false; this.enabled = utils.is.url(player.config.ads.tag); // Check if a tag URL is provided. @@ -25,10 +26,15 @@ class Ads { return; } - // Check if the Google IMA3 SDK is loaded + // Check if the Google IMA3 SDK is loaded or load ourselves. if (!utils.is.object(window.google)) { utils.loadScript(player.config.urls.googleIMA.api, () => { this.ready(); + }, () => { + + // Script failed to load or is blocked. + this.blocked = true; + this.player.debug.log('Ads error: Google IMA SDK failed to load'); }); } else { this.ready(); -- cgit v1.2.3 From afd695cb391535b508dc7a4301b6c2692ce2b607 Mon Sep 17 00:00:00 2001 From: ferdiemmen Date: Mon, 29 Jan 2018 22:45:46 +0100 Subject: Fix typo's --- src/js/plugins/ads.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index c59c4667..0059b963 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -26,13 +26,13 @@ class Ads { return; } - // Check if the Google IMA3 SDK is loaded or load ourselves. + // Check if the Google IMA3 SDK is loaded or load it ourselves if (!utils.is.object(window.google)) { utils.loadScript(player.config.urls.googleIMA.api, () => { this.ready(); }, () => { - // Script failed to load or is blocked. + // Script failed to load or is blocked this.blocked = true; this.player.debug.log('Ads error: Google IMA SDK failed to load'); }); -- cgit v1.2.3 From 317b08c703c94c5ce0e1d53603f4e1c9842df249 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sat, 3 Mar 2018 23:06:12 +1100 Subject: Ready event fix, YouTube play event fix, docs update --- src/js/plugins/ads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 72fd49d8..eee03752 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -553,7 +553,7 @@ class Ads { startSafetyTimer(time, from) { this.player.debug.log(`Safety timer invoked from: ${from}`); - this.safetyTimer = window.setTimeout(() => { + this.safetyTimer = setTimeout(() => { this.cancel(); this.clearSafetyTimer('startSafetyTimer()'); }, time); -- cgit v1.2.3 From 0300610108345a371ef9891439fd82ee2e1c8b5b Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sat, 3 Mar 2018 23:14:57 +1100 Subject: Typo --- src/js/plugins/ads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index eee03752..0faf0f2f 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -181,7 +181,7 @@ class Ads { const update = () => { const time = utils.formatTime(this.manager.getRemainingTime()); - const label = `${this.player.config.i18n.advertisment} - ${time}`; + const label = `${this.player.config.i18n.advertisement} - ${time}`; this.elements.container.setAttribute('data-badge-text', label); }; -- cgit v1.2.3 From e90a603d57836f6b8962a661b4668fd81c9b72a4 Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Tue, 6 Mar 2018 17:27:59 +0100 Subject: Removed a double this.enabled variable and updated a comment in ads.js. Also made sure the adsmanager promise also can fail, so we can use it to wait for getting the advertisement ready when someone clicks the play button. Otherwise there it can look glitchy when the actual video starts playing and the video ad plays a few seconds later because the vast tag was slow to retrieve. Also fixed a typo. --- src/js/plugins/ads.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 0faf0f2f..9318e01d 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -36,9 +36,8 @@ class Ads { this.playing = false; this.initialized = false; this.blocked = false; - this.enabled = utils.is.url(player.config.ads.tag); - // Check if a tag URL is provided. + // Check if ads are enabled. if (!this.enabled) { return; } @@ -83,14 +82,12 @@ class Ads { // thing doesn't resolve within our set time; we bail this.startSafetyTimer(12000, 'ready()'); - // Setup a simple promise to resolve if the IMA loader is ready - this.loaderPromise = new Promise(resolve => { - this.on('ADS_LOADER_LOADED', () => resolve()); - }); - // Setup a promise to resolve if the IMA manager is ready - this.managerPromise = new Promise(resolve => { + this.managerPromise = new Promise((resolve, reject) => { + // The ad is pre-loaded and ready this.on('ADS_MANAGER_LOADED', () => resolve()); + // Ads failed + this.on('ERROR', () => reject()); }); // Clear the safety timer -- cgit v1.2.3 From 409b588458fd598a88d56f3922da83ff82d80f5f Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 7 Mar 2018 15:17:30 +0100 Subject: Made sure that cue points for midrolls are not displayed when the ad rule for a midroll doesn't exceed the total play time of a video. --- src/js/plugins/ads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 9318e01d..0497c42c 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -206,7 +206,7 @@ class Ads { // Add advertisement cue's within the time line if available this.cuePoints.forEach(cuePoint => { - if (cuePoint !== 0 && cuePoint !== -1) { + if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) { const seekElement = this.player.elements.progress; if (seekElement) { -- cgit v1.2.3 From 819f7d1080ef2748fa1c123ad0f54d075c654ab9 Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Wed, 7 Mar 2018 15:43:48 +0100 Subject: Resizing the ad container while having it on display none will return offset width and height of 0, which will cause ads not to play when ad sizes are set within the clients DSP. Also making sure that the inner containers of the ad container are full size. The container is now hidden/ displayed using z-index. --- src/js/plugins/ads.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 0497c42c..eca523f7 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -111,7 +111,6 @@ class Ads { // Create the container for our advertisements this.elements.container = utils.createElement('div', { class: this.player.config.classNames.ads, - hidden: '', }); this.player.elements.container.appendChild(this.elements.container); @@ -451,8 +450,8 @@ class Ads { * Resume our video. */ resumeContent() { - // Hide our ad container - utils.toggleHidden(this.elements.container, true); + // Hide the advertisement container + this.elements.container.style.zIndex = ''; // Ad is stopped this.playing = false; @@ -467,8 +466,8 @@ class Ads { * Pause our video */ pauseContent() { - // Show our ad container. - utils.toggleHidden(this.elements.container, false); + // Show the advertisement container + this.elements.container.style.zIndex = '3'; // Ad is playing. this.playing = true; -- cgit v1.2.3 From 69ffcbad27353bfd0238ba7e79e6caf3835b0efd Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Fri, 9 Mar 2018 11:17:24 +0100 Subject: Ad block detection would not work when calling play() right after creating the player instance, so the adsManager now also rejects on such a case. Also made sure that calling play() will wait for the adsManager promise to resolve or otherwise return the media.play() method. --- src/js/plugins/ads.js | 67 ++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 36 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index eca523f7..cc11fa13 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -35,35 +35,6 @@ class Ads { this.enabled = player.config.ads.enabled; this.playing = false; this.initialized = false; - this.blocked = false; - - // Check if ads are enabled. - if (!this.enabled) { - return; - } - - // Check if the Google IMA3 SDK is loaded or load it ourselves - if (!utils.is.object(window.google)) { - utils.loadScript( - player.config.urls.googleIMA.api, - () => { - this.ready(); - }, - () => { - // Script failed to load or is blocked - this.blocked = true; - this.player.debug.log('Ads error: Google IMA SDK failed to load'); - }, - ); - } else { - this.ready(); - } - } - - /** - * Get the ads instance ready. - */ - ready() { this.elements = { container: null, displayContainer: null, @@ -75,26 +46,50 @@ class Ads { this.safetyTimer = null; this.countdownTimer = null; - // Set listeners on the Plyr instance - this.listeners(); - - // Start ticking our safety timer. If the whole advertisement - // thing doesn't resolve within our set time; we bail - this.startSafetyTimer(12000, 'ready()'); + if (this.enabled) { + // Check if the Google IMA3 SDK is loaded or load it ourselves + if (!utils.is.object(window.google)) { + utils.loadScript( + player.config.urls.googleIMA.api, + () => { + this.ready(); + }, + () => { + // Script failed to load or is blocked + this.handleEventListeners('ERROR'); + this.player.debug.log('Ads error: Google IMA SDK failed to load'); + }, + ); + } else { + this.ready(); + } + } - // Setup a promise to resolve if the IMA manager is ready + // Setup a promise to resolve when the IMA manager is ready this.managerPromise = new Promise((resolve, reject) => { // The ad is pre-loaded and ready this.on('ADS_MANAGER_LOADED', () => resolve()); // Ads failed this.on('ERROR', () => reject()); }); + } + + /** + * Get the ads instance ready. + */ + ready() { + // 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()'); }); + // Set listeners on the Plyr instance + this.listeners(); + // Setup the IMA SDK this.setupIMA(); } -- cgit v1.2.3 From 6a2ca534d219233b20941bfc987f7a4a488502c7 Mon Sep 17 00:00:00 2001 From: Arthur Hulsman Date: Fri, 9 Mar 2018 14:29:37 +0100 Subject: Removed redundant wrappers within the adsmanager promises. --- src/js/plugins/ads.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index cc11fa13..62162e84 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -68,9 +68,9 @@ class Ads { // Setup a promise to resolve when the IMA manager is ready this.managerPromise = new Promise((resolve, reject) => { // The ad is pre-loaded and ready - this.on('ADS_MANAGER_LOADED', () => resolve()); + this.on('ADS_MANAGER_LOADED', resolve); // Ads failed - this.on('ERROR', () => reject()); + this.on('ERROR', reject); }); } @@ -503,7 +503,7 @@ class Ads { // Re-set our adsManager promises this.managerPromise = new Promise(resolve => { - this.on('ADS_MANAGER_LOADED', () => resolve()); + this.on('ADS_MANAGER_LOADED', resolve); this.player.debug.log(this.manager); }); -- cgit v1.2.3 From e206edc1f6a09e6c2584e83a60d2632202545733 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 11 Mar 2018 02:03:35 +1100 Subject: Event listener fixes, loadScript promise, ads tweaks --- src/js/plugins/ads.js | 109 ++++++++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 48 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 62162e84..5cf743c2 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -8,22 +8,6 @@ import utils from '../utils'; -// Build the default tag URL -const getTagUrl = () => { - const params = { - AV_PUBLISHERID: '58c25bb0073ef448b1087ad6', - AV_CHANNELID: '5a0458dc28a06145e4519d21', - AV_URL: '127.0.0.1:3000', - cb: 1, - AV_WIDTH: 640, - AV_HEIGHT: 480, - }; - - const base = 'https://go.aniview.com/api/adserver6/vast/'; - - return `${base}?${utils.buildUrlParams(params)}`; -}; - class Ads { /** * Ads constructor. @@ -32,7 +16,8 @@ class Ads { */ constructor(player) { this.player = player; - this.enabled = player.config.ads.enabled; + this.publisherId = player.config.ads.publisherId; + this.enabled = player.isHTML5 && player.isVideo && player.config.ads.enabled && utils.is.string(this.publisherId) && this.publisherId.length; this.playing = false; this.initialized = false; this.elements = { @@ -46,32 +31,32 @@ class Ads { 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 pre-loaded and ready + this.on('ADS_MANAGER_LOADED', resolve); + + // Ads failed + this.on('ERROR', reject); + }); + if (this.enabled) { // Check if the Google IMA3 SDK is loaded or load it ourselves if (!utils.is.object(window.google)) { - utils.loadScript( - player.config.urls.googleIMA.api, - () => { + utils + .loadScript(player.config.urls.googleIMA.api) + .then(() => { this.ready(); - }, - () => { + }) + .catch(() => { // Script failed to load or is blocked - this.handleEventListeners('ERROR'); - this.player.debug.log('Ads error: Google IMA SDK failed to load'); - }, - ); + this.trigger('ERROR'); + this.player.debug.error('Google IMA SDK failed to load'); + }); } else { this.ready(); } } - - // Setup a promise to resolve when the IMA manager is ready - this.managerPromise = new Promise((resolve, reject) => { - // The ad is pre-loaded and ready - this.on('ADS_MANAGER_LOADED', resolve); - // Ads failed - this.on('ERROR', reject); - }); } /** @@ -94,6 +79,23 @@ class Ads { this.setupIMA(); } + // Build the default tag URL + get tagUrl() { + const params = { + AV_PUBLISHERID: '58c25bb0073ef448b1087ad6', + AV_CHANNELID: '5a0458dc28a06145e4519d21', + AV_URL: location.hostname, + cb: Date.now(), + AV_WIDTH: 640, + AV_HEIGHT: 480, + AV_CDIM2: this.publisherId, + }; + + const base = 'https://go.aniview.com/api/adserver6/vast/'; + + return `${base}?${utils.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. @@ -139,7 +141,7 @@ class Ads { // Request video ads const request = new google.ima.AdsRequest(); - request.adTagUrl = getTagUrl(); + request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK // to select the correct creative if multiple are returned @@ -153,7 +155,7 @@ class Ads { this.loader.requestAds(request); - this.handleEventListeners('ADS_LOADER_LOADED'); + this.trigger('ADS_LOADER_LOADED'); } catch (e) { this.onAdError(e); } @@ -171,7 +173,7 @@ class Ads { } const update = () => { - const time = utils.formatTime(this.manager.getRemainingTime()); + const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0)); const label = `${this.player.config.i18n.advertisement} - ${time}`; this.elements.container.setAttribute('data-badge-text', label); }; @@ -232,7 +234,7 @@ class Ads { }); // Resolve our adsManager - this.handleEventListeners('ADS_MANAGER_LOADED'); + this.trigger('ADS_MANAGER_LOADED'); } /** @@ -257,7 +259,7 @@ class Ads { 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.handleEventListeners('LOADED'); + this.trigger('LOADED'); // Bubble event dispatchEvent('loaded'); @@ -278,7 +280,7 @@ class Ads { 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 - this.handleEventListeners('ALL_ADS_COMPLETED'); + this.trigger('ALL_ADS_COMPLETED'); // Fire event dispatchEvent('allcomplete'); @@ -313,7 +315,7 @@ class Ads { // 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.handleEventListeners('CONTENT_PAUSE_REQUESTED'); + this.trigger('CONTENT_PAUSE_REQUESTED'); dispatchEvent('contentpause'); @@ -326,7 +328,7 @@ class Ads { // 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.handleEventListeners('CONTENT_RESUME_REQUESTED'); + this.trigger('CONTENT_RESUME_REQUESTED'); dispatchEvent('contentresume'); @@ -462,7 +464,7 @@ class Ads { */ pauseContent() { // Show the advertisement container - this.elements.container.style.zIndex = '3'; + this.elements.container.style.zIndex = 3; // Ad is playing. this.playing = true; @@ -484,7 +486,7 @@ class Ads { } // Tell our instance that we're done for now - this.handleEventListeners('ERROR'); + this.trigger('ERROR'); // Re-create our adsManager this.loadAds(); @@ -516,9 +518,15 @@ class Ads { * Handles callbacks after an ad event was invoked * @param {string} event - Event type */ - handleEventListeners(event) { - if (utils.is.function(this.events[event])) { - this.events[event].call(this); + trigger(event) { + const handlers = this.events[event]; + + if (utils.is.array(handlers)) { + handlers.forEach(handler => { + if (utils.is.function(handler)) { + handler.call(this); + } + }); } } @@ -529,7 +537,12 @@ class Ads { * @return {Ads} */ on(event, callback) { - this.events[event] = callback; + if (!utils.is.array(this.events[event])) { + this.events[event] = []; + } + + this.events[event].push(callback); + return this; } -- cgit v1.2.3 From ef27ba16f4d1e8520c90cecf9e945b1edebb7ec0 Mon Sep 17 00:00:00 2001 From: Albin Larsson Date: Sat, 10 Mar 2018 16:20:33 +0100 Subject: Add optional argument to Ads.trigger (currently only used for adblocker error) --- src/js/plugins/ads.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 5cf743c2..82599594 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -50,8 +50,9 @@ class Ads { }) .catch(() => { // Script failed to load or is blocked - this.trigger('ERROR'); - this.player.debug.error('Google IMA SDK failed to load'); + const message = 'Google IMA SDK failed to load'; + this.trigger('ERROR', new Error(message)); + this.player.debug.error(message); }); } else { this.ready(); @@ -518,13 +519,13 @@ class Ads { * Handles callbacks after an ad event was invoked * @param {string} event - Event type */ - trigger(event) { + trigger(event, ...args) { const handlers = this.events[event]; if (utils.is.array(handlers)) { handlers.forEach(handler => { if (utils.is.function(handler)) { - handler.call(this); + handler.apply(this, args); } }); } -- cgit v1.2.3 From b3365a7373a788453710d0601d664b3856f68307 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 11 Mar 2018 12:54:51 +1100 Subject: Normalised event names and removed unused --- src/js/plugins/ads.js | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 82599594..1cee1718 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -33,26 +33,31 @@ class Ads { // Setup a promise to resolve when the IMA manager is ready this.managerPromise = new Promise((resolve, reject) => { - // The ad is pre-loaded and ready - this.on('ADS_MANAGER_LOADED', resolve); + // The ad is loaded and ready + this.on('loaded', resolve); // Ads failed - this.on('ERROR', reject); + this.on('error', reject); }); + this.load(); + } + + /** + * Load the IMA SDK + */ + load() { if (this.enabled) { // Check if the Google IMA3 SDK is loaded or load it ourselves if (!utils.is.object(window.google)) { utils - .loadScript(player.config.urls.googleIMA.api) + .loadScript(this.player.config.urls.googleIMA.api) .then(() => { this.ready(); }) .catch(() => { // Script failed to load or is blocked - const message = 'Google IMA SDK failed to load'; - this.trigger('ERROR', new Error(message)); - this.player.debug.error(message); + this.trigger('error', new Error('Google IMA SDK failed to load')); }); } else { this.ready(); @@ -61,7 +66,7 @@ class Ads { } /** - * Get the ads instance ready. + * Get the ads instance ready */ ready() { // Start ticking our safety timer. If the whole advertisement @@ -156,7 +161,7 @@ class Ads { this.loader.requestAds(request); - this.trigger('ADS_LOADER_LOADED'); + this.trigger('loaded'); } catch (e) { this.onAdError(e); } @@ -235,7 +240,7 @@ class Ads { }); // Resolve our adsManager - this.trigger('ADS_MANAGER_LOADED'); + this.trigger('loaded'); } /** @@ -260,7 +265,7 @@ class Ads { 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'); + this.trigger('loaded'); // Bubble event dispatchEvent('loaded'); @@ -281,7 +286,6 @@ class Ads { 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 - this.trigger('ALL_ADS_COMPLETED'); // Fire event dispatchEvent('allcomplete'); @@ -316,7 +320,6 @@ class Ads { // 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.trigger('CONTENT_PAUSE_REQUESTED'); dispatchEvent('contentpause'); @@ -329,7 +332,6 @@ class Ads { // 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.trigger('CONTENT_RESUME_REQUESTED'); dispatchEvent('contentresume'); @@ -370,7 +372,7 @@ class Ads { */ onAdError(event) { this.cancel(); - this.player.debug.log('Ads error', event); + this.player.debug.warn('Ads error', event); } /** @@ -445,7 +447,7 @@ class Ads { } /** - * Resume our video. + * Resume our video */ resumeContent() { // Hide the advertisement container @@ -487,7 +489,7 @@ class Ads { } // Tell our instance that we're done for now - this.trigger('ERROR'); + this.trigger('error'); // Re-create our adsManager this.loadAds(); @@ -506,7 +508,7 @@ class Ads { // Re-set our adsManager promises this.managerPromise = new Promise(resolve => { - this.on('ADS_MANAGER_LOADED', resolve); + this.on('loaded', resolve); this.player.debug.log(this.manager); }); -- cgit v1.2.3 From 9981c349be60157dbaeaa3123a6ce44f312dc27a Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 11 Mar 2018 18:23:47 +1100 Subject: Fix for null manager race condition --- src/js/plugins/ads.js | 80 ++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 39 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index 1cee1718..a73ff934 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -49,7 +49,7 @@ class Ads { load() { if (this.enabled) { // Check if the Google IMA3 SDK is loaded or load it ourselves - if (!utils.is.object(window.google)) { + if (!utils.is.object(window.google) || !utils.is.object(window.google.ima)) { utils .loadScript(this.player.config.urls.googleIMA.api) .then(() => { @@ -160,8 +160,6 @@ class Ads { request.forceNonLinearFullSlot = false; this.loader.requestAds(request); - - this.trigger('loaded'); } catch (e) { this.onAdError(e); } @@ -191,7 +189,7 @@ class Ads { * This method is called whenever the ads are ready inside the AdDisplayContainer * @param {Event} adsManagerLoadedEvent */ - onAdsManagerLoaded(adsManagerLoadedEvent) { + onAdsManagerLoaded(event) { // Get the ads manager const settings = new google.ima.AdsRenderingSettings(); @@ -201,7 +199,7 @@ class Ads { // 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 = adsManagerLoadedEvent.getAdsManager(this.player, settings); + 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(); @@ -419,31 +417,33 @@ class Ads { const { container } = this.player.elements; if (!this.managerPromise) { - return; + this.resumeContent(); } // Play the requested advertisement whenever the adsManager is ready - this.managerPromise.then(() => { - // 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.managerPromise + .then(() => { + // 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); } - - this.initialized = true; - } catch (adError) { - // An error may be thrown if there was a problem with the - // VAST response - this.onAdError(adError); - } - }); + }) + .catch(() => {}); } /** @@ -500,21 +500,23 @@ class Ads { */ loadAds() { // Tell our adsManager to go bye bye - this.managerPromise.then(() => { - // Destroy our adsManager - if (this.manager) { - this.manager.destroy(); - } + 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); - }); + // Re-set our adsManager promises + this.managerPromise = new Promise(resolve => { + this.on('loaded', resolve); + this.player.debug.log(this.manager); + }); - // Now request some new advertisements - this.requestAds(); - }); + // Now request some new advertisements + this.requestAds(); + }) + .catch(() => {}); } /** -- cgit v1.2.3 From 1b13ddaa5479baa8100e953e5d0752e3bd8654ab Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Tue, 13 Mar 2018 21:42:01 +1100 Subject: Update ads --- src/js/plugins/ads.js | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index a73ff934..cbbf3a47 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -256,7 +256,8 @@ class Ads { // Proxy event const dispatchEvent = type => { - utils.dispatchEvent.call(this.player, this.player.media, `ads${type}`); + const event = `ads${type.replace(/_/g, '').toLowerCase()}`; + utils.dispatchEvent.call(this.player, this.player.media, event); }; switch (event.type) { @@ -266,7 +267,7 @@ class Ads { this.trigger('loaded'); // Bubble event - dispatchEvent('loaded'); + dispatchEvent(event.type); // Start countdown this.pollCountdown(true); @@ -286,7 +287,7 @@ class Ads { // in case the video is re-played // Fire event - dispatchEvent('allcomplete'); + dispatchEvent(event.type); // 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. @@ -319,7 +320,7 @@ class Ads { // 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 - dispatchEvent('contentpause'); + dispatchEvent(event.type); this.pauseContent(); @@ -331,7 +332,7 @@ class Ads { // Fired when content should be resumed. This usually happens when an ad finishes // or collapses - dispatchEvent('contentresume'); + dispatchEvent(event.type); this.pollCountdown(); @@ -340,23 +341,11 @@ class Ads { break; case google.ima.AdEvent.Type.STARTED: - dispatchEvent('started'); - break; - case google.ima.AdEvent.Type.MIDPOINT: - dispatchEvent('midpoint'); - break; - case google.ima.AdEvent.Type.COMPLETE: - dispatchEvent('complete'); - break; - case google.ima.AdEvent.Type.IMPRESSION: - dispatchEvent('impression'); - break; - case google.ima.AdEvent.Type.CLICK: - dispatchEvent('click'); + dispatchEvent(event.type); break; default: -- cgit v1.2.3 From c74b75e8e189b187b1323f0a5e0b2ec106fcfd34 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Tue, 13 Mar 2018 23:35:17 +1100 Subject: 3.0.0-beta.20 --- src/js/plugins/ads.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/js/plugins/ads.js') diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js index cbbf3a47..31a797c2 100644 --- a/src/js/plugins/ads.js +++ b/src/js/plugins/ads.js @@ -171,7 +171,7 @@ class Ads { */ pollCountdown(start = false) { if (!start) { - window.clearInterval(this.countdownTimer); + clearInterval(this.countdownTimer); this.elements.container.removeAttribute('data-badge-text'); return; } @@ -182,7 +182,7 @@ class Ads { this.elements.container.setAttribute('data-badge-text', label); }; - this.countdownTimer = window.setInterval(update, 100); + this.countdownTimer = setInterval(update, 100); } /** -- cgit v1.2.3