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();
});