hls.js 1.5.14-0.canary.10567 → 1.5.14-0.canary.10570

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/dist/hls.mjs CHANGED
@@ -421,7 +421,7 @@ function enableLogs(debugConfig, context, id) {
421
421
  // Some browsers don't allow to use bind on console object anyway
422
422
  // fallback to default if needed
423
423
  try {
424
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.14-0.canary.10567"}`);
424
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.14-0.canary.10570"}`);
425
425
  } catch (e) {
426
426
  /* log fn threw an exception. All logger methods are no-ops. */
427
427
  return createLogger();
@@ -1365,6 +1365,27 @@ function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCod
1365
1365
  };
1366
1366
  return [baseConfig];
1367
1367
  }
1368
+ function parsePlayReadyWRM(keyBytes) {
1369
+ const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);
1370
+ const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
1371
+
1372
+ // Parse Playready WRMHeader XML
1373
+ const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
1374
+ const parser = new DOMParser();
1375
+ const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
1376
+ const keyData = xmlDoc.getElementsByTagName('KID')[0];
1377
+ if (keyData) {
1378
+ const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
1379
+ if (keyId) {
1380
+ const keyIdArray = base64Decode(keyId).subarray(0, 16);
1381
+ // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
1382
+ // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
1383
+ changeEndianness(keyIdArray);
1384
+ return keyIdArray;
1385
+ }
1386
+ }
1387
+ return null;
1388
+ }
1368
1389
 
1369
1390
  function sliceUint8(array, start, end) {
1370
1391
  // @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
@@ -2703,6 +2724,8 @@ class LevelKey {
2703
2724
  if (keyBytes) {
2704
2725
  switch (this.keyFormat) {
2705
2726
  case KeySystemFormats.WIDEVINE:
2727
+ // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
2728
+ // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
2706
2729
  this.pssh = keyBytes;
2707
2730
  // In case of widevine keyID is embedded in PSSH box. Read Key ID.
2708
2731
  if (keyBytes.length >= 22) {
@@ -2712,25 +2735,11 @@ class LevelKey {
2712
2735
  case KeySystemFormats.PLAYREADY:
2713
2736
  {
2714
2737
  const PlayReadyKeySystemUUID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);
2738
+
2739
+ // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
2740
+ // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
2715
2741
  this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
2716
- const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);
2717
- const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
2718
-
2719
- // Parse Playready WRMHeader XML
2720
- const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
2721
- const parser = new DOMParser();
2722
- const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
2723
- const keyData = xmlDoc.getElementsByTagName('KID')[0];
2724
- if (keyData) {
2725
- const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
2726
- if (keyId) {
2727
- const keyIdArray = base64Decode(keyId).subarray(0, 16);
2728
- // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
2729
- // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
2730
- changeEndianness(keyIdArray);
2731
- this.keyId = keyIdArray;
2732
- }
2733
- }
2742
+ this.keyId = parsePlayReadyWRM(keyBytes);
2734
2743
  break;
2735
2744
  }
2736
2745
  default:
@@ -11070,7 +11079,7 @@ function concatUint8Arrays(chunks, dataLength) {
11070
11079
  return result;
11071
11080
  }
11072
11081
 
11073
- const version = "1.5.14-0.canary.10567";
11082
+ const version = "1.5.14-0.canary.10570";
11074
11083
 
11075
11084
  // ensure the worker ends up in the bundle
11076
11085
  // If the worker should not be included this gets aliased to empty.js
@@ -11388,23 +11397,11 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
11388
11397
  return timeOffset * 90000 + init90kHz;
11389
11398
  };
11390
11399
 
11391
- /**
11392
- * ADTS parser helper
11393
- * @link https://wiki.multimedia.cx/index.php?title=ADTS
11394
- */
11395
- function getAudioConfig(observer, data, offset, audioCodec) {
11396
- let adtsObjectType;
11397
- let originalAdtsObjectType;
11398
- let adtsExtensionSamplingIndex;
11399
- let adtsChannelConfig;
11400
- let config;
11401
- const userAgent = navigator.userAgent.toLowerCase();
11402
- const manifestCodec = audioCodec;
11400
+ function getAudioConfig(observer, data, offset, manifestCodec) {
11403
11401
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
11404
- // byte 2
11405
- adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
11406
- const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
11407
- if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
11402
+ const byte2 = data[offset + 2];
11403
+ const adtsSamplingIndex = byte2 >> 2 & 0xf;
11404
+ if (adtsSamplingIndex > 12) {
11408
11405
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
11409
11406
  observer.emit(Events.ERROR, Events.ERROR, {
11410
11407
  type: ErrorTypes.MEDIA_ERROR,
@@ -11415,53 +11412,12 @@ function getAudioConfig(observer, data, offset, audioCodec) {
11415
11412
  });
11416
11413
  return;
11417
11414
  }
11418
- adtsChannelConfig = (data[offset + 2] & 0x01) << 2;
11419
- // byte 3
11420
- adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
11421
- logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
11422
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
11423
- if (/firefox|palemoon/i.test(userAgent)) {
11424
- if (adtsSamplingIndex >= 6) {
11425
- adtsObjectType = 5;
11426
- config = new Array(4);
11427
- // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
11428
- // there is a factor 2 between frame sample rate and output sample rate
11429
- // multiply frequency by 2 (see table below, equivalent to substract 3)
11430
- adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
11431
- } else {
11432
- adtsObjectType = 2;
11433
- config = new Array(2);
11434
- adtsExtensionSamplingIndex = adtsSamplingIndex;
11435
- }
11436
- // Android : always use AAC
11437
- } else if (userAgent.indexOf('android') !== -1) {
11438
- adtsObjectType = 2;
11439
- config = new Array(2);
11440
- adtsExtensionSamplingIndex = adtsSamplingIndex;
11441
- } else {
11442
- /* for other browsers (Chrome/Vivaldi/Opera ...)
11443
- always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...)
11444
- */
11445
- adtsObjectType = 5;
11446
- config = new Array(4);
11447
- // if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz)
11448
- if (audioCodec && (audioCodec.indexOf('mp4a.40.29') !== -1 || audioCodec.indexOf('mp4a.40.5') !== -1) || !audioCodec && adtsSamplingIndex >= 6) {
11449
- // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
11450
- // there is a factor 2 between frame sample rate and output sample rate
11451
- // multiply frequency by 2 (see table below, equivalent to substract 3)
11452
- adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
11453
- } else {
11454
- // if (manifest codec is AAC) AND (frequency less than 24kHz AND nb channel is 1) OR (manifest codec not specified and mono audio)
11455
- // Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC. This is not a problem with stereo.
11456
- if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSamplingIndex >= 6 && adtsChannelConfig === 1 || /vivaldi/i.test(userAgent)) || !audioCodec && adtsChannelConfig === 1) {
11457
- adtsObjectType = 2;
11458
- config = new Array(2);
11459
- }
11460
- adtsExtensionSamplingIndex = adtsSamplingIndex;
11461
- }
11462
- }
11415
+ // MPEG-4 Audio Object Type (profile_ObjectType+1)
11416
+ const adtsObjectType = (byte2 >> 6 & 0x3) + 1;
11417
+ const channelCount = data[offset + 3] >> 6 & 0x3 | (byte2 & 1) << 2;
11418
+ const codec = 'mp4a.40.' + adtsObjectType;
11463
11419
  /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
11464
- ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig()
11420
+ ISO/IEC 14496-3 - Table 1.13 — Syntax of AudioSpecificConfig()
11465
11421
  Audio Profile / Audio Object Type
11466
11422
  0: Null
11467
11423
  1: AAC Main
@@ -11494,27 +11450,22 @@ function getAudioConfig(observer, data, offset, audioCodec) {
11494
11450
  2: 2 channels: front-left, front-right
11495
11451
  */
11496
11452
  // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1
11497
- config[0] = adtsObjectType << 3;
11498
- // samplingFrequencyIndex
11499
- config[0] |= (adtsSamplingIndex & 0x0e) >> 1;
11500
- config[1] |= (adtsSamplingIndex & 0x01) << 7;
11501
- // channelConfiguration
11502
- config[1] |= adtsChannelConfig << 3;
11503
- if (adtsObjectType === 5) {
11504
- // adtsExtensionSamplingIndex
11505
- config[1] |= (adtsExtensionSamplingIndex & 0x0e) >> 1;
11506
- config[2] = (adtsExtensionSamplingIndex & 0x01) << 7;
11507
- // adtsObjectType (force to 2, chrome is checking that object type is less than 5 ???
11508
- // https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc
11509
- config[2] |= 2 << 2;
11510
- config[3] = 0;
11511
- }
11453
+ const samplerate = adtsSamplingRates[adtsSamplingIndex];
11454
+ let aacSampleIndex = adtsSamplingIndex;
11455
+ if (adtsObjectType === 5 || adtsObjectType === 29) {
11456
+ // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
11457
+ // there is a factor 2 between frame sample rate and output sample rate
11458
+ // multiply frequency by 2 (see table above, equivalent to substract 3)
11459
+ aacSampleIndex -= 3;
11460
+ }
11461
+ const config = [adtsObjectType << 3 | (aacSampleIndex & 0x0e) >> 1, (aacSampleIndex & 0x01) << 7 | channelCount << 3];
11462
+ logger.log(`manifest codec:${manifestCodec}, parsed codec:${codec}, channels:${channelCount}, rate:${samplerate} (ADTS object type:${adtsObjectType} sampling index:${adtsSamplingIndex})`);
11512
11463
  return {
11513
11464
  config,
11514
- samplerate: adtsSamplingRates[adtsSamplingIndex],
11515
- channelCount: adtsChannelConfig,
11516
- codec: 'mp4a.40.' + adtsObjectType,
11517
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
11465
+ samplerate,
11466
+ channelCount,
11467
+ codec,
11468
+ parsedCodec: codec,
11518
11469
  manifestCodec
11519
11470
  };
11520
11471
  }
@@ -11564,13 +11515,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
11564
11515
  if (!config) {
11565
11516
  return;
11566
11517
  }
11567
- track.config = config.config;
11568
- track.samplerate = config.samplerate;
11569
- track.channelCount = config.channelCount;
11570
- track.codec = config.codec;
11571
- track.manifestCodec = config.manifestCodec;
11572
- track.parsedCodec = config.parsedCodec;
11573
- logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
11518
+ _extends(track, config);
11574
11519
  }
11575
11520
  }
11576
11521
  function getFrameDuration(samplerate) {
@@ -14736,7 +14681,7 @@ class MP4 {
14736
14681
  vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
14737
14682
  }
14738
14683
  static esds(track) {
14739
- const configlen = track.config.length;
14684
+ const config = track.config;
14740
14685
  return new Uint8Array([0x00,
14741
14686
  // version 0
14742
14687
  0x00, 0x00, 0x00,
@@ -14744,16 +14689,18 @@ class MP4 {
14744
14689
 
14745
14690
  0x03,
14746
14691
  // descriptor_type
14747
- 0x17 + configlen,
14692
+ 0x19,
14748
14693
  // length
14694
+
14749
14695
  0x00, 0x01,
14750
14696
  // es_id
14697
+
14751
14698
  0x00,
14752
14699
  // stream_priority
14753
14700
 
14754
14701
  0x04,
14755
14702
  // descriptor_type
14756
- 0x0f + configlen,
14703
+ 0x11,
14757
14704
  // length
14758
14705
  0x40,
14759
14706
  // codec : mpeg4_audio
@@ -14766,8 +14713,12 @@ class MP4 {
14766
14713
  0x00, 0x00, 0x00, 0x00,
14767
14714
  // avgBitrate
14768
14715
 
14769
- 0x05 // descriptor_type
14770
- ].concat([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor
14716
+ 0x05,
14717
+ // descriptor_type
14718
+ 0x02,
14719
+ // length
14720
+ ...config, 0x06, 0x01, 0x02 // GASpecificConfig)); // length + audio config descriptor
14721
+ ]);
14771
14722
  }
14772
14723
  static audioStsd(track) {
14773
14724
  const samplerate = track.samplerate;
@@ -23626,7 +23577,7 @@ class EMEController extends Logger {
23626
23577
  }
23627
23578
  let keyId;
23628
23579
  let keySystemDomain;
23629
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
23580
+ if (initDataType === 'sinf' && this.getLicenseServerUrl(KeySystems.FAIRPLAY)) {
23630
23581
  // Match sinf keyId to playlist skd://keyId=
23631
23582
  const json = bin2str(new Uint8Array(initData));
23632
23583
  try {
@@ -23641,10 +23592,19 @@ class EMEController extends Logger {
23641
23592
  this.warn(`${logMessage} Failed to parse sinf: ${error}`);
23642
23593
  return;
23643
23594
  }
23644
- } else {
23595
+ } else if (this.getLicenseServerUrl(KeySystems.WIDEVINE)) {
23645
23596
  // Support Widevine clear-lead key-session creation (otherwise depend on playlist keys)
23646
23597
  const psshResults = parseMultiPssh(initData);
23647
- const psshInfo = psshResults.filter(pssh => pssh.systemId === KeySystemIds.WIDEVINE)[0];
23598
+
23599
+ // TODO: If using keySystemAccessPromises we might want to wait until one is resolved
23600
+ let keySystems = Object.keys(this.keySystemAccessPromises);
23601
+ if (!keySystems.length) {
23602
+ keySystems = getKeySystemsForConfig(this.config);
23603
+ }
23604
+ const psshInfo = psshResults.filter(pssh => {
23605
+ const keySystem = pssh.systemId ? keySystemIdToKeySystemDomain(pssh.systemId) : null;
23606
+ return keySystem ? keySystems.indexOf(keySystem) > -1 : false;
23607
+ })[0];
23648
23608
  if (!psshInfo) {
23649
23609
  if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
23650
23610
  this.warn(`${logMessage} contains incomplete or invalid pssh data`);
@@ -23655,8 +23615,12 @@ class EMEController extends Logger {
23655
23615
  }
23656
23616
  keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
23657
23617
  if (psshInfo.version === 0 && psshInfo.data) {
23658
- const offset = psshInfo.data.length - 22;
23659
- keyId = psshInfo.data.subarray(offset, offset + 16);
23618
+ if (keySystemDomain === KeySystems.WIDEVINE) {
23619
+ const offset = psshInfo.data.length - 22;
23620
+ keyId = psshInfo.data.subarray(offset, offset + 16);
23621
+ } else if (keySystemDomain === KeySystems.PLAYREADY) {
23622
+ keyId = parsePlayReadyWRM(psshInfo.data);
23623
+ }
23660
23624
  }
23661
23625
  }
23662
23626
  if (!keySystemDomain || !keyId) {
@@ -23760,7 +23724,13 @@ class EMEController extends Logger {
23760
23724
  if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
23761
23725
  return widevineLicenseUrl;
23762
23726
  }
23763
- throw new Error(`no license server URL configured for key-system "${keySystem}"`);
23727
+ }
23728
+ getLicenseServerUrlOrThrow(keySystem) {
23729
+ const url = this.getLicenseServerUrl(keySystem);
23730
+ if (url === undefined) {
23731
+ throw new Error(`no license server URL configured for key-system "${keySystem}"`);
23732
+ }
23733
+ return url;
23764
23734
  }
23765
23735
  getServerCertificateUrl(keySystem) {
23766
23736
  const {
@@ -24288,7 +24258,7 @@ class EMEController extends Logger {
24288
24258
  requestLicense(keySessionContext, licenseChallenge) {
24289
24259
  const keyLoadPolicy = this.config.keyLoadPolicy.default;
24290
24260
  return new Promise((resolve, reject) => {
24291
- const url = this.getLicenseServerUrl(keySessionContext.keySystem);
24261
+ const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
24292
24262
  this.log(`Sending license request to URL: ${url}`);
24293
24263
  const xhr = new XMLHttpRequest();
24294
24264
  xhr.responseType = 'arraybuffer';