UNPKG

shaka-player

Version:
552 lines (461 loc) 13 kB
/** @license * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.ads.AdManager'); goog.provide('shaka.ads.CuePoint'); goog.require('shaka.Player'); goog.require('shaka.ads.AdsStats'); goog.require('shaka.ads.ClientSideAdManager'); goog.require('shaka.ads.ServerSideAdManager'); goog.require('shaka.log'); goog.require('shaka.util.FakeEventTarget'); /** * @event shaka.ads.AdManager.ADS_LOADED * @description Fired when an ad has started playing. * @property {string} type * 'ads-loaded' * @property {number} loadTime * The time it takes to load ads. * @exportDoc */ /** * @event shaka.ads.AdManager.AdStartedEvent * @description Fired when an ad has started playing. * @property {string} type * 'ad-started' * @property {!shaka.extern.IAd} ad * The ad that has started playing. * @exportDoc */ /** * @event shaka.ads.AdManager.AdCompleteEvent * @description Fired when an ad has played through. * @property {string} type * 'ad-complete' * @exportDoc */ /** * @event shaka.ads.AdManager.AdSkippedEvent * @description Fired when an ad has been skipped. * @property {string} type * 'ad-skipped' * @exportDoc */ /** * @event shaka.ads.AdManager.AdFirstQuartileEvent * @description Fired when an ad has played through the first 1/4. * @property {string} type * 'ad-first-quartile' * @exportDoc */ /** * @event shaka.ads.AdManager.AdMidpointEvent * @description Fired when an ad has played through its midpoint. * @property {string} type * 'ad-midpoint' * @exportDoc */ /** * @event shaka.ads.AdManager.AdThirdQuartileEvent * @description Fired when an ad has played through the third quartile. * @property {string} type * 'ad-third-quartile' * @exportDoc */ /** * @event shaka.ads.AdManager.AdStoppedEvent * @description Fired when an ad has stopped playing, was skipped, * or was unable to proceed due to an error. * @property {string} type * 'ad-stopped' * @exportDoc */ /** * @event shaka.ads.AdManager.AdVolumeChangedEvent * @description Fired when an ad's volume changed. * @property {string} type * 'ad-volume-changed' * @exportDoc */ /** * @event shaka.ads.AdManager.AdMutedEvent * @description Fired when an ad was muted. * @property {string} type * 'ad-muted' * @exportDoc */ /** * @event shaka.ads.AdManager.AdPausedEvent * @description Fired when an ad was paused. * @property {string} type * 'ad-paused' * @exportDoc */ /** * @event shaka.ads.AdManager.AdResumedEvent * @description Fired when an ad was resumed after a pause. * @property {string} type * 'ad-resumed' * @exportDoc */ /** * @event shaka.ads.AdManager.AdSkipStateChangedEvent * @description Fired when an ad's skip state changes (for example, when * it becomes possible to skip the ad). * @property {string} type * 'ad-skip-state-changed' * @exportDoc */ /** * @event shaka.ads.AdManager.AdResumedEvent * @description Fired when the ad cue points change, signalling ad breaks * change. * @property {string} type * 'ad-cue-points-changed' * @exportDoc */ /** * A class responsible for ad-related interactions. * @implements {shaka.extern.IAdManager} * @export */ shaka.ads.AdManager = class extends shaka.util.FakeEventTarget { constructor() { super(); /** @private {shaka.ads.ClientSideAdManager} */ this.csAdManager_ = null; /** @private {shaka.ads.ServerSideAdManager} */ this.ssAdManager_ = null; /** @private {shaka.ads.AdsStats} */ this.stats_ = new shaka.ads.AdsStats(); /** @private {string} locale */ this.locale_ = navigator.language; } /** * @override * @export */ setLocale(locale) { this.locale_ = locale; } /** * @override * @export */ initClientSide(adContainer, video) { // Check that Client Side IMA SDK has been included // NOTE: (window['google'] && google.ima) check for any // IMA SDK, including SDK for Server Side ads. // The 3rd check insures we have the right SDK: // {google.ima.AdsLoader} is an object that's part of CS IMA SDK // but not SS SDK. if (!window['google'] || !google.ima || !google.ima.AdsLoader) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.ADS, shaka.util.Error.Code.CS_IMA_SDK_MISSING); } this.csAdManager_ = new shaka.ads.ClientSideAdManager( adContainer, video, this.locale_, (e) => { const event = /** @type {!shaka.util.FakeEvent} */ (e); if (event && event.type) { switch (event.type) { case shaka.ads.AdManager.ADS_LOADED: { const loadTime = (/** @type {!Object} */ (e))['loadTime']; this.stats_.addLoadTime(loadTime); break; } case shaka.ads.AdManager.AD_STARTED: this.stats_.incrementStarted(); break; case shaka.ads.AdManager.AD_COMPLETE: this.stats_.incrementPlayedCompletely(); break; case shaka.ads.AdManager.AD_SKIPPED: this.stats_.incrementSkipped(); break; } } this.dispatchEvent(event); }); } /** * @override * @export */ onAssetUnload() { if (this.csAdManager_) { this.csAdManager_.stop(); } this.dispatchEvent( new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STOPPED)); // TODO: // For SS DAI streams, if a different asset gets unloaded as // part of the process // of loading a DAI asset, stream manager state gets reset and we // don't get any ad events. // We need to figure out if it makes sense to stop the SS // manager on unload, and, if it does, find // a way to do it safely. // if (this.ssAdManager_) { // this.ssAdManager_.stop(); // } this.stats_ = new shaka.ads.AdsStats(); } /** * @override * @export */ requestClientSideAds(imaRequest) { if (!this.csAdManager_) { throw new shaka.util.Error( shaka.util.Error.Severity.RECOVERABLE, shaka.util.Error.Category.ADS, shaka.util.Error.Code.CS_AD_MANAGER_NOT_INITIALIZED); } this.csAdManager_.requestAds(imaRequest); } /** * @override * @export */ initServerSide(adContainer, video) { // Check that Client Side IMA SDK has been included // NOTE: (window['google'] && google.ima) check for any // IMA SDK, including SDK for Server Side ads. // The 3rd check insures we have the right SDK: // {google.ima.dai} is an object that's part of DAI IMA SDK // but not SS SDK. if (!window['google'] || !google.ima || !google.ima.dai) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.ADS, shaka.util.Error.Code.SS_IMA_SDK_MISSING); } this.ssAdManager_ = new shaka.ads.ServerSideAdManager( adContainer, video, this.locale_, (e) => { const event = /** @type {!shaka.util.FakeEvent} */ (e); if (event && event.type) { switch (event.type) { case shaka.ads.AdManager.ADS_LOADED: { const loadTime = (/** @type {!Object} */ (e))['loadTime']; this.stats_.addLoadTime(loadTime); break; } case shaka.ads.AdManager.AD_STARTED: this.stats_.incrementStarted(); break; case shaka.ads.AdManager.AD_COMPLETE: this.stats_.incrementPlayedCompletely(); break; case shaka.ads.AdManager.AD_SKIPPED: this.stats_.incrementSkipped(); break; } } this.dispatchEvent(event); }); // Set the name and version of the player in IMA for tracking. this.replaceServerSideAdTagParameters( { 'mpt': 'shaka', 'mpv': shaka.Player.version, }); } /** * @param {!google.ima.dai.api.StreamRequest} imaRequest * @param {string=} backupUrl * @return {!Promise.<string>} * @override * @export */ requestServerSideStream(imaRequest, backupUrl = '') { if (!this.ssAdManager_) { throw new shaka.util.Error( shaka.util.Error.Severity.RECOVERABLE, shaka.util.Error.Category.ADS, shaka.util.Error.Code.SS_AD_MANAGER_NOT_INITIALIZED); } return this.ssAdManager_.streamRequest(imaRequest, backupUrl); } /** * @override * @export */ replaceServerSideAdTagParameters(adTagParameters) { if (!this.ssAdManager_) { throw new shaka.util.Error( shaka.util.Error.Severity.RECOVERABLE, shaka.util.Error.Category.ADS, shaka.util.Error.Code.SS_AD_MANAGER_NOT_INITIALIZED); } this.ssAdManager_.replaceAdTagParameters(adTagParameters); } /** * @return {shaka.extern.AdsStats} * @override * @export */ getStats() { return this.stats_.getBlob(); } /** * @override * @export */ onDashTimedMetadata(region) { if (this.ssAdManager_ && region.schemeIdUri == 'urn:google:dai:2018') { const type = region.schemeIdUri; const data = region.eventElement ? region.eventElement.getAttribute('messageData') : null; const timestamp = region.startTime; this.ssAdManager_.onTimedMetadata(type, data, timestamp); } } /** * @override * @export */ onHlsTimedMetadata(metadata, timestampOffset) { for (const sample of metadata) { if (this.ssAdManager_ && sample['data'] && sample['cueTime']) { const timestamp = sample['cueTime'] + timestampOffset; this.ssAdManager_.onTimedMetadata('ID3', sample['data'], timestamp); } } } /** * @override * @export */ onCueMetadataChange(value) { if (this.ssAdManager_) { this.ssAdManager_.onCueMetadataChange(value); } else { shaka.log.warning('The method was called without initializing server ' + 'side logic and will not take effect'); } } }; shaka.ads.CuePoint = class { /** * @param {number} start * @param {?number=} end */ constructor(start, end = null) { /** @public {number} */ this.start = start; /** @public {?number} */ this.end = end; } }; /** * The event name for when a sequence of ads has been loaded. * * @const {string} * @export */ shaka.ads.AdManager.ADS_LOADED = 'ads-loaded'; /** * The event name for when an ad has started playing. * * @const {string} * @export */ shaka.ads.AdManager.AD_STARTED = 'ad-started'; /** * The event name for when an ad playhead crosses first quartile. * * @const {string} * @export */ shaka.ads.AdManager.AD_FIRST_QUARTILE = 'ad-first-quartile'; /** * The event name for when an ad playhead crosses midpoint. * * @const {string} * @export */ shaka.ads.AdManager.AD_MIDPOINT = 'ad-midpoint'; /** * The event name for when an ad playhead crosses third quartile. * * @const {string} * @export */ shaka.ads.AdManager.AD_THIRD_QUARTILE = 'ad-third-quartile'; /** * The event name for when an ad has completed playing. * * @const {string} * @export */ shaka.ads.AdManager.AD_COMPLETE = 'ad-complete'; /** * The event name for when an ad has finished playing * (played all the way through, was skipped, or was unable to proceed * due to an error). * * @const {string} * @export */ shaka.ads.AdManager.AD_STOPPED = 'ad-stopped'; /** * The event name for when an ad is skipped by the user.. * * @const {string} * @export */ shaka.ads.AdManager.AD_SKIPPED = 'ad-skipped'; /** * The event name for when the ad volume has changed. * * @const {string} * @export */ shaka.ads.AdManager.AD_VOLUME_CHANGED = 'ad-volume-changed'; /** * The event name for when the ad was muted. * * @const {string} * @export */ shaka.ads.AdManager.AD_MUTED = 'ad-muted'; /** * The event name for when the ad was paused. * * @const {string} * @export */ shaka.ads.AdManager.AD_PAUSED = 'ad-paused'; /** * The event name for when the ad was resumed after a pause. * * @const {string} * @export */ shaka.ads.AdManager.AD_RESUMED = 'ad-resumed'; /** * The event name for when the ad's skip status changes * (usually it becomes skippable when it wasn't before). * * @const {string} * @export */ shaka.ads.AdManager.AD_SKIP_STATE_CHANGED = 'ad-skip-state-changed'; /** * The event name for when the ad's cue points (start/end markers) * have changed. * * @const {string} * @export */ shaka.ads.AdManager.CUEPOINTS_CHANGED = 'ad-cue-points-changed'; /** * Set this is a default ad manager for the player. * Apps can also set their own ad manager, if they'd like. */ shaka.Player.setAdManagerFactory(() => new shaka.ads.AdManager());