diff --git a/demo/config.js b/demo/config.js index 7cddc6bdac..5ef4fbe10d 100644 --- a/demo/config.js +++ b/demo/config.js @@ -136,7 +136,9 @@ shakaDemo.Config = class { 'drm.parseInbandPsshEnabled') .addTextInput_('Min HDCP version', 'drm.minHdcpVersion') .addBoolInput_('Ignore duplicate init data', - 'drm.ignoreDuplicateInitData'); + 'drm.ignoreDuplicateInitData') + .addBoolInput_('Enable cache for mediacapabilities', + 'drm.enableMediaCapabilitiesCache'); const advanced = shakaDemoMain.getConfiguration().drm.advanced || {}; const addDRMAdvancedField = (name, valueName, suggestions) => { // All advanced fields of a given type are set at once. diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 1bc7c1994d..1af93bf42d 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -802,7 +802,8 @@ shaka.extern.PersistentSessionMetadata; * keySystemsMapping: !Object., * parseInbandPsshEnabled: boolean, * minHdcpVersion: string, - * ignoreDuplicateInitData: boolean + * ignoreDuplicateInitData: boolean, + * enableMediaCapabilitiesCache: boolean * }} * * @property {shaka.extern.RetryParameters} retryParameters @@ -865,6 +866,10 @@ shaka.extern.PersistentSessionMetadata; * Note: Tizen 2015 and 2016 models will send multiple webkitneedkey events * with the same init data. If the duplicates are supressed, playback * will stall without errors. + * @property {boolean} enableMediaCapabilitiesCache + * Custom optimization, enable aggressive cache on mediaCapabilities call. + * Should only be enabled on platform where polyfill for mediaCapabilities + * is used. * @exportDoc */ shaka.extern.DrmConfiguration; diff --git a/lib/media/drm_engine.js b/lib/media/drm_engine.js index 959fbbf6c0..03fff7770a 100644 --- a/lib/media/drm_engine.js +++ b/lib/media/drm_engine.js @@ -401,7 +401,8 @@ shaka.media.DrmEngine = class { // in the drm infos, and before queryMediaKeys_(). await shaka.util.StreamUtils.getDecodingInfosForVariants(variants, this.usePersistentLicenses_, this.srcEquals_, - this.config_.preferredKeySystems); + this.config_.preferredKeySystems, + this.config_.enableMediaCapabilitiesCache); const hasDrmInfo = hadDrmInfo || Object.keys(this.config_.servers).length; // An unencrypted content is initialized. diff --git a/lib/media/manifest_filterer.js b/lib/media/manifest_filterer.js index 8a0bf915ee..6a11d8b991 100644 --- a/lib/media/manifest_filterer.js +++ b/lib/media/manifest_filterer.js @@ -60,11 +60,11 @@ shaka.media.ManifestFilterer = class { async filterManifestWithStreamUtils_(manifest) { goog.asserts.assert(manifest, 'Manifest should exist!'); await shaka.util.StreamUtils.filterManifest(this.drmEngine_, manifest, - this.config_.drm.preferredKeySystems); + this.config_.drm.preferredKeySystems, + this.config_.drm.enableMediaCapabilitiesCache); this.checkPlayableVariants_(manifest); } - /** * @param {?shaka.extern.Manifest} manifest * @return {boolean} tracksChanged diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index 661feba9a1..a1520753e8 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -92,6 +92,7 @@ shaka.util.PlayerConfiguration = class { parseInbandPsshEnabled: shaka.util.Platform.isXboxOne(), minHdcpVersion: '', ignoreDuplicateInitData: !shaka.util.Platform.isTizen2(), + enableMediaCapabilitiesCache: false, }; // The Xbox One and PS4 only support the Playready DRM, so they should diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js index 98eaf7845d..119899b417 100644 --- a/lib/util/stream_utils.js +++ b/lib/util/stream_utils.js @@ -348,11 +348,13 @@ shaka.util.StreamUtils = class { * @param {shaka.media.DrmEngine} drmEngine * @param {shaka.extern.Manifest} manifest * @param {!Array=} preferredKeySystems + * @param {boolean} enableMediaCapabilitiesCache */ - static async filterManifest(drmEngine, manifest, preferredKeySystems = []) { + static async filterManifest(drmEngine, manifest, + preferredKeySystems = [], enableMediaCapabilitiesCache = false) { await shaka.util.StreamUtils.filterManifestByMediaCapabilities( drmEngine, manifest, manifest.offlineSessionIds.length > 0, - preferredKeySystems); + preferredKeySystems, enableMediaCapabilitiesCache); shaka.util.StreamUtils.filterTextStreams_(manifest); await shaka.util.StreamUtils.filterImageStreams_(manifest); } @@ -366,15 +368,17 @@ shaka.util.StreamUtils = class { * @param {shaka.extern.Manifest} manifest * @param {boolean} usePersistentLicenses * @param {!Array} preferredKeySystems + * @param {boolean} enableMediaCapabilitiesCache */ static async filterManifestByMediaCapabilities( - drmEngine, manifest, usePersistentLicenses, preferredKeySystems) { + drmEngine, manifest, usePersistentLicenses, + preferredKeySystems, enableMediaCapabilitiesCache = false) { goog.asserts.assert(navigator.mediaCapabilities, 'MediaCapabilities should be valid.'); await shaka.util.StreamUtils.getDecodingInfosForVariants( manifest.variants, usePersistentLicenses, /* srcEquals= */ false, - preferredKeySystems); + preferredKeySystems, enableMediaCapabilitiesCache); let keySystem = null; if (drmEngine) { @@ -666,10 +670,11 @@ shaka.util.StreamUtils = class { * @param {boolean} usePersistentLicenses * @param {boolean} srcEquals * @param {!Array} preferredKeySystems + * @param {boolean} enableMediaCapabilitiesCache * @exportDoc */ static async getDecodingInfosForVariants(variants, usePersistentLicenses, - srcEquals, preferredKeySystems) { + srcEquals, preferredKeySystems, enableMediaCapabilitiesCache = false) { const gotDecodingInfo = variants.some((variant) => variant.decodingInfos.length); if (gotDecodingInfo) { @@ -710,27 +715,112 @@ shaka.util.StreamUtils = class { } } // for (const preferredKeySystem of preferredKeySystems) - for (const variant of variants) { - /** @type {!Array.>} */ - const decodingConfigs = shaka.util.StreamUtils.getDecodingConfigs_( - variant, usePersistentLicenses, srcEquals) - .filter((configs) => { - // All configs in a batch will have the same keySystem. - const config = configs[0]; - const keySystem = config.keySystemConfiguration && - config.keySystemConfiguration.keySystem; - // Avoid checking preferred systems twice. - return !keySystem || !preferredKeySystems.includes(keySystem); - }); + /** + * For platforms which use the MediaCapabilities polyfill, calls to media + * capabilities are replaced with isTypeSupported and + * requestMediaKeySystemAccess instead. + * By grouping similar variants, we can improve performance for manifests + * with lots of variants. + */ + const enableCache = enableMediaCapabilitiesCache && + !srcEquals && !shaka.util.Platform.isChromecast(); + if (enableCache) { + /** @type {!Object.} */ + const decodeInfoCache = {}; + const buildCacheKey = (variant) => { + if (variant.audio.fullMimeTypes.size < 1 || + variant.video.fullMimeTypes.size < 1) { + return undefined; + } + const audio = variant.audio; + const video = variant.video; + + // if pure function for calculating the fulltype + const videoDrmInfos = video ? video.drmInfos : []; + const audioDrmInfos = audio ? audio.drmInfos : []; + const allDrmInfos = videoDrmInfos.concat(audioDrmInfos); + + const keySystems = new Set(allDrmInfos.map((info) => info.keySystem)); + const cacheKeys = []; + for (const keySystem of keySystems) { + for (const videoFullMimeType of video.fullMimeTypes) { + for (const audioFullMimeType of audio.fullMimeTypes) { + cacheKeys.push( + `${videoFullMimeType}#${audioFullMimeType}#${keySystem}`); + } + } + } + return cacheKeys; + }; + + // sort variants, so that high bandwidth checked first. + // High bandwidth tends to be less likely to be supported, or smooth + const sortedVariants = variants.sort((a, b) => b.bandwidth - a.bandwidth); + + for (const variant of sortedVariants) { + const cacheKeys = buildCacheKey(variant); + const useCache = cacheKeys && cacheKeys.length && + cacheKeys.every((key) => { + return decodeInfoCache[key] && decodeInfoCache[key].supported; + }); + if (useCache) { + variant.decodingInfos = + cacheKeys.map((key) => decodeInfoCache[key]); + continue; + } - // The reason we are performing this await in a loop rather than - // batching into a `promise.all` is performance related. - // https://github.com/shaka-project/shaka-player/pull/4708#discussion_r1022581178 - for (const configs of decodingConfigs) { // eslint-disable-next-line no-await-in-loop - await shaka.util.StreamUtils.getDecodingInfosForVariant_( - variant, configs); + await shaka.util.StreamUtils.getAndApplyDecodingInfosForVariant_( + variant, usePersistentLicenses, srcEquals, preferredKeySystems); + + if (cacheKeys) { + for (let i = 0; i < variant.decodingInfos.length; i++) { + const cacheKey = cacheKeys[i]; + decodeInfoCache[cacheKey] = variant.decodingInfos[i]; + delete decodeInfoCache[cacheKey]['configuration']; + } + } } + return; + } + + for (const variant of variants) { + // eslint-disable-next-line no-await-in-loop + await shaka.util.StreamUtils.getAndApplyDecodingInfosForVariant_( + variant, usePersistentLicenses, srcEquals, preferredKeySystems); + } + } + + /** + * Call mediaCapabilities + * Generate input for querying mediaCapabilities and call the API . + * @param {!shaka.extern.Variant} variant + * @param {boolean} usePersistentLicenses + * @param {boolean} srcEquals + * @param {!Array=} preferredKeySystems + * @private + */ + static async getAndApplyDecodingInfosForVariant_( + variant, usePersistentLicenses, srcEquals, preferredKeySystems = []) { + /** @type {!Array.>} */ + const decodingConfigs = shaka.util.StreamUtils.getDecodingConfigs_( + variant, usePersistentLicenses, srcEquals) + .filter((configs) => { + // All configs in a batch will have the same keySystem. + const config = configs[0]; + const keySystem = config.keySystemConfiguration && + config.keySystemConfiguration.keySystem; + // Avoid checking preferred systems twice. + return !keySystem || !preferredKeySystems.includes(keySystem); + }); + + // The reason we are performing this await in a loop rather than + // batching into a `promise.all` is performance related. + // https://github.com/shaka-project/shaka-player/pull/4708#discussion_r1022581178 + for (const configs of decodingConfigs) { + // eslint-disable-next-line no-await-in-loop + await shaka.util.StreamUtils.getDecodingInfosForVariant_( + variant, configs); } }