UNPKG

shaka-player

Version:
282 lines (232 loc) 9.31 kB
/** @license * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.ads.ClientSideAdManager'); goog.require('goog.asserts'); goog.require('shaka.ads.ClientSideAd'); /** * A class responsible for client-side ad interactions. */ shaka.ads.ClientSideAdManager = class { /** * @param {HTMLElement} adContainer * @param {HTMLMediaElement} video * @param {string} locale * @param {function(!shaka.util.FakeEvent)} onEvent */ constructor(adContainer, video, locale, onEvent) { /** @private {HTMLElement} */ this.adContainer_ = adContainer; /** @private {HTMLMediaElement} */ this.video_ = video; /** @private {number} */ this.requestAdsStartTime_ = NaN; /** @private {function(!shaka.util.FakeEvent)} */ this.onEvent_ = onEvent; /** @private {shaka.ads.ClientSideAd} */ this.ad_ = null; /** @private {shaka.util.EventManager} */ this.eventManager_ = new shaka.util.EventManager(); google.ima.settings.setLocale(locale); const adDisplayContainer = new google.ima.AdDisplayContainer( this.adContainer_, this.video_); // TODO: IMA: Must be done as the result of a user action on mobile adDisplayContainer.initialize(); // IMA: This instance should be re-used for the entire lifecycle of // the page. this.adsLoader_ = new google.ima.AdsLoader(adDisplayContainer); this.adsLoader_.getSettings().setPlayerType('shaka-player'); this.adsLoader_.getSettings().setPlayerVersion(shaka.Player.version); /** @private {google.ima.AdsManager} */ this.imaAdsManager_ = null; this.eventManager_.listenOnce(this.adsLoader_, google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, (e) => { this.onAdsManagerLoaded_( /** @type {!google.ima.AdsManagerLoadedEvent} */ (e)); }); this.eventManager_.listen(this.adsLoader_, google.ima.AdEvent.Type.AD_ERROR, (e) => { this.onAdError_( /** @type {!google.ima.AdErrorEvent} */ (e)); }); // Notify the SDK when the video has ended, so it can play post-roll ads. this.video_.onended = () => { this.adsLoader_.contentComplete(); }; } /** * @param {!google.ima.AdsRequest} imaRequest */ requestAds(imaRequest) { goog.asserts.assert(imaRequest.adTagUrl.length, 'The ad tag needs to be set up before requesting ads.'); this.requestAdsStartTime_ = Date.now() / 1000; this.adsLoader_.requestAds(imaRequest); } /** * Stop all currently playing ads. */ stop() { this.imaAdsManager_.stop(); } /** * @param {!google.ima.AdErrorEvent} e * @private */ onAdError_(e) { shaka.log.warning( 'There was an ad error from the IMA SDK: ' + e.getError()); shaka.log.warning('Resuming playback.'); this.onAdComplete_(); // Remove ad breaks from the timeline this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.CUEPOINTS_CHANGED, {'cuepoints': []})); } /** * @param {!google.ima.AdsManagerLoadedEvent} e * @private */ onAdsManagerLoaded_(e) { goog.asserts.assert(this.video_ != null, 'Video should not be null!'); const now = Date.now() / 1000; const loadTime = now - this.requestAdsStartTime_; this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.ADS_LOADED, {'loadTime': loadTime})); this.imaAdsManager_ = e.getAdsManager(this.video_); const cuePointStarts = this.imaAdsManager_.getCuePoints(); if (cuePointStarts.length) { /** @type {!Array.<!shaka.ads.CuePoint>} */ const cuePoints = []; for (const start of cuePointStarts) { const shakaCuePoint = new shaka.ads.CuePoint(start); cuePoints.push(shakaCuePoint); } this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.CUEPOINTS_CHANGED, {'cuepoints': cuePoints})); } this.eventManager_.listen(this.imaAdsManager_, google.ima.AdErrorEvent.Type.AD_ERROR, (error) => { this.onAdError_(/** @type {!google.ima.AdErrorEvent} */ (error)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, (e) => { this.onAdStart_(/** @type {!google.ima.AdEvent} */ (e)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.STARTED, (e) => { this.onAdStart_(/** @type {!google.ima.AdEvent} */ (e)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.FIRST_QUARTILE, () => { this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_FIRST_QUARTILE)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.MIDPOINT, () => { this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_MIDPOINT)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.THIRD_QUARTILE, () => { this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_THIRD_QUARTILE)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.COMPLETE, () => { this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_COMPLETE)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, () => { this.onAdComplete_(); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.ALL_ADS_COMPLETED, () => { this.onAdComplete_(); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.SKIPPED, () => { this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_SKIPPED)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.VOLUME_CHANGED, () => { this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_VOLUME_CHANGED)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.VOLUME_MUTED, () => { this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_MUTED)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.PAUSED, () => { goog.asserts.assert(this.ad_ != null, 'Ad should not be null!'); this.ad_.setPaused(true); this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_PAUSED)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.RESUMED, () => { goog.asserts.assert(this.ad_ != null, 'Ad should not be null!'); this.ad_.setPaused(false); this.onEvent_( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_RESUMED)); }); this.eventManager_.listen(this.imaAdsManager_, google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED, () => { goog.asserts.assert(this.ad_ != null, 'Ad should not be null!'); this.onEvent_(new shaka.util.FakeEvent( shaka.ads.AdManager.AD_SKIP_STATE_CHANGED)); }); try { const viewMode = document.fullscreenElement ? google.ima.ViewMode.FULLSCREEN : google.ima.ViewMode.NORMAL; this.imaAdsManager_.init(this.video_.offsetWidth, this.video_.offsetHeight, viewMode); // Wait on the 'loadeddata' event rather than the 'loadedmetadata' event // because 'loadedmetadata' is sometimes called before the video resizes // on some platforms (e.g. Safari). this.eventManager_.listen(this.video_, 'loadeddata', () => { const viewMode = document.fullscreenElement ? google.ima.ViewMode.FULLSCREEN : google.ima.ViewMode.NORMAL; this.imaAdsManager_.resize(this.video_.offsetWidth, this.video_.offsetHeight, viewMode); }); // Single video and overlay ads will start at this time // TODO (ismena): Need a better inderstanding of what this does. // The docs say it's called to 'start playing the ads,' but I haven't // seen the ads actually play until requestAds() is called. this.imaAdsManager_.start(); } catch (adError) { // If there was a problem with the VAST response, // we we won't be getting an ad. Hide ad UI if we showed it already // and get back to the presentation. this.onAdComplete_(); } } /** * @param {!google.ima.AdEvent} e * @private */ onAdStart_(e) { goog.asserts.assert(this.imaAdsManager_, 'Should have an ads manager at this point!'); const imaAd = e.getAd(); this.ad_ = new shaka.ads.ClientSideAd(imaAd, this.imaAdsManager_); this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STARTED, {'ad': this.ad_})); this.adContainer_.setAttribute('ad-active', 'true'); this.video_.pause(); } /** @private */ onAdComplete_() { this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STOPPED)); this.adContainer_.removeAttribute('ad-active'); this.video_.play(); } };