refactor(Cast): Simplify CastProxy internals (#10187)

This commit is contained in:
Álvaro Velad Galván
2026-06-08 15:17:37 +02:00
committed by GitHub
parent b8982567b9
commit 378011fa4e
3 changed files with 134 additions and 151 deletions
+74 -95
View File
@@ -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 = [];
}
/**
+15 -13
View File
@@ -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.
*
+45 -43
View File
@@ -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);
}