diff --git a/lib/cast/cast_proxy.js b/lib/cast/cast_proxy.js index dc8280117..8d698a94a 100644 --- a/lib/cast/cast_proxy.js +++ b/lib/cast/cast_proxy.js @@ -93,14 +93,18 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { /** @private {boolean} */ this.androidReceiverCompatible_ = androidReceiverCompatible; - /** @private {!Array} */ - this.addThumbnailsTrackCalls_ = []; - - /** @private {!Array} */ - this.addTextTrackAsyncCalls_ = []; - - /** @private {!Array} */ - this.addChaptersTrackCalls_ = []; + /** + * @private {{ + * addThumbnailsTrack: !Array, + * addTextTrackAsync: !Array, + * addChaptersTrack: !Array, + * }} + */ + this.trackedCalls_ = { + addThumbnailsTrack: [], + addTextTrackAsync: [], + addChaptersTrack: [], + }; /** @private {!Map} */ this.playerCompiledToExternNames_ = new Map(); @@ -411,8 +415,12 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { } if (COMPILED) { - this.mapCompiledToUncompiledPlayerMethodNames_(); - this.mapCompiledToUncompiledAdManagerMethodNames_(); + this.mapCompiledToUncompiledMethodNames_( + (op) => this.iterateOverPlayerMethods_(op), + this.playerCompiledToExternNames_); + this.mapCompiledToUncompiledMethodNames_( + (op) => this.iterateOverAdManagerMethods_(op), + this.adManagerCompiledToExternNames_); } this.videoEventTarget_ = new shaka.util.FakeEventTarget(); @@ -438,53 +446,28 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { /** - * Maps compiled to uncompiled player names so we can figure out + * Maps compiled to uncompiled names for a given object so we can figure out * which method to call in compiled build, while casting. + * @param {function(function(string, function()))} iterateFn Accepts an + * operation callback and calls iterateOver*Methods_ with it. + * @param {!Map} compiledMap * @private */ - mapCompiledToUncompiledPlayerMethodNames_() { - // In compiled mode, UI tries to access player methods by their internal - // renamed names, but the proxy object doesn't know about those. See + mapCompiledToUncompiledMethodNames_(iterateFn, compiledMap) { + // In compiled mode, UI tries to access methods by their internal renamed + // names, but the proxy object doesn't know about those. See // https://github.com/shaka-project/shaka-player/issues/2130 for details. const methodsToNames = new Map(); - this.iterateOverPlayerMethods_((name, method) => { + iterateFn((name, method) => { if (methodsToNames.has(method)) { - // If two method names, point to the same method, add them to the + // If two method names point to the same method, add them to the // map as aliases of each other. const name2 = methodsToNames.get(method); // Assumes that the compiled name is shorter if (name.length < name2.length) { - this.playerCompiledToExternNames_.set(name, name2); + compiledMap.set(name, name2); } else { - this.playerCompiledToExternNames_.set(name2, name); - } - } else { - methodsToNames.set(method, name); - } - }); - } - - - /** - * Maps compiled to uncompiled ad manager names so we can figure out - * which method to call in compiled build, while casting. - * @private - */ - mapCompiledToUncompiledAdManagerMethodNames_() { - // In compiled mode, UI tries to access ad methods by their internal - // renamed names, but the proxy object doesn't know about those. See - // https://github.com/shaka-project/shaka-player/issues/2130 for details. - const methodsToNames = new Map(); - this.iterateOverAdManagerMethods_((name, method) => { - if (methodsToNames.has(method)) { - // If two method names, point to the same method, add them to the - // map as aliases of each other. - const name2 = methodsToNames.get(method); - // Assumes that the compiled name is shorter - if (name.length < name2.length) { - this.adManagerCompiledToExternNames_.set(name, name2); - } else { - this.adManagerCompiledToExternNames_.set(name2, name); + compiledMap.set(name2, name); } } else { methodsToNames.set(method, name); @@ -583,9 +566,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { 'manifest': this.localPlayer_.getAssetUri(), 'startTime': null, 'mimeType': this.localPlayer_.getMimeType(), - 'addThumbnailsTrackCalls': this.addThumbnailsTrackCalls_, - 'addTextTrackAsyncCalls': this.addTextTrackAsyncCalls_, - 'addChaptersTrackCalls': this.addChaptersTrackCalls_, + 'addThumbnailsTrackCalls': this.trackedCalls_.addThumbnailsTrack, + 'addTextTrackAsyncCalls': this.trackedCalls_.addTextTrackAsync, + 'addChaptersTrackCalls': this.trackedCalls_.addChaptersTrack, }; // Pause local playback before capturing state. @@ -645,9 +628,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { /** @type {Object} */(this.localPlayer_)[setter](value); } - const addThumbnailsTrackCalls = this.addThumbnailsTrackCalls_; - const addTextTrackAsyncCalls = this.addTextTrackAsyncCalls_; - const addChaptersTrackCalls = this.addChaptersTrackCalls_; + const addThumbnailsTrackCalls = this.trackedCalls_.addThumbnailsTrack; + const addTextTrackAsyncCalls = this.trackedCalls_.addTextTrackAsync; + const addChaptersTrackCalls = this.trackedCalls_.addChaptersTrack; this.resetExternalTracks(); @@ -736,6 +719,33 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { }); } + /** + * Returns the local value/method for |name| on |localTarget| when not + * casting, or delegates to the sender for the given |targetName|. + * Centralises the repeated "use local if not casting" pattern shared by + * videoProxyGet_, playerProxyGet_, and adManagerProxyGet_. + * @param {!Object} localTarget + * @param {string} targetName + * @param {string} name + * @return {?} + * @private + */ + getLocalOrRemote_(localTarget, targetName, name) { + if (!this.sender_.isCasting()) { + const value = localTarget[name]; + if (typeof value == 'function') { + // eslint-disable-next-line no-restricted-syntax + return value.bind(localTarget); + } + return value; + } + // getCurrentAd is special: it must return the proxy, not the local ad. + if (name == 'getCurrentAd') { + return () => this.currentAdProxy_; + } + return this.sender_.get(targetName, name); + } + /** * @param {string} name * @return {?} @@ -763,17 +773,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { } } - // Use local values and methods if we are not casting. - if (!this.sender_.isCasting()) { - let value = this.localVideo_[name]; - if (typeof value == 'function') { - // eslint-disable-next-line no-restricted-syntax - value = value.bind(this.localVideo_); - } - return value; - } - - return this.sender_.get('video', name); + return this.getLocalOrRemote_( + /** @type {!Object} */(this.localVideo_), 'video', name); } /** @@ -882,21 +883,21 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { if (!dontRecordCalls) { if (name == 'addThumbnailsTrack') { return (...args) => { - this.addThumbnailsTrackCalls_.push(args); + this.trackedCalls_.addThumbnailsTrack.push(args); return this.playerProxyGet_( name, /* dontRecordCalls= */ true)(...args); }; } if (name == 'addTextTrackAsync') { return (...args) => { - this.addTextTrackAsyncCalls_.push(args); + this.trackedCalls_.addTextTrackAsync.push(args); return this.playerProxyGet_( name, /* dontRecordCalls= */ true)(...args); }; } if (name == 'addChaptersTrack') { return (...args) => { - this.addChaptersTrackCalls_.push(args); + this.trackedCalls_.addChaptersTrack.push(args); return this.playerProxyGet_( name, /* dontRecordCalls= */ true)(...args); }; @@ -934,17 +935,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { } } - // Use local getters and methods if we are not casting. - if (!this.sender_.isCasting()) { - const value = /** @type {Object} */(this.localPlayer_)[name]; - if (typeof value != 'function') { - return value; - } - // eslint-disable-next-line no-restricted-syntax - return value.bind(this.localPlayer_); - } - - return this.sender_.get('player', name); + return this.getLocalOrRemote_( + /** @type {!Object} */(this.localPlayer_), 'player', name); } /** @@ -985,21 +977,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { }; } - // Use local getters and methods if we are not casting. - if (!this.sender_.isCasting()) { - const value = /** @type {Object} */(this.localAdManager_)[name]; - if (typeof value != 'function') { - return value; - } - // eslint-disable-next-line no-restricted-syntax - return value.bind(this.localAdManager_); - } - - if (name == 'getCurrentAd') { - return () => this.currentAdProxy_; - } - - return this.sender_.get('adManager', name); + return this.getLocalOrRemote_( + /** @type {!Object} */(this.localAdManager_), 'adManager', name); } /** @@ -1070,9 +1049,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget { * Reset external tracks */ resetExternalTracks() { - this.addThumbnailsTrackCalls_ = []; - this.addTextTrackAsyncCalls_ = []; - this.addChaptersTrackCalls_ = []; + this.trackedCalls_.addThumbnailsTrack = []; + this.trackedCalls_.addTextTrackAsync = []; + this.trackedCalls_.addChaptersTrack = []; } /** diff --git a/lib/cast/cast_receiver.js b/lib/cast/cast_receiver.js index ac831d6b9..29c3b2150 100644 --- a/lib/cast/cast_receiver.js +++ b/lib/cast/cast_receiver.js @@ -173,11 +173,7 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget { * @export */ setContentTitle(title) { - if (!this.metadata_) { - this.metadata_ = { - 'metadataType': cast.receiver.media.MetadataType.GENERIC, - }; - } + this.ensureMetadata_(); this.metadata_['title'] = title; } @@ -189,11 +185,7 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget { * @export */ setContentImage(imageUrl) { - if (!this.metadata_) { - this.metadata_ = { - 'metadataType': cast.receiver.media.MetadataType.GENERIC, - }; - } + this.ensureMetadata_(); this.metadata_['images'] = [ { 'url': imageUrl, @@ -210,14 +202,24 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget { * @export */ setContentArtist(artist) { - if (!this.metadata_) { - this.metadata_ = {}; - } + this.ensureMetadata_(); this.metadata_['artist'] = artist; this.metadata_['metadataType'] = cast.receiver.media.MetadataType.MUSIC_TRACK; } + /** + * Ensures this.metadata_ exists, initialising it with GENERIC type if not. + * @private + */ + ensureMetadata_() { + if (!this.metadata_) { + this.metadata_ = { + 'metadataType': cast.receiver.media.MetadataType.GENERIC, + }; + } + } + /** * Destroys the underlying Player, then terminates the cast receiver app. * diff --git a/lib/cast/cast_sender.js b/lib/cast/cast_sender.js index cb5d8cb1e..77938c823 100644 --- a/lib/cast/cast_sender.js +++ b/lib/cast/cast_sender.js @@ -347,52 +347,54 @@ shaka.cast.CastSender = class { targetName == 'adManager' || targetName == 'currentAd', 'Unexpected target name'); const CastUtils = shaka.cast.CastUtils; - if (targetName == 'video') { - if (CastUtils.VideoVoidMethods.includes(property)) { - return (...args) => this.remoteCall_(targetName, property, ...args); - } - } else if (targetName == 'player') { - if (CastUtils.PlayerGetterMethodsThatRequireLive.has(property)) { - const isLive = this.get('player', 'isLive')(); - goog.asserts.assert(isLive, - property + ' should be called on a live stream!'); - // If the property shouldn't exist, return a fake function so that the - // user doesn't call an undefined function and get a second error. - if (!isLive) { - return () => undefined; - } - } - if (CastUtils.PlayerVoidMethods.includes(property)) { - return (...args) => this.remoteCall_(targetName, property, ...args); - } - if (CastUtils.PlayerPromiseMethods.includes(property)) { - return (...args) => - this.remoteAsyncCall_(targetName, property, ...args); - } - if (CastUtils.PlayerGetterMethods.has(property) || - CastUtils.LargePlayerGetterMethods.has(property)) { - return () => this.propertyGetter_(targetName, property); - } - } else if (targetName == 'adManager') { - if (CastUtils.AdManagerVoidMethods.includes(property)) { - return (...args) => this.remoteCall_(targetName, property, ...args); - } - if (CastUtils.AdManagerPromiseMethods.includes(property)) { - return (...args) => - this.remoteAsyncCall_(targetName, property, ...args); - } - if (CastUtils.LargeAdManagerGetterMethods.has(property)) { - return () => this.propertyGetter_(targetName, property); - } - } else if (targetName == 'currentAd') { - if (CastUtils.CurrentAdVoidMethods.includes(property)) { - return (...args) => this.remoteCall_(targetName, property, ...args); - } - if (CastUtils.CurrentAdGetterMethods.has(property)) { - return () => this.propertyGetter_(targetName, property); + + // Config table: for each target, which lists/maps govern dispatch. + const config = { + 'video': { + voidMethods: CastUtils.VideoVoidMethods, + }, + 'player': { + voidMethods: CastUtils.PlayerVoidMethods, + promiseMethods: CastUtils.PlayerPromiseMethods, + getterMethods: [CastUtils.PlayerGetterMethods, + CastUtils.LargePlayerGetterMethods], + requiresLiveMap: CastUtils.PlayerGetterMethodsThatRequireLive, + }, + 'adManager': { + voidMethods: CastUtils.AdManagerVoidMethods, + promiseMethods: CastUtils.AdManagerPromiseMethods, + getterMethods: [CastUtils.LargeAdManagerGetterMethods], + }, + 'currentAd': { + voidMethods: CastUtils.CurrentAdVoidMethods, + getterMethods: [CastUtils.CurrentAdGetterMethods], + }, + }; + + const cfg = config[targetName]; + + // Live-only guard (player only). + if (cfg.requiresLiveMap && cfg.requiresLiveMap.has(property)) { + const isLive = this.get('player', 'isLive')(); + goog.asserts.assert(isLive, + property + ' should be called on a live stream!'); + if (!isLive) { + return () => undefined; } } + if (cfg.voidMethods && cfg.voidMethods.includes(property)) { + return (...args) => this.remoteCall_(targetName, property, ...args); + } + if (cfg.promiseMethods && cfg.promiseMethods.includes(property)) { + return (...args) => + this.remoteAsyncCall_(targetName, property, ...args); + } + if (cfg.getterMethods && + cfg.getterMethods.some((map) => map.has(property))) { + return () => this.propertyGetter_(targetName, property); + } + return this.propertyGetter_(targetName, property); }