Skip to content

Commit

Permalink
feat: support in-manifest DRM data (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
forbesjo authored Mar 30, 2018
1 parent e06aa1e commit 7ce9aca
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 7 deletions.
54 changes: 51 additions & 3 deletions src/inheritAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { findChildren, getContent } from './utils/xml';
import { parseAttributes } from './parseAttributes';
import resolveUrl from './utils/resolveUrl';
import errors from './errors';
import decodeB64ToUint8Array from './utils/decodeB64ToUint8Array';

const keySystemsMap = {
'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': '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:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
};

/**
* Builds a list of urls that is the product of the reference urls and BaseURL values
Expand Down Expand Up @@ -154,6 +162,37 @@ export const inheritBaseUrls =
});
};

/**
* Tranforms a series of content protection nodes to
* an object containing pssh data by key system
*
* @param {Node[]} contentProtectionNodes
* Content protection nodes
* @return {Object}
* Object containing pssh data by key system
*/
const generateKeySystemInformation = (contentProtectionNodes) => {
return contentProtectionNodes.reduce((acc, node) => {
const attributes = parseAttributes(node);
const keySystem = keySystemsMap[attributes.schemeIdUri];

if (keySystem) {
acc[keySystem] = { attributes };

const psshNode = findChildren(node, 'cenc:pssh')[0];

if (psshNode) {
const pssh = getContent(psshNode);
const psshBuffer = pssh && decodeB64ToUint8Array(pssh);

acc[keySystem].pssh = psshBuffer;
}
}

return acc;
}, {});
};

/**
* Maps an AdaptationSet node to a list of Representation information objects
*
Expand Down Expand Up @@ -185,9 +224,18 @@ export const toRepresentations =
findChildren(adaptationSet, 'BaseURL'));
const role = findChildren(adaptationSet, 'Role')[0];
const roleAttributes = { role: parseAttributes(role) };
const attrs = merge(periodAttributes,
adaptationSetAttributes,
roleAttributes);

let attrs = merge(periodAttributes,
adaptationSetAttributes,
roleAttributes);

const contentProtection = generateKeySystemInformation(
findChildren(adaptationSet, 'ContentProtection'));

if (Object.keys(contentProtection).length) {
attrs = merge(attrs, { contentProtection });
}

const segmentInfo = getSegmentInformation(adaptationSet);
const representations = findChildren(adaptationSet, 'Representation');
const adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
Expand Down
16 changes: 14 additions & 2 deletions src/toM3u8.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const formatAudioPlaylist = ({ attributes, segments }) => {
return {
const playlist = {
attributes: {
NAME: attributes.id,
BANDWIDTH: attributes.bandwidth,
Expand All @@ -14,6 +14,12 @@ export const formatAudioPlaylist = ({ attributes, segments }) => {
segments,
mediaSequence: segments.length ? segments[0].number : 1
};

if (attributes.contentProtection) {
playlist.contentProtection = attributes.contentProtection;
}

return playlist;
};

export const formatVttPlaylist = ({ attributes, segments }) => {
Expand Down Expand Up @@ -98,7 +104,7 @@ export const organizeVttPlaylists = playlists => {
};

export const formatVideoPlaylist = ({ attributes, segments }) => {
return {
const playlist = {
attributes: {
NAME: attributes.id,
AUDIO: 'audio',
Expand All @@ -119,6 +125,12 @@ export const formatVideoPlaylist = ({ attributes, segments }) => {
segments,
mediaSequence: segments.length ? segments[0].number : 1
};

if (attributes.contentProtection) {
playlist.contentProtection = attributes.contentProtection;
}

return playlist;
};

export const toM3u8 = dashPlaylists => {
Expand Down
11 changes: 11 additions & 0 deletions src/utils/decodeB64ToUint8Array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import window from 'global/window';

export default function decodeB64ToUint8Array(b64Text) {
const decodedString = window.atob(b64Text);
const array = new Uint8Array(decodedString.length);

for (let i = 0; i < decodedString.length; i++) {
array[i] = decodedString.charCodeAt(i);
}
return array;
}
32 changes: 32 additions & 0 deletions test/manifests/maat_vtt_segmentTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ export const parsedManifest = {
NAME: '125000',
['PROGRAM-ID']: 1
},
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
},
endList: true,
mediaSequence: 0,
targetDuration: 1.984,
Expand Down Expand Up @@ -79,6 +87,14 @@ export const parsedManifest = {
NAME: '125000',
['PROGRAM-ID']: 1
},
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
},
endList: true,
targetDuration: 1.984,
mediaSequence: 0,
Expand Down Expand Up @@ -203,6 +219,14 @@ export const parsedManifest = {
width: 482
}
},
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
},
endList: true,
targetDuration: 1.9185833333333333,
mediaSequence: 0,
Expand Down Expand Up @@ -263,6 +287,14 @@ export const parsedManifest = {
width: 720
}
},
contentProtection: {
'com.widevine.alpha': {
attributes: {
schemeIdUri: 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
},
pssh: new Uint8Array([181, 235, 45])
}
},
endList: true,
targetDuration: 1.9185833333333333,
mediaSequence: 0,
Expand Down
20 changes: 18 additions & 2 deletions test/manifests/maat_vtt_segmentTemplate.mpd
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6S" minBufferTime="PT2.000S">
<BaseURL>https://www.example.com/base</BaseURL>
<Period>

<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="en">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>

<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate duration="95232" initialization="$RepresentationID$/init.m4f" media="$RepresentationID$/$Number$.m4f" startNumber="0" timescale="48000"></SegmentTemplate>
<Representation audioSamplingRate="48000" bandwidth="63000" codecs="mp4a.40.2" id="63000">
</Representation>
<Representation audioSamplingRate="48000" bandwidth="125000" codecs="mp4a.40.2" id="125000">
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="es">

<AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1" lang="es">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>

<Role schemeIdUri="urn:mpeg:dash:role:2011"></Role>
<SegmentTemplate duration="95232" initialization="$RepresentationID$/es/init.m4f" media="$RepresentationID$/es/$Number$.m4f" startNumber="0" timescale="48000"></SegmentTemplate>
<Representation audioSamplingRate="48000" bandwidth="63000" codecs="mp4a.40.2" id="63000">
Expand All @@ -21,6 +32,11 @@
</AdaptationSet>

<AdaptationSet mimeType="video/mp4" scanType="progressive" segmentAlignment="true" startWithSAP="1">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="aaa"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>test</cenc:pssh>
</ContentProtection>

<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate duration="46046" initialization="$RepresentationID$/init.m4f" media="$RepresentationID$/$Number$.m4f" startNumber="0" timescale="24000"></SegmentTemplate>
<Representation bandwidth="449000" codecs="avc1.420015" frameRate="2997/125" height="270" id="482" width="482">
Expand Down

0 comments on commit 7ce9aca

Please sign in to comment.