Skip to content

Commit

Permalink
Attach CDM on start when even when initial fragments do not have a ke…
Browse files Browse the repository at this point in the history
…y associated with them
  • Loading branch information
robwalch committed Oct 27, 2022
1 parent 0815a9c commit 666ad22
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 3 deletions.
2 changes: 2 additions & 0 deletions api-extractor/report/hls.js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,8 @@ export class LevelDetails {
// (undocumented)
get edge(): number;
// (undocumented)
encryptedFragments: Fragment[];
// (undocumented)
endCC: number;
// (undocumented)
endSN: number;
Expand Down
2 changes: 2 additions & 0 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@ export default class BaseStreamController
});
this.hls.trigger(Events.KEY_LOADING, { frag });
this.throwIfFragContextChanged('KEY_LOADING');
} else if (!frag.encrypted && details.encryptedFragments.length) {
this.keyLoader.loadClear(frag, details.encryptedFragments);
}

targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
Expand Down
2 changes: 1 addition & 1 deletion src/controller/eme-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ class EMEController implements ComponentAPI {
`key status change "${status}" for keyStatuses keyId: ${Hex.hexDump(
keyId
)} session keyId: ${Hex.hexDump(
mediaKeySessionContext.decryptdata.keyId
mediaKeySessionContext.decryptdata.keyId || []
)} uri: ${mediaKeySessionContext.decryptdata.uri}`
);
mediaKeySessionContext.keyStatus = status;
Expand Down
2 changes: 1 addition & 1 deletion src/loader/fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export class Fragment extends BaseSegment {
setKeyFormat(keyFormat: KeySystemFormats) {
if (this.levelkeys) {
const key = this.levelkeys[keyFormat];
if (key) {
if (key && !this._decryptdata) {
this._decryptdata = key.getDecryptData(this.sn);
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/loader/key-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,24 @@ export default class KeyLoader implements ComponentAPI {
});
}

loadClear(loadingFrag: Fragment, encryptedFragments: Fragment[]): void | Promise<void> {
if (this.emeController && this.config.emeEnabled) {
// access key-system with nearest key on start (loaidng frag is unencrypted)
const { sn, cc } = loadingFrag;
for (let i = 0; i < encryptedFragments.length; i++) {
const frag = encryptedFragments[i];
if (cc <= frag.cc && (sn === 'initSegment' || sn < frag.sn)) {
this.emeController
.selectKeySystemFormat(frag)
.then((keySystemFormat) => {
frag.setKeyFormat(keySystemFormat);
});
break;
}
}
}
}

load(frag: Fragment): Promise<KeyLoadedData> {
if (!frag.decryptdata && frag.encrypted && this.emeController) {
// Multiple keys, but none selected, resolve in eme-controller
Expand Down
2 changes: 2 additions & 0 deletions src/loader/level-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ export class LevelDetails {
public driftEndTime: number = 0;
public driftStart: number = 0;
public driftEnd: number = 0;
public encryptedFragments: Fragment[];

constructor(baseUrl) {
this.fragments = [];
this.encryptedFragments = [];
this.dateRanges = {};
this.url = baseUrl;
}
Expand Down
18 changes: 17 additions & 1 deletion src/loader/m3u8-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,18 @@ export default class M3U8Parser {
frag.start = totalduration;
if (levelkeys) {
frag.levelkeys = levelkeys;
const { encryptedFragments } = level;
if (
frag.levelkeys &&
Object.keys(frag.levelkeys).some(
(format) => frag.levelkeys![format].isCommonEncryption
) &&
(!encryptedFragments.length ||
encryptedFragments[encryptedFragments.length - 1].levelkeys !==
levelkeys)
) {
encryptedFragments.push(frag);
}
}
frag.sn = currentSN;
frag.level = id;
Expand Down Expand Up @@ -406,7 +418,11 @@ export default class M3U8Parser {
.filter(Number.isFinite);

if (isKeyTagSupported(decryptkeyformat, decryptmethod)) {
if (decryptmethod === 'NONE' || !levelkeys) {
if (decryptmethod === 'NONE') {
levelkeys = undefined;
break;
}
if (!levelkeys) {
levelkeys = {};
}
if (levelkeys[decryptkeyformat]) {
Expand Down
88 changes: 88 additions & 0 deletions tests/unit/loader/playlist-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1722,6 +1722,94 @@ media_1638278.m4s`;
pdt += frag.duration * 1000;
}
});

it('parse clear->enc->clear->enc playlist', function () {
const level = `#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:6
#EXT-X-MAP:URI="init.mp4"
#EXTINF:5.5,
1.mp4
#EXTINF:5.0,
2.mp4
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://a",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,YQo=",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYFORMATVERSIONS="1"
#EXT-X-MAP:URI="init.mp4"
#EXTINF:5.5,
3.mp4
#EXTINF:5.0,
4.mp4
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=NONE
#EXT-X-MAP:URI="init.mp4"
#EXTINF:5.5,
5.mp4
#EXTINF:5.0,
6.mp4
#EXT-X-DISCONTINUITY
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://b",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,Yg==",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYFORMATVERSIONS="1"
#EXT-X-MAP:URI="init.mp4"
#EXTINF:5.0,
7.mp4
#EXTINF:4.0,
8.mp4
#EXT-X-ENDLIST`;
const result = M3U8Parser.parseLevelPlaylist(
level,
'http://foo.com/adaptive/test.m3u8',
0,
PlaylistLevelType.MAIN,
0
);
expect(result.fragments.length).to.equal(8);
expect(result.fragments[0].levelkeys, 'first segment has no keys').to.equal(
undefined
);
expect(
result.fragments[1].levelkeys,
'second segment has no keys'
).to.equal(undefined);
expect(result.fragments[2].levelkeys, 'third segment has two keys')
.to.be.an('object')
.with.keys([
'com.apple.streamingkeydelivery',
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
]);
expect(result.fragments[3].levelkeys, 'forth segment has two keys')
.to.be.an('object')
.with.keys([
'com.apple.streamingkeydelivery',
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
]);
expect(result.fragments[4].levelkeys, 'fifth segment has no keys').to.equal(
undefined
);
expect(result.fragments[5].levelkeys, 'sixth segment has no keys').to.equal(
undefined
);
expect(result.fragments[6].levelkeys, 'seventh segment has two keys')
.to.be.an('object')
.with.keys([
'com.apple.streamingkeydelivery',
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
]);
expect(result.fragments[7].levelkeys, 'eighth segment has two keys')
.to.be.an('object')
.with.keys([
'com.apple.streamingkeydelivery',
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
]);
expect(result)
.to.have.property('encryptedFragments')
.which.is.an('array')
.which.has.members([result.fragments[2], result.fragments[6]]);
});
});

function expectWithJSONMessage(value: any, msg?: string) {
Expand Down

0 comments on commit 666ad22

Please sign in to comment.