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

Add support for multiple system strings for a single key system #3859

Merged
merged 9 commits into from
Jan 31, 2022
2 changes: 1 addition & 1 deletion samples/drm/clearkey.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
};
var video,
player,
url = "https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_ClearKey.mpd";
url = "https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd";

video = document.querySelector("video");
player = dashjs.MediaPlayer().create();
Expand Down
33 changes: 28 additions & 5 deletions samples/drm/mpds/laurl.mpd
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Version information:
Axinom.MediaProcessing v3.0.0 targeting General Purpose Media Format specification v7
ffmpeg version N-81423-g61fac0e Copyright (c) 2000-2016 the FFmpeg developers
x265 [info]: HEVC encoder version 2.0+12-49a0d1176aef5bc6
x264 0.148.2705 3f5ed56
MP4Box - GPAC version 0.6.2-DEV-rev683-g7b29fbe-master
MediaInfoLib - v0.7.87

For more info about this video, see https://github.com/Axinom/dash-test-vectors
-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500S" type="static"
mediaPresentationDuration="PT0H12M14.000S" maxSegmentDuration="PT0H0M4.000S"
profiles="urn:mpeg:dash:profile:isoff-live:2011,http://dashif.org/guidelines/dash264"
xmlns:cenc="urn:mpeg:cenc:2013" xmlns:dashif="https://dashif.org/">
xmlns:dashif="https://dashif.org/"
xmlns:cenc="urn:mpeg:cenc:2013">
<Period duration="PT0H12M14.000S">
<BaseURL>https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/</BaseURL>
<AdaptationSet segmentAlignment="true" group="1" maxWidth="1920" maxHeight="1080" maxFrameRate="24" par="16:9"
Expand All @@ -15,7 +28,7 @@
<pro xmlns="urn:microsoft:playready">
xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARABRAFcAMABuAGsAdgBrAEEAawBpAFQATABpAGYAWABVAEkAUABpAFoAZwA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
</pro>
<dashif:Laurl>https://drm-widevine-licensing.axtest.net/AcquireLicense</dashif:Laurl>
<dashif:Laurl>https://drm-playready-licensing.axtest.net/AcquireLicense</dashif:Laurl>
</ContentProtection>
<ContentProtection value="Widevine" schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<dashif:Laurl>https://drm-widevine-licensing.axtest.net/AcquireLicense</dashif:Laurl>
Expand Down Expand Up @@ -45,20 +58,30 @@
<pro xmlns="urn:microsoft:playready">
xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ARABRAFcAMABuAGsAdgBrAEEAawBpAFQATABpAGYAWABVAEkAUABpAFoAZwA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=
</pro>
<dashif:Laurl>https://drm-widevine-licensing.axtest.net/AcquireLicense</dashif:Laurl>
<dashif:Laurl>https://drm-playready-licensing.axtest.net/AcquireLicense</dashif:Laurl>
</ContentProtection>
<ContentProtection value="Widevine" schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQnrQFDeRLSAKTLifXUIPiZg==</cenc:pssh>
<dashif:Laurl>https://drm-widevine-licensing.axtest.net/AcquireLicense</dashif:Laurl>
<cenc:pssh>AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQnrQFDeRLSAKTLifXUIPiZg==</cenc:pssh>
</ContentProtection>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<SegmentTemplate timescale="24000" media="$RepresentationID$/$Number%04d$.m4s" startNumber="1"
duration="95232" initialization="$RepresentationID$/init.mp4"/>
duration="95232"
initialization="$RepresentationID$/init.mp4"/>
<Representation id="15" mimeType="audio/mp4" codecs="mp4a.40.29" audioSamplingRate="48000" startWithSAP="1"
bandwidth="134642">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
value="2"/>
</Representation>
</AdaptationSet>
<AdaptationSet segmentAlignment="true" group="3" lang="en">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle"/>
<SegmentTemplate timescale="1000" media="$RepresentationID$/$Number%04d$.m4s" startNumber="1"
duration="4000"
initialization="$RepresentationID$/init.mp4"/>
<Representation id="18" mimeType="application/mp4" codecs="wvtt" startWithSAP="1"
bandwidth="428"></Representation>
</AdaptationSet>
</Period>
</MPD>
119 changes: 119 additions & 0 deletions samples/drm/system-string-priority.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DRM system string priority example</title>

<script src="../../dist/dash.all.debug.js"></script>

<!-- Bootstrap core CSS -->
<link href="../lib/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="../lib/main.css" rel="stylesheet">

<style>
video {
width: 640px;
height: 360px;
}
</style>

<script class="code">
function init() {
var protData = {
'com.widevine.alpha': {
'serverURL': 'https://drm-widevine-licensing.axtest.net/AcquireLicense',
'systemStringPriority': ['com.widevine.something', 'com.widevine.alpha'],
'priority': 2,
'httpRequestHeaders': {
'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImZpcnN0X3BsYXlfZXhwaXJhdGlvbiI6NjAsInBsYXlyZWFkeSI6eyJyZWFsX3RpbWVfZXhwaXJhdGlvbiI6dHJ1ZX0sImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.FAbIiPxX8BHi9RwfzD7Yn-wugU19ghrkBFKsaCPrZmU'
}
},
'com.microsoft.playready': {
'serverURL': 'https://drm-playready-licensing.axtest.net/AcquireLicense',
'systemStringPriority': ['com.microsoft.playready.something', 'com.microsoft.playready.recommendation', 'com.microsoft.playready.hardware', 'com.microsoft.playready'],
'priority': 1,
'httpRequestHeaders': {
'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImZpcnN0X3BsYXlfZXhwaXJhdGlvbiI6NjAsInBsYXlyZWFkeSI6eyJyZWFsX3RpbWVfZXhwaXJhdGlvbiI6dHJ1ZX0sImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.FAbIiPxX8BHi9RwfzD7Yn-wugU19ghrkBFKsaCPrZmU'
}
}
};
var video,
player,
url = 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p.mpd';

video = document.querySelector('video');
player = dashjs.MediaPlayer().create();
player.updateSettings({
debug: {
logLevel: 5
}
});
player.initialize(video, url, true);
player.setProtectionData(protData);
}

function check() {
if (location.protocol === 'http:' && location.hostname !== 'localhost') {
var out = 'This page has been loaded under http. This might result in the EME APIs not being available to the player and any DRM-protected content will fail to play. ' +
'If you wish to test manifest URLs that require EME support, then <a href=\'https:' + window.location.href.substring(window.location.protocol.length) + '\'>reload this page under https</a>.'
var div = document.getElementById('http-warning');
div.innerHTML = out;
div.style.display = ''
}
}
</script>
</head>
<body>

<main>
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<img class=""
src="../lib/img/dashjs-logo.png"
width="200">
</header>
<div class="row">
<div class="col-md-12">
<div class="alert alert-danger" role="alert" style="display: none" id="http-warning">

</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="h-100 p-5 bg-light border rounded-3">
<h3>DRM system string priority example</h3>
<p>This example shows how to specify the system string priority for the call to
requestMediaKeySystemAccess. For example, Playready might be supported
with the system strings "com.microsoft.playready.recommendation" and
"com.microsoft.playready". </p>
<p>For a detailed explanation on DRM playback in dash.js checkout the
<a href="https://github.com/Dash-Industry-Forum/dash.js/wiki/Digital-Rights-Management-(DRM)-and-license-acquisition"
target="_blank">Wiki</a>.</p>
</div>
</div>
<div class="col-md-8">
<video controls="true"></video>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="code-output"></div>
</div>
</div>
<footer class="pt-3 mt-4 text-muted border-top">
&copy; DASH-IF
</footer>
</div>
</main>


<script>
document.addEventListener('DOMContentLoaded', function () {
check();
init();
});
</script>
<script src="../highlighter.js"></script>
</body>
</html>
14 changes: 14 additions & 0 deletions samples/samples.json
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,20 @@
"Audio"
]
},
{
"title": "Keysystem string priority",
"description": "This example shows how to specify the system string priority for the call to requestMediaKeySystemAccess. For example, Playready might be supported with the system strings \"com.microsoft.playready.recommendation\" and \"com.microsoft.playready\". ",
"href": "drm/system-string-priority.html",
"image": "lib/img/tos-1.jpg",
"labels": [
"VoD",
"DRM",
"Widevine",
"Playready",
"Video",
"Audio"
]
},
{
"title": "License server via MPD",
"description": "This example shows how to specify the license server url as part of the MPD using 'dashif:laurl'",
Expand Down
1 change: 1 addition & 0 deletions src/streaming/constants/ProtectionConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ProtectionConstants {
this.CLEARKEY_KEYSTEM_STRING = 'org.w3.clearkey';
this.WIDEVINE_KEYSTEM_STRING = 'com.widevine.alpha';
this.PLAYREADY_KEYSTEM_STRING = 'com.microsoft.playready';
this.PLAYREADY_RECOMMENDATION_KEYSTEM_STRING = 'com.microsoft.playready.recommendation';
this.INITIALIZATION_DATA_TYPE_CENC = 'cenc';
this.INITIALIZATION_DATA_TYPE_KEYIDS = 'keyids'
this.INITIALIZATION_DATA_TYPE_WEBM = 'webm'
Expand Down
6 changes: 4 additions & 2 deletions src/streaming/protection/controllers/ProtectionController.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ function ProtectionController(config) {
const keySystemConfiguration = _getKeySystemConfiguration(supportedKS[i]);
requestedKeySystems.push({
ks: supportedKS[i].ks,
configs: [keySystemConfiguration]
configs: [keySystemConfiguration],
protData: supportedKS[i].protData
});
}

Expand All @@ -193,7 +194,8 @@ function ProtectionController(config) {
protectionModel.requestKeySystemAccess(requestedKeySystems)
.then((event) => {
keySystemAccess = event.data;
logger.info('DRM: KeySystem Access Granted (' + keySystemAccess.keySystem.systemString + ')! Selecting key system...');
let selectedSystemString = keySystemAccess.mksa && keySystemAccess.mksa.selectedSystemString ? keySystemAccess.mksa.selectedSystemString : keySystemAccess.keySystem.systemString;
logger.info('DRM: KeySystem Access Granted for system string (' + selectedSystemString + ')! Selecting key system...');
return protectionModel.selectKeySystem(keySystemAccess);
})
.then((keySystem) => {
Expand Down
72 changes: 61 additions & 11 deletions src/streaming/protection/models/ProtectionModel_21Jan2015.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ import KeyMessage from '../vo/KeyMessage';
import KeySystemAccess from '../vo/KeySystemAccess';
import ProtectionConstants from '../../constants/ProtectionConstants';

const SYSTEM_STRING_PRIORITY = {};
SYSTEM_STRING_PRIORITY[ProtectionConstants.PLAYREADY_KEYSTEM_STRING] = [ProtectionConstants.PLAYREADY_RECOMMENDATION_KEYSTEM_STRING, ProtectionConstants.PLAYREADY_KEYSTEM_STRING];
SYSTEM_STRING_PRIORITY[ProtectionConstants.WIDEVINE_KEYSTEM_STRING] = [ProtectionConstants.WIDEVINE_KEYSTEM_STRING];
SYSTEM_STRING_PRIORITY[ProtectionConstants.CLEARKEY_KEYSTEM_STRING] = [ProtectionConstants.CLEARKEY_KEYSTEM_STRING];

function ProtectionModel_21Jan2015(config) {

config = config || {};
Expand Down Expand Up @@ -153,6 +158,8 @@ function ProtectionModel_21Jan2015(config) {
* @private
*/
function _requestKeySystemAccessInternal(ksConfigurations, idx, resolve, reject) {

// In case requestMediaKeySystemAccess is not available we can not proceed and dispatch an error
if (navigator.requestMediaKeySystemAccess === undefined ||
typeof navigator.requestMediaKeySystemAccess !== 'function') {
const msg = 'Insecure origins are not allowed';
Expand All @@ -161,32 +168,75 @@ function ProtectionModel_21Jan2015(config) {
return;
}

const keySystem = ksConfigurations[idx].ks;
// If a systemStringPriority is defined by the application we use these values. Otherwise we use the default system string
// This is useful for DRM systems such as Playready for which multiple system strings are possible for instance com.microsoft.playready and com.microsoft.playready.recommendation
const protDataSystemStringPriority = ksConfigurations[idx].protData && ksConfigurations[idx].protData.systemStringPriority ? ksConfigurations[idx].protData.systemStringPriority : null;
const configs = ksConfigurations[idx].configs;
let systemString = keySystem.systemString;
const currentKeySystem = ksConfigurations[idx].ks;
let systemString = currentKeySystem.systemString;

// Patch to support persistent licenses on Edge browser (see issue #2658)
if (systemString === ProtectionConstants.PLAYREADY_KEYSTEM_STRING && configs[0].persistentState === 'required') {
systemString += '.recommendation';
}
// Use the default values in case no values are provided by the application
const systemStringsToApply = protDataSystemStringPriority ? protDataSystemStringPriority : SYSTEM_STRING_PRIORITY[systemString] ? SYSTEM_STRING_PRIORITY[systemString] : [systemString];

navigator.requestMediaKeySystemAccess(systemString, configs)
// Check all the available system strings and the available configurations for support
_checkAccessForKeySystem(systemStringsToApply, configs)
.then((mediaKeySystemAccess) => {
const configuration = (typeof mediaKeySystemAccess.getConfiguration === 'function') ?
mediaKeySystemAccess.getConfiguration() : null;
const keySystemAccess = new KeySystemAccess(keySystem, configuration);
const keySystemAccess = new KeySystemAccess(currentKeySystem, configuration);

keySystemAccess.mksa = mediaKeySystemAccess;
eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { data: keySystemAccess });
resolve({ data: keySystemAccess });
})
.catch((error) => {
.catch((e) => {
if (idx + 1 < ksConfigurations.length) {
_requestKeySystemAccessInternal(ksConfigurations, idx + 1, resolve, reject);
} else {
const errorMessage = 'Key system access denied! ';
eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: errorMessage + error.message });
reject({ error: errorMessage + error.message });
eventBus.trigger(events.KEY_SYSTEM_ACCESS_COMPLETE, { error: errorMessage + e.message });
reject({ error: errorMessage + e.message });
}
})
}

/**
* For a specific key system: Iterate over the possible system strings and resolve once a valid configuration was found
* @param {array} systemStringsToApply
* @param {object} configs
* @return {Promise}
* @private
*/
function _checkAccessForKeySystem(systemStringsToApply, configs) {
return new Promise((resolve, reject) => {
_checkAccessForSystemStrings(systemStringsToApply, configs, 0, resolve, reject);
})
}

/**
* Recursively iterate over the possible system strings until a supported configuration is found or we ran out of options
* @param {array} systemStringsToApply
* @param {object} configs
* @param {number} idx
* @param {function} resolve
* @param {function} reject
* @private
*/
function _checkAccessForSystemStrings(systemStringsToApply, configs, idx, resolve, reject) {
const systemString = systemStringsToApply[idx];

logger.debug(`Requesting key system access for system string ${systemString}`);

navigator.requestMediaKeySystemAccess(systemString, configs)
.then((mediaKeySystemAccess) => {
mediaKeySystemAccess.selectedSystemString = systemString;
resolve(mediaKeySystemAccess);
})
.catch((e) => {
if (idx + 1 < systemStringsToApply.length) {
_checkAccessForSystemStrings(systemStringsToApply, configs, idx + 1, resolve, reject);
} else {
reject(e);
}
});
}
Expand Down
Loading