Skip to content

Commit abb6945

Browse files
committed
Get KEYID from init segment 'tenc' when not found elsewhere
Fixes video-dev#7541
1 parent b282f03 commit abb6945

File tree

5 files changed

+101
-72
lines changed

5 files changed

+101
-72
lines changed

src/controller/eme-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,7 @@ class EMEController extends Logger implements ComponentAPI {
877877
handleKeyStatus(keyStatus, context);
878878
} else {
879879
// Timeout key-status
880-
const timeout = 0;
880+
const timeout = 1000;
881881
context.keyStatusTimeouts ||= {};
882882
context.keyStatusTimeouts[keyId] ||= self.setTimeout(() => {
883883
if ((!context.mediaKeysSession as any) || !this.mediaKeys) {

src/loader/fragment.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -259,25 +259,24 @@ export class Fragment extends BaseSegment {
259259

260260
get decryptdata(): LevelKey | null {
261261
const { levelkeys } = this;
262-
if (!levelkeys && !this._decryptdata) {
262+
263+
if (!levelkeys || levelkeys.NONE) {
263264
return null;
264265
}
265266

266-
if (!this._decryptdata && this.levelkeys && !this.levelkeys.NONE) {
267-
const key = this.levelkeys.identity;
268-
if (key) {
269-
this._decryptdata = key.getDecryptData(this.sn);
270-
} else {
271-
const keyFormats = Object.keys(this.levelkeys);
272-
if (keyFormats.length === 1) {
273-
const levelKey = (this._decryptdata =
274-
this.levelkeys[keyFormats[0]] || null);
275-
if (levelKey) {
276-
return levelKey.getDecryptData(this.sn, this.levelkeys);
277-
}
278-
} else {
279-
// Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system.
267+
if (levelkeys.identity) {
268+
if (!this._decryptdata) {
269+
this._decryptdata = levelkeys.identity.getDecryptData(this.sn);
270+
}
271+
} else if (!this._decryptdata?.keyId) {
272+
const keyFormats = Object.keys(levelkeys);
273+
if (keyFormats.length === 1) {
274+
const levelKey = (this._decryptdata = levelkeys[keyFormats[0]] || null);
275+
if (levelKey) {
276+
this._decryptdata = levelKey.getDecryptData(this.sn, levelkeys);
280277
}
278+
} else {
279+
// Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system.
281280
}
282281
}
283282

@@ -367,8 +366,8 @@ export class Fragment extends BaseSegment {
367366
const levelkeys = this.levelkeys;
368367
if (levelkeys) {
369368
const key = levelkeys[keyFormat];
370-
if (key && !this._decryptdata) {
371-
this._decryptdata = key.getDecryptData(this.sn, this.levelkeys);
369+
if (key && !this._decryptdata?.keyId) {
370+
this._decryptdata = key.getDecryptData(this.sn, levelkeys);
372371
}
373372
}
374373
}

src/loader/key-loader.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { LoadError } from './fragment-loader';
2+
import { LevelKey } from './level-key';
23
import { ErrorDetails, ErrorTypes } from '../errors';
34
import { type Fragment, isMediaFragment } from '../loader/fragment';
45
import { arrayToHex } from '../utils/hex';
@@ -8,7 +9,7 @@ import {
89
keySystemFormatToKeySystemDomain,
910
} from '../utils/mediakeys-helper';
1011
import { KeySystemFormats } from '../utils/mediakeys-helper';
11-
import type { LevelKey } from './level-key';
12+
import { parseKeyIdsFromTenc } from '../utils/mp4-tools';
1213
import type { HlsConfig } from '../config';
1314
import type EMEController from '../controller/eme-controller';
1415
import type {
@@ -258,6 +259,19 @@ export default class KeyLoader extends Logger implements ComponentAPI {
258259
loadKeyEME(keyInfo: KeyLoaderInfo, frag: Fragment): Promise<KeyLoadedData> {
259260
const keyLoadedData: KeyLoadedData = { frag, keyInfo };
260261
if (this.emeController && this.config.emeEnabled) {
262+
if (!keyInfo.decryptdata.keyId && frag.initSegment?.data) {
263+
const keyIds = parseKeyIdsFromTenc(
264+
frag.initSegment.data as Uint8Array<ArrayBuffer>,
265+
);
266+
if (keyIds.length) {
267+
const keyId = keyIds[0];
268+
if (keyId.some((b) => b !== 0)) {
269+
this.log(`Using keyId found in init segment ${arrayToHex(keyId)}`);
270+
keyInfo.decryptdata.keyId = keyId;
271+
LevelKey.setKeyIdForUri(keyInfo.decryptdata.uri, keyId);
272+
}
273+
}
274+
}
261275
const keySessionContextPromise =
262276
this.emeController.loadKey(keyLoadedData);
263277
return (keyInfo.keyLoadPromise = keySessionContextPromise.then(

src/loader/level-key.ts

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,6 @@ export class LevelKey implements DecryptData {
169169
}
170170
if (!this.keyId) {
171171
this.keyId = getKeyIdFromPlayReadyKey(levelKeys);
172-
if (!this.keyId) {
173-
const offset = keyBytes.length - 22;
174-
this.keyId = keyBytes.subarray(offset, offset + 16);
175-
}
176172
}
177173
break;
178174
case KeySystemFormats.PLAYREADY: {
@@ -202,25 +198,20 @@ export class LevelKey implements DecryptData {
202198
}
203199
}
204200

205-
// Default behavior: assign a new keyId for each uri
201+
// Default behavior: get keyId from other KEY tag or URI lookup
206202
if (!this.keyId || this.keyId.byteLength !== 16) {
207-
let keyId: Uint8Array<ArrayBuffer> | null | undefined =
208-
keyUriToKeyIdMap[this.uri];
203+
let keyId: Uint8Array<ArrayBuffer> | null | undefined;
204+
keyId = getKeyIdFromWidevineKey(levelKeys);
209205
if (!keyId) {
210-
keyId = getKeyIdFromWidevineKey(levelKeys);
206+
keyId = getKeyIdFromPlayReadyKey(levelKeys);
211207
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-
}
208+
keyId = keyUriToKeyIdMap[this.uri];
220209
}
210+
}
211+
if (keyId) {
212+
this.keyId = keyId;
221213
LevelKey.setKeyIdForUri(this.uri, keyId);
222214
}
223-
this.keyId = keyId;
224215
}
225216

226217
return this;

src/utils/mp4-tools.ts

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type { DecryptData } from '../loader/level-key';
77
import type { PassthroughTrack, UserdataSample } from '../types/demuxer';
88
import type { ILogger } from '../utils/logger';
99

10+
type BoxDataOrUndefined = Uint8Array<ArrayBuffer> | undefined;
11+
1012
const UINT32_MAX = Math.pow(2, 32) - 1;
1113
const push = [].push;
1214

@@ -573,51 +575,74 @@ export function patchEncyptionData(
573575
}
574576
const keyId = decryptdata.keyId;
575577
if (keyId && decryptdata.isCommonEncryption) {
576-
const traks = findBox(initSegment, ['moov', 'trak']);
577-
traks.forEach((trak) => {
578-
const stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
579-
580-
// skip the sample entry count
581-
const sampleEntries = stsd.subarray(8);
582-
let encBoxes = findBox(sampleEntries, ['enca']);
583-
const isAudio = encBoxes.length > 0;
584-
if (!isAudio) {
585-
encBoxes = findBox(sampleEntries, ['encv']);
578+
applyToTencBoxes(initSegment, (tenc, isAudio) => {
579+
// Look for default key id (keyID offset is always 8 within the tenc box):
580+
const tencKeyId = tenc.subarray(8, 24);
581+
if (!tencKeyId.some((b) => b !== 0)) {
582+
logger.log(
583+
`[eme] Patching keyId in 'enc${
584+
isAudio ? 'a' : 'v'
585+
}>sinf>>tenc' box: ${arrayToHex(tencKeyId)} -> ${arrayToHex(keyId)}`,
586+
);
587+
tenc.set(keyId, 8);
586588
}
587-
encBoxes.forEach((enc) => {
588-
const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78);
589-
const sinfBoxes = findBox(encBoxChildren, ['sinf']);
590-
sinfBoxes.forEach((sinf) => {
591-
const tenc = parseSinf(sinf);
592-
if (tenc) {
593-
// Look for default key id (keyID offset is always 8 within the tenc box):
594-
const tencKeyId = tenc.subarray(8, 24) as Uint8Array<ArrayBuffer>;
595-
if (!tencKeyId.some((b) => b !== 0)) {
596-
logger.log(
597-
`[eme] Patching keyId in 'enc${
598-
isAudio ? 'a' : 'v'
599-
}>sinf>>tenc' box: ${arrayToHex(tencKeyId)} -> ${arrayToHex(
600-
keyId,
601-
)}`,
602-
);
603-
tenc.set(keyId, 8);
604-
}
605-
}
606-
});
607-
});
608589
});
609590
}
610591
}
611592

612-
export function parseSinf(sinf: Uint8Array): Uint8Array | null {
613-
const schm = findBox(sinf, ['schm'])[0];
614-
if (schm as any) {
593+
export function parseKeyIdsFromTenc(
594+
initSegment: Uint8Array<ArrayBuffer>,
595+
): Uint8Array<ArrayBuffer>[] {
596+
const keyIds: Uint8Array<ArrayBuffer>[] = [];
597+
applyToTencBoxes(initSegment, (tenc) => keyIds.push(tenc.subarray(8, 24)));
598+
return keyIds;
599+
}
600+
601+
function applyToTencBoxes(
602+
initSegment: Uint8Array<ArrayBuffer>,
603+
predicate: (tenc: Uint8Array<ArrayBuffer>, isAudio: boolean) => void,
604+
) {
605+
const traks = findBox(initSegment, ['moov', 'trak']);
606+
traks.forEach((trak) => {
607+
const stsd = findBox(trak, [
608+
'mdia',
609+
'minf',
610+
'stbl',
611+
'stsd',
612+
])[0] as BoxDataOrUndefined;
613+
if (!stsd) return;
614+
const sampleEntries = stsd.subarray(8);
615+
let encBoxes = findBox(sampleEntries, ['enca']);
616+
const isAudio = encBoxes.length > 0;
617+
if (!isAudio) {
618+
encBoxes = findBox(sampleEntries, ['encv']);
619+
}
620+
encBoxes.forEach((enc) => {
621+
const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78);
622+
const sinfBoxes = findBox(encBoxChildren, ['sinf']);
623+
sinfBoxes.forEach((sinf) => {
624+
const tenc = parseSinf(sinf);
625+
if (tenc) {
626+
predicate(tenc, isAudio);
627+
}
628+
});
629+
});
630+
});
631+
}
632+
633+
export function parseSinf(sinf: Uint8Array): BoxDataOrUndefined {
634+
const schm = findBox(sinf, ['schm'])[0] as BoxDataOrUndefined;
635+
if (schm) {
615636
const scheme = bin2str(schm.subarray(4, 8));
616637
if (scheme === 'cbcs' || scheme === 'cenc') {
617-
return findBox(sinf, ['schi', 'tenc'])[0];
638+
const tenc = findBox(sinf, ['schi', 'tenc'])[0] as BoxDataOrUndefined;
639+
if (tenc) {
640+
return tenc;
641+
}
642+
} else if (scheme === 'cbc2') {
643+
/* no-op */
618644
}
619645
}
620-
return null;
621646
}
622647

623648
/*

0 commit comments

Comments
 (0)