From 19e24b1d741b4ba6946011748be8b759b4b71773 Mon Sep 17 00:00:00 2001 From: sr90 Date: Wed, 30 Mar 2022 09:10:14 -0700 Subject: [PATCH] feat(dash): Parse ClearKey license URL in MPD (#4066) Example content protection tags parsed: ```xml License Url ``` The player parses the default key id and license url, and sends a POST request to license url as per https://w3c.github.io/encrypted-media/#clear-key-request-format to retrieve the decryption key/s. --- AUTHORS | 1 + CONTRIBUTORS | 1 + lib/dash/content_protection.js | 32 +++++++++++++- lib/util/player_configuration.js | 2 + .../dash_parser_content_protection_unit.js | 44 +++++++++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index f4402639d5..7f65582102 100644 --- a/AUTHORS +++ b/AUTHORS @@ -67,6 +67,7 @@ Rostislav Hejduk SameGoal Inc. <*@samegoal.com> Sanborn Hilland Sander Saares +Sanil Raut Swank Motion Pictures Inc. <*@swankmp.com> TalkTalk Plc <*@talktalkplc.com> Tatsiana Gelahova diff --git a/CONTRIBUTORS b/CONTRIBUTORS index e83fd31d18..26c5952226 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -97,6 +97,7 @@ Sam Dutton Sanborn Hilland Sander Saares Sandra Lokshina +Sanil Raut Satheesh Velmurugan Semih Gokceoglu Seth Madison diff --git a/lib/dash/content_protection.js b/lib/dash/content_protection.js index 8165a6fc94..8e3fe75b39 100644 --- a/lib/dash/content_protection.js +++ b/lib/dash/content_protection.js @@ -180,6 +180,27 @@ shaka.dash.ContentProtection = class { return ''; } + /** + * Gets a ClearKey license URL from a content protection element + * containing a custom `clearkey::Laurl` element + * + * @param {shaka.dash.ContentProtection.Element} element + * @return {string} + */ + static getClearKeyLicenseUrl(element) { + const clearKeyLaurlNode = shaka.util.XmlUtils.findChildNS( + element.node, shaka.dash.ContentProtection.ClearKeyNamespaceUri_, + 'Laurl', + ); + if (clearKeyLaurlNode && + clearKeyLaurlNode.getAttribute('Lic_type') === 'EME-1.0') { + if (clearKeyLaurlNode.textContent) { + return clearKeyLaurlNode.textContent; + } + } + return ''; + } + /** * Parses an Array buffer starting at byteOffset for PlayReady Object Records. * Each PRO Record is preceded by its PlayReady Record type and length in @@ -562,7 +583,9 @@ shaka.dash.ContentProtection.licenseUrlParsers_ = new Map() .set('com.microsoft.playready.software', shaka.dash.ContentProtection.getPlayReadyLicenseUrl) .set('com.microsoft.playready.hardware', - shaka.dash.ContentProtection.getPlayReadyLicenseUrl); + shaka.dash.ContentProtection.getPlayReadyLicenseUrl) + .set('org.w3.clearkey', + shaka.dash.ContentProtection.getClearKeyLicenseUrl); /** * @const {string} @@ -577,3 +600,10 @@ shaka.dash.ContentProtection.MP4Protection_ = * @private */ shaka.dash.ContentProtection.CencNamespaceUri_ = 'urn:mpeg:cenc:2013'; + +/** + * @const {string} + * @private + */ +shaka.dash.ContentProtection.ClearKeyNamespaceUri_ = + 'http://dashif.org/guidelines/clearKey'; diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index fe4f5d27e9..d5e37aff52 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -100,6 +100,8 @@ shaka.util.PlayerConfiguration = class { keySystemsByURI: { 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey', + 'urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e': + 'org.w3.clearkey', 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha', 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': diff --git a/test/dash/dash_parser_content_protection_unit.js b/test/dash/dash_parser_content_protection_unit.js index f9a046bb9a..093512f74b 100644 --- a/test/dash/dash_parser_content_protection_unit.js +++ b/test/dash/dash_parser_content_protection_unit.js @@ -814,6 +814,50 @@ describe('DashParser ContentProtection', () => { }); }); + describe('getClearKeyLicenseUrl', () => { + it('valid clearkey:Laurl node', () => { + const input = { + init: null, + keyId: null, + schemeUri: '', + node: strToXml([ + '', + ' www.example.com', + '', + ].join('\n')), + }; + const actual = ContentProtection.getClearKeyLicenseUrl(input); + expect(actual).toBe('www.example.com'); + }); + + it('clearkey:Laurl without license url', () => { + const input = { + init: null, + keyId: null, + schemeUri: '', + node: strToXml([ + '', + ' ', + '', + ].join('\n')), + }; + const actual = ContentProtection.getClearKeyLicenseUrl(input); + expect(actual).toBe(''); + }); + + it('no clearkey:Laurl node', () => { + const input = { + init: null, + keyId: null, + schemeUri: '', + node: strToXml(''), + }; + const actual = ContentProtection.getClearKeyLicenseUrl(input); + expect(actual).toBe(''); + }); + }); + describe('getPlayReadyLicenseURL', () => { it('mspro', () => { const laurl = [