diff --git a/docs/tutorials/drm-config.md b/docs/tutorials/drm-config.md index 1bf06de1bc..9230f64417 100644 --- a/docs/tutorials/drm-config.md +++ b/docs/tutorials/drm-config.md @@ -208,3 +208,64 @@ you should provide an empty string as robustness ##### Other key-systems Values for other key systems are not known to us at this time. + +#### Re-use persistent license DRM for online playback + +If your DRM provider configuration allows you to deliver persistent license, +you could re-use the created MediaKeys session for the next online playback. + +Configure Shaka to start DRM sessions with the `persistent-license` type +instead of the `temporary` one: + +```js +player.configure({ + drm: { + advanced: { + 'com.widevine.alpha': { + 'sessionType': 'persistent-license' + } + } + } +}); +``` + +**Using `persistent-license` might not work on every devices, use this feature +carefully.** + +When the playback starts, you can retrieve the sessions metadata: + +```js +const activeDrmSessions = this.player.getActiveSessionsMetadata(); +const persistentDrmSessions = activeDrmSessions.filter( + ({ sessionType }) => sessionType === 'persistent-license'); + +// Add your own storage mechanism here, give it an unique known identifier for +// the playing video +``` + +When starting the same video again, retrieve the metadata from the storage, and +set it back to Shaka's configuration. + +Shaka will load the given DRM persistent sessions and will only request a +license if some keys are missing for the content. + +```js +player.configure({ + drm: { + persistentSessionOnlinePlayback: true, + persistentSessionsMetadata: [{ + sessionId: 'deadbeefdeadbeefdeadbeefdeadbeef', + initData: new InitData(0), + initDataType: 'cenc' + }] + } +}); +``` + +NB: Shaka doesn't provide a out-of-the-box storage mechanism for the sessions +metadata. + +#### Continue the Tutorials + +Next, check out {@tutorial license-server-auth}. +Or check out {@tutorial fairplay}. diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 1cc0fe9156..ea0b782195 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -681,12 +681,60 @@ shaka.extern.ProducerReferenceTime; shaka.extern.AdvancedDrmConfiguration; +/** + * @typedef {{ + * sessionId: string, + * sessionType: string, + * initData: ?Uint8Array, + * initDataType: ?string + * }} + * + * @description + * DRM Session Metadata for an active session + * + * @property {string} sessionId + * Session id + * @property {string} sessionType + * Session type + * @property {?Uint8Array} initData + * Initialization data in the format indicated by initDataType. + * @property {string} initDataType + * A string to indicate what format initData is in. + * @exportDoc + */ +shaka.extern.DrmSessionMetadata; + + +/** + * @typedef {{ + * sessionId: string, + * initData: ?Uint8Array, + * initDataType: ?string + * }} + * + * @description + * DRM Session Metadata for saved persistent session + * + * @property {string} sessionId + * Session id + * @property {?Uint8Array} initData + * Initialization data in the format indicated by initDataType. + * @property {?string} initDataType + * A string to indicate what format initData is in. + * @exportDoc + */ +shaka.extern.PersistentSessionMetadata; + + /** * @typedef {{ * retryParameters: shaka.extern.RetryParameters, * servers: !Object., * clearKeys: !Object., * delayLicenseRequestUntilPlayed: boolean, + * persistentSessionOnlinePlayback: boolean, + * persistentSessionsMetadata: + * !Array., * advanced: Object., * initDataTransform: * ((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)| @@ -713,6 +761,13 @@ shaka.extern.AdvancedDrmConfiguration; * Defaults to false.
* True to configure drm to delay sending a license request until a user * actually starts playing content. + * @property {boolean} persistentSessionOnlinePlayback + * Defaults to false.
+ * True to configure drm to try playback with given persistent session ids + * before requesting a license. Also prevents the session removal at playback + * stop, as-to be able to re-use it later. + * @property {!Array.} persistentSessionsMetadata + * Persistent sessions metadata to load before starting playback * @property {Object.} advanced * Optional.
* A dictionary which maps key system IDs to advanced DRM configuration for diff --git a/lib/media/drm_engine.js b/lib/media/drm_engine.js index 9873b5e5b0..e425887617 100644 --- a/lib/media/drm_engine.js +++ b/lib/media/drm_engine.js @@ -71,8 +71,11 @@ shaka.media.DrmEngine = class { */ this.activeSessions_ = new Map(); - /** @private {!Array.} */ - this.offlineSessionIds_ = []; + /** + * @private {!Map} + */ + this.storedPersistentSessions_ = new Map(); /** @private {!shaka.util.PublicPromise} */ this.allSessionsLoaded_ = new shaka.util.PublicPromise(); @@ -82,7 +85,10 @@ shaka.media.DrmEngine = class { /** @private {function(!shaka.util.Error)} */ this.onError_ = (err) => { - this.allSessionsLoaded_.reject(err); + if (err.severity == shaka.util.Error.Severity.CRITICAL) { + this.allSessionsLoaded_.reject(err); + } + playerInterface.onError(err); }; @@ -184,7 +190,7 @@ shaka.media.DrmEngine = class { this.currentDrmInfo_ = null; this.supportedTypes_.clear(); this.mediaKeys_ = null; - this.offlineSessionIds_ = []; + this.storedPersistentSessions_ = new Map(); this.config_ = null; this.onError_ = () => {}; this.playerInterface_ = null; @@ -228,7 +234,7 @@ shaka.media.DrmEngine = class { // 2. We are about to remove the offline sessions for this manifest - in // that case, we don't need to know about them right now either as // we will be told which ones to remove later. - this.offlineSessionIds_ = []; + this.storedPersistentSessions_ = new Map(); // What we really need to know is whether or not they are expecting to use // persistent licenses. @@ -246,8 +252,20 @@ shaka.media.DrmEngine = class { * @return {!Promise} */ initForPlayback(variants, offlineSessionIds) { - this.offlineSessionIds_ = offlineSessionIds; - this.usePersistentLicenses_ = offlineSessionIds.length > 0; + this.storedPersistentSessions_ = new Map(); + + for (const sessionId of offlineSessionIds) { + this.storedPersistentSessions_.set( + sessionId, {initData: null, initDataType: null}); + } + + for (const metadata of this.config_.persistentSessionsMetadata) { + this.storedPersistentSessions_.set( + metadata.sessionId, + {initData: metadata.initData, initDataType: metadata.initDataType}); + } + + this.usePersistentLicenses_ = this.storedPersistentSessions_.size > 0; return this.init_(variants); } @@ -301,7 +319,7 @@ shaka.media.DrmEngine = class { /** * Negotiate for a key system and set up MediaKeys. * This will assume that both |usePersistentLicences_| and - * |offlineSessionIds_| have been properly set. + * |storedPersistentSessions_| have been properly set. * * @param {!Array.} variants * The variants that we expect to operate with during the drm engine's @@ -493,18 +511,21 @@ shaka.media.DrmEngine = class { */ if (manifestInitData || this.currentDrmInfo_.keySystem !== 'com.apple.fps' || - this.offlineSessionIds_.length) { + this.storedPersistentSessions_.size) { await this.attachMediaKeys_(); } - this.createOrLoad(); + this.createOrLoad().catch(() => { + // Silence errors + // createOrLoad will run async, errors are triggered through onError_ + }); // Explicit init data for any one stream or an offline session is // sufficient to suppress 'encrypted' events for all streams. // Also suppress 'encrypted' events when parsing in-band ppsh // from media segments because that serves the same purpose as the // 'encrypted' events. - if (!manifestInitData && !this.offlineSessionIds_.length && + if (!manifestInitData && !this.storedPersistentSessions_.size && !this.config_.parseInbandPsshEnabled) { this.eventManager_.listen( this.video_, 'encrypted', (e) => this.onEncryptedEvent_(e)); @@ -592,7 +613,8 @@ shaka.media.DrmEngine = class { goog.asserts.assert(this.mediaKeys_, 'Must call init() before removeSession'); - const session = await this.loadOfflineSession_(sessionId); + const session = await this.loadOfflineSession_( + sessionId, {initData: null, initDataType: null}); // This will be null on error, such as session not found. if (!session) { @@ -625,8 +647,30 @@ shaka.media.DrmEngine = class { * * @return {!Promise} */ - createOrLoad() { - // Create temp sessions. + async createOrLoad() { + if (this.storedPersistentSessions_.size) { + this.storedPersistentSessions_.forEach((metadata, sessionId) => { + this.loadOfflineSession_(sessionId, metadata); + }); + + await this.allSessionsLoaded_; + + const keyIds = (this.currentDrmInfo_ && this.currentDrmInfo_.keyIds) || + new Set([]); + + // All the needed keys are already loaded, we don't need another license + // Therefore we prevent starting a new session + if (keyIds.size > 0 && this.areAllKeysUsable_()) { + return this.allSessionsLoaded_; + } + + // Reset the promise for the next sessions to come if key needs aren't + // satisfied with persistent sessions + this.allSessionsLoaded_ = new shaka.util.PublicPromise(); + this.allSessionsLoaded_.catch(() => {}); + } + + // Create sessions. const initDatas = (this.currentDrmInfo_ ? this.currentDrmInfo_.initData : []) || []; for (const initDataOverride of initDatas) { @@ -634,14 +678,9 @@ shaka.media.DrmEngine = class { initDataOverride.initDataType, initDataOverride.initData); } - // Load each session. - for (const sessionId of this.offlineSessionIds_) { - this.loadOfflineSession_(sessionId); - } - // If we have no sessions, we need to resolve the promise right now or else // it will never get resolved. - if (!initDatas.length && !this.offlineSessionIds_.length) { + if (!initDatas.length) { this.allSessionsLoaded_.resolve(); } @@ -769,6 +808,28 @@ shaka.media.DrmEngine = class { return Array.from(ids); } + /** + * Returns the active sessions metadata + * + * @return {!Array.} + */ + getActiveSessionsMetadata() { + const sessions = this.activeSessions_.keys(); + + const metadata = shaka.util.Iterables.map(sessions, (session) => { + const metadata = this.activeSessions_.get(session); + + return { + sessionId: session.sessionId, + sessionType: metadata.type, + initData: metadata.initData, + initDataType: metadata.initDataType, + }; + }); + + return Array.from(metadata); + } + /** * Returns the next expiration time, or Infinity. * @return {number} @@ -1187,12 +1248,41 @@ shaka.media.DrmEngine = class { }; } + /** + * Resolves the allSessionsLoaded_ promise when all the sessions are loaded + * + * @private + */ + checkSessionsLoaded_() { + if (this.areAllSessionsLoaded_()) { + this.allSessionsLoaded_.resolve(); + } + } + + /** + * In case there are no key statuses, consider this session loaded + * after a reasonable timeout. It should definitely not take 5 + * seconds to process a license. + * @param {!shaka.media.DrmEngine.SessionMetaData} metadata + * @private + */ + setLoadSessionTimeoutTimer_(metadata) { + const timer = new shaka.util.Timer(() => { + metadata.loaded = true; + this.checkSessionsLoaded_(); + }); + + timer.tickAfter( + /* seconds= */ shaka.media.DrmEngine.SESSION_LOAD_TIMEOUT_); + } + /** * @param {string} sessionId + * @param {{initData: ?Uint8Array, initDataType: ?string}} sessionMetadata * @return {!Promise.} * @private */ - async loadOfflineSession_(sessionId) { + async loadOfflineSession_(sessionId, sessionMetadata) { let session; const sessionType = 'persistent-license'; @@ -1217,8 +1307,8 @@ shaka.media.DrmEngine = class { (event) => this.onKeyStatusesChange_(event)); const metadata = { - initData: null, - initDataType: null, + initData: sessionMetadata.initData, + initDataType: sessionMetadata.initDataType, loaded: false, oldExpiration: Infinity, updatePromise: null, @@ -1234,32 +1324,42 @@ shaka.media.DrmEngine = class { if (!present) { this.activeSessions_.delete(session); + const severity = this.config_.persistentSessionOnlinePlayback ? + shaka.util.Error.Severity.RECOVERABLE : + shaka.util.Error.Severity.CRITICAL; + this.onError_(new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, + severity, shaka.util.Error.Category.DRM, shaka.util.Error.Code.OFFLINE_SESSION_REMOVED)); - return Promise.resolve(); - } - // TODO: We should get a key status change event. Remove once Chrome CDM - // is fixed. - metadata.loaded = true; - if (this.areAllSessionsLoaded_()) { - this.allSessionsLoaded_.resolve(); + metadata.loaded = true; } + this.setLoadSessionTimeoutTimer_(metadata); + this.checkSessionsLoaded_(); + return session; } catch (error) { this.destroyer_.ensureNotDestroyed(error); this.activeSessions_.delete(session); + const severity = this.config_.persistentSessionOnlinePlayback ? + shaka.util.Error.Severity.RECOVERABLE : + shaka.util.Error.Severity.CRITICAL; + this.onError_(new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, + severity, shaka.util.Error.Category.DRM, shaka.util.Error.Code.FAILED_TO_CREATE_SESSION, error.message)); + + metadata.loaded = true; + + this.checkSessionsLoaded_(); } + return Promise.resolve(); } @@ -1485,18 +1585,8 @@ shaka.media.DrmEngine = class { if (metadata.updatePromise) { metadata.updatePromise.resolve(); } - // In case there are no key statuses, consider this session loaded - // after a reasonable timeout. It should definitely not take 5 - // seconds to process a license. - const timer = new shaka.util.Timer(() => { - metadata.loaded = true; - if (this.areAllSessionsLoaded_()) { - this.allSessionsLoaded_.resolve(); - } - }); - timer.tickAfter( - /* seconds= */ shaka.media.DrmEngine.SESSION_LOAD_TIMEOUT_); + this.setLoadSessionTimeoutTimer_(metadata); } } @@ -1844,8 +1934,9 @@ shaka.media.DrmEngine = class { * the playback session ends */ if (!this.initializedForStorage_ && - !this.offlineSessionIds_.includes(session.sessionId) && - metadata.type === 'persistent-license') { + !this.storedPersistentSessions_.has(session.sessionId) && + metadata.type === 'persistent-license' && + !this.config_.persistentSessionOnlinePlayback) { shaka.log.v1('Removing session', session.sessionId); await session.remove(); @@ -2019,6 +2110,25 @@ shaka.media.DrmEngine = class { return shaka.util.Iterables.every(metadatas, (data) => data.loaded); } + /** + * @return {boolean} + * @private + */ + areAllKeysUsable_() { + const keyIds = (this.currentDrmInfo_ && this.currentDrmInfo_.keyIds) || + new Set([]); + + for (const keyId of keyIds) { + const status = this.keyStatusByKeyId_.get(keyId); + + if (status !== 'usable') { + return false; + } + } + + return true; + } + /** * Replace the drm info used in each variant in |variants| to reflect each * key service in |keySystems|. diff --git a/lib/player.js b/lib/player.js index cdb0dc8e32..f9835192e5 100644 --- a/lib/player.js +++ b/lib/player.js @@ -3665,6 +3665,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget { return this.drmEngine_ ? this.drmEngine_.getExpiration() : Infinity; } + /** + * Returns the active sessions metadata + * + * @return {!Array.} + * @export + */ + getActiveSessionsMetadata() { + return this.drmEngine_ ? this.drmEngine_.getActiveSessionsMetadata() : []; + } + /** * Gets a map of EME key ID to the current key status. * diff --git a/lib/util/error.js b/lib/util/error.js index 0dc56976b4..09ff0ed3dc 100644 --- a/lib/util/error.js +++ b/lib/util/error.js @@ -858,7 +858,8 @@ shaka.util.Error.Code = { 'NO_LICENSE_SERVER_GIVEN': 6012, /** - * A required offline session was removed. The content is not playable. + * A required offline session was removed. The content might not be playable + * depending of the playback context. */ 'OFFLINE_SESSION_REMOVED': 6013, diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index 04b7dac54f..3dd2ddc870 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -68,6 +68,8 @@ shaka.util.PlayerConfiguration = class { clearKeys: {}, // key is arbitrary key system ID, value must be string advanced: {}, // key is arbitrary key system ID, value is a record type delayLicenseRequestUntilPlayed: false, + persistentSessionOnlinePlayback: false, + persistentSessionsMetadata: [], initDataTransform: (initData, initDataType, drmInfo) => { const keySystem = drmInfo.keySystem; if (keySystem == 'com.apple.fps.1_0' && initDataType == 'skd') { diff --git a/test/cast/cast_utils_unit.js b/test/cast/cast_utils_unit.js index 04e1d90012..52dc0c7184 100644 --- a/test/cast/cast_utils_unit.js +++ b/test/cast/cast_utils_unit.js @@ -25,6 +25,7 @@ describe('CastUtils', () => { 'getManifest', // Too large to proxy 'getManifestParserFactory', // Would not serialize. 'setVideoContainer', + 'getActiveSessionsMetadata', // Test helper methods (not @export'd) 'createDrmEngine', diff --git a/test/demo/demo_unit.js b/test/demo/demo_unit.js index 3626b1f7f2..8bcff57286 100644 --- a/test/demo/demo_unit.js +++ b/test/demo/demo_unit.js @@ -97,7 +97,9 @@ describe('Demo', () => { .add('manifest.hls.mediaPlaylistFullMimeType') .add('manifest.mss.keySystemsBySystemId') .add('drm.keySystemsMapping') - .add('streaming.parsePrftBox'); + .add('streaming.parsePrftBox') + .add('drm.persistentSessionOnlinePlayback') + .add('drm.persistentSessionsMetadata'); /** * @param {!Object} section diff --git a/test/media/drm_engine_unit.js b/test/media/drm_engine_unit.js index f34c70deb1..e6320f4638 100644 --- a/test/media/drm_engine_unit.js +++ b/test/media/drm_engine_unit.js @@ -1095,6 +1095,178 @@ describe('DrmEngine', () => { shaka.util.Error.Code.FAILED_TO_GENERATE_LICENSE_REQUEST, message, nativeError, undefined)); }); + + it('should throw a OFFLINE_SESSION_REMOVED error', async () => { + // Given persistent session is not available + session1.load.and.returnValue(false); + + onErrorSpy.and.stub(); + + await drmEngine.initForPlayback( + manifest.variants, ['persistent-session-id']); + await drmEngine.attach(mockVideo); + + expect(drmEngine.initialized()).toBe(true); + + await Util.shortDelay(); + + expect(mockMediaKeys.createSession).toHaveBeenCalledTimes(1); + expect(mockMediaKeys.createSession) + .toHaveBeenCalledWith('persistent-license'); + expect(session1.load).toHaveBeenCalledWith('persistent-session-id'); + + expect(onErrorSpy).toHaveBeenCalled(); + const error = onErrorSpy.calls.argsFor(0)[0]; + shaka.test.Util.expectToEqualError(error, new shaka.util.Error( + shaka.util.Error.Severity.CRITICAL, + shaka.util.Error.Category.DRM, + shaka.util.Error.Code.OFFLINE_SESSION_REMOVED)); + }); + + it('uses persistent session ids when available', async () => { + const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; + + const keyId1 = makeKeyId(1); + const keyId2 = makeKeyId(2); + + /** @type {!Uint8Array} */ + const initData1 = new Uint8Array(5); + + // Key IDs in manifest + tweakDrmInfos((drmInfos) => { + drmInfos[0].keyIds = new Set([ + Uint8ArrayUtils.toHex(keyId1), Uint8ArrayUtils.toHex(keyId2), + ]); + drmInfos[0].initData = [ + {initData: initData1, initDataType: 'cenc', keyId: null}, + ]; + }); + + // Given persistent session is available + session1.load.and.returnValue(true); + + config.persistentSessionOnlinePlayback = true; + config.persistentSessionsMetadata = [{ + sessionId: 'persistent-session-id', + initData: initData1, + initDataType: 'cenc'}]; + + drmEngine.configure(config); + + await initAndAttach(); + + await Util.shortDelay(); + + session1.keyStatuses.forEach.and.callFake((callback) => { + callback(keyId1, 'usable'); + callback(keyId2, 'usable'); + }); + + session1.on['keystatuseschange']({target: session1}); + + expect(mockMediaKeys.createSession).toHaveBeenCalledTimes(1); + expect(mockMediaKeys.createSession) + .toHaveBeenCalledWith('persistent-license'); + expect(session1.load).toHaveBeenCalledWith('persistent-session-id'); + + expect(session2.generateRequest).not.toHaveBeenCalled(); + }); + + it( + 'tries persistent session ids before requesting a license', + async () => { + const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; + + const keyId1 = makeKeyId(1); + + /** @type {!Uint8Array} */ + const initData1 = new Uint8Array(5); + + // Key IDs in manifest + tweakDrmInfos((drmInfos) => { + drmInfos[0].keyIds = new Set([ + Uint8ArrayUtils.toHex(keyId1), + ]); + drmInfos[0].sessionType = 'temporary'; + drmInfos[0].initData = [ + {initData: initData1, initDataType: 'cenc', keyId: null}, + ]; + }); + + // Given persistent sessions aren't available + session1.load.and.returnValue(Promise.resolve(false)); + session2.load.and.returnValue( + Promise.reject(new Error('This should be a recoverable error'))); + + manifest.offlineSessionIds = ['persistent-session-id-1']; + + config.persistentSessionsMetadata = [{ + sessionId: 'persistent-session-id-2', + initData: initData1, + initDataType: 'cenc'}]; + config.persistentSessionOnlinePlayback = true; + + drmEngine.configure(config); + + onErrorSpy.and.stub(); + + await initAndAttach(); + + await Util.shortDelay(); + + shaka.test.Util.expectToEqualError( + onErrorSpy.calls.argsFor(0)[0], + new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.DRM, + shaka.util.Error.Code.OFFLINE_SESSION_REMOVED)); + + shaka.test.Util.expectToEqualError( + onErrorSpy.calls.argsFor(1)[0], + new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.DRM, + shaka.util.Error.Code.FAILED_TO_CREATE_SESSION, + 'This should be a recoverable error')); + + // We need to go through the whole license request / update, + // otherwise the DrmEngine will be destroyed while waiting for + // sessions to be marked as loaded, throwing an unhandled exception + const operation = shaka.util.AbortableOperation.completed( + new Uint8Array(0)); + fakeNetEngine.request.and.returnValue(operation); + + await Util.shortDelay(); + + session3.on['message']({ + target: session3, + message: new Uint8Array(0), + messageType: 'license-request'}); + + session3.keyStatuses.forEach.and.callFake((callback) => { + callback(keyId1, 'usable'); + }); + + session3.on['keystatuseschange']({target: session3}); + + await Util.shortDelay(); + + expect(mockMediaKeys.createSession).toHaveBeenCalledTimes(3); + expect(mockMediaKeys.createSession) + .toHaveBeenCalledWith('persistent-license'); + expect(session1.load) + .toHaveBeenCalledWith('persistent-session-id-1'); + + expect(mockMediaKeys.createSession) + .toHaveBeenCalledWith('persistent-license'); + expect(session2.load) + .toHaveBeenCalledWith('persistent-session-id-2'); + + expect(mockMediaKeys.createSession) + .toHaveBeenCalledWith('temporary'); + expect(session3.generateRequest) + .toHaveBeenCalledWith('cenc', initData1); + }); }); // describe('attach') describe('events', () => { @@ -1632,6 +1804,31 @@ describe('DrmEngine', () => { expect(session2.remove).toHaveBeenCalled(); }); + it( + // eslint-disable-next-line max-len + 'tears down & does not remove active persistent sessions based on configuration flag', + async () => { + config.advanced['drm.abc'] = createAdvancedConfig(null); + config.advanced['drm.abc'].sessionType = 'persistent-license'; + config.persistentSessionOnlinePlayback = true; + + drmEngine.configure(config); + + await initAndAttach(); + await sendEncryptedEvent('cenc', new Uint8Array(2)); + + const message = new Uint8Array(0); + session1.on['message']({target: session1, message: message}); + session1.update.and.returnValue(Promise.resolve()); + + await shaka.test.Util.shortDelay(); + mockVideo.setMediaKeys.calls.reset(); + await drmEngine.destroy(); + + expect(session1.close).toHaveBeenCalled(); + expect(session1.remove).not.toHaveBeenCalled(); + }); + it('swallows errors when closing sessions', async () => { await initAndAttach(); await sendEncryptedEvent('webm'); diff --git a/test/offline/storage_integration.js b/test/offline/storage_integration.js index a8009407f9..1f3ab5f182 100644 --- a/test/offline/storage_integration.js +++ b/test/offline/storage_integration.js @@ -1751,7 +1751,8 @@ filterDescribe('Storage', storageSupport, () => { * @suppress {accessControls} */ function loadOfflineSession(drmEngine, sessionName) { - return drmEngine.loadOfflineSession_(sessionName); + return drmEngine.loadOfflineSession_( + sessionName, {initData: null, initDataType: null}); } /**