mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
feat(MSF): Add CMSF contentProtection signaling support (#9972)
See https://github.com/moq-wg/cmsf/pull/18
This commit is contained in:
committed by
GitHub
parent
f48bd96197
commit
aa2dfaec3f
@@ -14,6 +14,49 @@
|
||||
var msfCatalog = {};
|
||||
|
||||
|
||||
/**
|
||||
* Represents a URL object used in DRM system fields such as laURL, certURL
|
||||
* and authzURL, as defined in the CMSF Content Protection section.
|
||||
*
|
||||
* @typedef {{
|
||||
* url: string,
|
||||
* type: (string|undefined),
|
||||
* }}
|
||||
*/
|
||||
msfCatalog.DrmUrl;
|
||||
|
||||
|
||||
/**
|
||||
* Describes a single DRM system configuration within a Content Protection
|
||||
* entry, as defined in the CMSF Content Protection section (drmSystem field).
|
||||
*
|
||||
* @typedef {{
|
||||
* systemID: string,
|
||||
* laURL: (!msfCatalog.DrmUrl|undefined),
|
||||
* certURL: (!msfCatalog.DrmUrl|undefined),
|
||||
* authzURL: (!msfCatalog.DrmUrl|undefined),
|
||||
* pssh: (string|undefined),
|
||||
* robustness: (string|undefined),
|
||||
* }}
|
||||
*/
|
||||
msfCatalog.DrmSystem;
|
||||
|
||||
|
||||
/**
|
||||
* Describes a single content protection entry at the root level of the
|
||||
* catalog, as defined in the CMSF Content Protection section.
|
||||
* Tracks reference these entries via contentProtectionRefIDs.
|
||||
*
|
||||
* @typedef {{
|
||||
* refID: string,
|
||||
* defaultKID: !Array<string>,
|
||||
* scheme: string,
|
||||
* drmSystem: !msfCatalog.DrmSystem,
|
||||
* }}
|
||||
*/
|
||||
msfCatalog.ContentProtection;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* namespace: (string|undefined),
|
||||
@@ -44,6 +87,7 @@ var msfCatalog = {};
|
||||
* trackDuration: (number|undefined),
|
||||
* eventType: (string|undefined),
|
||||
* parentName: (string|undefined),
|
||||
* contentProtectionRefIDs: (Array<string>|undefined),
|
||||
* }}
|
||||
*/
|
||||
msfCatalog.Track;
|
||||
@@ -55,6 +99,7 @@ msfCatalog.Track;
|
||||
* generatedAt: (number|undefined),
|
||||
* isComplete: (boolean|undefined),
|
||||
* deltaUpdate: (boolean|undefined),
|
||||
* contentProtections: (Array<!msfCatalog.ContentProtection>|undefined),
|
||||
* addTracks: (Array<!msfCatalog.Track>|undefined),
|
||||
* removeTracks: (Array<!msfCatalog.Track>|undefined),
|
||||
* cloneTracks: (Array<!msfCatalog.Track>|undefined),
|
||||
|
||||
@@ -277,6 +277,48 @@ shaka.drm.DrmUtils = class {
|
||||
static isMediaKeysPolyfilled(polyfillType) {
|
||||
return polyfillType === window.shakaMediaKeysPolyfill;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mapping between DRM system UUIDs and their corresponding
|
||||
* EME key system identifiers used.
|
||||
*
|
||||
* @param {boolean=} withoutDashes
|
||||
* @param {boolean=} useUrnFormat
|
||||
* @return {!Object<string, string>}
|
||||
*/
|
||||
static getUuidMap(withoutDashes = false, useUrnFormat = false) {
|
||||
const baseMap = {
|
||||
'1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
|
||||
'e2719d58-a985-b3c9-781a-b030af78d30e': 'org.w3.clearkey',
|
||||
'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
|
||||
'9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
|
||||
'79f0049a-4098-8642-ab92-e65be0885f95': 'com.microsoft.playready',
|
||||
'94ce86fb-07ff-4f43-adb8-93d2fa968ca2': 'com.apple.fps',
|
||||
'3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c': 'com.huawei.wiseplay',
|
||||
};
|
||||
|
||||
if (!withoutDashes && !useUrnFormat) {
|
||||
return baseMap;
|
||||
}
|
||||
|
||||
/** @type {!Object<string, string>} */
|
||||
const result = {};
|
||||
|
||||
for (const key in baseMap) {
|
||||
const value = baseMap[key];
|
||||
|
||||
let finalKey = key;
|
||||
if (useUrnFormat) {
|
||||
finalKey = 'urn:uuid:' + key;
|
||||
} else if (withoutDashes) {
|
||||
finalKey = key.replace(/-/g, '');
|
||||
}
|
||||
|
||||
result[finalKey] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -307,6 +307,8 @@ shaka.media.SegmentUtils = class {
|
||||
}
|
||||
};
|
||||
|
||||
let uuidMap;
|
||||
|
||||
new Mp4Parser()
|
||||
.box('moof', shaka.util.Mp4Parser.children)
|
||||
.box('moov', Mp4Parser.children)
|
||||
@@ -569,15 +571,9 @@ shaka.media.SegmentUtils = class {
|
||||
/* clone= */ false);
|
||||
const systemId = shaka.util.Uint8ArrayUtils.toHex(systemIdData);
|
||||
|
||||
const uuidMap = {
|
||||
'1077efecc0b24d02ace33c1e52e2fb4b': 'org.w3.clearkey',
|
||||
'e2719d58a985b3c9781ab030af78d30e': 'org.w3.clearkey',
|
||||
'edef8ba979d64acea3c827dcd51d21ed': 'com.widevine.alpha',
|
||||
'9a04f07998404286ab92e65be0885f95': 'com.microsoft.playready',
|
||||
'79f0049a40988642ab92e65be0885f95': 'com.microsoft.playready',
|
||||
'94ce86fb07ff4f43adb893d2fa968ca2': 'com.apple.fps',
|
||||
'3d5e6d359b9a41e8b843dd3c6e72c42c': 'com.huawei.wiseplay',
|
||||
};
|
||||
if (!uuidMap) {
|
||||
uuidMap = shaka.drm.DrmUtils.getUuidMap(/* withoutDashes= */ true);
|
||||
}
|
||||
|
||||
const keySystem = uuidMap[systemId.toLowerCase()];
|
||||
if (!keySystem) {
|
||||
|
||||
+84
-4
@@ -10,6 +10,7 @@ goog.provide('shaka.msf.MSFParser');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.drm.DrmUtils');
|
||||
goog.require('shaka.drm.PlayReady');
|
||||
goog.require('shaka.media.InitSegmentReference');
|
||||
goog.require('shaka.media.ManifestParser');
|
||||
goog.require('shaka.media.PresentationTimeline');
|
||||
@@ -529,6 +530,7 @@ shaka.msf.MSFParser = class {
|
||||
let isLive = true;
|
||||
let duration = Infinity;
|
||||
let targetLatency = 0;
|
||||
const contentProtections = this.getContentProtections_(catalog);
|
||||
for (const track of catalog.tracks) {
|
||||
if ('isLive' in track) {
|
||||
isLive = track.isLive;
|
||||
@@ -539,7 +541,7 @@ shaka.msf.MSFParser = class {
|
||||
if (track.trackDuration) {
|
||||
duration = Math.min(duration, track.trackDuration);
|
||||
}
|
||||
promises.push(this.processTrack_(track));
|
||||
promises.push(this.processTrack_(track, contentProtections));
|
||||
}
|
||||
if (targetLatency) {
|
||||
/** @type {shaka.extern.ServiceDescription} */
|
||||
@@ -560,10 +562,72 @@ shaka.msf.MSFParser = class {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {msfCatalog.Track} track
|
||||
* @param {msfCatalog.Catalog} catalog
|
||||
* @return {!Map<string, !shaka.extern.DrmInfo>}
|
||||
* @private
|
||||
*/
|
||||
processTrack_(track) {
|
||||
getContentProtections_(catalog) {
|
||||
const uuidMap = shaka.drm.DrmUtils.getUuidMap();
|
||||
|
||||
/** @type {!Map<string, !shaka.extern.DrmInfo>} */
|
||||
const mapContentProtections = new Map();
|
||||
const contentProtections = catalog.contentProtections || [];
|
||||
for (const contentProtection of contentProtections) {
|
||||
const refId = contentProtection.refID;
|
||||
const drmSystem = contentProtection.drmSystem;
|
||||
|
||||
if (!drmSystem) {
|
||||
continue;
|
||||
}
|
||||
const keySystem = uuidMap[drmSystem.systemID.toLowerCase()];
|
||||
if (!keySystem) {
|
||||
continue;
|
||||
}
|
||||
const encryptionScheme = contentProtection.scheme || 'cenc';
|
||||
|
||||
let initData = null;
|
||||
if (drmSystem.pssh) {
|
||||
initData = [{
|
||||
initDataType: 'cenc',
|
||||
initData: shaka.util.Uint8ArrayUtils.fromBase64(drmSystem.pssh),
|
||||
}];
|
||||
}
|
||||
|
||||
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
||||
keySystem, encryptionScheme, initData);
|
||||
|
||||
if (drmSystem.laURL?.url) {
|
||||
drmInfo.licenseServerUri = drmSystem.laURL.url;
|
||||
} else if (initData &&
|
||||
shaka.drm.DrmUtils.isPlayReadyKeySystem(keySystem)) {
|
||||
drmInfo.licenseServerUri =
|
||||
shaka.drm.PlayReady.getLicenseUrlFromPssh(initData[0].initData);
|
||||
}
|
||||
if (drmSystem.certURL?.url) {
|
||||
drmInfo.serverCertificateUri = drmSystem.certURL.url;
|
||||
}
|
||||
if (drmSystem.robustness) {
|
||||
drmInfo.videoRobustness = drmSystem.robustness;
|
||||
drmInfo.audioRobustness = drmSystem.robustness;
|
||||
}
|
||||
if (contentProtection.defaultKID) {
|
||||
for (const kid of contentProtection.defaultKID) {
|
||||
const normalizedKid = kid.replace(/-/g, '').toLowerCase();
|
||||
drmInfo.keyIds.add(normalizedKid);
|
||||
}
|
||||
}
|
||||
|
||||
mapContentProtections.set(refId, drmInfo);
|
||||
}
|
||||
return mapContentProtections;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {msfCatalog.Track} track
|
||||
* @param {!Map<string, !shaka.extern.DrmInfo>} contentProtections
|
||||
* @private
|
||||
*/
|
||||
processTrack_(track, contentProtections) {
|
||||
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
||||
const ManifestParserUtils = shaka.util.ManifestParserUtils;
|
||||
|
||||
@@ -653,6 +717,22 @@ shaka.msf.MSFParser = class {
|
||||
}
|
||||
}
|
||||
|
||||
let drmInfos = [];
|
||||
const contentProtectionRefIDs = track.contentProtectionRefIDs;
|
||||
if (contentProtectionRefIDs) {
|
||||
for (const refId of contentProtectionRefIDs) {
|
||||
const drmInfo = contentProtections.get(refId);
|
||||
if (drmInfo) {
|
||||
drmInfos.push(drmInfo);
|
||||
} else {
|
||||
shaka.log.alwaysWarn('Unrecognized contentProtectionRefID', refId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
drmInfos = basicInfo.drmInfos;
|
||||
}
|
||||
|
||||
/** @type {shaka.extern.Stream} */
|
||||
const stream = {
|
||||
id: this.globalId_++,
|
||||
@@ -665,7 +745,7 @@ shaka.msf.MSFParser = class {
|
||||
supplementalCodecs: '',
|
||||
kind,
|
||||
encrypted: false,
|
||||
drmInfos: basicInfo.drmInfos,
|
||||
drmInfos,
|
||||
keyIds: new Set(),
|
||||
language: shaka.util.LanguageUtils.normalize(language || 'und'),
|
||||
originalLanguage: language || null,
|
||||
|
||||
@@ -138,22 +138,8 @@ shaka.util.PlayerConfiguration = class {
|
||||
ignoreSuggestedPresentationDelay: false,
|
||||
ignoreEmptyAdaptationSet: false,
|
||||
ignoreMaxSegmentDuration: false,
|
||||
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':
|
||||
'com.microsoft.playready',
|
||||
'urn:uuid:79f0049a-4098-8642-ab92-e65be0885f95':
|
||||
'com.microsoft.playready',
|
||||
'urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2':
|
||||
'com.apple.fps',
|
||||
'urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c':
|
||||
'com.huawei.wiseplay',
|
||||
},
|
||||
keySystemsByURI: shaka.drm.DrmUtils.getUuidMap(
|
||||
/* withoutDashes= */ false, /* useUrnFormat= */ true),
|
||||
manifestPreprocessorTXml:
|
||||
shaka.util.PlayerConfiguration.defaultManifestPreprocessorTXml,
|
||||
sequenceMode: false,
|
||||
|
||||
@@ -254,4 +254,116 @@ describe('DrmUtils', () => {
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUuidMap', () => {
|
||||
it('returns UUIDs with dashes by default', () => {
|
||||
const map = shaka.drm.DrmUtils.getUuidMap();
|
||||
|
||||
expect(map['edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'])
|
||||
.toBe('com.widevine.alpha');
|
||||
|
||||
expect(map['9a04f079-9840-4286-ab92-e65be0885f95'])
|
||||
.toBe('com.microsoft.playready');
|
||||
});
|
||||
|
||||
it('returns UUIDs without dashes when requested', () => {
|
||||
const map = shaka.drm.DrmUtils.getUuidMap(/* withoutDashes= */ true);
|
||||
|
||||
expect(map['edef8ba979d64acea3c827dcd51d21ed'])
|
||||
.toBe('com.widevine.alpha');
|
||||
|
||||
expect(map['9a04f07998404286ab92e65be0885f95'])
|
||||
.toBe('com.microsoft.playready');
|
||||
});
|
||||
|
||||
it('does not include dashed UUIDs when withoutDashes is true', () => {
|
||||
const map = shaka.drm.DrmUtils.getUuidMap(/* withoutDashes= */ true);
|
||||
|
||||
expect(map['edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'])
|
||||
.toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns consistent number of entries in both modes', () => {
|
||||
const withDashes = shaka.drm.DrmUtils.getUuidMap();
|
||||
const withoutDashes =
|
||||
shaka.drm.DrmUtils.getUuidMap(/* withoutDashes= */ true);
|
||||
|
||||
expect(Object.keys(withDashes).length)
|
||||
.toBe(Object.keys(withoutDashes).length);
|
||||
});
|
||||
|
||||
it('correctly normalizes UUIDs by removing all dashes', () => {
|
||||
const withDashes = shaka.drm.DrmUtils.getUuidMap();
|
||||
const withoutDashes =
|
||||
shaka.drm.DrmUtils.getUuidMap(/* withoutDashes= */ true);
|
||||
|
||||
for (const key in withDashes) {
|
||||
const normalized = key.replace(/-/g, '');
|
||||
expect(withoutDashes[normalized]).toBe(withDashes[key]);
|
||||
}
|
||||
});
|
||||
|
||||
it('maps multiple UUIDs to the same key system (ClearKey)', () => {
|
||||
const map = shaka.drm.DrmUtils.getUuidMap();
|
||||
|
||||
expect(map['1077efec-c0b2-4d02-ace3-3c1e52e2fb4b'])
|
||||
.toBe('org.w3.clearkey');
|
||||
|
||||
expect(map['e2719d58-a985-b3c9-781a-b030af78d30e'])
|
||||
.toBe('org.w3.clearkey');
|
||||
});
|
||||
|
||||
it('maps multiple UUIDs to the same key system (PlayReady)', () => {
|
||||
const map = shaka.drm.DrmUtils.getUuidMap();
|
||||
|
||||
expect(map['9a04f079-9840-4286-ab92-e65be0885f95'])
|
||||
.toBe('com.microsoft.playready');
|
||||
|
||||
expect(map['79f0049a-4098-8642-ab92-e65be0885f95'])
|
||||
.toBe('com.microsoft.playready');
|
||||
});
|
||||
|
||||
it('returns UUIDs in URN format when requested', () => {
|
||||
const map = shaka.drm.DrmUtils.getUuidMap(
|
||||
/* withoutDashes= */ false,
|
||||
/* useUrnFormat= */ true);
|
||||
|
||||
expect(map['urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'])
|
||||
.toBe('com.widevine.alpha');
|
||||
|
||||
expect(map['urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95'])
|
||||
.toBe('com.microsoft.playready');
|
||||
});
|
||||
|
||||
it('does not include non-URN UUIDs when useUrnFormat is true', () => {
|
||||
const map = shaka.drm.DrmUtils.getUuidMap(false, true);
|
||||
|
||||
expect(map['edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'])
|
||||
.toBeUndefined();
|
||||
});
|
||||
|
||||
it('useUrnFormat takes precedence over withoutDashes', () => {
|
||||
const map = shaka.drm.DrmUtils.getUuidMap(
|
||||
/* withoutDashes= */ true,
|
||||
/* useUrnFormat= */ true);
|
||||
|
||||
expect(map['urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'])
|
||||
.toBe('com.widevine.alpha');
|
||||
|
||||
expect(map['urn:uuid:edef8ba979d64acea3c827dcd51d21ed'])
|
||||
.toBeUndefined();
|
||||
});
|
||||
|
||||
it('useUrnFormat takes precedence over withoutDashes', () => {
|
||||
const map = shaka.drm.DrmUtils.getUuidMap(
|
||||
/* withoutDashes= */ true,
|
||||
/* useUrnFormat= */ true);
|
||||
|
||||
expect(map['urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'])
|
||||
.toBe('com.widevine.alpha');
|
||||
|
||||
expect(map['urn:uuid:edef8ba979d64acea3c827dcd51d21ed'])
|
||||
.toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user