From 2dea350d7483ba2c68b900e983a37562c15b8257 Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Mon, 6 May 2024 00:08:54 -0700 Subject: [PATCH] feat: Add support for probing encryption scheme support (#6506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will add encryption schemes to the DRM support report generated by probeSupport() and support.html. Related to shaka-project/eme-encryption-scheme-polyfill#62, PR #6484, and issue #1419. --------- Co-authored-by: Álvaro Velad Galván --- externs/shaka/player.js | 7 +- lib/media/drm_engine.js | 159 +++++++++++++++++++++-------- test/hls/hls_parser_integration.js | 7 +- 3 files changed, 127 insertions(+), 46 deletions(-) diff --git a/externs/shaka/player.js b/externs/shaka/player.js index df8f904725..9e087c4db6 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -436,11 +436,16 @@ shaka.extern.Restrictions; /** * @typedef {{ - * persistentState: boolean + * persistentState: boolean, + * encryptionSchemes: !Array * }} * * @property {boolean} persistentState * Whether this key system supports persistent state. + * @property {!Array} encryptionSchemes + * An array of encryption schemes that are reported to work, through either + * EME or MCap APIs. An empty array indicates that encryptionScheme queries + * are not supported. This should not happen if our polyfills are installed. * @exportDoc */ shaka.extern.DrmSupportType; diff --git a/lib/media/drm_engine.js b/lib/media/drm_engine.js index db9abf6e00..bf143864a8 100644 --- a/lib/media/drm_engine.js +++ b/lib/media/drm_engine.js @@ -21,6 +21,7 @@ goog.require('shaka.util.Lazy'); goog.require('shaka.util.ManifestParserUtils'); goog.require('shaka.util.MapUtils'); goog.require('shaka.util.MimeUtils'); +goog.require('shaka.util.ObjectUtils'); goog.require('shaka.util.Platform'); goog.require('shaka.util.Pssh'); goog.require('shaka.util.PublicPromise'); @@ -1810,24 +1811,127 @@ shaka.media.DrmEngine = class { {contentType: 'video/webm; codecs="vp8"'}, ]; - const basicConfig = { - initDataTypes: ['cenc'], + const basicConfigTemplate = { videoCapabilities: basicVideoCapabilities, - }; - const offlineConfig = { - videoCapabilities: basicVideoCapabilities, - persistentState: 'required', - sessionTypes: ['persistent-license'], + initDataTypes: ['cenc', 'sinf', 'skd', 'keyids'], }; - // Try the offline config first, then fall back to the basic config. - const configs = [offlineConfig, basicConfig]; + const testEncryptionSchemes = [ + null, + 'cenc', + 'cbcs', + 'cbcs-1-9', + ]; /** @type {!Map.} */ const support = new Map(); - const testSystem = async (keySystem) => { + /** + * @param {string} keySystem + * @param {MediaKeySystemAccess} access + * @return {!Promise} + */ + const processMediaKeySystemAccess = async (keySystem, access) => { + try { + await access.createMediaKeys(); + } catch (error) { + // In some cases, we can get a successful access object but fail to + // create a MediaKeys instance. When this happens, don't update the + // support structure. If a previous test succeeded, we won't overwrite + // any of the results. + return; + } + + // If sessionTypes is missing, assume no support for persistent-license. + const sessionTypes = access.getConfiguration().sessionTypes; + let persistentState = sessionTypes ? + sessionTypes.includes('persistent-license') : false; + + // Tizen 3.0 doesn't support persistent licenses, but reports that it + // does. It doesn't fail until you call update() with a license + // response, which is way too late. + // This is a work-around for #894. + if (shaka.util.Platform.isTizen3()) { + persistentState = false; + } + + const videoCapabilities = access.getConfiguration().videoCapabilities; + + let supportValue = {persistentState, encryptionSchemes: []}; + if (support.has(keySystem) && support.get(keySystem)) { + // Update the existing non-null value. + supportValue = support.get(keySystem); + } else { + // Set a new one. + support.set(keySystem, supportValue); + } + + // If the returned config doesn't mention encryptionScheme, the field + // is not supported. If installed, our polyfills should make sure this + // doesn't happen. + const returnedScheme = videoCapabilities[0].encryptionScheme; + if (returnedScheme && + !supportValue.encryptionSchemes.includes(returnedScheme)) { + supportValue.encryptionSchemes.push(returnedScheme); + } + }; + + const testSystemEme = async (keySystem, encryptionScheme) => { try { + const basicConfig = + shaka.util.ObjectUtils.cloneObject(basicConfigTemplate); + for (const cap of basicConfig.videoCapabilities) { + cap.encryptionScheme = encryptionScheme; + } + + const offlineConfig = shaka.util.ObjectUtils.cloneObject(basicConfig); + offlineConfig.persistentState = 'required'; + offlineConfig.sessionTypes = ['persistent-license']; + + const configs = [offlineConfig, basicConfig]; + + const access = await navigator.requestMediaKeySystemAccess( + keySystem, configs); + await processMediaKeySystemAccess(keySystem, access); + } catch (error) {} // Ignore errors. + }; + + const testSystemMcap = async (keySystem, encryptionScheme) => { + try { + const decodingConfig = { + type: 'media-source', + video: { + contentType: 'video/mp4; codecs="avc1.42E01E"', + width: 640, + height: 480, + bitrate: 1, + framerate: 1, + }, + keySystemConfiguration: { + keySystem, + video: { + encryptionScheme, + }, + }, + }; + + const decodingInfo = + await navigator.mediaCapabilities.decodingInfo(decodingConfig); + + const access = decodingInfo.keySystemAccess; + await processMediaKeySystemAccess(keySystem, access); + } catch (error) {} // Ignore errors. + }; + + // Initialize the support structure for each key system. + for (const keySystem of testKeySystems) { + support.set(keySystem, null); + } + + // Test each key system and encryption scheme. + const tests = []; + for (const encryptionScheme of testEncryptionSchemes) { + for (const keySystem of testKeySystems) { // Our Polyfill will reject anything apart com.apple.fps key systems. // It seems the Safari modern EME API will allow to request a // MediaKeySystemAccess for the ClearKey CDM, create and update a key @@ -1835,40 +1939,13 @@ shaka.media.DrmEngine = class { // Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006 if (keySystem === 'org.w3.clearkey' && shaka.util.Platform.isSafari()) { - throw new Error('Unsupported keySystem'); - } - - const access = await navigator.requestMediaKeySystemAccess( - keySystem, configs); - - // Edge doesn't return supported session types, but current versions - // do not support persistent-license. If sessionTypes is missing, - // assume no support for persistent-license. - // TODO: Polyfill Edge to return known supported session types. - // Edge bug: https://bit.ly/2IeKzho - const sessionTypes = access.getConfiguration().sessionTypes; - let persistentState = sessionTypes ? - sessionTypes.includes('persistent-license') : false; - - // Tizen 3.0 doesn't support persistent licenses, but reports that it - // does. It doesn't fail until you call update() with a license - // response, which is way too late. - // This is a work-around for #894. - if (shaka.util.Platform.isTizen3()) { - persistentState = false; + continue; } - - support.set(keySystem, {persistentState: persistentState}); - await access.createMediaKeys(); - } catch (e) { - // Either the request failed or createMediaKeys failed. - // Either way, write null to the support object. - support.set(keySystem, null); + tests.push(testSystemEme(keySystem, encryptionScheme)); + tests.push(testSystemMcap(keySystem, encryptionScheme)); } - }; + } - // Test each key system. - const tests = testKeySystems.map((keySystem) => testSystem(keySystem)); await Promise.all(tests); return shaka.util.MapUtils.asObject(support); } diff --git a/test/hls/hls_parser_integration.js b/test/hls/hls_parser_integration.js index ef277f7213..88f71bca61 100644 --- a/test/hls/hls_parser_integration.js +++ b/test/hls/hls_parser_integration.js @@ -26,12 +26,11 @@ describe('HlsParser', () => { let waiter; function checkClearKeySupport() { - // Some versions of Tizen doesn't support CBCS, so omit it for now. - // See: https://github.com/shaka-project/shaka-player/issues/1419 - if (shaka.util.Platform.isTizen()) { + const clearKeySupport = support['org.w3.clearkey']; + if (!clearKeySupport) { return false; } - return support['org.w3.clearkey']; + return clearKeySupport.encryptionSchemes.includes('cbcs'); } beforeAll(async () => {