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

Load persistent sessions #2451

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
29 changes: 29 additions & 0 deletions samples/dash-if-reference-player/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
$scope.fastSwitchSelected = true;
$scope.ABRStrategy = 'abrDynamic';

// Persistent license
$scope.persistentSessionId = {};
$scope.selectedKeySystem = null;

// Error management
$scope.error = '';
$scope.errorType = '';
Expand Down Expand Up @@ -294,6 +298,20 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
}
}, $scope);

$scope.player.on(dashjs.MediaPlayer.events.KEY_SYSTEM_SELECTED, function (e) { /* jshint ignore:line */
if (e.data) {
$scope.selectedKeySystem = e.data.keySystem.systemString;
}
}, $scope);

$scope.player.on(dashjs.MediaPlayer.events.KEY_SESSION_CREATED, function (e) { /* jshint ignore:line */
if (e.data) {
var session = e.data;
if (session.getSessionType() === 'persistent-license') {
$scope.persistentSessionId[$scope.selectedItem.url] = session.getSessionID();
}
}
}, $scope);

////////////////////////////////////////
//
Expand Down Expand Up @@ -366,6 +384,12 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
protData = null;
}

// Check if persistent license session ID is stored for current stream
var sessionId = $scope.persistentSessionId[$scope.selectedItem.url];
if (sessionId) {
protData[$scope.selectedKeySystem].sessionId = sessionId;
}

var bufferConfig = {
liveDelay: $scope.defaultLiveDelay,
stableBufferTime: $scope.defaultStableBufferDelay,
Expand Down Expand Up @@ -417,6 +441,11 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
$scope.controlbar.enable();
};

$scope.doStop = function () {
$scope.player.attachSource(null);
$scope.controlbar.reset();
}

$scope.changeTrackSwitchMode = function (mode, type) {
$scope.player.setTrackSwitchModeFor(type, mode);
};
Expand Down
10 changes: 10 additions & 0 deletions samples/dash-if-reference-player/app/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,16 @@
{
"name": "Unified Streaming Live",
"url": "http://live.unified-streaming.com/loop/loop.isml/loop.mpd?format=mp4&session_id=25020"
},
{
"name": "Unified Streaming (Widevine, persistent)",
"url": "http://demo.unified-streaming.com/video/tears-of-steel/tears-of-steel-dash-widevine.ism/.mpd",
"protData": {
"com.widevine.alpha": {
"serverURL": "https://cwip-shaka-proxy.appspot.com/no_auth",
"sessionType": "persistent-license"
}
}
}
]
},
Expand Down
1 change: 1 addition & 0 deletions samples/dash-if-reference-player/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
<input type="text" class="form-control" ng-model="selectedItem.url">
<span class="input-group-btn">
<button class="btn btn-default" ng-click="toggleOptionsGutter(!optionsGutter)" ng-cloak>{{getOptionsButtonLabel()}}</button>
<button class="btn btn-default" type="button" ng-click="doStop()">Stop</button>
<button class="btn btn-primary" type="button" ng-click="doLoad()">Load</button>
</span>
</div>
Expand Down
36 changes: 28 additions & 8 deletions src/streaming/protection/controllers/ProtectionController.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,12 @@ function ProtectionController(config) {
}
}
try {
protectionModel.createKeySession(initDataForKS, protData, sessionType, cdmData);
protectionModel.createKeySession(initDataForKS, protData, getSessionType(keySystem), cdmData);
} catch (error) {
eventBus.trigger(events.KEY_SESSION_CREATED, {data: null, error: 'Error creating key session! ' + error.message});
}
} else if (initData) {
protectionModel.createKeySession(initData, protData, sessionType, cdmData);
protectionModel.createKeySession(initData, protData, getSessionType(keySystem), cdmData);
} else {
eventBus.trigger(events.KEY_SESSION_CREATED, {data: null, error: 'Selected key system is ' + keySystem.systemString + '. needkey/encrypted event contains no initData corresponding to that key system!'});
}
Expand All @@ -184,12 +184,13 @@ function ProtectionController(config) {
* essentially creates a new key session
*
* @param {string} sessionID
* @param {string} initData
* @memberof module:ProtectionController
* @instance
* @fires ProtectionController#KeySessionCreated
*/
function loadKeySession(sessionID) {
protectionModel.loadKeySession(sessionID);
function loadKeySession(sessionID, initData) {
protectionModel.loadKeySession(sessionID, initData, getSessionType(keySystem));
}

/**
Expand Down Expand Up @@ -338,6 +339,7 @@ function ProtectionController(config) {
const videoCapabilities = [];
const audioRobustness = (protData && protData.audioRobustness && protData.audioRobustness.length > 0) ? protData.audioRobustness : robustnessLevel;
const videoRobustness = (protData && protData.videoRobustness && protData.videoRobustness.length > 0) ? protData.videoRobustness : robustnessLevel;
const ksSessionType = getSessionType(keySystem);

if (audioInfo) {
audioCapabilities.push(new MediaCapability(audioInfo.codec, audioRobustness));
Expand All @@ -348,8 +350,14 @@ function ProtectionController(config) {

return new KeySystemConfiguration(
audioCapabilities, videoCapabilities, 'optional',
(sessionType === 'temporary') ? 'optional' : 'required',
[sessionType]);
(ksSessionType === 'temporary') ? 'optional' : 'required',
[ksSessionType]);
}

function getSessionType(keySystem) {
const protData = getProtData(keySystem);
const ksSessionType = (protData && protData.sessionType) ? protData.sessionType : sessionType;
return ksSessionType;
}

function selectKeySystem(supportedKS, fromManifest) {
Expand All @@ -375,7 +383,13 @@ function ProtectionController(config) {
} else {
log('DRM: KeySystem Access Granted');
eventBus.trigger(events.KEY_SYSTEM_SELECTED, {data: event.data});
createKeySession(supportedKS[ksIdx].initData, supportedKS[ksIdx].cdmData);
if (supportedKS[ksIdx].sessionId) {
// Load MediaKeySession with sessionId
loadKeySession(supportedKS[ksIdx].sessionId, supportedKS[ksIdx].initData);
} else if (supportedKS[ksIdx].initData) {
// Create new MediaKeySession with initData
createKeySession(supportedKS[ksIdx].initData, supportedKS[ksIdx].cdmData);
}
}
};
eventBus.on(events.KEY_SYSTEM_ACCESS_COMPLETE, onKeySystemAccessComplete, self);
Expand Down Expand Up @@ -427,7 +441,13 @@ function ProtectionController(config) {
const initData = { kids: Object.keys(protData.clearkeys) };
pendingNeedKeyData[i][ksIdx].initData = new TextEncoder().encode(JSON.stringify(initData));
}
createKeySession(pendingNeedKeyData[i][ksIdx].initData, pendingNeedKeyData[i][ksIdx].cdmData);
if (pendingNeedKeyData[i][ksIdx].sessionId) {
// Load MediaKeySession with sessionId
loadKeySession(pendingNeedKeyData[i][ksIdx].sessionId, pendingNeedKeyData[i][ksIdx].initData);
} else if (pendingNeedKeyData[i][ksIdx].initData !== null) {
// Create new MediaKeySession with initData
createKeySession(pendingNeedKeyData[i][ksIdx].initData, pendingNeedKeyData[i][ksIdx].cdmData);
}
break;
}
}
Expand Down
33 changes: 15 additions & 18 deletions src/streaming/protection/controllers/ProtectionKeyController.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,19 +192,12 @@ function ProtectionKeyController() {
cp = cps[cpIdx];
if (cp.schemeIdUri.toLowerCase() === ks.schemeIdURI) {
// Look for DRM-specific ContentProtection
let initData = ks.getInitData(cp);
if (!!initData) {
supportedKS.push({
ks: keySystems[ksIdx],
initData: initData,
cdmData: ks.getCDMData()
});
} else if (this.isClearKey(ks)) {
supportedKS.push({
ks: ks,
initData: null
});
}
supportedKS.push({
ks: ks,
initData: ks.getInitData(cp),
cdmData: ks.getCDMData(),
sessionId: ks.getSessionId(cp)
});
}
}
}
Expand All @@ -231,15 +224,19 @@ function ProtectionKeyController() {
function getSupportedKeySystems(initData, protDataSet) {
let supportedKS = [];
let pssh = CommonEncryption.parsePSSHList(initData);
let ks, keySystemString, shouldNotFilterOutKeySystem;

for (let ksIdx = 0; ksIdx < keySystems.length; ++ksIdx) {
let keySystemString = keySystems[ksIdx].systemString;
let shouldNotFilterOutKeySystem = (protDataSet) ? keySystemString in protDataSet : true;
ks = keySystems[ksIdx];
keySystemString = ks.systemString;
shouldNotFilterOutKeySystem = (protDataSet) ? keySystemString in protDataSet : true;

if (keySystems[ksIdx].uuid in pssh && shouldNotFilterOutKeySystem) {
if (ks.uuid in pssh && shouldNotFilterOutKeySystem) {
supportedKS.push({
ks: keySystems[ksIdx],
initData: pssh[keySystems[ksIdx].uuid]
ks: ks,
initData: pssh[ks.uuid],
cdmData: ks.getCDMData(),
sessionId: ks.getSessionId()
});
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/streaming/protection/drm/KeySystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,12 @@
* @function
* @name MediaPlayer.dependencies.protection.KeySystem#getCDMData
* @returns {ArrayBuffer} the CDM (custom) data
*/
*/

/**
* Returns MediaKeySession session ID.
*
* @function
* @name MediaPlayer.dependencies.protection.KeySystem#getSessionId
* @returns {String} the MediaKeySession session ID
*/
5 changes: 5 additions & 0 deletions src/streaming/protection/drm/KeySystemClearKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ function KeySystemClearKey(config) {
return null;
}

function getSessionId(/*cp*/) {
return null;
}

instance = {
uuid: uuid,
schemeIdURI: schemeIdURI,
Expand All @@ -104,6 +108,7 @@ function KeySystemClearKey(config) {
getLicenseRequestFromMessage: getLicenseRequestFromMessage,
getLicenseServerURLFromInitData: getLicenseServerURLFromInitData,
getCDMData: getCDMData,
getSessionId: getSessionId,
getClearKeysFromProtectionData: getClearKeysFromProtectionData
};

Expand Down
5 changes: 5 additions & 0 deletions src/streaming/protection/drm/KeySystemPlayReady.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ function KeySystemPlayReady(config) {
return null;
}

function getSessionId(/*cp*/) {
return null;
}

instance = {
uuid: uuid,
schemeIdURI: schemeIdURI,
Expand All @@ -282,6 +286,7 @@ function KeySystemPlayReady(config) {
getLicenseRequestFromMessage: getLicenseRequestFromMessage,
getLicenseServerURLFromInitData: getLicenseServerURLFromInitData,
getCDMData: getCDMData,
getSessionId: getSessionId,
setPlayReadyMessageFormat: setPlayReadyMessageFormat,
init: init
};
Expand Down
5 changes: 5 additions & 0 deletions src/streaming/protection/drm/KeySystemW3CClearKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ function KeySystemW3CClearKey(config) {
return null;
}

function getSessionId(/*cp*/) {
return null;
}

instance = {
uuid: uuid,
schemeIdURI: schemeIdURI,
Expand All @@ -104,6 +108,7 @@ function KeySystemW3CClearKey(config) {
getLicenseRequestFromMessage: getLicenseRequestFromMessage,
getLicenseServerURLFromInitData: getLicenseServerURLFromInitData,
getCDMData: getCDMData,
getSessionId: getSessionId,
getClearKeysFromProtectionData: getClearKeysFromProtectionData
};

Expand Down
13 changes: 12 additions & 1 deletion src/streaming/protection/drm/KeySystemWidevine.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ function KeySystemWidevine(config) {
return null;
}

function getSessionId(cp) {
// Get sessionId from protectionData or from manifest
if (protData && protData.sessionId) {
return protData.sessionId;
} else if (cp && cp.sessionId) {
return cp.sessionId;
}
return null;
}

instance = {
uuid: uuid,
schemeIdURI: schemeIdURI,
Expand All @@ -85,7 +95,8 @@ function KeySystemWidevine(config) {
getRequestHeadersFromMessage: getRequestHeadersFromMessage,
getLicenseRequestFromMessage: getLicenseRequestFromMessage,
getLicenseServerURLFromInitData: getLicenseServerURLFromInitData,
getCDMData: getCDMData
getCDMData: getCDMData,
getSessionId: getSessionId
};

return instance;
Expand Down
2 changes: 2 additions & 0 deletions src/streaming/protection/models/ProtectionModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ let ProtectionModel = function () { };
* @memberof ProtectionModel
* @param {string} sessionID the session ID corresponding to the persisted
* session data to be loaded
* @param {ArrayBuffer} the corresponding initData PSSH box for the currently
* selected key system.
*/

/**
Expand Down
25 changes: 19 additions & 6 deletions src/streaming/protection/models/ProtectionModel_21Jan2015.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ function ProtectionModel_21Jan2015(config) {
function getAllInitData() {
const retVal = [];
for (let i = 0; i < sessions.length; i++) {
retVal.push(sessions[i].initData);
if (sessions[i].initData) {
retVal.push(sessions[i].initData);
}
}
return retVal;
}
Expand Down Expand Up @@ -210,23 +212,33 @@ function ProtectionModel_21Jan2015(config) {
});
}

function loadKeySession(sessionID) {
function loadKeySession(sessionID, initData, sessionType) {
if (!keySystem || !mediaKeys) {
throw new Error('Can not load sessions until you have selected a key system');
}

const session = mediaKeys.createSession();
// Check if session Id is not already loaded or loading
for (let i = 0; i < sessions.length; i++) {
if (sessionID === sessions[i].sessionId) {
log('DRM: Ignoring session ID because we have already seen it!');
return;
}
}

const session = mediaKeys.createSession(sessionType);
const sessionToken = createSessionToken(session, initData, sessionType, sessionID);

// Load persisted session data into our newly created session object
session.load(sessionID).then(function (success) {
if (success) {
const sessionToken = createSessionToken(session);
log('DRM: Session created. SessionID = ' + sessionToken.getSessionID());
log('DRM: Session loaded. SessionID = ' + sessionToken.getSessionID());
eventBus.trigger(events.KEY_SESSION_CREATED, {data: sessionToken});
} else {
removeSession(sessionToken);
eventBus.trigger(events.KEY_SESSION_CREATED, {data: null, error: 'Could not load session! Invalid Session ID (' + sessionID + ')'});
}
}).catch(function (error) {
removeSession(sessionToken);
eventBus.trigger(events.KEY_SESSION_CREATED, {data: null, error: 'Could not load session (' + sessionID + ')! ' + error.name});
});
}
Expand Down Expand Up @@ -321,10 +333,11 @@ function ProtectionModel_21Jan2015(config) {

// Function to create our session token objects which manage the EME
// MediaKeySession and session-specific event handler
function createSessionToken(session, initData, sessionType) {
function createSessionToken(session, initData, sessionType, sessionID) {
const token = { // Implements SessionToken
session: session,
initData: initData,
sessionId: sessionID,

// This is our main event handler for all desired MediaKeySession events
// These events are translated into our API-independent versions of the
Expand Down