From f625b35dae610c33c45e9c93489b04977f9a9f38 Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Mon, 21 Nov 2022 18:35:12 -0800 Subject: [PATCH] Changed cast namespace check to include both window.cast and cast. Fallback to isTypeSupported() if 'cast' is available, but canDisplayType() isn't. --- lib/polyfill/media_capabilities.js | 39 ++++++++++----------- test/polyfill/media_capabilities_unit.js | 43 ++++++++++++++++++------ 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 5ffa3fffdc5..7a6a9e93ce5 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -105,30 +105,20 @@ shaka.polyfill.MediaCapabilities = class { return res; } // Use 'MediaSource.isTypeSupported' to check if the stream is supported. + // Cast platforms will additionally check canDisplayType(), which + // accepts extended MIME type parameters. + // See: https://github.com/shaka-project/shaka-player/issues/4726 if (videoConfig) { const contentType = videoConfig.contentType; let isSupported = MediaSource.isTypeSupported(contentType); if (shaka.util.Platform.isChromecast()) { - // Cast platforms will additionally check canDisplayType(), which - // accepts extended MIME type parameters. - // There will be at most 2 calls: - // - The first call determines the stability of - // the API by validating a contentType input limited only to MIME type - // and codecs is identical to MediaSource.isTypeSupported(). - // - If the same result is observed, a second call will be executed - // containing resolution, frame rate, and transfer function support. - // See: https://github.com/shaka-project/shaka-player/issues/4726 - if (isSupported === - shaka.polyfill.MediaCapabilities.canCastDisplayType_({ - contentType})) { - isSupported = shaka.polyfill.MediaCapabilities.canCastDisplayType_({ - contentType, - width: videoConfig.width, - height: videoConfig.height, - frameRate: videoConfig.framerate, - transferFunction: videoConfig.transferFunction, - }); - } + isSupported = shaka.polyfill.MediaCapabilities.canCastDisplayType_({ + contentType, + width: videoConfig.width, + height: videoConfig.height, + frameRate: videoConfig.framerate, + transferFunction: videoConfig.transferFunction, + }); } if (!isSupported) { return res; @@ -269,14 +259,19 @@ shaka.polyfill.MediaCapabilities = class { frameRate = undefined, transferFunction = undefined, }) { - if (!(window.cast && cast.__platform__ && - cast.__platform__.canDisplayType)) { + if (!window.cast || !cast) { shaka.log.error('Expected cast namespace to be available!'); throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.CAST, shaka.util.Error.Code.CAST_UNEXPECTED_PLATFORM); } + if (!(cast.__platform__ && cast.__platform__.canDisplayType)) { + shaka.log.warning('Expected canDisplayType() API to be available!'); + // We don't have access to the required API, so fallback to + // MediaSource.isTypeSupported(). + return MediaSource.isTypeSupported(contentType); + } let displayType = contentType; if (width && height) { displayType += `; width=${width}; height=${height}`; diff --git a/test/polyfill/media_capabilities_unit.js b/test/polyfill/media_capabilities_unit.js index a77fb295ebb..60a077c62af 100644 --- a/test/polyfill/media_capabilities_unit.js +++ b/test/polyfill/media_capabilities_unit.js @@ -6,12 +6,12 @@ describe('MediaCapabilities', () => { const Util = shaka.test.Util; + const originalCast = window['cast']; const originalVendor = navigator.vendor; const originalUserAgent = navigator.userAgent; const originalRequestMediaKeySystemAccess = navigator.requestMediaKeySystemAccess; const originalMediaCapabilities = navigator.mediaCapabilities; - const originalCast = window['cast']; /** @type {MediaDecodingConfiguration} */ let mockDecodingConfig; @@ -182,9 +182,8 @@ describe('MediaCapabilities', () => { .toHaveBeenCalledTimes(1); }); - it('fails when the cast namespace is not available', async () => { - // Mock Shaka throwing an error from identifying an unexpected, non-Cast - // platform when performing Cast logic. + it('throws when the cast namespace is not available', async () => { + // We don't set window['cast'] here to signal error. const isChromecastSpy = spyOn(shaka['util']['Platform'], 'isChromecast').and.returnValue(true); @@ -202,11 +201,34 @@ describe('MediaCapabilities', () => { // 1 (during install()) + 1 (for video config check). expect(isChromecastSpy).toHaveBeenCalledTimes(2); - // Called for decodingConfig.audio. This is never reached because of the - // error throw. - expect(isTypeSupportedSpy).not.toHaveBeenCalled(); + // Called for decodingConfig.video. decodingConfig.audio is never reached + // due to the error thrown. + expect(isTypeSupportedSpy).toHaveBeenCalledTimes(1); }); + it('falls back to isTypeSupported() when canDisplayType() missing', + async () => { + // Only set window['cast'], not cast.__platform__ or + // cast.__platform__.canDisplayType(). + window['cast'] = {}; + const isChromecastSpy = + spyOn(shaka['util']['Platform'], + 'isChromecast').and.returnValue(true); + const isTypeSupportedSpy = + spyOn(window['MediaSource'], 'isTypeSupported') + .and.returnValue(true); + + shaka.polyfill.MediaCapabilities.install(); + await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig); + + expect(mockCanDisplayType).not.toHaveBeenCalled(); + // 1 (during install()) + 1 (for video config check). + expect(isChromecastSpy).toHaveBeenCalledTimes(2); + // 1 (mockDecodingConfig.video) + 1 (canCastDisplayType() fallback) + // + 1 (mockDecodingConfig.audio). + expect(isTypeSupportedSpy).toHaveBeenCalledTimes(3); + }); + it('should use cast.__platform__.canDisplayType for "supported" field ' + 'when platform is Cast', async () => { // We're using quotes to access window.cast because the compiler @@ -241,10 +263,9 @@ describe('MediaCapabilities', () => { // 1 (during install()) + 1 (for video config check). expect(isChromecastSpy).toHaveBeenCalledTimes(2); - // Called once for mockDecodingConfig.audio. Resolution, frame rate, and - // EOTF aren't applicable for audio, so isTypeSupported() is sufficient. - expect(isTypeSupportedSpy).toHaveBeenCalledTimes(1); - // Called once for mockDecodingConfig.video. + // 1 (mockDecodingConfig.video) + 1 (mockDecodingConfig.audio). + expect(isTypeSupportedSpy).toHaveBeenCalledTimes(2); + // Called once in canCastDisplayType. expect(mockCanDisplayType).toHaveBeenCalledTimes(1); }); });