Skip to content

Commit

Permalink
test: Fix test environment access to cast.__platform__ (#6553)
Browse files Browse the repository at this point in the history
This shim requires the latest version of the Chromecast WebDriver Server's receiver app, which can receive messages and proxy async access to `cast.__platform__`.
  • Loading branch information
joeyparrish committed May 8, 2024
1 parent ed93987 commit 9c1e621
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 151 deletions.
283 changes: 150 additions & 133 deletions lib/polyfill/media_capabilities.js
Expand Up @@ -88,6 +88,7 @@ shaka.polyfill.MediaCapabilities = class {
* @private
*/
static async decodingInfo_(mediaDecodingConfig) {
/** @type {!MediaCapabilitiesDecodingInfo} */
const res = {
supported: false,
powerEfficient: true,
Expand All @@ -96,60 +97,26 @@ shaka.polyfill.MediaCapabilities = class {
configuration: mediaDecodingConfig,
};

if (!mediaDecodingConfig) {
return res;
}

const videoConfig = mediaDecodingConfig['video'];
const audioConfig = mediaDecodingConfig['audio'];

const Capabilities = shaka.media.Capabilities;

if (mediaDecodingConfig.type == 'media-source') {
if (!shaka.util.Platform.supportsMediaSource()) {
return res;
}
// Use 'shaka.media.Capabilities.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) {
let isSupported;
if (shaka.util.Platform.isChromecast()) {
isSupported =
shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
} else if (shaka.util.Platform.isTizen()) {
let extendedType = videoConfig.contentType;
if (videoConfig.width && videoConfig.height) {
extendedType += `; width=${videoConfig.width}`;
extendedType += `; height=${videoConfig.height}`;
}
if (videoConfig.framerate) {
extendedType += `; framerate=${videoConfig.framerate}`;
}
if (videoConfig.bitrate) {
extendedType += `; bitrate=${videoConfig.bitrate}`;
}
isSupported = Capabilities.isTypeSupported(extendedType);
} else {
isSupported = Capabilities.isTypeSupported(videoConfig.contentType);
}
const isSupported =
await shaka.polyfill.MediaCapabilities.checkVideoSupport_(
videoConfig);
if (!isSupported) {
return res;
}
}

if (audioConfig) {
let isSupported;
if (shaka.util.Platform.isChromecast() &&
audioConfig.spatialRendering) {
const extendedType =
audioConfig.contentType + '; spatialRendering=true';
isSupported = Capabilities.isTypeSupported(extendedType);
} else {
isSupported = Capabilities.isTypeSupported(audioConfig.contentType);
}
const isSupported =
shaka.polyfill.MediaCapabilities.checkAudioSupport_(audioConfig);
if (!isSupported) {
return res;
}
Expand Down Expand Up @@ -178,122 +145,172 @@ shaka.polyfill.MediaCapabilities = class {
if (!mediaDecodingConfig.keySystemConfiguration) {
// The variant is supported if it's unencrypted.
res.supported = true;
return Promise.resolve(res);
return res;
} else {
// Get the MediaKeySystemAccess for the key system.
// Convert the MediaDecodingConfiguration object to a
// MediaKeySystemConfiguration object.

/** @type {MediaCapabilitiesKeySystemConfiguration} */
const mediaCapkeySystemConfig =
mediaDecodingConfig.keySystemConfiguration;
const audioCapabilities = [];
const videoCapabilities = [];

if (mediaCapkeySystemConfig.audio) {
let capability = {
robustness: mediaCapkeySystemConfig.audio.robustness || '',
contentType: mediaDecodingConfig.audio.contentType,
};

// Some Tizen devices seem to misreport AC-3 support, but correctly
// report EC-3 support. So query EC-3 as a fallback for AC-3.
// See https://github.com/shaka-project/shaka-player/issues/2989 for
// details.
if (shaka.util.Platform.isTizen() &&
mediaDecodingConfig.audio.contentType.includes('codecs="ac-3"')) {
capability = {
robustness: capability.robustness,
contentType: 'audio/mp4; codecs="ec-3"',
};
}
const mcapKeySystemConfig = mediaDecodingConfig.keySystemConfiguration;
const keySystemAccess =
await shaka.polyfill.MediaCapabilities.checkDrmSupport_(
videoConfig, audioConfig, mcapKeySystemConfig);
if (keySystemAccess) {
res.supported = true;
res.keySystemAccess = keySystemAccess;
}
}

if (mediaCapkeySystemConfig.audio.encryptionScheme) {
capability = {
robustness: capability.robustness,
contentType: capability.contentType,
encryptionScheme: mediaCapkeySystemConfig.audio.encryptionScheme,
};
}
return res;
}

audioCapabilities.push(capability);
/**
* @param {!VideoConfiguration} videoConfig The 'video' field of the
* MediaDecodingConfiguration.
* @return {!Promise<boolean>}
* @private
*/
static async checkVideoSupport_(videoConfig) {
// Use 'shaka.media.Capabilities.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 (shaka.util.Platform.isChromecast()) {
const isSupported =
await shaka.polyfill.MediaCapabilities.canCastDisplayType_(
videoConfig);
return isSupported;
} else if (shaka.util.Platform.isTizen()) {
let extendedType = videoConfig.contentType;
if (videoConfig.width && videoConfig.height) {
extendedType += `; width=${videoConfig.width}`;
extendedType += `; height=${videoConfig.height}`;
}

if (mediaCapkeySystemConfig.video) {
let capability = {
robustness: mediaCapkeySystemConfig.video.robustness || '',
contentType: mediaDecodingConfig.video.contentType,
};
if (mediaCapkeySystemConfig.video.encryptionScheme) {
capability = {
robustness: capability.robustness,
contentType: capability.contentType,
encryptionScheme: mediaCapkeySystemConfig.video.encryptionScheme,
};
}
videoCapabilities.push(capability);
if (videoConfig.framerate) {
extendedType += `; framerate=${videoConfig.framerate}`;
}
if (videoConfig.bitrate) {
extendedType += `; bitrate=${videoConfig.bitrate}`;
}
return shaka.media.Capabilities.isTypeSupported(extendedType);
}
return shaka.media.Capabilities.isTypeSupported(videoConfig.contentType);
}

/**
* @param {!AudioConfiguration} audioConfig The 'audio' field of the
* MediaDecodingConfiguration.
* @return {boolean}
* @private
*/
static checkAudioSupport_(audioConfig) {
let extendedType = audioConfig.contentType;
if (shaka.util.Platform.isChromecast() && audioConfig.spatialRendering) {
extendedType += '; spatialRendering=true';
}
return shaka.media.Capabilities.isTypeSupported(extendedType);
}

/** @type {MediaKeySystemConfiguration} */
const mediaKeySystemConfig = {
initDataTypes: [mediaCapkeySystemConfig.initDataType],
distinctiveIdentifier: mediaCapkeySystemConfig.distinctiveIdentifier,
persistentState: mediaCapkeySystemConfig.persistentState,
sessionTypes: mediaCapkeySystemConfig.sessionTypes,
/**
* @param {VideoConfiguration} videoConfig The 'video' field of the
* MediaDecodingConfiguration.
* @param {AudioConfiguration} audioConfig The 'audio' field of the
* MediaDecodingConfiguration.
* @param {!MediaCapabilitiesKeySystemConfiguration} mcapKeySystemConfig The
* 'keySystemConfiguration' field of the MediaDecodingConfiguration.
* @return {Promise<MediaKeySystemAccess>}
* @private
*/
static async checkDrmSupport_(videoConfig, audioConfig, mcapKeySystemConfig) {
const audioCapabilities = [];
const videoCapabilities = [];

if (mcapKeySystemConfig.audio) {
const capability = {
robustness: mcapKeySystemConfig.audio.robustness || '',
contentType: audioConfig.contentType,
};

// Only add the audio video capabilities if they have valid data.
// Otherwise the query will fail.
if (audioCapabilities.length) {
mediaKeySystemConfig.audioCapabilities = audioCapabilities;
// Some Tizen devices seem to misreport AC-3 support, but correctly
// report EC-3 support. So query EC-3 as a fallback for AC-3.
// See https://github.com/shaka-project/shaka-player/issues/2989 for
// details.
if (shaka.util.Platform.isTizen() &&
audioConfig.contentType.includes('codecs="ac-3"')) {
capability.contentType = 'audio/mp4; codecs="ec-3"';
}
if (videoCapabilities.length) {
mediaKeySystemConfig.videoCapabilities = videoCapabilities;

if (mcapKeySystemConfig.audio.encryptionScheme) {
capability.encryptionScheme =
mcapKeySystemConfig.audio.encryptionScheme;
}

const cacheKey = shaka.polyfill.MediaCapabilities
.generateKeySystemCacheKey_(
mediaDecodingConfig.video ?
mediaDecodingConfig.video.contentType : '',
mediaDecodingConfig.audio ?
mediaDecodingConfig.audio.contentType : '',
mediaDecodingConfig.keySystemConfiguration.keySystem);

let keySystemAccess;
try {
if (cacheKey in shaka.polyfill.MediaCapabilities
.memoizedMediaKeySystemAccessRequests_) {
keySystemAccess = shaka.polyfill.MediaCapabilities
.memoizedMediaKeySystemAccessRequests_[cacheKey];
} else {
keySystemAccess = await navigator.requestMediaKeySystemAccess(
mediaCapkeySystemConfig.keySystem, [mediaKeySystemConfig]);
shaka.polyfill.MediaCapabilities
.memoizedMediaKeySystemAccessRequests_[cacheKey] =
keySystemAccess;
}
} catch (e) {
shaka.log.info('navigator.requestMediaKeySystemAccess failed.');
audioCapabilities.push(capability);
}

if (mcapKeySystemConfig.video) {
const capability = {
robustness: mcapKeySystemConfig.video.robustness || '',
contentType: videoConfig.contentType,
};
if (mcapKeySystemConfig.video.encryptionScheme) {
capability.encryptionScheme =
mcapKeySystemConfig.video.encryptionScheme;
}
videoCapabilities.push(capability);
}

if (keySystemAccess) {
res.supported = true;
res.keySystemAccess = keySystemAccess;
/** @type {MediaKeySystemConfiguration} */
const mediaKeySystemConfig = {
initDataTypes: [mcapKeySystemConfig.initDataType],
distinctiveIdentifier: mcapKeySystemConfig.distinctiveIdentifier,
persistentState: mcapKeySystemConfig.persistentState,
sessionTypes: mcapKeySystemConfig.sessionTypes,
};

// Only add audio / video capabilities if they have valid data.
// Otherwise the query will fail.
if (audioCapabilities.length) {
mediaKeySystemConfig.audioCapabilities = audioCapabilities;
}
if (videoCapabilities.length) {
mediaKeySystemConfig.videoCapabilities = videoCapabilities;
}

const cacheKey = shaka.polyfill.MediaCapabilities
.generateKeySystemCacheKey_(
videoConfig ? videoConfig.contentType : '',
audioConfig ? audioConfig.contentType : '',
mcapKeySystemConfig.keySystem);

/** @type {MediaKeySystemAccess} */
let keySystemAccess = null;
try {
if (cacheKey in shaka.polyfill.MediaCapabilities
.memoizedMediaKeySystemAccessRequests_) {
keySystemAccess = shaka.polyfill.MediaCapabilities
.memoizedMediaKeySystemAccessRequests_[cacheKey];
} else {
keySystemAccess = await navigator.requestMediaKeySystemAccess(
mcapKeySystemConfig.keySystem, [mediaKeySystemConfig]);
shaka.polyfill.MediaCapabilities
.memoizedMediaKeySystemAccessRequests_[cacheKey] =
keySystemAccess;
}
} catch (e) {
shaka.log.info('navigator.requestMediaKeySystemAccess failed.');
}

return res;
return keySystemAccess;
}

/**
* Checks if the given media parameters of the video or audio streams are
* supported by the Cast platform.
* @param {!VideoConfiguration} videoConfig The 'video' field of the
* MediaDecodingConfiguration.
* @return {boolean} `true` when the stream can be displayed on a Cast device.
* MediaDecodingConfiguration.
* @return {!Promise<boolean>} `true` when the stream can be displayed on a
* Cast device.
* @private
*/
static canCastDisplayType_(videoConfig) {
static async canCastDisplayType_(videoConfig) {
if (!(window.cast &&
cast.__platform__ && cast.__platform__.canDisplayType)) {
shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
Expand Down Expand Up @@ -321,7 +338,7 @@ shaka.polyfill.MediaCapabilities = class {
result = shaka.polyfill.MediaCapabilities
.memoizedCanDisplayTypeRequests_[displayType];
} else {
result = cast.__platform__.canDisplayType(displayType);
result = await cast.__platform__.canDisplayType(displayType);
shaka.polyfill.MediaCapabilities
.memoizedCanDisplayTypeRequests_[displayType] = result;
}
Expand Down
28 changes: 18 additions & 10 deletions lib/util/platform.js
Expand Up @@ -614,16 +614,24 @@ shaka.util.Platform = class {
// above 1080p. It would be a waste to select a higher res anyway, given
// that the device only outputs 1080p to begin with.
// Chromecast has an extension to query the device/display's resolution.
if (window.cast && cast.__platform__ &&
cast.__platform__.canDisplayType && cast.__platform__.canDisplayType(
'video/mp4; codecs="avc1.640028"; width=3840; height=2160')) {
// The device and display can both do 4k. Assume a 4k limit.
maxResolution.width = 3840;
maxResolution.height = 2160;
} else {
// Chromecast has always been able to do 1080p. Assume a 1080p limit.
maxResolution.width = 1920;
maxResolution.height = 1080;
const hasCanDisplayType = window.cast && cast.__platform__ &&
cast.__platform__.canDisplayType;

// Most Chromecasts can do 1080p.
maxResolution.width = 1920;
maxResolution.height = 1080;

try {
if (hasCanDisplayType && await cast.__platform__.canDisplayType(
'video/mp4; codecs="avc1.640028"; width=3840; height=2160')) {
// The device and display can both do 4k. Assume a 4k limit.
maxResolution.width = 3840;
maxResolution.height = 2160;
}
} catch (error) {
// This shouldn't generally happen. Log the error.
shaka.log.alwaysError('Failed to check canDisplayType:', error);
// Now ignore the error and let the 1080p default stand.
}
} else if (Platform.isTizen()) {
maxResolution.width = 1920;
Expand Down

0 comments on commit 9c1e621

Please sign in to comment.