From aa973af4103832b401c34682da3d862309c4e9dd Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Thu, 17 Nov 2022 13:27:18 -0800 Subject: [PATCH 01/15] Initial fix with temp debug logs. --- lib/polyfill/media_capabilities.js | 139 +++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 9 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 038bb72f19..1078a1fd41 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -96,37 +96,54 @@ shaka.polyfill.MediaCapabilities = class { return res; } + const videoConfig = mediaDecodingConfig['video']; + const audioConfig = mediaDecodingConfig['audio']; + if (mediaDecodingConfig.type == 'media-source') { if (!shaka.util.Platform.supportsMediaSource()) { return res; } // Use 'MediaSource.isTypeSupported' to check if the stream is supported. - if (mediaDecodingConfig['video']) { - const contentType = mediaDecodingConfig['video'].contentType; - const isSupported = MediaSource.isTypeSupported(contentType); + // Cast platforms will instead use 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; + if (shaka.util.Platform.isChromecast()) { + isSupported = shaka.polyfill.MediaCapabilities.canDisplayType_({ + contentType, + width: videoConfig.width, + height: videoConfig.height, + frameRate: videoConfig.frameRate, + transferFunction: videoConfig.transferFunction, + }); + } else { + isSupported = MediaSource.isTypeSupported(contentType); + } if (!isSupported) { return res; } } - if (mediaDecodingConfig['audio']) { - const contentType = mediaDecodingConfig['audio'].contentType; + if (audioConfig) { + const contentType = audioConfig.contentType; const isSupported = MediaSource.isTypeSupported(contentType); if (!isSupported) { return res; } } } else if (mediaDecodingConfig.type == 'file') { - if (mediaDecodingConfig['video']) { - const contentType = mediaDecodingConfig['video'].contentType; + if (videoConfig) { + const contentType = videoConfig.contentType; const isSupported = shaka.util.Platform.supportsMediaType(contentType); if (!isSupported) { return res; } } - if (mediaDecodingConfig['audio']) { - const contentType = mediaDecodingConfig['audio'].contentType; + if (audioConfig) { + const contentType = audioConfig.contentType; const isSupported = shaka.util.Platform.supportsMediaType(contentType); if (!isSupported) { return res; @@ -201,6 +218,110 @@ shaka.polyfill.MediaCapabilities = class { return res; } + + + /** + * Checks if the given media parameters of the video or audio streams are + * supported by the platform. + * @param {{ + * contentType: string, + * width: (number|undefined), + * height: (number|undefined), + * frameRate: (number|undefined), + * transferFunction: (string|undefined) + * }} options canDisplayType() options. + * contentType: A valid MIME type and (optionally) a codecs parameter. + * width: Describes the stream horizontal resolution in pixels. + * height: Describes the stream vertical resolution in pixels. + * frameRate: Describes the frame rate of the stream. + * transferFunction: Describes the video transfer function supported by + * the rendering capabilities of the user agent. + * @return {boolean} `true` when the stream can be displayed on a Cast device. + * @private + */ + static canDisplayType_({ + contentType, + width = undefined, + height = undefined, + frameRate = undefined, + transferFunction = undefined, + }) { + if (!cast.__platform__) { + return true; + } + let displayType = contentType; + if (width && height) { + displayType += `; width=${width}; height=${height}`; + } + if (frameRate) { + displayType += `; framerate=${frameRate}`; + } + if (transferFunction.toLowerCase() === 'pq') { + // Necessary to properly check for HDR support on Cast. + displayType += '; eotf=smpte2084'; + } + console.log(`#########################################################`); + console.log(`[JULIAN]: Checking canDisplayType for: ${displayType}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hev1.2.4.L153.B0"; width=1920; height=1080;' + + ' eotf="smpte2084"\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hev1.2.4.L153.B0"; width=1920; height=1080;' + + ' eotf="smpte2084"')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hev1.2.4.L153.B0"; width=3840; height=2160;' + + ' eotf="smpte2084"\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hev1.2.4.L153.B0"; width=3840; height=2160;' + + ' eotf="smpte2084"')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hev1.2.4.L153.B0"; width=1920; height=1080\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hev1.2.4.L153.B0"; width=1920; height=1080')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hev1.2.4.L153.B0"; width=3840; height=2160\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hev1.2.4.L153.B0"; width=3840; height=2160')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hev1.2.4.L153.B0"\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hev1.2.4.L153.B0"')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hev1.2.4.L153.B0; eotf="smpte2084"\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hev1.2.4.L153.B0"; eotf="smpte2084"')}`); + + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=1920; height=1080;' + + ' eotf="smpte2084"\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=1920; height=1080;' + + ' eotf="smpte2084"')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=3840; height=2160;' + + ' eotf="smpte2084"\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=3840; height=2160;' + + ' eotf="smpte2084"')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=1920; height=1080\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=1920; height=1080')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=3840; height=2160\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=3840; height=2160')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hvc1.2.4.L153.B0"\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hvc1.2.4.L153.B0"')}`); + console.log('[JULIAN]: canDisplayType(' + + 'video/mp4; codecs="hvc1.2.4.L153.B0; eotf="smpte2084"\') => ' + + `${cast.__platform__.canDisplayType( + 'video/mp4; codecs="hvc1.2.4.L153.B0"; eotf="smpte2084"')}`); + console.log(`#########################################################`); + return cast.__platform__.canDisplayType(displayType); + } }; /** From 0ee3554df48399c6935541f8ed35737861a0cde8 Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Fri, 18 Nov 2022 11:48:47 -0800 Subject: [PATCH 02/15] Removed console debug logs after verifying eotf param on a 4K HDR Roku TV. --- lib/polyfill/media_capabilities.js | 60 ------------------------------ 1 file changed, 60 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 1078a1fd41..36a79abc7e 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -260,66 +260,6 @@ shaka.polyfill.MediaCapabilities = class { // Necessary to properly check for HDR support on Cast. displayType += '; eotf=smpte2084'; } - console.log(`#########################################################`); - console.log(`[JULIAN]: Checking canDisplayType for: ${displayType}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hev1.2.4.L153.B0"; width=1920; height=1080;' + - ' eotf="smpte2084"\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hev1.2.4.L153.B0"; width=1920; height=1080;' + - ' eotf="smpte2084"')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hev1.2.4.L153.B0"; width=3840; height=2160;' + - ' eotf="smpte2084"\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hev1.2.4.L153.B0"; width=3840; height=2160;' + - ' eotf="smpte2084"')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hev1.2.4.L153.B0"; width=1920; height=1080\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hev1.2.4.L153.B0"; width=1920; height=1080')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hev1.2.4.L153.B0"; width=3840; height=2160\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hev1.2.4.L153.B0"; width=3840; height=2160')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hev1.2.4.L153.B0"\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hev1.2.4.L153.B0"')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hev1.2.4.L153.B0; eotf="smpte2084"\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hev1.2.4.L153.B0"; eotf="smpte2084"')}`); - - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=1920; height=1080;' + - ' eotf="smpte2084"\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=1920; height=1080;' + - ' eotf="smpte2084"')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=3840; height=2160;' + - ' eotf="smpte2084"\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=3840; height=2160;' + - ' eotf="smpte2084"')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=1920; height=1080\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=1920; height=1080')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=3840; height=2160\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hvc1.2.4.L153.B0"; width=3840; height=2160')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hvc1.2.4.L153.B0"\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hvc1.2.4.L153.B0"')}`); - console.log('[JULIAN]: canDisplayType(' + - 'video/mp4; codecs="hvc1.2.4.L153.B0; eotf="smpte2084"\') => ' + - `${cast.__platform__.canDisplayType( - 'video/mp4; codecs="hvc1.2.4.L153.B0"; eotf="smpte2084"')}`); - console.log(`#########################################################`); return cast.__platform__.canDisplayType(displayType); } }; From 4873f279505745a2051625c18647636043d5c7b1 Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Fri, 18 Nov 2022 15:58:34 -0800 Subject: [PATCH 03/15] Updated comment on smpte2048 check. --- lib/polyfill/media_capabilities.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 36a79abc7e..06fb6f7d7a 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -257,7 +257,9 @@ shaka.polyfill.MediaCapabilities = class { displayType += `; framerate=${frameRate}`; } if (transferFunction.toLowerCase() === 'pq') { - // Necessary to properly check for HDR support on Cast. + // A "PQ" transfer function indicates this is an HDR-capable stream; + // "smpte2084" is the published standard. We need to inform the platform + // this query is specifically for HDR. displayType += '; eotf=smpte2084'; } return cast.__platform__.canDisplayType(displayType); From ef09c30ac2beb881202a667f35fa5c7450a42056 Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Fri, 18 Nov 2022 16:20:04 -0800 Subject: [PATCH 04/15] Added existence check for transferFunction parameter. --- lib/polyfill/media_capabilities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 77d7d9b731..347fd866f4 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -270,7 +270,7 @@ shaka.polyfill.MediaCapabilities = class { if (frameRate) { displayType += `; framerate=${frameRate}`; } - if (transferFunction.toLowerCase() === 'pq') { + if (transferFunction && transferFunction.toLowerCase() === 'pq') { // A "PQ" transfer function indicates this is an HDR-capable stream; // "smpte2084" is the published standard. We need to inform the platform // this query is specifically for HDR. From 51a96ea94d0007f5b806099e562947d5f88b4c0e Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Fri, 18 Nov 2022 18:55:14 -0800 Subject: [PATCH 05/15] Handled review comments (iter #0) and added unit tests. Also corrected capitalization error for the 'framerate' parameter. --- lib/polyfill/media_capabilities.js | 22 +++++--- lib/util/error.js | 7 +++ test/polyfill/media_capabilities_unit.js | 64 ++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 347fd866f4..0cda1ef669 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -8,6 +8,7 @@ goog.provide('shaka.polyfill.MediaCapabilities'); goog.require('shaka.log'); goog.require('shaka.polyfill'); +goog.require('shaka.util.Error'); goog.require('shaka.util.Platform'); @@ -111,11 +112,11 @@ shaka.polyfill.MediaCapabilities = class { const contentType = videoConfig.contentType; let isSupported; if (shaka.util.Platform.isChromecast()) { - isSupported = shaka.polyfill.MediaCapabilities.canDisplayType_({ + isSupported = shaka.polyfill.MediaCapabilities.canCastDisplayType_({ contentType, width: videoConfig.width, height: videoConfig.height, - frameRate: videoConfig.frameRate, + frameRate: videoConfig.framerate, transferFunction: videoConfig.transferFunction, }); } else { @@ -236,14 +237,14 @@ shaka.polyfill.MediaCapabilities = class { /** * Checks if the given media parameters of the video or audio streams are - * supported by the platform. + * supported by the Cast platform. * @param {{ * contentType: string, * width: (number|undefined), * height: (number|undefined), * frameRate: (number|undefined), * transferFunction: (string|undefined) - * }} options canDisplayType() options. + * }} options canCastDisplayType() options. * contentType: A valid MIME type and (optionally) a codecs parameter. * width: Describes the stream horizontal resolution in pixels. * height: Describes the stream vertical resolution in pixels. @@ -253,15 +254,20 @@ shaka.polyfill.MediaCapabilities = class { * @return {boolean} `true` when the stream can be displayed on a Cast device. * @private */ - static canDisplayType_({ + static canCastDisplayType_({ contentType, width = undefined, height = undefined, frameRate = undefined, transferFunction = undefined, }) { - if (!cast.__platform__) { - return true; + if (!(window.cast && cast.__platform__ && + cast.__platform__.canDisplayType)) { + 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); } let displayType = contentType; if (width && height) { @@ -270,7 +276,7 @@ shaka.polyfill.MediaCapabilities = class { if (frameRate) { displayType += `; framerate=${frameRate}`; } - if (transferFunction && transferFunction.toLowerCase() === 'pq') { + if (transferFunction === 'pq') { // A "PQ" transfer function indicates this is an HDR-capable stream; // "smpte2084" is the published standard. We need to inform the platform // this query is specifically for HDR. diff --git a/lib/util/error.js b/lib/util/error.js index 19152361b5..d0650514fd 100644 --- a/lib/util/error.js +++ b/lib/util/error.js @@ -932,6 +932,13 @@ 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 b709c93fd7..b0e6898631 100644 --- a/test/polyfill/media_capabilities_unit.js +++ b/test/polyfill/media_capabilities_unit.js @@ -5,6 +5,7 @@ */ describe('MediaCapabilities', () => { + const Util = shaka.test.Util; const originalVendor = navigator.vendor; const originalUserAgent = navigator.userAgent; const originalRequestMediaKeySystemAccess = @@ -12,6 +13,8 @@ describe('MediaCapabilities', () => { const originalMediaCapabilities = navigator.mediaCapabilities; /** @type {MediaDecodingConfiguration} */ let mockDecodingConfig; + /** @type {!jasmine.Spy} */ + let mockCanDisplayType; beforeAll(() => { Object.defineProperty(window['navigator'], @@ -64,6 +67,9 @@ describe('MediaCapabilities', () => { }, }; shaka.polyfill.MediaCapabilities.memoizedMediaKeySystemAccessRequests_ = {}; + + mockCanDisplayType = jasmine.createSpy('canDisplayType'); + mockCanDisplayType.and.returnValue(false); }); afterAll(() => { @@ -172,5 +178,63 @@ describe('MediaCapabilities', () => { expect(requestKeySystemAccessSpy) .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. + 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)); + const isTypeSupportedSpy = + spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true); + + shaka.polyfill.MediaCapabilities.install(); + await expectAsync( + navigator.mediaCapabilities.decodingInfo(mockDecodingConfig)). + toBeRejectedWith(expected); + }); + + 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 + // knows about lots of Cast-specific APIs we aren't mocking. We + // don't need this mock strictly type-checked. + window['cast'] = { + __platform__: {canDisplayType: mockCanDisplayType}, + }; + const isChromecastSpy = + spyOn(shaka['util']['Platform'], + 'isChromecast').and.returnValue(true); + const isTypeSupportedSpy = + spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true); + + // Tests an HDR stream's extended MIME type is correctly provided. + mockDecodingConfig.video.transferFunction = 'pq'; + mockDecodingConfig.video.contentType = + 'video/mp4; codecs="hev1.2.4.L153.B0"'; + mockCanDisplayType.and.callFake((type) => { + expect(type).toBe( + 'video/mp4; ' + + 'codecs="hev1.2.4.L153.B0"; ' + + 'width=512; ' + + 'height=288; ' + + 'framerate=23.976023976023978; ' + + 'eotf=smpte2084'); + return true; + }); + + shaka.polyfill.MediaCapabilities.install(); + await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig); + + // Called once for audioConfig. Resolution, frame rate, and EOTF aren't + // applicable for audio, so isTypeSupported() is sufficient. + expect(isTypeSupportedSpy).toHaveBeenCalledTimes(1); + // Called once for videoConfig. + expect(mockCanDisplayType).toHaveBeenCalledTimes(1); + }); }); }); From 9588678e5a04eef07c5ad2c66c505bed3cf663ca Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Fri, 18 Nov 2022 19:12:31 -0800 Subject: [PATCH 06/15] Fixed unit test linting errors. --- lib/polyfill/media_capabilities.js | 6 +++--- test/polyfill/media_capabilities_unit.js | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 0cda1ef669..0ca1e751ac 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -265,9 +265,9 @@ shaka.polyfill.MediaCapabilities = class { cast.__platform__.canDisplayType)) { 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.Severity.CRITICAL, + shaka.util.Error.Category.CAST, + shaka.util.Error.Code.CAST_UNEXPECTED_PLATFORM); } let displayType = contentType; if (width && height) { diff --git a/test/polyfill/media_capabilities_unit.js b/test/polyfill/media_capabilities_unit.js index b0e6898631..c2f353c255 100644 --- a/test/polyfill/media_capabilities_unit.js +++ b/test/polyfill/media_capabilities_unit.js @@ -194,8 +194,14 @@ describe('MediaCapabilities', () => { shaka.polyfill.MediaCapabilities.install(); await expectAsync( - navigator.mediaCapabilities.decodingInfo(mockDecodingConfig)). - toBeRejectedWith(expected); + navigator.mediaCapabilities.decodingInfo(mockDecodingConfig)) + .toBeRejectedWith(expected); + + // 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('should use cast.__platform__.canDisplayType for "supported" field ' + @@ -230,10 +236,12 @@ describe('MediaCapabilities', () => { shaka.polyfill.MediaCapabilities.install(); await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig); - // Called once for audioConfig. Resolution, frame rate, and EOTF aren't - // applicable for audio, so isTypeSupported() is sufficient. + // 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 videoConfig. + // Called once for mockDecodingConfig.video. expect(mockCanDisplayType).toHaveBeenCalledTimes(1); }); }); From b60d59a0d9a6bdcebb92236be8cb0a993d8fe2cb Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Fri, 18 Nov 2022 19:46:17 -0800 Subject: [PATCH 07/15] Restored cast namespace after polyfill test executions. --- lib/polyfill/media_capabilities.js | 2 +- test/polyfill/media_capabilities_unit.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 0ca1e751ac..22ba184221 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -261,7 +261,7 @@ shaka.polyfill.MediaCapabilities = class { frameRate = undefined, transferFunction = undefined, }) { - if (!(window.cast && cast.__platform__ && + if (!(cast && cast.__platform__ && cast.__platform__.canDisplayType)) { shaka.log.error('Expected cast namespace to be available!'); throw new shaka.util.Error( diff --git a/test/polyfill/media_capabilities_unit.js b/test/polyfill/media_capabilities_unit.js index c2f353c255..a77fb295eb 100644 --- a/test/polyfill/media_capabilities_unit.js +++ b/test/polyfill/media_capabilities_unit.js @@ -11,6 +11,8 @@ describe('MediaCapabilities', () => { const originalRequestMediaKeySystemAccess = navigator.requestMediaKeySystemAccess; const originalMediaCapabilities = navigator.mediaCapabilities; + const originalCast = window['cast']; + /** @type {MediaDecodingConfiguration} */ let mockDecodingConfig; /** @type {!jasmine.Spy} */ @@ -73,6 +75,7 @@ describe('MediaCapabilities', () => { }); afterAll(() => { + window['cast'] = originalCast; Object.defineProperty(window['navigator'], 'userAgent', {value: originalUserAgent}); Object.defineProperty(window['navigator'], From bf81981d1e9827c415ea0f49df8bb3b9513ff7fb Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Fri, 18 Nov 2022 23:06:53 -0800 Subject: [PATCH 08/15] Use window.cast instead of cast for namespace checking. --- lib/polyfill/media_capabilities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 22ba184221..0ca1e751ac 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -261,7 +261,7 @@ shaka.polyfill.MediaCapabilities = class { frameRate = undefined, transferFunction = undefined, }) { - if (!(cast && cast.__platform__ && + if (!(window.cast && cast.__platform__ && cast.__platform__.canDisplayType)) { shaka.log.error('Expected cast namespace to be available!'); throw new shaka.util.Error( From 279b029a5682b77769978c03811a88c820735c0f Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Mon, 21 Nov 2022 17:11:59 -0800 Subject: [PATCH 09/15] Sanity check to confirm issue with CDT() support on Selenium lab test Chromecast. --- lib/polyfill/media_capabilities.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 0ca1e751ac..3a6bd7e679 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -110,18 +110,19 @@ shaka.polyfill.MediaCapabilities = class { // See: https://github.com/shaka-project/shaka-player/issues/4726 if (videoConfig) { const contentType = videoConfig.contentType; - let isSupported; - if (shaka.util.Platform.isChromecast()) { - isSupported = shaka.polyfill.MediaCapabilities.canCastDisplayType_({ - contentType, - width: videoConfig.width, - height: videoConfig.height, - frameRate: videoConfig.framerate, - transferFunction: videoConfig.transferFunction, - }); - } else { - isSupported = MediaSource.isTypeSupported(contentType); - } + const isSupported = MediaSource.isTypeSupported(contentType); + // let isSupported; + // if (shaka.util.Platform.isChromecast()) { + // 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; } From 1dd07227b2154639b8daaabcabb3f125d476f172 Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Mon, 21 Nov 2022 17:58:22 -0800 Subject: [PATCH 10/15] Updated canDisplayType() checks to guard against instable implementations. --- lib/polyfill/media_capabilities.js | 39 ++++++++++++++++++------------ 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 3a6bd7e679..5ffa3fffdc 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -105,24 +105,31 @@ shaka.polyfill.MediaCapabilities = class { return res; } // Use 'MediaSource.isTypeSupported' to check if the stream is supported. - // Cast platforms will instead use canDisplayType() which accepts extended - // MIME type parameters. - // See: https://github.com/shaka-project/shaka-player/issues/4726 if (videoConfig) { const contentType = videoConfig.contentType; - const isSupported = MediaSource.isTypeSupported(contentType); - // let isSupported; - // if (shaka.util.Platform.isChromecast()) { - // isSupported = shaka.polyfill.MediaCapabilities.canCastDisplayType_({ - // contentType, - // width: videoConfig.width, - // height: videoConfig.height, - // frameRate: videoConfig.framerate, - // transferFunction: videoConfig.transferFunction, - // }); - // } else { - // isSupported = MediaSource.isTypeSupported(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, + }); + } + } if (!isSupported) { return res; } From 6a8cf51a13682443e0f35fdf31301a2ea2083023 Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Mon, 21 Nov 2022 18:35:12 -0800 Subject: [PATCH 11/15] 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); }); }); From d0701ea15f6662a6b9f6639f5f70784a7b09d4d4 Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Wed, 23 Nov 2022 09:37:09 -0800 Subject: [PATCH 12/15] Removed window.cast to trigger CAST_API_UNAVAILABLE error for lab chromecast tests. --- test/polyfill/media_capabilities_unit.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/polyfill/media_capabilities_unit.js b/test/polyfill/media_capabilities_unit.js index 532c45a4e9..a75f9f1f41 100644 --- a/test/polyfill/media_capabilities_unit.js +++ b/test/polyfill/media_capabilities_unit.js @@ -74,6 +74,10 @@ describe('MediaCapabilities', () => { mockCanDisplayType.and.returnValue(false); }); + afterEach(() => { + window['cast'] = originalCast; + }); + afterAll(() => { window['cast'] = originalCast; Object.defineProperty(window['navigator'], @@ -183,7 +187,8 @@ describe('MediaCapabilities', () => { }); it('throws when the cast namespace is not available', async () => { - // We don't set a mock cast namespace here to signal an error. + delete window['cast']; + const isChromecastSpy = spyOn(shaka['util']['Platform'], 'isChromecast').and.returnValue(true); From 720fe110167f8fece93481f329c555275428dcfa Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Wed, 23 Nov 2022 14:18:04 -0800 Subject: [PATCH 13/15] Verifying lab tests fail without check for cast.__platform__ and cast.__platform.__canDisplayType --- lib/polyfill/media_capabilities.js | 4 ---- test/polyfill/media_capabilities_unit.js | 24 ++---------------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index fc25add80a..f534cca8a8 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -267,10 +267,6 @@ shaka.polyfill.MediaCapabilities = class { shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.CAST, 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; diff --git a/test/polyfill/media_capabilities_unit.js b/test/polyfill/media_capabilities_unit.js index a75f9f1f41..ca659a69fb 100644 --- a/test/polyfill/media_capabilities_unit.js +++ b/test/polyfill/media_capabilities_unit.js @@ -187,6 +187,8 @@ describe('MediaCapabilities', () => { }); it('throws when the cast namespace is not available', async () => { + // Temporarily remove window.cast to trigger error. It's restored after + // every test. delete window['cast']; const isChromecastSpy = @@ -209,28 +211,6 @@ describe('MediaCapabilities', () => { expect(isChromecastSpy).toHaveBeenCalledTimes(2); }); - 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 From 8465a9931374036bc3c9d700c2a3b43dbec09d62 Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Wed, 23 Nov 2022 15:20:05 -0800 Subject: [PATCH 14/15] Re-instated deleted code from previous commit, which prevented cast lab failures. --- lib/polyfill/media_capabilities.js | 4 ++++ test/polyfill/media_capabilities_unit.js | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index f534cca8a8..fc25add80a 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -267,6 +267,10 @@ shaka.polyfill.MediaCapabilities = class { shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.CAST, 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; diff --git a/test/polyfill/media_capabilities_unit.js b/test/polyfill/media_capabilities_unit.js index ca659a69fb..4fe0c283bf 100644 --- a/test/polyfill/media_capabilities_unit.js +++ b/test/polyfill/media_capabilities_unit.js @@ -211,6 +211,28 @@ describe('MediaCapabilities', () => { expect(isChromecastSpy).toHaveBeenCalledTimes(2); }); + 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 From be1065a78d46f07fa9a5de05eaa81651d0390c2c Mon Sep 17 00:00:00 2001 From: JulianDomingo Date: Mon, 28 Nov 2022 19:37:55 -0800 Subject: [PATCH 15/15] Handling PR review comments. --- lib/polyfill/media_capabilities.js | 52 +++++++----------------- test/polyfill/media_capabilities_unit.js | 5 ++- 2 files changed, 19 insertions(+), 38 deletions(-) diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index fc25add80a..38bdceff70 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -109,18 +109,12 @@ shaka.polyfill.MediaCapabilities = class { // accepts extended MIME type parameters. // See: https://github.com/shaka-project/shaka-player/issues/4726 if (videoConfig) { - const contentType = videoConfig.contentType; let isSupported; if (shaka.util.Platform.isChromecast()) { - isSupported = shaka.polyfill.MediaCapabilities.canCastDisplayType_({ - contentType, - width: videoConfig.width, - height: videoConfig.height, - frameRate: videoConfig.framerate, - transferFunction: videoConfig.transferFunction, - }); + isSupported = + shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig); } else { - isSupported = MediaSource.isTypeSupported(contentType); + isSupported = MediaSource.isTypeSupported(videoConfig.contentType); } if (!isSupported) { return res; @@ -238,30 +232,13 @@ shaka.polyfill.MediaCapabilities = class { /** * Checks if the given media parameters of the video or audio streams are * supported by the Cast platform. - * @param {{ - * contentType: string, - * width: (number|undefined), - * height: (number|undefined), - * frameRate: (number|undefined), - * transferFunction: (string|undefined) - * }} options canCastDisplayType() options. - * contentType: A valid MIME type and (optionally) a codecs parameter. - * width: Describes the stream horizontal resolution in pixels. - * height: Describes the stream vertical resolution in pixels. - * frameRate: Describes the frame rate of the stream. - * transferFunction: Describes the video transfer function supported by - * the rendering capabilities of the user agent. + * @param {!VideoConfiguration} videoConfig The 'video' field of the + * MediaDecodingConfiguration. * @return {boolean} `true` when the stream can be displayed on a Cast device. * @private */ - static canCastDisplayType_({ - contentType, - width = undefined, - height = undefined, - frameRate = undefined, - transferFunction = undefined, - }) { - if (!(window.cast && cast)) { + static canCastDisplayType_(videoConfig) { + if (!(window.cast)) { shaka.log.error('Expected cast namespace to be available!'); throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, @@ -270,17 +247,18 @@ shaka.polyfill.MediaCapabilities = class { } 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); + return MediaSource.isTypeSupported(videoConfig.contentType); } - let displayType = contentType; - if (width && height) { - displayType += `; width=${width}; height=${height}`; + let displayType = videoConfig.contentType; + if (videoConfig.width && videoConfig.height) { + displayType += + `; width=${videoConfig.width}; height=${videoConfig.height}`; } - if (frameRate) { - displayType += `; framerate=${frameRate}`; + if (videoConfig.framerate) { + displayType += `; framerate=${videoConfig.framerate}`; } - if (transferFunction === 'pq') { + if (videoConfig.transferFunction === 'pq') { // A "PQ" transfer function indicates this is an HDR-capable stream; // "smpte2084" is the published standard. We need to inform the platform // this query is specifically for HDR. diff --git a/test/polyfill/media_capabilities_unit.js b/test/polyfill/media_capabilities_unit.js index 4fe0c283bf..cda21813eb 100644 --- a/test/polyfill/media_capabilities_unit.js +++ b/test/polyfill/media_capabilities_unit.js @@ -251,13 +251,16 @@ describe('MediaCapabilities', () => { mockDecodingConfig.video.transferFunction = 'pq'; mockDecodingConfig.video.contentType = 'video/mp4; codecs="hev1.2.4.L153.B0"'; + // Round to a whole number since we can't rely on number => string + // conversion precision on all devices. + mockDecodingConfig.video.framerate = 24; mockCanDisplayType.and.callFake((type) => { expect(type).toBe( 'video/mp4; ' + 'codecs="hev1.2.4.L153.B0"; ' + 'width=512; ' + 'height=288; ' + - 'framerate=23.976023976023978; ' + + 'framerate=24; ' + 'eotf=smpte2084'); return true; });