Skip to content

Commit d9836b5

Browse files
authored
Adds "maximumOpenMediaKeySessions" to Settings for configurable maximum number of KeySessions (Dash-Industry-Forum#4817)
* Added "maximumOpenMediaKeySessions" to Settings under protection * If keepProtectionMediaKeys is enabled and the maximumOpenMediaKeySessions setting has a valid number, then the oldest MediaKeySession will be closed when the maximum number of sessions is reached. * Added check if "settings" is available * Added tests for new maximumOpenMediaKeySessions setting. ProtectionModelMock gained mock session handling functions. * Renamed maximumOpenMediaKeySessions to keepProtectionMediaKeysMaximumOpenSessions and Default value now is -1. Additionally, a warning is logged, when keepProtectionMediaKeysMaximumOpenSessions is set, but keepProtectionMediaKeys is disabled. * Added "keepProtectionMediaKeysMaximumOpenSessions" to index.d.ts and fixed description of keepProtectionMediaKeys in Settings. Additionally, moved implementation to limit MediaKeySessions to a new function called "_enforceMediaKeySessionLimit()" in ProtectionController.
1 parent 6833861 commit d9836b5

File tree

5 files changed

+137
-3
lines changed

5 files changed

+137
-3
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,7 @@ declare namespace dashjs {
16731673
},
16741674
protection?: {
16751675
keepProtectionMediaKeys?: boolean,
1676+
keepProtectionMediaKeysMaximumOpenSessions?: number,
16761677
ignoreEmeEncryptedEvent?: boolean,
16771678
detectPlayreadyMessageFormat?: boolean,
16781679
ignoreKeyStatuses?: boolean,

src/core/Settings.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ import Events from './events/Events.js';
103103
* },
104104
* protection: {
105105
* keepProtectionMediaKeys: false,
106+
* keepProtectionMediaKeysMaximumOpenSessions: -1,
106107
* ignoreEmeEncryptedEvent: false,
107108
* detectPlayreadyMessageFormat: true,
108109
* ignoreKeyStatuses: false
@@ -689,8 +690,11 @@ import Events from './events/Events.js';
689690
* @typedef {Object} Protection
690691
* @property {boolean} [keepProtectionMediaKeys=false]
691692
* Set the value for the ProtectionController and MediaKeys life cycle.
692-
*
693693
* If true, the ProtectionController and then created MediaKeys and MediaKeySessions will be preserved during the MediaPlayer lifetime.
694+
*
695+
* @property {number} [keepProtectionMediaKeysMaximumOpenSessions=-1]
696+
* Maximum number of open MediaKeySessions, when keepProtectionMediaKeys is enabled. If set, dash.js will close the oldest sessions when the limit is exceeded. -1 means unlimited.
697+
*
694698
* @property {boolean} [ignoreEmeEncryptedEvent=false]
695699
* If set to true the player will ignore "encrypted" and "needkey" events thrown by the EME.
696700
*
@@ -1144,6 +1148,7 @@ function Settings() {
11441148
},
11451149
protection: {
11461150
keepProtectionMediaKeys: false,
1151+
keepProtectionMediaKeysMaximumOpenSessions: -1,
11471152
ignoreEmeEncryptedEvent: false,
11481153
detectPlayreadyMessageFormat: true,
11491154
ignoreKeyStatuses: false

src/streaming/protection/controllers/ProtectionController.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,9 @@ function ProtectionController(config) {
384384
return;
385385
}
386386

387+
// Enforce maximum number of open MediaKeySessions, if settings are provided
388+
_enforceMediaKeySessionLimit();
389+
387390
const initDataForKS = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, keySystemMetadata ? keySystemMetadata.initData : null);
388391
if (initDataForKS) {
389392

@@ -411,6 +414,33 @@ function ProtectionController(config) {
411414
}
412415
}
413416

417+
/**
418+
* Enforces the maximum number of open MediaKeySessions, if settings are provided.
419+
* @description This method checks the current number of open sessions and closes the oldest session if the limit is reached.
420+
* @requires keepProtectionMediaKeys is enabled and keepProtectionMediaKeysMaximumOpenSessions is set with a positive value.
421+
* @private
422+
*/
423+
function _enforceMediaKeySessionLimit() {
424+
if (!settings) { return; }
425+
const isKeepProtectionMediaKeysEnabled = settings.get().streaming.protection.keepProtectionMediaKeys;
426+
const maxSessions = settings.get().streaming.protection.keepProtectionMediaKeysMaximumOpenSessions;
427+
if (typeof maxSessions !== 'number' || maxSessions <= 0) { return; }
428+
if (!isKeepProtectionMediaKeysEnabled) {
429+
logger.warn('DRM: keepProtectionMediaKeysMaximumOpenSessions is set to ' + maxSessions + ', but keepProtectionMediaKeys is not enabled. Therefore, keepProtectionMediaKeysMaximumOpenSessions will be ignored.');
430+
return;
431+
}
432+
// Ensure protectionModel is available before accessing sessions
433+
if (!protectionModel || typeof protectionModel.getSessionTokens !== 'function') { return; }
434+
const sessionTokens = protectionModel.getSessionTokens() || [];
435+
if (sessionTokens.length < maxSessions) { return; }
436+
// Limit reached. Close the oldest session to make room for a new one.
437+
const oldestSession = sessionTokens[0];
438+
if (oldestSession) {
439+
logger.info('DRM: Maximum number of open MediaKeySessions reached (' + maxSessions + '), closing oldest session.');
440+
closeKeySession(oldestSession);
441+
}
442+
}
443+
414444
/**
415445
* Returns the protectionData for a specific keysystem as specified by the application.
416446
* @param {object} keySystem

test/unit/mocks/ProtectionModelMock.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import DashJSError from '../../../src/streaming/vo/DashJSError.js';
33

44
function ProtectionModelMock (config) {
55

6+
let sessions = [];
7+
let nextId = 0;
8+
69
this.setServerCertificate = function (/*elt*/) {
710
config.eventBus.trigger(config.events.SERVER_CERTIFICATE_UPDATED, {error: new DashJSError(ProtectionErrors.SERVER_CERTIFICATE_UPDATED_ERROR_CODE, ProtectionErrors.SERVER_CERTIFICATE_UPDATED_ERROR_MESSAGE)});
811
};
@@ -11,11 +14,38 @@ function ProtectionModelMock (config) {
1114
};
1215

1316
this.reset = function () {
17+
sessions = [];
18+
nextId = 0;
1419
};
1520

1621
this.requestKeySystemAccess = function () {
1722
return Promise.resolve();
1823
};
24+
25+
this.getSessionTokens = function () { return sessions; };
26+
this.createKeySession = function (keySystemMetadata) {
27+
const keyId = keySystemMetadata && keySystemMetadata.keyId !== undefined ? keySystemMetadata.keyId : nextId;
28+
const sessionType = keySystemMetadata && keySystemMetadata.sessionType ? keySystemMetadata.sessionType : 'temporary';
29+
const sessionId = keySystemMetadata && keySystemMetadata.sessionId ? keySystemMetadata.sessionId : null;
30+
const session = {
31+
id: nextId++,
32+
keyId: keyId,
33+
initData: keySystemMetadata.initData,
34+
sessionType: sessionType,
35+
sessionId: sessionId,
36+
hasTriggeredKeyStatusMapUpdate: false,
37+
getKeyId: function() { return this.keyId; },
38+
getSessionId: function() { return this.sessionId; },
39+
getSessionType: function() { return this.sessionType; }
40+
};
41+
sessions.push(session);
42+
};
43+
this.closeKeySession = function (session) {
44+
const idx = sessions.findIndex(s => s.id === session.id);
45+
if (idx !== -1) {
46+
sessions.splice(idx, 1);
47+
}
48+
};
1949
}
2050

2151
export default ProtectionModelMock;

test/unit/test/streaming/streaming.protection.controllers.ProtectionController.js

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import EventBus from '../../../../src/core/EventBus.js';
66
import DebugMock from '../../mocks/DebugMock.js';
77
import ProtectionKeyControllerMock from '../../mocks/ProtectionKeyControllerMock.js';
88
import ProtectionModelMock from '../../mocks/ProtectionModelMock.js';
9+
import CommonEncryption from '../../../../src/streaming/protection/CommonEncryption.js';
910

1011
import {expect} from 'chai';
1112
const context = {};
@@ -66,15 +67,20 @@ describe('ProtectionController', function () {
6667
});
6768

6869
describe('Well initialized', function () {
70+
let protectionModelMock, settingsMock;
71+
6972
beforeEach(function () {
7073
const protectionKeyControllerMock = new ProtectionKeyControllerMock();
74+
settingsMock = { get: () => ({ streaming: { protection: {} } }) };
75+
protectionModelMock = new ProtectionModelMock({ events: ProtectionEvents, eventBus: eventBus });
7176
protectionController = ProtectionController(context).create({
7277
protectionKeyController: protectionKeyControllerMock,
7378
events: ProtectionEvents,
7479
debug: new DebugMock(),
75-
protectionModel: new ProtectionModelMock({ events: ProtectionEvents, eventBus: eventBus }),
80+
protectionModel: protectionModelMock,
7681
eventBus: eventBus,
77-
constants: Constants
82+
constants: Constants,
83+
settings: settingsMock
7884
});
7985
});
8086

@@ -129,5 +135,67 @@ describe('ProtectionController', function () {
129135
expect(keySystems).not.to.be.empty;
130136
});
131137

138+
// tests for keepProtectionMediaKeysMaximumOpenSessions feature
139+
it('should close the oldest session when the maximum is reached and keepProtectionMediaKeys is true', function () {
140+
settingsMock.get = () => ({
141+
streaming: {
142+
protection: {
143+
keepProtectionMediaKeys: true,
144+
keepProtectionMediaKeysMaximumOpenSessions: 2
145+
}
146+
}
147+
});
148+
CommonEncryption.getPSSHForKeySystem = (selectedKeySystem, initData) => initData;
149+
protectionController.selectedKeySystem = { systemString: 'mock-system' };
150+
151+
protectionController.createKeySession({ initData: new ArrayBuffer(8), keyId: 'session-1', sessionType: 'temporary' });
152+
protectionController.createKeySession({ initData: new ArrayBuffer(16), keyId: 'session-2', sessionType: 'temporary' });
153+
// add third session, should close the first one
154+
protectionController.createKeySession({ initData: new ArrayBuffer(24), keyId: 'session-3', sessionType: 'temporary' });
155+
156+
expect(protectionModelMock.getSessionTokens().length, 'Session count should still be 2').to.equal(2);
157+
expect(protectionModelMock.getSessionTokens().map(s => s.keyId)).to.deep.equal(['session-2', 'session-3']);
158+
});
159+
160+
it('should add a session if keepProtectionMediaKeys is false', function () {
161+
settingsMock.get = () => ({
162+
streaming: {
163+
protection: {
164+
keepProtectionMediaKeys: false,
165+
keepProtectionMediaKeysMaximumOpenSessions: 2
166+
}
167+
}
168+
});
169+
CommonEncryption.getPSSHForKeySystem = (selectedKeySystem, initData) => initData;
170+
protectionController.selectedKeySystem = { systemString: 'mock-system' };
171+
172+
expect(protectionModelMock.getSessionTokens().length).to.equal(0);
173+
protectionController.createKeySession({ initData: new ArrayBuffer(8), keyId: 'session-1', sessionType: 'temporary' });
174+
protectionController.createKeySession({ initData: new ArrayBuffer(16), keyId: 'session-2', sessionType: 'temporary' });
175+
protectionController.createKeySession({ initData: new ArrayBuffer(24), keyId: 'session-3', sessionType: 'temporary' });
176+
expect(protectionModelMock.getSessionTokens().length).to.equal(3);
177+
expect(protectionModelMock.getSessionTokens().map(s => s.keyId)).to.deep.equal(['session-1', 'session-2', 'session-3']);
178+
});
179+
180+
it('should not close any session if keepProtectionMediaKeys is true, but keepProtectionMediaKeysMaximumOpenSessions is not set', function () {
181+
settingsMock.get = () => ({
182+
streaming: {
183+
protection: {
184+
keepProtectionMediaKeys: true
185+
// keepProtectionMediaKeysMaximumOpenSessions is undefined
186+
}
187+
}
188+
});
189+
CommonEncryption.getPSSHForKeySystem = (selectedKeySystem, initData) => initData;
190+
protectionController.selectedKeySystem = { systemString: 'mock-system' };
191+
192+
expect(protectionModelMock.getSessionTokens().length).to.equal(0);
193+
protectionController.createKeySession({ initData: new ArrayBuffer(8), keyId: 'session-1', sessionType: 'temporary' });
194+
protectionController.createKeySession({ initData: new ArrayBuffer(16), keyId: 'session-2', sessionType: 'temporary' });
195+
protectionController.createKeySession({ initData: new ArrayBuffer(24), keyId: 'session-3', sessionType: 'temporary' });
196+
expect(protectionModelMock.getSessionTokens().length).to.equal(3);
197+
expect(protectionModelMock.getSessionTokens().map(s => s.keyId)).to.deep.equal(['session-1', 'session-2', 'session-3']);
198+
});
199+
132200
});
133201
});

0 commit comments

Comments
 (0)