Skip to content

Commit

Permalink
Fall back to MediaSource.isTypeSupported() if cast APIs aren't availa…
Browse files Browse the repository at this point in the history
…ble. Only throw an error if the entire cast namespace is unavailable.
  • Loading branch information
JulianDomingo committed Nov 23, 2022
1 parent 1dd0722 commit 6a8cf51
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 42 deletions.
44 changes: 20 additions & 24 deletions lib/polyfill/media_capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}`;
Expand Down
7 changes: 0 additions & 7 deletions lib/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <br> 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,

Expand Down
40 changes: 29 additions & 11 deletions test/polyfill/media_capabilities_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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
Expand Down Expand Up @@ -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);
});
});
Expand Down

0 comments on commit 6a8cf51

Please sign in to comment.