diff --git a/demo/config.js b/demo/config.js index 50a9a4d3dc..20bea81768 100644 --- a/demo/config.js +++ b/demo/config.js @@ -519,7 +519,9 @@ shakaDemo.Config = class { 'streaming.vodDynamicPlaybackRateBufferRatio', /* canBeDecimal= */ true) .addBoolInput_('Infinite Live Stream Duration', - 'streaming.infiniteLiveStreamDuration'); + 'streaming.infiniteLiveStreamDuration') + .addBoolInput_('Clear decodingInfo cache on unload', + 'streaming.clearDecodingCache'); if (!shakaDemoMain.getNativeControlsEnabled()) { this.addBoolInput_('Always Stream Text', 'streaming.alwaysStreamText'); } else { diff --git a/externs/shaka/player.js b/externs/shaka/player.js index d3111c5852..f90ccd3975 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -1270,7 +1270,8 @@ shaka.extern.ManifestConfiguration; * vodDynamicPlaybackRateLowBufferRate: number, * vodDynamicPlaybackRateBufferRatio: number, * infiniteLiveStreamDuration: boolean, - * preloadNextUrlWindow: number + * preloadNextUrlWindow: number, + * clearDecodingCache: boolean * }} * * @description @@ -1470,6 +1471,12 @@ shaka.extern.ManifestConfiguration; * in DASH. Measured in seconds. If the value is 0, the next URL will not * be preloaded at all. * Defaults to 30 . + * @property {boolean} clearDecodingCache + * Clears decodingInfo and MediaKeySystemAccess cache during player unload + * as these objects may become corrupt and cause issues during subsequent + * playbacks on some platforms. + * Defaults to true on PlayStation devices and to + * false on other devices. * @exportDoc */ shaka.extern.StreamingConfiguration; diff --git a/lib/media/drm_engine.js b/lib/media/drm_engine.js index b480f55ab4..86a12d1be6 100644 --- a/lib/media/drm_engine.js +++ b/lib/media/drm_engine.js @@ -2719,6 +2719,73 @@ shaka.media.DrmEngine = class { this.newInitData('cenc', combinedData); return this.allSessionsLoaded_; } + + /** + * A method for generating a key for the MediaKeySystemAccessRequests cache. + * + * @param {string} videoCodec + * @param {string} audioCodec + * @param {string} keySystem + * @return {string} + * @private + */ + static generateKeySystemCacheKey_(videoCodec, audioCodec, keySystem) { + return `${videoCodec}#${audioCodec}#${keySystem}`; + } + + /** + * Check does MediaKeySystemAccess cache contains something for following + * attributes. + * + * @param {string} videoCodec + * @param {string} audioCodec + * @param {string} keySystem + * @return {boolean} + */ + static hasMediaKeySystemAccess(videoCodec, audioCodec, keySystem) { + const DrmEngine = shaka.media.DrmEngine; + const key = DrmEngine.generateKeySystemCacheKey_( + videoCodec, audioCodec, keySystem); + return DrmEngine.memoizedMediaKeySystemAccessRequests_.has(key); + } + + /** + * Get MediaKeySystemAccess object for following attributes. + * + * @param {string} videoCodec + * @param {string} audioCodec + * @param {string} keySystem + * @return {?MediaKeySystemAccess} + */ + static getMediaKeySystemAccess(videoCodec, audioCodec, keySystem) { + const DrmEngine = shaka.media.DrmEngine; + const key = DrmEngine.generateKeySystemCacheKey_( + videoCodec, audioCodec, keySystem); + return DrmEngine.memoizedMediaKeySystemAccessRequests_.get(key) || null; + } + + /** + * Store MediaKeySystemAccess object associated with specified attributes. + * + * @param {string} videoCodec + * @param {string} audioCodec + * @param {string} keySystem + * @param {!MediaKeySystemAccess} mksa + */ + static setMediaKeySystemAccess(videoCodec, audioCodec, keySystem, mksa) { + const DrmEngine = shaka.media.DrmEngine; + const key = DrmEngine.generateKeySystemCacheKey_( + videoCodec, audioCodec, keySystem); + return DrmEngine.memoizedMediaKeySystemAccessRequests_.set(key, mksa); + } + + /** + * Clears underlying cache. + */ + static clearMediaKeySystemAccessMap() { + const DrmEngine = shaka.media.DrmEngine; + DrmEngine.memoizedMediaKeySystemAccessRequests_.clear(); + } }; @@ -2812,3 +2879,13 @@ shaka.media.DrmEngine.KEY_STATUS_BATCH_TIME = 0.5; */ shaka.media.DrmEngine.DUMMY_KEY_ID = new shaka.util.Lazy( () => shaka.util.BufferUtils.toArrayBuffer(new Uint8Array([0]))); + + +/** + * A cache that stores the MediaKeySystemAccess result of calling + * `navigator.requestMediaKeySystemAccess` by a key combination of + * video/audio codec and key system string. + * + * @private {!Map} + */ +shaka.media.DrmEngine.memoizedMediaKeySystemAccessRequests_ = new Map(); diff --git a/lib/player.js b/lib/player.js index 6c91969613..9b34c13928 100644 --- a/lib/player.js +++ b/lib/player.js @@ -1450,6 +1450,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } } + // On some devices, cached MediaKeySystemAccess objects may corrupt + // after several playbacks, and they are not able anymore to properly + // create MediaKeys objects. To prevent it, clear the cache after + // each playback. + if (this.config_.streaming.clearDecodingCache) { + shaka.util.StreamUtils.clearDecodingConfigCache(); + shaka.media.DrmEngine.clearMediaKeySystemAccessMap(); + } + this.manifest_ = null; this.stats_ = new shaka.util.Stats(); // Replace with a clean object. this.lastTextFactory_ = null; diff --git a/lib/polyfill/media_capabilities.js b/lib/polyfill/media_capabilities.js index 1979bd27da..becfe4c732 100644 --- a/lib/polyfill/media_capabilities.js +++ b/lib/polyfill/media_capabilities.js @@ -8,6 +8,7 @@ goog.provide('shaka.polyfill.MediaCapabilities'); goog.require('shaka.log'); goog.require('shaka.media.Capabilities'); +goog.require('shaka.media.DrmEngine'); goog.require('shaka.polyfill'); goog.require('shaka.util.Platform'); @@ -274,25 +275,22 @@ shaka.polyfill.MediaCapabilities = class { mediaKeySystemConfig.videoCapabilities = videoCapabilities; } - const cacheKey = shaka.polyfill.MediaCapabilities - .generateKeySystemCacheKey_( - videoConfig ? videoConfig.contentType : '', - audioConfig ? audioConfig.contentType : '', - mcapKeySystemConfig.keySystem); + const videoCodec = videoConfig ? videoConfig.contentType : ''; + const audioCodec = audioConfig ? audioConfig.contentType : ''; + const keySystem = mcapKeySystemConfig.keySystem; /** @type {MediaKeySystemAccess} */ let keySystemAccess = null; try { - if (cacheKey in shaka.polyfill.MediaCapabilities - .memoizedMediaKeySystemAccessRequests_) { - keySystemAccess = shaka.polyfill.MediaCapabilities - .memoizedMediaKeySystemAccessRequests_[cacheKey]; + if (shaka.media.DrmEngine.hasMediaKeySystemAccess( + videoCodec, audioCodec, keySystem)) { + keySystemAccess = shaka.media.DrmEngine.getMediaKeySystemAccess( + videoCodec, audioCodec, keySystem); } else { keySystemAccess = await navigator.requestMediaKeySystemAccess( mcapKeySystemConfig.keySystem, [mediaKeySystemConfig]); - shaka.polyfill.MediaCapabilities - .memoizedMediaKeySystemAccessRequests_[cacheKey] = - keySystemAccess; + shaka.media.DrmEngine.setMediaKeySystemAccess( + videoCodec, audioCodec, keySystem, keySystemAccess); } } catch (e) { shaka.log.info('navigator.requestMediaKeySystemAccess failed.'); @@ -344,19 +342,6 @@ shaka.polyfill.MediaCapabilities = class { } return result; } - - /** - * A method for generating a key for the MediaKeySystemAccessRequests cache. - * - * @param {!string} videoCodec - * @param {!string} audioCodec - * @param {!string} keySystem - * @return {!string} - * @private - */ - static generateKeySystemCacheKey_(videoCodec, audioCodec, keySystem) { - return `${videoCodec}#${audioCodec}#${keySystem}`; - } }; /** @@ -369,16 +354,6 @@ shaka.polyfill.MediaCapabilities = class { */ shaka.polyfill.MediaCapabilities.originalMcap = null; -/** - * A cache that stores the MediaKeySystemAccess result of calling - * `navigator.requestMediaKeySystemAccess` by a key combination of - * video/audio codec and key system string. - * - * @type {(Object<(!string), (!MediaKeySystemAccess)>)} - * @export - */ -shaka.polyfill.MediaCapabilities.memoizedMediaKeySystemAccessRequests_ = {}; - /** * A cache that stores the canDisplayType result of calling * `cast.__platform__.canDisplayType`. diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index 069ea96e29..22ae0e8c73 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -251,6 +251,8 @@ shaka.util.PlayerConfiguration = class { vodDynamicPlaybackRateBufferRatio: 0.5, infiniteLiveStreamDuration: false, preloadNextUrlWindow: 30, + clearDecodingCache: shaka.util.Platform.isPS4() || + shaka.util.Platform.isPS5(), }; // WebOS, Tizen, Chromecast and Hisense have long hardware pipelines diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js index 03c77c910f..f07392e018 100644 --- a/lib/util/stream_utils.js +++ b/lib/util/stream_utils.js @@ -1853,6 +1853,14 @@ shaka.util.StreamUtils = class { return 'unexpected stream type'; } + + + /** + * Clears underlying decoding config cache. + */ + static clearDecodingConfigCache() { + shaka.util.StreamUtils.decodingConfigCache_ = {}; + } }; diff --git a/test/polyfill/media_capabilities_unit.js b/test/polyfill/media_capabilities_unit.js index 9737c68227..d019c5a4c6 100644 --- a/test/polyfill/media_capabilities_unit.js +++ b/test/polyfill/media_capabilities_unit.js @@ -68,7 +68,7 @@ describe('MediaCapabilities', () => { width: 512, }, }; - shaka.polyfill.MediaCapabilities.memoizedMediaKeySystemAccessRequests_ = {}; + shaka.media.DrmEngine.clearMediaKeySystemAccessMap(); supportMap.clear(); mockCanDisplayType = jasmine.createSpy('canDisplayType'); @@ -163,7 +163,7 @@ describe('MediaCapabilities', () => { expect(result.keySystemAccess).toEqual(mockResult); }); - it('should read previously requested codec/key system'+ + it('should read previously requested codec/key system ' + 'combinations from cache', async () => { const mockResult = {mockKeySystemAccess: 'mockKeySystemAccess'}; spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true); diff --git a/test/test/boot.js b/test/test/boot.js index fb20ff7992..de3cd02ca6 100644 --- a/test/test/boot.js +++ b/test/test/boot.js @@ -372,7 +372,7 @@ function configureJasmineEnvironment() { // Reset decoding config cache after each test. afterEach(/** @suppress {accessControls} */ () => { - shaka.util.StreamUtils.decodingConfigCache_ = {}; + shaka.util.StreamUtils.clearDecodingConfigCache(); shaka.media.Capabilities.MediaSourceTypeSupportMap.clear(); });