Skip to content

Commit

Permalink
Support for @ref and @refid in ContentProtection elements (#4380)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsilhavy authored Jan 31, 2024
1 parent 0759671 commit a568ae9
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 108 deletions.
15 changes: 14 additions & 1 deletion samples/dash-if-reference-player/app/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
104 changes: 75 additions & 29 deletions src/dash/DashAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions src/dash/constants/DashConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,30 +108,36 @@ 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',
PATCH_LOCATION: 'PatchLocation',
PERIOD: 'Period',
PRESENTATION_TIME: 'presentationTime',
PRESENTATION_TIME_OFFSET: 'presentationTimeOffset',
PRO: 'pro',
PRODUCER_REFERENCE_TIME: 'ProducerReferenceTime',
PRODUCER_REFERENCE_TIME_TYPE: {
ENCODER: 'encoder',
CAPTURED: 'captured',
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',
Expand Down
79 changes: 59 additions & 20 deletions src/dash/models/DashManifestModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -236,39 +237,44 @@ 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
});
}

function getRolesForAdaptation(adaptation) {
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
});
}

function getAccessibilityForAdaptation(adaptation) {
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
});
}

function getAudioChannelConfigurationForAdaptation(adaptation) {
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
});
}

function getAudioChannelConfigurationForRepresentation(representation) {
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
});
}

Expand Down Expand Up @@ -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 [];
Expand All @@ -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) {
Expand Down Expand Up @@ -1314,15 +1351,17 @@ 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
});
}

function getSupplementalPropertiesForRepresentation(representation) {
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
});
}

Expand Down Expand Up @@ -1359,9 +1398,9 @@ function DashManifestModel() {
getCodec,
getSelectionPriority,
getMimeType,
getKID,
getLabelsForAdaptation,
getContentProtectionData,
getContentProtectionByAdaptation,
getContentProtectionByManifest,
getIsDynamic,
getId,
hasProfile,
Expand Down
Loading

0 comments on commit a568ae9

Please sign in to comment.