Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add config to clear decodingInfo cache on unload #6678

Merged
merged 4 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ' +
avelad marked this conversation as resolved.
Show resolved Hide resolved
'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
Loading