Skip to content

Commit 1f54b01

Browse files
committed
Use Widevine KEYID or parse Playready when level keys are present
Update keyUriToKeyIdMap set after KEY_LOADING
1 parent 2a192fb commit 1f54b01

File tree

3 files changed

+64
-16
lines changed

3 files changed

+64
-16
lines changed

api-extractor/report/hls.js.api.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3361,7 +3361,9 @@ export class LevelKey implements DecryptData {
33613361
// (undocumented)
33623362
readonly encrypted: boolean;
33633363
// (undocumented)
3364-
getDecryptData(sn: number | 'initSegment'): LevelKey | null;
3364+
getDecryptData(sn: number | 'initSegment', levelKeys?: {
3365+
[key: string]: LevelKey | undefined;
3366+
}): LevelKey | null;
33653367
// (undocumented)
33663368
readonly isCommonEncryption: boolean;
33673369
// (undocumented)

src/loader/fragment.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ export class Fragment extends BaseSegment {
273273
const levelKey = (this._decryptdata =
274274
this.levelkeys[keyFormats[0]] || null);
275275
if (levelKey) {
276-
return levelKey.getDecryptData(this.sn);
276+
return levelKey.getDecryptData(this.sn, this.levelkeys);
277277
}
278278
} else {
279279
// Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system.
@@ -364,10 +364,11 @@ export class Fragment extends BaseSegment {
364364
}
365365

366366
setKeyFormat(keyFormat: KeySystemFormats) {
367-
if (this.levelkeys) {
368-
const key = this.levelkeys[keyFormat];
367+
const levelkeys = this.levelkeys;
368+
if (levelkeys) {
369+
const key = levelkeys[keyFormat];
369370
if (key && !this._decryptdata) {
370-
this._decryptdata = key.getDecryptData(this.sn);
371+
this._decryptdata = key.getDecryptData(this.sn, this.levelkeys);
371372
}
372373
}
373374
}

src/loader/level-key.ts

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ export class LevelKey implements DecryptData {
100100
return false;
101101
}
102102

103-
public getDecryptData(sn: number | 'initSegment'): LevelKey | null {
103+
public getDecryptData(
104+
sn: number | 'initSegment',
105+
levelKeys?: { [key: string]: LevelKey | undefined },
106+
): LevelKey | null {
104107
if (!this.encrypted || !this.uri) {
105108
return null;
106109
}
@@ -135,10 +138,19 @@ export class LevelKey implements DecryptData {
135138
return this;
136139
}
137140

138-
if (this.pssh && this.keyId) {
139-
return this;
141+
if (this.keyId) {
142+
// Handle case where key id is changed in KEY_LOADING event handler #7542#issuecomment-3305203929
143+
const assignedKeyId = keyUriToKeyIdMap[this.uri];
144+
if (assignedKeyId && !arrayValuesMatch(this.keyId, assignedKeyId)) {
145+
LevelKey.setKeyIdForUri(this.uri, this.keyId);
146+
}
147+
148+
if (this.pssh) {
149+
return this;
150+
}
140151
}
141152

153+
// Key bytes are signalled the KEYID attribute, typically only found on WideVine KEY tags
142154
// Initialize keyId if possible
143155
const keyBytes = convertDataUriToArrayBytes(this.uri);
144156
if (keyBytes) {
@@ -156,8 +168,11 @@ export class LevelKey implements DecryptData {
156168
}
157169
}
158170
if (!this.keyId) {
159-
const offset = keyBytes.length - 22;
160-
this.keyId = keyBytes.subarray(offset, offset + 16);
171+
this.keyId = getKeyIdFromPlayReadyKey(levelKeys);
172+
if (!this.keyId) {
173+
const offset = keyBytes.length - 22;
174+
this.keyId = keyBytes.subarray(offset, offset + 16);
175+
}
161176
}
162177
break;
163178
case KeySystemFormats.PLAYREADY: {
@@ -189,13 +204,20 @@ export class LevelKey implements DecryptData {
189204

190205
// Default behavior: assign a new keyId for each uri
191206
if (!this.keyId || this.keyId.byteLength !== 16) {
192-
let keyId = keyUriToKeyIdMap[this.uri];
207+
let keyId: Uint8Array<ArrayBuffer> | null | undefined =
208+
keyUriToKeyIdMap[this.uri];
193209
if (!keyId) {
194-
const val =
195-
Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER;
196-
keyId = new Uint8Array(16);
197-
const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes
198-
dv.setUint32(0, val);
210+
keyId = getKeyIdFromWidevineKey(levelKeys);
211+
if (!keyId) {
212+
keyId = getKeyIdFromPlayReadyKey(levelKeys);
213+
if (!keyId) {
214+
const val =
215+
Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER;
216+
keyId = new Uint8Array(16);
217+
const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes
218+
dv.setUint32(0, val);
219+
}
220+
}
199221
LevelKey.setKeyIdForUri(this.uri, keyId);
200222
}
201223
this.keyId = keyId;
@@ -205,6 +227,29 @@ export class LevelKey implements DecryptData {
205227
}
206228
}
207229

230+
function getKeyIdFromWidevineKey(
231+
levelKeys: { [key: string]: LevelKey | undefined } | undefined,
232+
) {
233+
const widevineKey = levelKeys?.[KeySystemFormats.WIDEVINE];
234+
if (widevineKey) {
235+
return widevineKey.keyId;
236+
}
237+
return null;
238+
}
239+
240+
function getKeyIdFromPlayReadyKey(
241+
levelKeys: { [key: string]: LevelKey | undefined } | undefined,
242+
) {
243+
const playReadyKey = levelKeys?.[KeySystemFormats.PLAYREADY];
244+
if (playReadyKey) {
245+
const playReadyKeyBytes = convertDataUriToArrayBytes(playReadyKey.uri);
246+
if (playReadyKeyBytes) {
247+
return parsePlayReadyWRM(playReadyKeyBytes);
248+
}
249+
}
250+
return null;
251+
}
252+
208253
function createInitializationVector(segmentNumber: number) {
209254
const uint8View = new Uint8Array(16);
210255
for (let i = 12; i < 16; i++) {

0 commit comments

Comments
 (0)