From 6a8cf51a13682443e0f35fdf31301a2ea2083023 Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Mon, 21 Nov 2022 18:35:12 -0800 Subject: [PATCH] Fall back to MediaSource.isTypeSupported() if cast APIs aren't available. Only throw an error if the entire cast namespace is unavailable. --- lib/polyfill/media_capabilities.js | 44 +++++++++++------------- lib/util/error.js | 7 ---- test/polyfill/media_capabilities_unit.js | 40 +++++++++++++++------ 3 files changed, 49 insertions(+), 42 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 5ffa3fffdc..fc25add80a 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -105,30 +105,22 @@ 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); + let isSupported; 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, + }); + } else { + isSupported = MediaSource.isTypeSupported(contentType); } if (!isSupported) { return res; @@ -269,14 +261,18 @@ 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); + shaka.util.Error.Code.CAST_API_UNAVAILABLE); + } else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) { + shaka.log.warning('Expected cast APIs to be available! Falling back to ' + + 'MediaSource.isTypeSupported() for type support.'); + return MediaSource.isTypeSupported(contentType); } + let displayType = contentType; if (width && height) { displayType += `; width=${width}; height=${height}`; diff --git a/lib/util/error.js b/lib/util/error.js index d0650514fd..19152361b5 100644 --- a/lib/util/error.js +++ b/lib/util/error.js @@ -932,13 +932,6 @@ shaka.util.Error.Code = { */ 'CAST_RECEIVER_APP_UNAVAILABLE': 8006, - /** - * The Cast platform namespace was expected to be available, but wasn't. - *
This may be caused by Shaka failing to identify the correct platform - * through the userAgent string. - */ - 'CAST_UNEXPECTED_PLATFORM': 8007, - // RETIRED: CAST_RECEIVER_APP_ID_MISSING': 8007, diff --git a/test/polyfill/media_capabilities_unit.js b/test/polyfill/media_capabilities_unit.js index a77fb295eb..532c45a4e9 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,16 +182,15 @@ 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 a mock cast namespace here to signal an error. const isChromecastSpy = spyOn(shaka['util']['Platform'], 'isChromecast').and.returnValue(true); const expected = Util.jasmineError(new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.CAST, - shaka.util.Error.Code.CAST_UNEXPECTED_PLATFORM)); + shaka.util.Error.Code.CAST_API_UNAVAILABLE)); const isTypeSupportedSpy = spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true); @@ -200,13 +199,33 @@ describe('MediaCapabilities', () => { navigator.mediaCapabilities.decodingInfo(mockDecodingConfig)) .toBeRejectedWith(expected); + expect(isTypeSupportedSpy).not.toHaveBeenCalled(); // 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(); }); + it('falls back to isTypeSupported() when canDisplayType() missing', + async () => { + // We only set the cast namespace, but not the canDisplayType() API. + 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 (fallback in canCastDisplayType()) + + // 1 (mockDecodingConfig.audio). + expect(isTypeSupportedSpy).toHaveBeenCalledTimes(2); + }); + 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 +260,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. + // 1 (mockDecodingConfig.audio). expect(isTypeSupportedSpy).toHaveBeenCalledTimes(1); - // Called once for mockDecodingConfig.video. + // Called once in canCastDisplayType. expect(mockCanDisplayType).toHaveBeenCalledTimes(1); }); });