Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Expose the maximum hardware resolution through probeSupport() #6569

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ def check_tests(args):
[closure_base_js]))
files.add(os.path.join(base, 'demo', 'common', 'asset.js'))
files.add(os.path.join(base, 'demo', 'common', 'assets.js'))
files.add(os.path.join(base, 'proxy-cast-platform.js'))

localizations = compiler.GenerateLocalizations(None)
localizations.generate(args.force)
Expand Down
2 changes: 2 additions & 0 deletions build/conformance.textproto
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ requirement {
whitelist_regexp: "demo/"
whitelist_regexp: "test/"
whitelist_regexp: "node_modules/"
whitelist_regexp: "proxy-cast-platform.js"

# This global variable is generated by Google-internal tooling, and should be
# allowed. It will not end up in the compiled code, only at an intermediate
Expand Down Expand Up @@ -286,6 +287,7 @@ requirement: {
"shaka.util.Timer instead."
whitelist_regexp: "demo/"
whitelist_regexp: "test/"
whitelist_regexp: "proxy-cast-platform.js"
}

# Disallow eval, except when testing for modern JS syntax in demo
Expand Down
21 changes: 20 additions & 1 deletion externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,8 @@ shaka.extern.DrmSupportType;
* @typedef {{
* manifest: !Object.<string, boolean>,
* media: !Object.<string, boolean>,
* drm: !Object.<string, ?shaka.extern.DrmSupportType>
* drm: !Object.<string, ?shaka.extern.DrmSupportType>,
* hardwareResolution: shaka.extern.Resolution
* }}
*
* @description
Expand All @@ -471,6 +472,10 @@ shaka.extern.DrmSupportType;
* A map of supported key systems.
* The keys are the key system names. The value is <code>null</code> if it is
* not supported. Key systems not probed will not be in this dictionary.
* @property {shaka.extern.Resolution} hardwareResolution
* The maximum detected hardware resolution, which may have
* height==width==Infinity for devices without a maximum resolution or
* without a way to detect the maximum.
*
* @exportDoc
*/
Expand Down Expand Up @@ -1992,3 +1997,17 @@ shaka.extern.Thumbnail;
* @exportDoc
*/
shaka.extern.Chapter;

/**
* @typedef {{
* width: number,
* height: number
* }}
*
* @property {number} width
* Width in pixels.
* @property {number} height
* Height in pixels.
* @exportDoc
*/
shaka.extern.Resolution;
2 changes: 1 addition & 1 deletion externs/tizen.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ webapis.systeminfo = {};


/**
* @return {{width: number, height: number}}
* @return {shaka.extern.Resolution}
*/
webapis.systeminfo.getMaxVideoResolution = function() {};

Expand Down
3 changes: 3 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ module.exports = (config) => {
// test utilities next, which fill in that namespace
'test/test/util/*.js',

// Proxy cast.__platform__ methods across frames, necessary in testing
'proxy-cast-platform.js',

// bootstrapping for the test suite last; this will load the actual tests
'test/test/boot.js',

Expand Down
4 changes: 2 additions & 2 deletions lib/media/manifest_filterer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ goog.require('shaka.util.Error');
shaka.media.ManifestFilterer = class {
/**
* @param {?shaka.extern.PlayerConfiguration} config
* @param {{width: number, height: number}} maxHwRes
* @param {shaka.extern.Resolution} maxHwRes
* @param {?shaka.media.DrmEngine} drmEngine
*/
constructor(config, maxHwRes, drmEngine) {
Expand All @@ -28,7 +28,7 @@ shaka.media.ManifestFilterer = class {
/** @private {!shaka.extern.PlayerConfiguration} */
this.config_ = config;

/** @private {{width: number, height: number}} */
/** @private {shaka.extern.Resolution} */
this.maxHwRes_ = maxHwRes;

/** @private {?shaka.media.DrmEngine} drmEngine */
Expand Down
13 changes: 9 additions & 4 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
*/
this.lastTextFactory_;

/** @private {{width: number, height: number}} */
/** @private {shaka.extern.Resolution} */
this.maxHwRes_ = {width: Infinity, height: Infinity};

/** @private {!shaka.media.ManifestFilterer} */
Expand Down Expand Up @@ -1015,10 +1015,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
const manifest = shaka.media.ManifestParser.probeSupport();
const media = shaka.media.MediaSourceEngine.probeSupport();
const hardwareResolution =
await shaka.util.Platform.detectMaxHardwareResolution();

/** @type {shaka.extern.SupportType} */
const ret = {
manifest: manifest,
media: media,
drm: drm,
manifest,
media,
drm,
hardwareResolution,
};

const plugins = shaka.Player.supportPlugins_;
Expand Down
4 changes: 3 additions & 1 deletion lib/util/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -600,10 +600,12 @@ shaka.util.Platform = class {
/**
* Detect the maximum resolution that the platform's hardware can handle.
*
* @return {!Promise.<{width: number, height: number}>}
* @return {!Promise.<shaka.extern.Resolution>}
*/
static async detectMaxHardwareResolution() {
const Platform = shaka.util.Platform;

/** @type {shaka.extern.Resolution} */
const maxResolution = {
width: Infinity,
height: Infinity,
Expand Down
6 changes: 3 additions & 3 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ shaka.util.StreamUtils = class {
*
* @param {!shaka.extern.Manifest} manifest
* @param {shaka.extern.Restrictions} restrictions
* @param {{width: number, height:number}} maxHwResolution
* @param {shaka.extern.Resolution} maxHwResolution
*/
static filterByRestrictions(manifest, restrictions, maxHwResolution) {
manifest.variants = manifest.variants.filter((variant) => {
Expand All @@ -245,7 +245,7 @@ shaka.util.StreamUtils = class {
* @param {shaka.extern.Variant} variant
* @param {shaka.extern.Restrictions} restrictions
* Configured restrictions from the user.
* @param {{width: number, height: number}} maxHwRes
* @param {shaka.extern.Resolution} maxHwRes
* The maximum resolution the hardware can handle.
* This is applied separately from user restrictions because the setting
* should not be easily replaced by the user's configuration.
Expand Down Expand Up @@ -312,7 +312,7 @@ shaka.util.StreamUtils = class {
/**
* @param {!Array.<shaka.extern.Variant>} variants
* @param {shaka.extern.Restrictions} restrictions
* @param {{width: number, height: number}} maxHwRes
* @param {shaka.extern.Resolution} maxHwRes
* @return {boolean} Whether the tracks changed.
*/
static applyRestrictions(variants, restrictions, maxHwRes) {
Expand Down
138 changes: 138 additions & 0 deletions proxy-cast-platform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview Proxy cast platform methods across frames. Shared between the
* test environment and support.html.
*/

/**
* Patch Cast's cast.__platform__.canDisplayType to allow it to operate across
* frames and origins in our testing environment.
*
* The Cast runtime only exposes cast.__platform__ on the top window, not
* iframes embedded within it. However, both Chromecast WebDriver Server in
* our lab and the test runner Karma use iframes, and there are three different
* origins involved: WebDriver Server's receiver at github.io, Karma's
* top-level, and an inner frame of Karma that is raw HTML as text with no
* origin.
*
* With all of these complexities, the only way to access cast.__platform__ is
* asynchronously via postMessage. This means all callers of
* cast.__platform__.canDisplayType must use `await`, even though the
* underlying method is synchronous. If the caller is running in the top frame
* (as in a real receiver), `await` will do no harm. If the caller is running
* inside our tests in Karma, the `await` is critical to access __platform__
* via this shim.
*/
function proxyCastCanDisplayType() {
if (!navigator.userAgent.includes('CrKey')) {
// Not Chromecast, do nothing.
return;
}

// Create the namespaces if needed.
if (!window.cast) {
window['cast'] = {};
}
if (!cast.__platform__) {
cast['__platform__'] = {};
}

if (cast.__platform__.canDisplayType) {
// Already exists, do nothing.
return;
}

// Create an async shim. Calls to canDisplayType will be translated into
// async messages to the top frame, which will then execute the method and
// post a message back with results (or an error). The resolve/reject
// functions for the shim's returned Promise will be stored temporarily in
// these maps and matched up by request ID.
/** @type {!Map<number, function(?)>} */
const resolveMap = new Map();
/** @type {!Map<number, function(?)>} */
const rejectMap = new Map();
/** @type {number} */
let nextId = 0;

/**
* @typedef {{
* id: number,
* type: string,
* result: *,
* }}
*/
let CastShimMessage;

// Listen for message events for results/errors from the top frame.
window.addEventListener('message', (event) => {
const data = /** @type {CastShimMessage} */(event['data']);
console.log('Received cross-frame message', data);

if (data.type == 'cast.__platform__:result') {
// Find the matching resolve function and resolve the promise for this
// request.
const resolve = resolveMap.get(data.id);
if (resolve) {
resolve(data.result);

// Clear both resolve and reject from the maps for this ID.
resolveMap.delete(data.id);
rejectMap.delete(data.id);
}
} else if (data.type == 'cast.__platform__:error') {
// Find the matching reject function and reject the promise for this
// request.
const reject = rejectMap.get(data.id);
if (reject) {
reject(data.result);

// Clear both resolve and reject from the maps for this ID.
resolveMap.delete(data.id);
rejectMap.delete(data.id);
}
}
});

// Shim canDisplayType to proxy the request up to the top frame.
cast.__platform__.canDisplayType = /** @type {?} */(castCanDisplayTypeShim);

/**
* @param {string} type
* @return {!Promise<boolean>}
*/
function castCanDisplayTypeShim(type) {
return new Promise((resolve, reject) => {
// Craft a message for the top frame to execute this method for us.
const message = {
id: nextId++,
type: 'cast.__platform__',
command: 'canDisplayType',
args: Array.from(arguments),
};

// Store the resolve and reject functions so we can act on results/errors
// later.
resolveMap.set(message.id, resolve);
rejectMap.set(message.id, reject);

// Reject after a 5s timeout. This can happen if we're running under an
// incompatible version of Chromecast WebDriver Server's receiver app.
setTimeout(() => {
reject(new Error('canDisplayType timeout!'));

// Clear both resolve and reject from the maps for this ID.
resolveMap.delete(message.id);
rejectMap.delete(message.id);
}, 5000);

// Send the message to the top frame.
console.log('Sending cross-frame message', message);
window.top.postMessage(message, '*');
});
}
}
3 changes: 3 additions & 0 deletions support.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

</style>
<script src="dist/shaka-player.compiled.js"></script>
<script src="proxy-cast-platform.js"></script>
<script>
function whenLoaded(fn) {
// IE 9 fires DOMContentLoaded, and enters the "interactive"
Expand Down Expand Up @@ -73,7 +74,9 @@
}

function doTest() {
proxyCastCanDisplayType();
shaka.polyfill.installAll();

if (shaka.Player.isBrowserSupported()) {
shaka.Player.probeSupport().then(function(support) {
printSupport(JSON.stringify(support, null, ' '));
Expand Down
Loading