Skip to content

Commit

Permalink
feat: add config to clear decodingInfo cache on unload (#6678)
Browse files Browse the repository at this point in the history
On PlayStation, 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.
Make it configurable via `streaming.clearDecodingCache`.
  • Loading branch information
tykus160 authored May 27, 2024
1 parent bcc6791 commit e0eeb5b
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 40 deletions.
4 changes: 3 additions & 1 deletion demo/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 8 additions & 1 deletion externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1270,7 +1270,8 @@ shaka.extern.ManifestConfiguration;
* vodDynamicPlaybackRateLowBufferRate: number,
* vodDynamicPlaybackRateBufferRatio: number,
* infiniteLiveStreamDuration: boolean,
* preloadNextUrlWindow: number
* preloadNextUrlWindow: number,
* clearDecodingCache: boolean
* }}
*
* @description
Expand Down Expand Up @@ -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 <code> 30 </code>.
* @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 <code>true</code> on PlayStation devices and to
* <code>false</code> on other devices.
* @exportDoc
*/
shaka.extern.StreamingConfiguration;
Expand Down
77 changes: 77 additions & 0 deletions lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
};


Expand Down Expand Up @@ -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<string, !MediaKeySystemAccess>}
*/
shaka.media.DrmEngine.memoizedMediaKeySystemAccessRequests_ = new Map();
9 changes: 9 additions & 0 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
45 changes: 10 additions & 35 deletions lib/polyfill/media_capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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.');
Expand Down Expand Up @@ -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}`;
}
};

/**
Expand All @@ -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`.
Expand Down
2 changes: 2 additions & 0 deletions lib/util/player_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,14 @@ shaka.util.StreamUtils = class {

return 'unexpected stream type';
}


/**
* Clears underlying decoding config cache.
*/
static clearDecodingConfigCache() {
shaka.util.StreamUtils.decodingConfigCache_ = {};
}
};


Expand Down
4 changes: 2 additions & 2 deletions test/polyfill/media_capabilities_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe('MediaCapabilities', () => {
width: 512,
},
};
shaka.polyfill.MediaCapabilities.memoizedMediaKeySystemAccessRequests_ = {};
shaka.media.DrmEngine.clearMediaKeySystemAccessMap();
supportMap.clear();

mockCanDisplayType = jasmine.createSpy('canDisplayType');
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion test/test/boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand Down

0 comments on commit e0eeb5b

Please sign in to comment.