hls.js 1.5.14 → 1.5.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -130,5 +130,5 @@
130
130
  "url-toolkit": "2.2.5",
131
131
  "wrangler": "3.22.4"
132
132
  },
133
- "version": "1.5.14"
133
+ "version": "1.5.16"
134
134
  }
@@ -232,9 +232,8 @@ export default class ContentSteeringController implements NetworkComponentAPI {
232
232
  this.log(
233
233
  `Found ${pathwayLevels.length}/${levels.length} levels in Pathway "${this.pathwayId}"`,
234
234
  );
235
- return pathwayLevels;
236
235
  }
237
- return levels;
236
+ return pathwayLevels;
238
237
  }
239
238
 
240
239
  private getLevelsForPathway(pathwayId: string): Level[] {
@@ -14,8 +14,6 @@ import {
14
14
  keySystemFormatToKeySystemDomain,
15
15
  KeySystemIds,
16
16
  keySystemIdToKeySystemDomain,
17
- } from '../utils/mediakeys-helper';
18
- import {
19
17
  KeySystems,
20
18
  requestMediaKeySystemAccess,
21
19
  } from '../utils/mediakeys-helper';
@@ -23,7 +21,13 @@ import { strToUtf8array } from '../utils/keysystem-util';
23
21
  import { base64Decode } from '../utils/numeric-encoding-utils';
24
22
  import { DecryptData, LevelKey } from '../loader/level-key';
25
23
  import Hex from '../utils/hex';
26
- import { bin2str, parsePssh, parseSinf } from '../utils/mp4-tools';
24
+ import {
25
+ bin2str,
26
+ parseMultiPssh,
27
+ parseSinf,
28
+ PsshData,
29
+ PsshInvalidResult,
30
+ } from '../utils/mp4-tools';
27
31
  import { EventEmitter } from 'eventemitter3';
28
32
  import type Hls from '../hls';
29
33
  import type { ComponentAPI } from '../types/component-api';
@@ -525,7 +529,8 @@ class EMEController implements ComponentAPI {
525
529
 
526
530
  private _onMediaEncrypted(event: MediaEncryptedEvent) {
527
531
  const { initDataType, initData } = event;
528
- this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
532
+ const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
533
+ this.debug(logMessage);
529
534
 
530
535
  // Ignore event when initData is null
531
536
  if (initData === null) {
@@ -545,30 +550,42 @@ class EMEController implements ComponentAPI {
545
550
  const sinf = base64Decode(JSON.parse(json).sinf);
546
551
  const tenc = parseSinf(new Uint8Array(sinf));
547
552
  if (!tenc) {
548
- return;
553
+ throw new Error(
554
+ `'schm' box missing or not cbcs/cenc with schi > tenc`,
555
+ );
549
556
  }
550
557
  keyId = tenc.subarray(8, 24);
551
558
  keySystemDomain = KeySystems.FAIRPLAY;
552
559
  } catch (error) {
553
- this.warn('Failed to parse sinf "encrypted" event message initData');
560
+ this.warn(`${logMessage} Failed to parse sinf: ${error}`);
554
561
  return;
555
562
  }
556
563
  } else {
557
- // Support clear-lead key-session creation (otherwise depend on playlist keys)
558
- const psshInfo = parsePssh(initData);
559
- if (psshInfo === null) {
564
+ // Support Widevine clear-lead key-session creation (otherwise depend on playlist keys)
565
+ const psshResults = parseMultiPssh(initData);
566
+ const psshInfo = psshResults.filter(
567
+ (pssh): pssh is PsshData => pssh.systemId === KeySystemIds.WIDEVINE,
568
+ )[0];
569
+ if (!psshInfo) {
570
+ if (
571
+ psshResults.length === 0 ||
572
+ psshResults.some((pssh): pssh is PsshInvalidResult => !pssh.systemId)
573
+ ) {
574
+ this.warn(`${logMessage} contains incomplete or invalid pssh data`);
575
+ } else {
576
+ this.log(
577
+ `ignoring ${logMessage} for ${(psshResults as PsshData[])
578
+ .map((pssh) => keySystemIdToKeySystemDomain(pssh.systemId))
579
+ .join(',')} pssh data in favor of playlist keys`,
580
+ );
581
+ }
560
582
  return;
561
583
  }
562
- if (
563
- psshInfo.version === 0 &&
564
- psshInfo.systemId === KeySystemIds.WIDEVINE &&
565
- psshInfo.data
566
- ) {
567
- keyId = psshInfo.data.subarray(8, 24);
584
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
585
+ if (psshInfo.version === 0 && psshInfo.data) {
586
+ const offset = psshInfo.data.length - 22;
587
+ keyId = psshInfo.data.subarray(offset, offset + 16);
568
588
  }
569
- keySystemDomain = keySystemIdToKeySystemDomain(
570
- psshInfo.systemId as KeySystemIds,
571
- );
572
589
  }
573
590
 
574
591
  if (!keySystemDomain || !keyId) {
@@ -583,7 +600,7 @@ class EMEController implements ComponentAPI {
583
600
  // Match playlist key
584
601
  const keyContext = mediaKeySessions[i];
585
602
  const decryptdata = keyContext.decryptdata;
586
- if (decryptdata.pssh || !decryptdata.keyId) {
603
+ if (!decryptdata.keyId) {
587
604
  continue;
588
605
  }
589
606
  const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
@@ -592,6 +609,9 @@ class EMEController implements ComponentAPI {
592
609
  decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1
593
610
  ) {
594
611
  keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
612
+ if (decryptdata.pssh) {
613
+ break;
614
+ }
595
615
  delete keyIdToKeySessionPromise[oldKeyIdHex];
596
616
  decryptdata.pssh = new Uint8Array(initData);
597
617
  decryptdata.keyId = keyId;
@@ -36,10 +36,10 @@ export function keySystemFormatToKeySystemDomain(
36
36
 
37
37
  // System IDs for which we can extract a key ID from "encrypted" event PSSH
38
38
  export const enum KeySystemIds {
39
- // CENC = '1077efecc0b24d02ace33c1e52e2fb4b'
40
- // CLEARKEY = 'e2719d58a985b3c9781ab030af78d30e',
41
- // FAIRPLAY = '94ce86fb07ff4f43adb893d2fa968ca2',
42
- // PLAYREADY = '9a04f07998404286ab92e65be0885f95',
39
+ CENC = '1077efecc0b24d02ace33c1e52e2fb4b',
40
+ CLEARKEY = 'e2719d58a985b3c9781ab030af78d30e',
41
+ FAIRPLAY = '94ce86fb07ff4f43adb893d2fa968ca2',
42
+ PLAYREADY = '9a04f07998404286ab92e65be0885f95',
43
43
  WIDEVINE = 'edef8ba979d64acea3c827dcd51d21ed',
44
44
  }
45
45
 
@@ -48,10 +48,13 @@ export function keySystemIdToKeySystemDomain(
48
48
  ): KeySystems | undefined {
49
49
  if (systemId === KeySystemIds.WIDEVINE) {
50
50
  return KeySystems.WIDEVINE;
51
- // } else if (systemId === KeySystemIds.PLAYREADY) {
52
- // return KeySystems.PLAYREADY;
53
- // } else if (systemId === KeySystemIds.CENC || systemId === KeySystemIds.CLEARKEY) {
54
- // return KeySystems.CLEARKEY;
51
+ } else if (systemId === KeySystemIds.PLAYREADY) {
52
+ return KeySystems.PLAYREADY;
53
+ } else if (
54
+ systemId === KeySystemIds.CENC ||
55
+ systemId === KeySystemIds.CLEARKEY
56
+ ) {
57
+ return KeySystems.CLEARKEY;
55
58
  }
56
59
  }
57
60
 
@@ -3,6 +3,7 @@ import { sliceUint8 } from './typed-array';
3
3
  import { utf8ArrayToStr } from '../demux/id3';
4
4
  import { logger } from '../utils/logger';
5
5
  import Hex from './hex';
6
+ import type { KeySystemIds } from './mediakeys-helper';
6
7
  import type { PassthroughTrack, UserdataSample } from '../types/demuxer';
7
8
  import type { DecryptData } from '../loader/level-key';
8
9
 
@@ -545,7 +546,6 @@ export function parseSinf(sinf: Uint8Array): Uint8Array | null {
545
546
  return findBox(sinf, ['schi', 'tenc'])[0];
546
547
  }
547
548
  }
548
- logger.error(`[eme] missing 'schm' box`);
549
549
  return null;
550
550
  }
551
551
 
@@ -1349,41 +1349,86 @@ export function mp4pssh(
1349
1349
  );
1350
1350
  }
1351
1351
 
1352
- export function parsePssh(initData: ArrayBuffer) {
1353
- if (!(initData instanceof ArrayBuffer) || initData.byteLength < 32) {
1354
- return null;
1352
+ export type PsshData = {
1353
+ version: 0 | 1;
1354
+ systemId: KeySystemIds;
1355
+ kids: null | Uint8Array[];
1356
+ data: null | Uint8Array;
1357
+ offset: number;
1358
+ size: number;
1359
+ };
1360
+
1361
+ export type PsshInvalidResult = {
1362
+ systemId?: undefined;
1363
+ offset: number;
1364
+ size: number;
1365
+ };
1366
+
1367
+ export function parseMultiPssh(
1368
+ initData: ArrayBuffer,
1369
+ ): (PsshData | PsshInvalidResult)[] {
1370
+ const results: (PsshData | PsshInvalidResult)[] = [];
1371
+ if (initData instanceof ArrayBuffer) {
1372
+ const length = initData.byteLength;
1373
+ let offset = 0;
1374
+ while (offset + 32 < length) {
1375
+ const view = new DataView(initData, offset);
1376
+ const pssh = parsePssh(view);
1377
+ results.push(pssh);
1378
+ offset += pssh.size;
1379
+ }
1355
1380
  }
1356
- const result = {
1357
- version: 0,
1358
- systemId: '',
1359
- kids: null as null | Uint8Array[],
1360
- data: null as null | Uint8Array,
1361
- };
1362
- const view = new DataView(initData);
1363
- const boxSize = view.getUint32(0);
1364
- if (initData.byteLength !== boxSize && boxSize > 44) {
1365
- return null;
1381
+ return results;
1382
+ }
1383
+
1384
+ function parsePssh(view: DataView): PsshData | PsshInvalidResult {
1385
+ const size = view.getUint32(0);
1386
+ const offset = view.byteOffset;
1387
+ const length = view.byteLength;
1388
+ if (length < size) {
1389
+ return {
1390
+ offset,
1391
+ size: length,
1392
+ };
1366
1393
  }
1367
1394
  const type = view.getUint32(4);
1368
1395
  if (type !== 0x70737368) {
1369
- return null;
1396
+ return { offset, size };
1370
1397
  }
1371
- result.version = view.getUint32(8) >>> 24;
1372
- if (result.version > 1) {
1373
- return null;
1398
+ const version = view.getUint32(8) >>> 24;
1399
+ if (version !== 0 && version !== 1) {
1400
+ return { offset, size };
1374
1401
  }
1375
- result.systemId = Hex.hexDump(new Uint8Array(initData, 12, 16));
1402
+ const buffer = view.buffer;
1403
+ const systemId = Hex.hexDump(
1404
+ new Uint8Array(buffer, offset + 12, 16),
1405
+ ) as KeySystemIds;
1376
1406
  const dataSizeOrKidCount = view.getUint32(28);
1377
- if (result.version === 0) {
1378
- if (boxSize - 32 < dataSizeOrKidCount) {
1379
- return null;
1407
+ let kids: null | Uint8Array[] = null;
1408
+ let data: null | Uint8Array = null;
1409
+ if (version === 0) {
1410
+ if (size - 32 < dataSizeOrKidCount || dataSizeOrKidCount < 22) {
1411
+ return { offset, size };
1412
+ }
1413
+ data = new Uint8Array(buffer, offset + 32, dataSizeOrKidCount);
1414
+ } else if (version === 1) {
1415
+ if (
1416
+ !dataSizeOrKidCount ||
1417
+ length < offset + 32 + dataSizeOrKidCount * 16 + 16
1418
+ ) {
1419
+ return { offset, size };
1380
1420
  }
1381
- result.data = new Uint8Array(initData, 32, dataSizeOrKidCount);
1382
- } else if (result.version === 1) {
1383
- result.kids = [];
1421
+ kids = [];
1384
1422
  for (let i = 0; i < dataSizeOrKidCount; i++) {
1385
- result.kids.push(new Uint8Array(initData, 32 + i * 16, 16));
1423
+ kids.push(new Uint8Array(buffer, offset + 32 + i * 16, 16));
1386
1424
  }
1387
1425
  }
1388
- return result;
1426
+ return {
1427
+ version,
1428
+ systemId,
1429
+ kids,
1430
+ data,
1431
+ offset,
1432
+ size,
1433
+ };
1389
1434
  }