UNPKG

videojs-ima

Version:

[![Build Status](https://travis-ci.org/googleads/videojs-ima.svg?branch=master)](https://travis-ci.org/googleads/videojs-ima)

793 lines (658 loc) 19.9 kB
/** * Copyright 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * IMA SDK integration plugin for Video.js. For more information see * https://www.github.com/googleads/videojs-ima */ import PlayerWrapper from './player-wrapper.js'; import AdUi from './ad-ui.js'; import SdkImpl from './sdk-impl.js'; /** * The grand coordinator of the plugin. Facilitates communication between all * other plugin classes. * * @param {Object} player Instance of the video.js player. * @param {Object} options Options provided by the implementation. * @constructor * @struct * @final */ const Controller = function(player, options) { /** * Stores user-provided settings. * @type {Object} */ this.settings = {}; /** * Content and ads ended listeners passed by the publisher to the plugin. * These will be called when the plugin detects that content *and all * ads* have completed. This differs from the contentEndedListeners in that * contentEndedListeners will fire between content ending and a post-roll * playing, whereas the contentAndAdsEndedListeners will fire after the * post-roll completes. */ this.contentAndAdsEndedListeners = []; /** * Whether or not we are running on a mobile platform. */ this.isMobile = (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/Android/i)); /** * Whether or not we are running on an iOS platform. */ this.isIos = (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i)); this.initWithSettings(options); /** * Stores contrib-ads default settings. */ const contribAdsDefaults = { debug: this.settings.debug, timeout: this.settings.timeout, prerollTimeout: this.settings.prerollTimeout, }; const adsPluginSettings = this.extend( {}, contribAdsDefaults, options.contribAdsSettings || {}); this.playerWrapper = new PlayerWrapper(player, adsPluginSettings, this); this.adUi = new AdUi(this); this.sdkImpl = new SdkImpl(this); }; Controller.IMA_DEFAULTS = { debug: false, timeout: 5000, prerollTimeout: 1000, adLabel: 'Advertisement', adLabelNofN: 'of', showControlsForJSAds: true, requestMode: 'onLoad', }; /** * Extends the settings to include user-provided settings. * * @param {Object} options Options to be used in initialization. */ Controller.prototype.initWithSettings = function(options) { this.settings = this.extend({}, Controller.IMA_DEFAULTS, options || {}); this.warnAboutDeprecatedSettings(); // Default showing countdown timer to true. this.showCountdown = true; if (this.settings.showCountdown === false) { this.showCountdown = false; } }; /** * Logs console warnings when deprecated settings are used. */ Controller.prototype.warnAboutDeprecatedSettings = function() { const deprecatedSettings = [ 'adWillAutoplay', 'adsWillAutoplay', 'adWillPlayMuted', 'adsWillPlayMuted', ]; deprecatedSettings.forEach((setting) => { if (this.settings[setting] !== undefined) { console.warn( 'WARNING: videojs.ima setting ' + setting + ' is deprecated'); } }); }; /** * Return the settings object. * * @return {Object} The settings object. */ Controller.prototype.getSettings = function() { return this.settings; }; /** * Return whether or not we're in a mobile environment. * * @return {boolean} True if running on mobile, false otherwise. */ Controller.prototype.getIsMobile = function() { return this.isMobile; }; /** * Return whether or not we're in an iOS environment. * * @return {boolean} True if running on iOS, false otherwise. */ Controller.prototype.getIsIos = function() { return this.isIos; }; /** * Inject the ad container div into the DOM. * * @param{HTMLElement} adContainerDiv The ad container div. */ Controller.prototype.injectAdContainerDiv = function(adContainerDiv) { this.playerWrapper.injectAdContainerDiv(adContainerDiv); }; /** * @return {HTMLElement} The div for the ad container. */ Controller.prototype.getAdContainerDiv = function() { return this.adUi.getAdContainerDiv(); }; /** * @return {Object} The content player. */ Controller.prototype.getContentPlayer = function() { return this.playerWrapper.getContentPlayer(); }; /** * Returns the content playhead tracker. * * @return {Object} The content playhead tracker. */ Controller.prototype.getContentPlayheadTracker = function() { return this.playerWrapper.getContentPlayheadTracker(); }; /** * Requests ads. */ Controller.prototype.requestAds = function() { this.sdkImpl.requestAds(); }; /** * Add or modify a setting. * * @param {string} key Key to modify * @param {Object} value Value to set at key. */ Controller.prototype.setSetting = function(key, value) { this.settings[key] = value; }; /** * Called when there is an error loading ads. * * @param {Object} adErrorEvent The ad error event thrown by the IMA SDK. */ Controller.prototype.onErrorLoadingAds = function(adErrorEvent) { this.adUi.onAdError(); this.playerWrapper.onAdError(adErrorEvent); }; /** * Called by the ad UI when the play/pause button is clicked. */ Controller.prototype.onAdPlayPauseClick = function() { if (this.sdkImpl.isAdPlaying()) { this.adUi.onAdsPaused(); this.sdkImpl.pauseAds(); } else { this.adUi.onAdsPlaying(); this.sdkImpl.resumeAds(); } }; /** * Called by the ad UI when the mute button is clicked. * */ Controller.prototype.onAdMuteClick = function() { if (this.sdkImpl.isAdMuted()) { this.playerWrapper.unmute(); this.adUi.unmute(); this.sdkImpl.unmute(); } else { this.playerWrapper.mute(); this.adUi.mute(); this.sdkImpl.mute(); } }; /** * Set the volume of the player and ads. 0-1. * * @param {number} volume The new volume. */ Controller.prototype.setVolume = function(volume) { this.playerWrapper.setVolume(volume); this.sdkImpl.setVolume(volume); }; /** * @return {number} The volume of the content player. */ Controller.prototype.getPlayerVolume = function() { return this.playerWrapper.getVolume(); }; /** * Toggle fullscreen state. */ Controller.prototype.toggleFullscreen = function() { this.playerWrapper.toggleFullscreen(); }; /** * Relays ad errors to the player wrapper. * * @param {Object} adErrorEvent The ad error event thrown by the IMA SDK. */ Controller.prototype.onAdError = function(adErrorEvent) { this.adUi.onAdError(); this.playerWrapper.onAdError(adErrorEvent); }; /** * Handles ad break starting. * * @param {Object} adEvent The event fired by the IMA SDK. */ Controller.prototype.onAdBreakStart = function(adEvent) { this.playerWrapper.onAdBreakStart(); this.adUi.onAdBreakStart(adEvent); }; /** * Show the ad container. */ Controller.prototype.showAdContainer = function() { this.adUi.showAdContainer(); }; /** * Handles ad break ending. */ Controller.prototype.onAdBreakEnd = function() { this.playerWrapper.onAdBreakEnd(); this.adUi.onAdBreakEnd(); }; /** * Handles when all ads have finished playing. */ Controller.prototype.onAllAdsCompleted = function() { this.adUi.onAllAdsCompleted(); this.playerWrapper.onAllAdsCompleted(); }; /** * Handles the SDK firing an ad paused event. */ Controller.prototype.onAdsPaused = function() { this.adUi.onAdsPaused(); }; /** * Handles the SDK firing an ad resumed event. */ Controller.prototype.onAdsResumed = function() { this.adUi.onAdsResumed(); }; /** * Takes data from the sdk impl and passes it to the ad UI to update the UI. * * @param {number} currentTime Current time of the ad. * @param {number} remainingTime Remaining time of the ad. * @param {number} duration Duration of the ad. * @param {number} adPosition Index of the ad in the pod. * @param {number} totalAds Total number of ads in the pod. */ Controller.prototype.onAdPlayheadUpdated = function(currentTime, remainingTime, duration, adPosition, totalAds) { this.adUi.updateAdUi( currentTime, remainingTime, duration, adPosition, totalAds); }; /** * Handles ad log messages. * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the IMA SDK. */ Controller.prototype.onAdLog = function(adEvent) { this.playerWrapper.onAdLog(adEvent); }; /** * @return {Object} The current ad. */ Controller.prototype.getCurrentAd = function() { return this.sdkImpl.getCurrentAd(); }; /** * Play content. */ Controller.prototype.playContent = function() { this.playerWrapper.play(); }; /** * Handles when a linear ad starts. */ Controller.prototype.onLinearAdStart = function() { this.adUi.onLinearAdStart(); this.playerWrapper.onAdStart(); }; /** * Handles when a non-linear ad loads. */ Controller.prototype.onNonLinearAdLoad = function() { this.adUi.onNonLinearAdLoad(); }; /** * Handles when a non-linear ad starts. */ Controller.prototype.onNonLinearAdStart = function() { this.adUi.onNonLinearAdLoad(); this.playerWrapper.onAdStart(); }; /** * Get the player width. * * @return {number} The width of the player. */ Controller.prototype.getPlayerWidth = function() { return this.playerWrapper.getPlayerWidth(); }; /** * Get the player height. * * @return {number} The height of the player. */ Controller.prototype.getPlayerHeight = function() { return this.playerWrapper.getPlayerHeight(); }; /** * Tells the player wrapper that ads are ready. */ Controller.prototype.onAdsReady = function() { this.playerWrapper.onAdsReady(); }; /** * Called when the player wrapper detects that the player has been resized. * * @param {number} width The post-resize width of the player. * @param {number} height The post-resize height of the player. */ Controller.prototype.onPlayerResize = function(width, height) { this.sdkImpl.onPlayerResize(width, height); }; /** * Called by the player wrapper when content completes. */ Controller.prototype.onContentComplete = function() { this.sdkImpl.onContentComplete(); }; /** * Called by the player wrapper when it's time to play a post-roll but we don't * have one to play. */ Controller.prototype.onNoPostroll = function() { this.playerWrapper.onNoPostroll(); }; /** * Called when content and all ads have completed. */ Controller.prototype.onContentAndAdsCompleted = function() { for (let index in this.contentAndAdsEndedListeners) { if (typeof this.contentAndAdsEndedListeners[index] === 'function') { this.contentAndAdsEndedListeners[index](); } } }; /** * Called when the player is disposed. */ Controller.prototype.onPlayerDisposed = function() { this.contentAndAdsEndedListeners = []; this.sdkImpl.onPlayerDisposed(); }; /** * Called when the player is ready to play a pre-roll. */ Controller.prototype.onPlayerReadyForPreroll = function() { this.sdkImpl.onPlayerReadyForPreroll(); }; /** * Called when the player is ready. */ Controller.prototype.onPlayerReady = function() { this.sdkImpl.onPlayerReady(); }; /** * Called when the player enters fullscreen. */ Controller.prototype.onPlayerEnterFullscreen = function() { this.adUi.onPlayerEnterFullscreen(); this.sdkImpl.onPlayerEnterFullscreen(); }; /** * Called when the player exits fullscreen. */ Controller.prototype.onPlayerExitFullscreen = function() { this.adUi.onPlayerExitFullscreen(); this.sdkImpl.onPlayerExitFullscreen(); }; /** * Called when the player volume changes. * * @param {number} volume The new player volume. */ Controller.prototype.onPlayerVolumeChanged = function(volume) { this.adUi.onPlayerVolumeChanged(volume); this.sdkImpl.onPlayerVolumeChanged(volume); }; /** * Sets the content of the video player. You should use this method instead * of setting the content src directly to ensure the proper ad tag is * requested when the video content is loaded. * @param {?string} contentSrc The URI for the content to be played. Leave * blank to use the existing content. * @param {?string} adTag The ad tag to be requested when the content loads. * Leave blank to use the existing ad tag. */ Controller.prototype.setContentWithAdTag = function(contentSrc, adTag) { this.reset(); this.settings.adTagUrl = adTag ? adTag : this.settings.adTagUrl; this.playerWrapper.changeSource(contentSrc); }; /** * Sets the content of the video player. You should use this method instead * of setting the content src directly to ensure the proper ads response is * used when the video content is loaded. * @param {?string} contentSrc The URI for the content to be played. Leave * blank to use the existing content. * @param {?string} adsResponse The ads response to be requested when the * content loads. Leave blank to use the existing ads response. */ Controller.prototype.setContentWithAdsResponse = function(contentSrc, adsResponse) { this.reset(); this.settings.adsResponse = adsResponse ? adsResponse : this.settings.adsResponse; this.playerWrapper.changeSource(contentSrc); }; /** * Sets the content of the video player. You should use this method instead * of setting the content src directly to ensure the proper ads request is * used when the video content is loaded. * @param {?string} contentSrc The URI for the content to be played. Leave * blank to use the existing content. * @param {?Object} adsRequest The ads request to be requested when the * content loads. Leave blank to use the existing ads request. */ Controller.prototype.setContentWithAdsRequest = function(contentSrc, adsRequest) { this.reset(); this.settings.adsRequest = adsRequest ? adsRequest : this.settings.adsRequest; this.playerWrapper.changeSource(contentSrc); }; /** * Resets the state of the plugin. */ Controller.prototype.reset = function() { this.sdkImpl.reset(); this.playerWrapper.reset(); this.adUi.reset(); }; /** * Listener JSDoc for ESLint. This listener can be passed to * (add|remove)ContentEndedListener. * @callback listener */ /** * Adds a listener for the 'contentended' event of the video player. This should * be used instead of setting an 'contentended' listener directly to ensure that * the ima can do proper cleanup of the SDK before other event listeners are * called. * @param {listener} listener The listener to be called when content * completes. */ Controller.prototype.addContentEndedListener = function(listener) { this.playerWrapper.addContentEndedListener(listener); }; /** * Adds a listener that will be called when content and all ads have * finished playing. * @param {listener} listener The listener to be called when content and ads * complete. */ Controller.prototype.addContentAndAdsEndedListener = function(listener) { this.contentAndAdsEndedListeners.push(listener); }; /** * Sets the listener to be called to trigger manual ad break playback. * @param {listener} listener The listener to be called to trigger manual ad * break playback. */ Controller.prototype.setAdBreakReadyListener = function(listener) { this.sdkImpl.setAdBreakReadyListener(listener); }; /** * Changes the flag to show or hide the ad countdown timer. * * @param {boolean} showCountdownIn Show or hide the countdown timer. */ Controller.prototype.setShowCountdown = function(showCountdownIn) { this.adUi.setShowCountdown(showCountdownIn); this.showCountdown = showCountdownIn; this.countdownDiv.style.display = this.showCountdown ? 'block' : 'none'; }; /** * Initializes the AdDisplayContainer. On mobile, this must be done as a * result of user action. */ Controller.prototype.initializeAdDisplayContainer = function() { this.sdkImpl.initializeAdDisplayContainer(); }; /** * Called by publishers in manual ad break playback mode to start an ad * break. */ Controller.prototype.playAdBreak = function() { this.sdkImpl.playAdBreak(); }; /** * Callback JSDoc for ESLint. This callback can be passed to addEventListener. * @callback callback */ /** * Ads an EventListener to the AdsManager. For a list of available events, * see * https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type * @param {google.ima.AdEvent.Type} event The AdEvent.Type for which to * listen. * @param {callback} callback The method to call when the event is fired. */ Controller.prototype.addEventListener = function(event, callback) { this.sdkImpl.addEventListener(event, callback); }; /** * Returns the instance of the AdsManager. * @return {google.ima.AdsManager} The AdsManager being used by the plugin. */ Controller.prototype.getAdsManager = function() { return this.sdkImpl.getAdsManager(); }; /** * Returns the instance of the player id. * @return {string} The player id. */ Controller.prototype.getPlayerId = function() { return this.playerWrapper.getPlayerId(); }; /** * Changes the ad tag. You will need to call requestAds after this method * for the new ads to be requested. * @param {?string} adTag The ad tag to be requested the next time * requestAds is called. */ Controller.prototype.changeAdTag = function(adTag) { this.reset(); this.settings.adTagUrl = adTag; }; /** * Pauses the ad. */ Controller.prototype.pauseAd = function() { this.adUi.onAdsPaused(); this.sdkImpl.pauseAds(); }; /** * Resumes the ad. */ Controller.prototype.resumeAd = function() { this.adUi.onAdsPlaying(); this.sdkImpl.resumeAds(); }; /** * @return {boolean} true if we expect that ads will autoplay. false otherwise. */ Controller.prototype.adsWillAutoplay = function() { if (this.settings.adsWillAutoplay !== undefined) { return this.settings.adsWillAutoplay; } else if (this.settings.adWillAutoplay !== undefined) { return this.settings.adWillAutoplay; } else { return !!this.playerWrapper.getPlayerOptions().autoplay; } }; /** * @return {boolean} true if we expect that ads will autoplay. false otherwise. */ Controller.prototype.adsWillPlayMuted = function() { if (this.settings.adsWillPlayMuted !== undefined) { return this.settings.adsWillPlayMuted; } else if (this.settings.adWillPlayMuted !== undefined) { return this.settings.adWillPlayMuted; } else if (this.playerWrapper.getPlayerOptions().muted !== undefined) { return this.playerWrapper.getPlayerOptions().muted; } else { return this.playerWrapper.getVolume() == 0; } }; /** * Triggers an event on the VJS player * @param {string} name The event name. * @param {Object} data The event data. */ Controller.prototype.triggerPlayerEvent = function(name, data) { this.playerWrapper.triggerPlayerEvent(name, data); }; /** * Extends an object to include the contents of objects at parameters 2 onward. * * @param {Object} obj The object onto which the subsequent objects' parameters * will be extended. This object will be modified. * @param {...Object} var_args The objects whose properties are to be extended * onto obj. * @return {Object} The extended object. */ Controller.prototype.extend = function(obj, ...args) { let arg; let index; let key; for (index = 0; index < args.length; index++) { arg = args[index]; for (key in arg) { if (arg.hasOwnProperty(key)) { obj[key] = arg[key]; } } } return obj; }; export default Controller;