diff --git a/samples/dash-if-reference-player/app/sources.json b/samples/dash-if-reference-player/app/sources.json index 6566998a1c..1c0d7b1b45 100644 --- a/samples/dash-if-reference-player/app/sources.json +++ b/samples/dash-if-reference-player/app/sources.json @@ -404,9 +404,22 @@ { "name": "DRM (modern)", "submenu": [ + { + "name": "Multiperiod - ContentProtection Reference", + "url": "https://d24rwxnt7vw9qb.cloudfront.net/out/v1/feb9354da126479386ae8d47ba103cf8/index.mpd", + "protData": { + "com.widevine.alpha": { + "serverURL": "https://lic.staging.drmtoday.com/license-proxy-widevine/cenc/?specConform=true", + "httpRequestHeaders": { + "x-dt-custom-data": "ewogICAgInVzZXJJZCI6ICJhd3MtZWxlbWVudGFsOjpzcGVrZS10ZXN0aW5nIiwKICAgICJzZXNzaW9uSWQiOiAidGVzdHNlc3Npb25tdWx0aWtleSIsCiAgICAibWVyY2hhbnQiOiAiYXdzLWVsZW1lbnRhbCIKfQ==" + } + } + }, + "provider": "aws" + }, { "name": "Multiperiod - Supplemental Property \"urn:mpeg:dash:adaptation-set-switching:2016\" ", - "url": "https://d24rwxnt7vw9qb.cloudfront.net/out/v1/a234169feb7b4b4ba9fd100b36629ae1/index.mpd", + "url": "https://d24rwxnt7vw9qb.cloudfront.net/out/v1/d0409ade052145c5a639d8db3c5ce4b4/index.mpd", "protData": { "com.widevine.alpha": { "serverURL": "https://lic.staging.drmtoday.com/license-proxy-widevine/cenc/?specConform=true", diff --git a/src/dash/DashAdapter.js b/src/dash/DashAdapter.js index 86855a2349..33cee51da9 100644 --- a/src/dash/DashAdapter.js +++ b/src/dash/DashAdapter.js @@ -979,53 +979,99 @@ function DashAdapter() { mediaInfo.roles = dashManifestModel.getRolesForAdaptation(realAdaptation); mediaInfo.codec = dashManifestModel.getCodec(realAdaptation); mediaInfo.mimeType = dashManifestModel.getMimeType(realAdaptation); - mediaInfo.contentProtection = dashManifestModel.getContentProtectionData(realAdaptation); + mediaInfo.contentProtection = dashManifestModel.getContentProtectionByAdaptation(realAdaptation); mediaInfo.bitrateList = dashManifestModel.getBitrateListForAdaptation(realAdaptation); mediaInfo.selectionPriority = dashManifestModel.getSelectionPriority(realAdaptation); - if (mediaInfo.contentProtection) { - // Get the default key ID and apply it to all key systems - const keyIds = mediaInfo.contentProtection.map(cp => dashManifestModel.getKID(cp)).filter(kid => kid !== null); - if (keyIds.length) { - const keyId = keyIds[0]; - mediaInfo.contentProtection.forEach(cp => { - cp.keyId = keyId; - }); - } + if (mediaInfo.contentProtection && mediaInfo.contentProtection.length > 0) { + mediaInfo.contentProtection = _applyContentProtectionReferencing(mediaInfo.contentProtection, adaptation.period.mpd.manifest); + mediaInfo.contentProtection = _applyDefaultKeyId(mediaInfo.contentProtection); } mediaInfo.isText = dashManifestModel.getIsText(realAdaptation); mediaInfo.supplementalProperties = dashManifestModel.getSupplementalPropertiesForAdaptation(realAdaptation); if ((!mediaInfo.supplementalProperties || mediaInfo.supplementalProperties.length === 0) && realAdaptation.Representation && realAdaptation.Representation.length > 0) { - let arr = realAdaptation.Representation.map(repr => { - return dashManifestModel.getSupplementalPropertiesForRepresentation(repr); - }); - if (arr.every(v => JSON.stringify(v) === JSON.stringify(arr[0]))) { - // only output Representation.supplementalProperties to mediaInfo, if they are present on all Representations - mediaInfo.supplementalProperties = arr[0]; - } + mediaInfo.supplementalProperties = _getCommonRepresentationSupplementalProperties(mediaInfo, realAdaptation); } mediaInfo.isFragmented = dashManifestModel.getIsFragmented(realAdaptation); mediaInfo.isEmbedded = false; mediaInfo.hasProtectedRepresentations = dashManifestModel.getAdaptationHasProtectedRepresentations(realAdaptation); + mediaInfo.adaptationSetSwitchingCompatibleIds = _getAdaptationSetSwitchingCompatibleIds(mediaInfo); - // Save IDs of AS that we can switch to - try { - const adaptationSetSwitching = mediaInfo.supplementalProperties.filter((sp) => { - return sp.schemeIdUri === DashConstants.ADAPTATION_SET_SWITCHING_SCHEME_ID_URI + return mediaInfo; + } + + function _applyDefaultKeyId(contentProtection) { + const keyIds = contentProtection.map(cp => cp.cencDefaultKid).filter(kid => kid !== null); + if (keyIds.length) { + const keyId = keyIds[0]; + contentProtection.forEach(cp => { + cp.keyId = keyId; }); - if (adaptationSetSwitching && adaptationSetSwitching.length > 0) { - const ids = adaptationSetSwitching[0].value.toString().split(',') - mediaInfo.adaptationSetSwitchingCompatibleIds = ids.map((id) => { - return id - }) + } + + return contentProtection + } + + function _applyContentProtectionReferencing(contentProtection, manifest) { + if (!contentProtection || !contentProtection.length || !manifest) { + return contentProtection + } + + const allContentProtectionElements = dashManifestModel.getContentProtectionByManifest(manifest) + if (!allContentProtectionElements || !allContentProtectionElements.length) { + return contentProtection + } + + const contentProtectionElementsByRefId = allContentProtectionElements.reduce((acc, curr) => { + if (curr.refId) { + acc.set(curr.refId, curr); } - } catch (e) { - return mediaInfo; + return acc + }, new Map()) + + return contentProtection.map((contentProtectionElement) => { + if (contentProtectionElement.ref) { + const contentProtectionElementSource = contentProtectionElementsByRefId.get(contentProtectionElement.ref); + if (contentProtectionElementSource) { + contentProtectionElement.mergeAttributesFromReference(contentProtectionElementSource) + } + } + return contentProtectionElement + }) + } + + function _getCommonRepresentationSupplementalProperties(mediaInfo, realAdaptation) { + let arr = realAdaptation.Representation.map(repr => { + return dashManifestModel.getSupplementalPropertiesForRepresentation(repr); + }); + + if (arr.every(v => JSON.stringify(v) === JSON.stringify(arr[0]))) { + // only output Representation.supplementalProperties to mediaInfo, if they are present on all Representations + return arr[0]; } - return mediaInfo; + return [] + } + + function _getAdaptationSetSwitchingCompatibleIds(mediaInfo) { + if (!mediaInfo || !mediaInfo.supplementalProperties) { + return [] + } + + let adaptationSetSwitchingCompatibleIds = [] + const adaptationSetSwitching = mediaInfo.supplementalProperties.filter((sp) => { + return sp.schemeIdUri === DashConstants.ADAPTATION_SET_SWITCHING_SCHEME_ID_URI + }); + if (adaptationSetSwitching && adaptationSetSwitching.length > 0) { + const ids = adaptationSetSwitching[0].value.toString().split(',') + adaptationSetSwitchingCompatibleIds = ids.map((id) => { + return id + }) + } + + return adaptationSetSwitchingCompatibleIds } function convertVideoInfoToEmbeddedTextInfo(mediaInfo, channel, lang) { diff --git a/src/dash/constants/DashConstants.js b/src/dash/constants/DashConstants.js index 049039077c..8a8a146cc8 100644 --- a/src/dash/constants/DashConstants.js +++ b/src/dash/constants/DashConstants.js @@ -108,6 +108,7 @@ export default { MIME_TYPE: 'mimeType', MINIMUM_UPDATE_PERIOD: 'minimumUpdatePeriod', MIN_BUFFER_TIME: 'minBufferTime', + MP4_PROTECTION_SCHEME: 'urn:mpeg:dash:mp4protection:2011', MPD: 'MPD', ORIGINAL_MPD_ID: 'mpdId', ORIGINAL_PUBLISH_TIME: 'originalPublishTime', @@ -115,6 +116,7 @@ export default { PERIOD: 'Period', PRESENTATION_TIME: 'presentationTime', PRESENTATION_TIME_OFFSET: 'presentationTimeOffset', + PRO: 'pro', PRODUCER_REFERENCE_TIME: 'ProducerReferenceTime', PRODUCER_REFERENCE_TIME_TYPE: { ENCODER: 'encoder', @@ -122,16 +124,20 @@ export default { APPLICATION: 'application' }, PROFILES: 'profiles', + PSSH: 'pssh', PUBLISH_TIME: 'publishTime', QUALITY_RANKING : 'qualityRanking', QUERY_BEFORE_START: 'queryBeforeStart', RANGE: 'range', RATING: 'Rating', + REF: 'ref', + REF_ID: 'refId', REMOVE: 'remove', REPLACE: 'replace', REPORTING: 'Reporting', REPRESENTATION: 'Representation', REPRESENTATION_INDEX: 'RepresentationIndex', + ROBUSTNESS: 'robustness', ROLE: 'Role', S: 'S', SAR: 'sar', diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js index 7458f93929..f2a466cf66 100644 --- a/src/dash/models/DashManifestModel.js +++ b/src/dash/models/DashManifestModel.js @@ -50,6 +50,7 @@ import Errors from '../../core/errors/Errors.js'; import {THUMBNAILS_SCHEME_ID_URIS} from '../../streaming/thumbnail/ThumbnailTracks.js'; import MpdLocation from '../vo/MpdLocation.js'; import PatchLocation from '../vo/PatchLocation.js'; +import ContentProtection from '../vo/ContentProtection.js'; function DashManifestModel() { let instance, @@ -236,7 +237,8 @@ function DashManifestModel() { if (!adaptation || !adaptation.hasOwnProperty(DashConstants.VIEWPOINT) || !adaptation[DashConstants.VIEWPOINT].length) return []; return adaptation[DashConstants.VIEWPOINT].map(viewpoint => { const vp = new DescriptorType(); - return vp.init(viewpoint); + vp.init(viewpoint); + return vp }); } @@ -244,7 +246,8 @@ function DashManifestModel() { if (!adaptation || !adaptation.hasOwnProperty(DashConstants.ROLE) || !adaptation[DashConstants.ROLE].length) return []; return adaptation[DashConstants.ROLE].map(role => { const r = new DescriptorType(); - return r.init(role); + r.init(role); + return r }); } @@ -252,7 +255,8 @@ function DashManifestModel() { if (!adaptation || !adaptation.hasOwnProperty(DashConstants.ACCESSIBILITY) || !adaptation[DashConstants.ACCESSIBILITY].length) return []; return adaptation[DashConstants.ACCESSIBILITY].map(accessibility => { const a = new DescriptorType(); - return a.init(accessibility); + a.init(accessibility); + return a }); } @@ -260,7 +264,8 @@ function DashManifestModel() { if (!adaptation || !adaptation.hasOwnProperty(DashConstants.AUDIO_CHANNEL_CONFIGURATION) || !adaptation[DashConstants.AUDIO_CHANNEL_CONFIGURATION].length) return []; return adaptation[DashConstants.AUDIO_CHANNEL_CONFIGURATION].map(audioChanCfg => { const acc = new DescriptorType(); - return acc.init(audioChanCfg); + acc.init(audioChanCfg); + return acc }); } @@ -268,7 +273,8 @@ function DashManifestModel() { if (!representation || !representation.hasOwnProperty(DashConstants.AUDIO_CHANNEL_CONFIGURATION) || !representation[DashConstants.AUDIO_CHANNEL_CONFIGURATION].length) return []; return representation[DashConstants.AUDIO_CHANNEL_CONFIGURATION].map(audioChanCfg => { const acc = new DescriptorType(); - return acc.init(audioChanCfg); + acc.init(audioChanCfg); + return acc }); } @@ -398,13 +404,6 @@ function DashManifestModel() { return false } - function getKID(adaptation) { - if (!adaptation || !adaptation.hasOwnProperty(DashConstants.CENC_DEFAULT_KID)) { - return null; - } - return adaptation[DashConstants.CENC_DEFAULT_KID]; - } - function getLabelsForAdaptation(adaptation) { if (!adaptation || !adaptation.Label) { return []; @@ -422,11 +421,49 @@ function DashManifestModel() { return labelArray; } - function getContentProtectionData(adaptation) { - if (!adaptation || !adaptation.hasOwnProperty(DashConstants.CONTENT_PROTECTION) || adaptation.ContentProtection.length === 0) { - return null; + + function getContentProtectionByManifest(manifest) { + let protectionElements = []; + + if (!manifest) { + return protectionElements } - return adaptation.ContentProtection; + + const mpdElements = _getContentProtectionFromElement(manifest); + protectionElements = protectionElements.concat(mpdElements); + + if (manifest.hasOwnProperty(DashConstants.PERIOD) && manifest[DashConstants.PERIOD].length > 0) { + manifest[DashConstants.PERIOD].forEach((period) => { + const curr = _getContentProtectionFromElement(period); + protectionElements = protectionElements.concat(curr); + + if (period.hasOwnProperty(DashConstants.ADAPTATION_SET) && period[DashConstants.ADAPTATION_SET].length > 0) { + period[DashConstants.ADAPTATION_SET].forEach((as) => { + const curr = _getContentProtectionFromElement(as); + protectionElements = protectionElements.concat(curr); + }) + } + }) + } + + return protectionElements + } + + + function getContentProtectionByAdaptation(adaptation) { + return _getContentProtectionFromElement(adaptation); + } + + function _getContentProtectionFromElement(element) { + if (!element || !element.hasOwnProperty(DashConstants.CONTENT_PROTECTION) || element.ContentProtection.length === 0) { + return []; + } + + return element[DashConstants.CONTENT_PROTECTION].map(contentProtectionData => { + const cp = new ContentProtection(); + cp.init(contentProtectionData); + return cp + }); } function getAdaptationHasProtectedRepresentations(adaptation) { @@ -1314,7 +1351,8 @@ function DashManifestModel() { if (!adaptation || !adaptation.hasOwnProperty(DashConstants.SUPPLEMENTAL_PROPERTY) || !adaptation.SupplementalProperty.length) return []; return adaptation.SupplementalProperty.map(supp => { const s = new DescriptorType(); - return s.init(supp); + s.init(supp); + return s }); } @@ -1322,7 +1360,8 @@ function DashManifestModel() { if (!representation || !representation.hasOwnProperty(DashConstants.SUPPLEMENTAL_PROPERTY) || !representation.SupplementalProperty.length) return []; return representation.SupplementalProperty.map(supp => { const s = new DescriptorType(); - return s.init(supp); + s.init(supp); + return s }); } @@ -1359,9 +1398,9 @@ function DashManifestModel() { getCodec, getSelectionPriority, getMimeType, - getKID, getLabelsForAdaptation, - getContentProtectionData, + getContentProtectionByAdaptation, + getContentProtectionByManifest, getIsDynamic, getId, hasProfile, diff --git a/src/dash/vo/ContentProtection.js b/src/dash/vo/ContentProtection.js new file mode 100644 index 0000000000..7bbc34b537 --- /dev/null +++ b/src/dash/vo/ContentProtection.js @@ -0,0 +1,85 @@ +/** + * The copyright in this software is being made available under the BSD License, + * included below. This software may be subject to other third party and contributor + * rights, including patent rights, and no such rights are granted under this license. + * + * Copyright (c) 2023, Dash Industry Forum. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * Neither the name of Dash Industry Forum nor the names of its + * contributors may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +import DescriptorType from './DescriptorType.js' +import DashConstants from '../constants/DashConstants.js'; + +/** + * @class + * @ignore + */ +class ContentProtection extends DescriptorType { + + constructor() { + super(); + this.ref = null; + this.refId = null; + this.robustness = null; + this.keyId = null; + } + + init(data) { + super.init(data); + if (data) { + this.ref = data.hasOwnProperty(DashConstants.REF) ? data[DashConstants.REF] : null; + this.refId = data.hasOwnProperty(DashConstants.REF_ID) ? data[DashConstants.REF_ID] : null; + this.robustness = data.hasOwnProperty(DashConstants.ROBUSTNESS) ? data[DashConstants.ROBUSTNESS] : null; + this.cencDefaultKid = data.hasOwnProperty(DashConstants.CENC_DEFAULT_KID) ? data[DashConstants.CENC_DEFAULT_KID] : null; + this.pssh = data.hasOwnProperty(DashConstants.PSSH) ? data[DashConstants.PSSH] : null; + this.pro = data.hasOwnProperty(DashConstants.PRO) ? data[DashConstants.PRO] : null; + } + } + + mergeAttributesFromReference(reference) { + if (this.schemeIdUri === null) { + this.schemeIdUri = reference.schemeIdUri + } + if (this.value === null) { + this.value = reference.value + } + if (this.id === null) { + this.id = reference.id + } + if (this.robustness === null) { + this.robustness = reference.robustness; + } + if (this.cencDefaultKid === null) { + this.cencDefaultKid = reference.cencDefaultKid; + } + if (this.pro === null) { + this.pro = reference.pro; + } + if (this.pssh === null) { + this.pssh = reference.pssh; + } + } +} + +export default ContentProtection; diff --git a/src/dash/vo/DescriptorType.js b/src/dash/vo/DescriptorType.js index be8a1460a7..9acba6b03b 100644 --- a/src/dash/vo/DescriptorType.js +++ b/src/dash/vo/DescriptorType.js @@ -45,7 +45,6 @@ class DescriptorType { this.value = data.value ? data.value : null; this.id = data.id ? data.id : null; } - return this; } } diff --git a/src/streaming/protection/CommonEncryption.js b/src/streaming/protection/CommonEncryption.js index 7bdaad5afd..d46c3ce221 100644 --- a/src/streaming/protection/CommonEncryption.js +++ b/src/streaming/protection/CommonEncryption.js @@ -28,6 +28,7 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ +import DashConstants from '../../dash/constants/DashConstants.js'; const LICENSE_SERVER_MANIFEST_CONFIGURATIONS = { attributes: ['Laurl', 'laurl'], @@ -41,17 +42,17 @@ const LICENSE_SERVER_MANIFEST_CONFIGURATIONS = { class CommonEncryption { /** * Find and return the ContentProtection element in the given array - * that indicates support for MPEG Common Encryption + * that indicates support for MP4 Common Encryption * * @param {Array} cpArray array of content protection elements * @returns {Object|null} the Common Encryption content protection element or * null if one was not found */ - static findCencContentProtection(cpArray) { + static findMp4ProtectionElement(cpArray) { let retVal = null; for (let i = 0; i < cpArray.length; ++i) { let cp = cpArray[i]; - if (cp.schemeIdUri.toLowerCase() === 'urn:mpeg:dash:mp4protection:2011' && + if (cp.schemeIdUri && cp.schemeIdUri.toLowerCase() === DashConstants.MP4_PROTECTION_SCHEME && cp.value && (cp.value.toLowerCase() === 'cenc' || cp.value.toLowerCase() === 'cbcs')) retVal = cp; } @@ -108,7 +109,7 @@ class CommonEncryption { * @returns {ArrayBuffer|null} the init data or null if not found */ static parseInitDataFromContentProtection(cpData, BASE64) { - if ('pssh' in cpData) { + if ('pssh' in cpData && cpData.pssh) { // Remove whitespaces and newlines from pssh text cpData.pssh.__text = cpData.pssh.__text.replace(/\r?\n|\r/g, '').replace(/\s+/g, ''); diff --git a/src/streaming/protection/controllers/ProtectionKeyController.js b/src/streaming/protection/controllers/ProtectionKeyController.js index d56f4955af..77f6c37502 100644 --- a/src/streaming/protection/controllers/ProtectionKeyController.js +++ b/src/streaming/protection/controllers/ProtectionKeyController.js @@ -194,48 +194,50 @@ function ProtectionKeyController() { * key systems that are supported by this player will be returned. * Key systems are returned in priority order (highest first). * - * @param {Array.} cps - array of content protection elements parsed + * @param {Array.} contentProtectionElements - array of content protection elements parsed * from the manifest - * @param {ProtectionData} protDataSet user specified protection data - license server url etc + * @param {ProtectionData} applicationSpecifiedProtectionData user specified protection data - license server url etc * supported by the content - * @param {string} default session type + * @param {string} sessionType session type * @returns {Array.} array of objects indicating which supported key - * systems were found. Empty array is returned if no - * supported key systems were found + * systems were found. Empty array is returned if no supported key systems were found * @memberof module:ProtectionKeyController * @instance */ - function getSupportedKeySystemsFromContentProtection(cps, protDataSet, sessionType) { + function getSupportedKeySystemsFromContentProtection(contentProtectionElements, applicationSpecifiedProtectionData, sessionType) { let cp, ks, ksIdx, cpIdx; let supportedKS = []; - if (cps) { - const cencContentProtection = CommonEncryption.findCencContentProtection(cps); - for (ksIdx = 0; ksIdx < keySystems.length; ++ksIdx) { - ks = keySystems[ksIdx]; - - // Get protection data that applies for current key system - const protData = _getProtDataForKeySystem(ks.systemString, protDataSet); - - for (cpIdx = 0; cpIdx < cps.length; ++cpIdx) { - cp = cps[cpIdx]; - if (cp.schemeIdUri.toLowerCase() === ks.schemeIdURI) { - // Look for DRM-specific ContentProtection - let initData = ks.getInitData(cp, cencContentProtection); - - supportedKS.push({ - ks: keySystems[ksIdx], - keyId: cp.keyId, - initData: initData, - protData: protData, - cdmData: ks.getCDMData(protData ? protData.cdmData : null), - sessionId: _getSessionId(protData, cp), - sessionType: _getSessionType(protData, sessionType) - }); - } + if (!contentProtectionElements || !contentProtectionElements.length) { + return supportedKS + } + + const mp4ProtectionElement = CommonEncryption.findMp4ProtectionElement(contentProtectionElements); + for (ksIdx = 0; ksIdx < keySystems.length; ++ksIdx) { + ks = keySystems[ksIdx]; + + // Get protection data that applies for current key system + const protData = _getProtDataForKeySystem(ks.systemString, applicationSpecifiedProtectionData); + + for (cpIdx = 0; cpIdx < contentProtectionElements.length; ++cpIdx) { + cp = contentProtectionElements[cpIdx]; + if (cp.schemeIdUri.toLowerCase() === ks.schemeIdURI) { + // Look for DRM-specific ContentProtection + let initData = ks.getInitData(cp, mp4ProtectionElement); + + supportedKS.push({ + ks: keySystems[ksIdx], + keyId: cp.keyId, + initData: initData, + protData: protData, + cdmData: ks.getCDMData(protData ? protData.cdmData : null), + sessionId: _getSessionId(protData, cp), + sessionType: _getSessionType(protData, sessionType) + }); } } } + return supportedKS; } @@ -359,7 +361,9 @@ function ProtectionKeyController() { } function _getProtDataForKeySystem(systemString, protDataSet) { - if (!protDataSet) return null; + if (!protDataSet) { + return null; + } return (systemString in protDataSet) ? protDataSet[systemString] : null; } diff --git a/src/streaming/protection/drm/KeySystemClearKey.js b/src/streaming/protection/drm/KeySystemClearKey.js index 12e9b53884..7638e24710 100644 --- a/src/streaming/protection/drm/KeySystemClearKey.js +++ b/src/streaming/protection/drm/KeySystemClearKey.js @@ -80,7 +80,7 @@ function KeySystemClearKey(config) { let initData = CommonEncryption.parseInitDataFromContentProtection(cp, BASE64); if (!initData && cencContentProtection) { - const cencDefaultKid = cencDefaultKidToBase64Representation(cencContentProtection['cenc:default_KID']); + const cencDefaultKid = cencDefaultKidToBase64Representation(cencContentProtection.cencDefaultKid); const data = { kids: [cencDefaultKid] }; initData = new TextEncoder().encode(JSON.stringify(data)); } diff --git a/src/streaming/protection/drm/KeySystemPlayReady.js b/src/streaming/protection/drm/KeySystemPlayReady.js index d83414418b..1d8d1c4335 100644 --- a/src/streaming/protection/drm/KeySystemPlayReady.js +++ b/src/streaming/protection/drm/KeySystemPlayReady.js @@ -197,14 +197,14 @@ function KeySystemPlayReady(config) { return null; } // Handle common encryption PSSH - if ('pssh' in cpData) { + if ('pssh' in cpData && cpData.pssh) { return CommonEncryption.parseInitDataFromContentProtection(cpData, BASE64); } // Handle native MS PlayReady ContentProtection elements - if ('pro' in cpData) { + if ('pro' in cpData && cpData.pro) { uint8arraydecodedPROHeader = BASE64.decodeArray(cpData.pro.__text); } - else if ('prheader' in cpData) { + else if ('prheader' in cpData && cpData.prheader) { uint8arraydecodedPROHeader = BASE64.decodeArray(cpData.prheader.__text); } else { diff --git a/test/unit/dash.models.DashManifestModel.js b/test/unit/dash.models.DashManifestModel.js index d2fdb8512b..79c093c4d1 100644 --- a/test/unit/dash.models.DashManifestModel.js +++ b/test/unit/dash.models.DashManifestModel.js @@ -389,18 +389,6 @@ describe('DashManifestModel', function () { expect(mimeType).to.be.null; }); - it('should return null when getKID is called and adaptation is undefined', () => { - const kid = dashManifestModel.getKID(); - - expect(kid).to.be.null; - }); - - it('should return kid value when getKID is called and adaptation is well defined', () => { - const kid = dashManifestModel.getKID({ 'cenc:default_KID': 'testKid' }); - - expect(kid).to.equal('testKid'); - }); - it('should return empty array when getLabelsForAdaptation is called and adaptation is undefined', () => { const labels = dashManifestModel.getLabelsForAdaptation(); @@ -442,17 +430,60 @@ describe('DashManifestModel', function () { expect(labels[0].lang).to.equal('fre'); }); - it('should return null when getContentProtectionData is called and adaptation is undefined', () => { - const contentProtection = dashManifestModel.getContentProtectionData(); + it('should return an empty array when getContentProtectionByAdaptation is called and adaptation is undefined', () => { + const contentProtection = dashManifestModel.getContentProtectionByAdaptation(); - expect(contentProtection).to.be.null; + expect(contentProtection).to.be.empty; }); - it('should return null when getContentProtectionData is called and adaptation is defined, but ContentProtection is an empty array', () => { + it('should return an empty array when getContentProtectionByAdaptation is called and adaptation is defined, but ContentProtection is an empty array', () => { const adaptation = { ContentProtection: [] }; - const contentProtection = dashManifestModel.getContentProtectionData(adaptation); + const contentProtection = dashManifestModel.getContentProtectionByAdaptation(adaptation); + + expect(contentProtection).to.be.empty; + }); + + it('should return an empty array when getContentProtectionByManifest is called and manifest is undefined', () => { + const contentProtection = dashManifestModel.getContentProtectionByManifest(); + + expect(contentProtection).to.be.empty; + }); + + it('should return an empty array when getContentProtectionByManifest is called and manifest is defined, but ContentProtection is an empty array', () => { + const manifest = { ContentProtection: [] }; + const contentProtection = dashManifestModel.getContentProtectionByManifest(manifest); + + expect(contentProtection).to.be.empty; + }); + + it('should return all content protection elements when getContentProtectionByManifest is called and manifest is defined', () => { + const manifest = { + ContentProtection: [{ + 'value': 'manifest_value', + 'schemeIdUri': 'manifest_scheme' + }], + Period: [{ + ContentProtection: [{ + 'value': 'period_value', + 'schemeIdUri': 'period_scheme' + }, { + 'value': 'period_value_2', + 'schemeIdUri': 'period_scheme_2' + }], + AdaptationSet: [{ + ContentProtection: [{ + 'value': 'as_value', + 'schemeIdUri': 'as_scheme' + }, { + 'value': 'as_value_2', + 'schemeIdUri': 'as_scheme_2' + }] + }] + }] + }; + const contentProtection = dashManifestModel.getContentProtectionByManifest(manifest); - expect(contentProtection).to.be.null; + expect(contentProtection).to.have.lengthOf(5); }); it('should return false when getIsDynamic is called and manifest is undefined', () => {