hls.js 1.5.18 → 1.5.20

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
@@ -411,7 +411,7 @@ function enableLogs(debugConfig, id) {
411
411
  // Some browsers don't allow to use bind on console object anyway
412
412
  // fallback to default if needed
413
413
  try {
414
- exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.18"}`);
414
+ exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.20"}`);
415
415
  } catch (e) {
416
416
  exportedLogger = fakeLogger;
417
417
  }
@@ -1157,6 +1157,27 @@ function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCod
1157
1157
  };
1158
1158
  return [baseConfig];
1159
1159
  }
1160
+ function parsePlayReadyWRM(keyBytes) {
1161
+ const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);
1162
+ const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
1163
+
1164
+ // Parse Playready WRMHeader XML
1165
+ const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
1166
+ const parser = new DOMParser();
1167
+ const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
1168
+ const keyData = xmlDoc.getElementsByTagName('KID')[0];
1169
+ if (keyData) {
1170
+ const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
1171
+ if (keyId) {
1172
+ const keyIdArray = base64Decode(keyId).subarray(0, 16);
1173
+ // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
1174
+ // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
1175
+ changeEndianness(keyIdArray);
1176
+ return keyIdArray;
1177
+ }
1178
+ }
1179
+ return null;
1180
+ }
1160
1181
 
1161
1182
  function sliceUint8(array, start, end) {
1162
1183
  // @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
@@ -2773,6 +2794,8 @@ class LevelKey {
2773
2794
  if (keyBytes) {
2774
2795
  switch (this.keyFormat) {
2775
2796
  case KeySystemFormats.WIDEVINE:
2797
+ // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
2798
+ // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
2776
2799
  this.pssh = keyBytes;
2777
2800
  // In case of widevine keyID is embedded in PSSH box. Read Key ID.
2778
2801
  if (keyBytes.length >= 22) {
@@ -2782,25 +2805,11 @@ class LevelKey {
2782
2805
  case KeySystemFormats.PLAYREADY:
2783
2806
  {
2784
2807
  const PlayReadyKeySystemUUID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);
2808
+
2809
+ // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
2810
+ // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
2785
2811
  this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
2786
- const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);
2787
- const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
2788
-
2789
- // Parse Playready WRMHeader XML
2790
- const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
2791
- const parser = new DOMParser();
2792
- const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
2793
- const keyData = xmlDoc.getElementsByTagName('KID')[0];
2794
- if (keyData) {
2795
- const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
2796
- if (keyId) {
2797
- const keyIdArray = base64Decode(keyId).subarray(0, 16);
2798
- // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
2799
- // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
2800
- changeEndianness(keyIdArray);
2801
- this.keyId = keyIdArray;
2802
- }
2803
- }
2812
+ this.keyId = parsePlayReadyWRM(keyBytes);
2804
2813
  break;
2805
2814
  }
2806
2815
  default:
@@ -5250,15 +5259,16 @@ function mergeDetails(oldDetails, newDetails) {
5250
5259
  delete oldDetails.fragmentHint.endPTS;
5251
5260
  }
5252
5261
  // check if old/new playlists have fragments in common
5253
- // loop through overlapping SN and update startPTS , cc, and duration if any found
5254
- let ccOffset = 0;
5262
+ // loop through overlapping SN and update startPTS, cc, and duration if any found
5255
5263
  let PTSFrag;
5256
- mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag) => {
5257
- if (oldFrag.relurl) {
5258
- // Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts.
5259
- // It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end
5260
- // of the playlist.
5261
- ccOffset = oldFrag.cc - newFrag.cc;
5264
+ mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag, newFragIndex, newFragments) => {
5265
+ if (newDetails.skippedSegments) {
5266
+ if (newFrag.cc !== oldFrag.cc) {
5267
+ const ccOffset = oldFrag.cc - newFrag.cc;
5268
+ for (let i = newFragIndex; i < newFragments.length; i++) {
5269
+ newFragments[i].cc += ccOffset;
5270
+ }
5271
+ }
5262
5272
  }
5263
5273
  if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
5264
5274
  newFrag.start = newFrag.startPTS = oldFrag.startPTS;
@@ -5283,8 +5293,9 @@ function mergeDetails(oldDetails, newDetails) {
5283
5293
  currentInitSegment = oldFrag.initSegment;
5284
5294
  }
5285
5295
  });
5296
+ const newFragments = newDetails.fragments;
5286
5297
  if (currentInitSegment) {
5287
- const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
5298
+ const fragmentsToCheck = newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint) : newFragments;
5288
5299
  fragmentsToCheck.forEach(frag => {
5289
5300
  var _currentInitSegment;
5290
5301
  if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) {
@@ -5293,27 +5304,20 @@ function mergeDetails(oldDetails, newDetails) {
5293
5304
  });
5294
5305
  }
5295
5306
  if (newDetails.skippedSegments) {
5296
- newDetails.deltaUpdateFailed = newDetails.fragments.some(frag => !frag);
5307
+ newDetails.deltaUpdateFailed = newFragments.some(frag => !frag);
5297
5308
  if (newDetails.deltaUpdateFailed) {
5298
5309
  logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist');
5299
5310
  for (let i = newDetails.skippedSegments; i--;) {
5300
- newDetails.fragments.shift();
5311
+ newFragments.shift();
5312
+ }
5313
+ newDetails.startSN = newFragments[0].sn;
5314
+ } else {
5315
+ if (newDetails.canSkipDateRanges) {
5316
+ newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges);
5301
5317
  }
5302
- newDetails.startSN = newDetails.fragments[0].sn;
5303
- newDetails.startCC = newDetails.fragments[0].cc;
5304
- } else if (newDetails.canSkipDateRanges) {
5305
- newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges);
5306
- }
5307
- }
5308
- const newFragments = newDetails.fragments;
5309
- if (ccOffset) {
5310
- logger.warn('discontinuity sliding from playlist, take drift into account');
5311
- for (let i = 0; i < newFragments.length; i++) {
5312
- newFragments[i].cc += ccOffset;
5313
5318
  }
5314
- }
5315
- if (newDetails.skippedSegments) {
5316
5319
  newDetails.startCC = newDetails.fragments[0].cc;
5320
+ newDetails.endCC = newFragments[newFragments.length - 1].cc;
5317
5321
  }
5318
5322
 
5319
5323
  // Merge parts
@@ -5397,7 +5401,7 @@ function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
5397
5401
  newFrag = newDetails.fragments[i] = oldFrag;
5398
5402
  }
5399
5403
  if (oldFrag && newFrag) {
5400
- intersectionFn(oldFrag, newFrag);
5404
+ intersectionFn(oldFrag, newFrag, i, newFrags);
5401
5405
  }
5402
5406
  }
5403
5407
  }
@@ -6819,11 +6823,10 @@ function matchesOption(option, track, matchPredicate) {
6819
6823
  name,
6820
6824
  lang,
6821
6825
  assocLang,
6822
- characteristics,
6823
6826
  default: isDefault
6824
6827
  } = option;
6825
6828
  const forced = option.forced;
6826
- return (groupId === undefined || track.groupId === groupId) && (name === undefined || track.name === name) && (lang === undefined || track.lang === lang) && (lang === undefined || track.assocLang === assocLang) && (isDefault === undefined || track.default === isDefault) && (forced === undefined || track.forced === forced) && (characteristics === undefined || characteristicsMatch(characteristics, track.characteristics)) && (matchPredicate === undefined || matchPredicate(option, track));
6829
+ return (groupId === undefined || track.groupId === groupId) && (name === undefined || track.name === name) && (lang === undefined || track.lang === lang) && (lang === undefined || track.assocLang === assocLang) && (isDefault === undefined || track.default === isDefault) && (forced === undefined || track.forced === forced) && (!('characteristics' in option) || characteristicsMatch(option.characteristics || '', track.characteristics)) && (matchPredicate === undefined || matchPredicate(option, track));
6827
6830
  }
6828
6831
  function characteristicsMatch(characteristicsA, characteristicsB = '') {
6829
6832
  const arrA = characteristicsA.split(',');
@@ -15697,30 +15700,6 @@ class TransmuxerInterface {
15697
15700
  }
15698
15701
  }
15699
15702
 
15700
- function subtitleOptionsIdentical(trackList1, trackList2) {
15701
- if (trackList1.length !== trackList2.length) {
15702
- return false;
15703
- }
15704
- for (let i = 0; i < trackList1.length; i++) {
15705
- if (!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) {
15706
- return false;
15707
- }
15708
- }
15709
- return true;
15710
- }
15711
- function mediaAttributesIdentical(attrs1, attrs2, customAttributes) {
15712
- // Media options with the same rendition ID must be bit identical
15713
- const stableRenditionId = attrs1['STABLE-RENDITION-ID'];
15714
- if (stableRenditionId && !customAttributes) {
15715
- return stableRenditionId === attrs2['STABLE-RENDITION-ID'];
15716
- }
15717
- // When rendition ID is not present, compare attributes
15718
- return !(customAttributes || ['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED', 'ASSOC-LANGUAGE']).some(subtitleAttribute => attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]);
15719
- }
15720
- function subtitleTrackMatchesTextTrack(subtitleTrack, textTrack) {
15721
- return textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && (!textTrack.language || textTrack.language.toLowerCase() === (subtitleTrack.lang || '').toLowerCase());
15722
- }
15723
-
15724
15703
  const TICK_INTERVAL$2 = 100; // how often to tick in ms
15725
15704
 
15726
15705
  class AudioStreamController extends BaseStreamController {
@@ -15969,11 +15948,7 @@ class AudioStreamController extends BaseStreamController {
15969
15948
  if (bufferInfo === null) {
15970
15949
  return;
15971
15950
  }
15972
- const {
15973
- bufferedTrack,
15974
- switchingTrack
15975
- } = this;
15976
- if (!switchingTrack && this._streamEnded(bufferInfo, trackDetails)) {
15951
+ if (!this.switchingTrack && this._streamEnded(bufferInfo, trackDetails)) {
15977
15952
  hls.trigger(Events.BUFFER_EOS, {
15978
15953
  type: 'audio'
15979
15954
  });
@@ -15985,13 +15960,10 @@ class AudioStreamController extends BaseStreamController {
15985
15960
  const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len);
15986
15961
  const fragments = trackDetails.fragments;
15987
15962
  const start = fragments[0].start;
15988
- let targetBufferTime = this.flushing ? this.getLoadPosition() : bufferInfo.end;
15989
- if (switchingTrack && media) {
15990
- const pos = this.getLoadPosition();
15991
- // STABLE
15992
- if (bufferedTrack && !mediaAttributesIdentical(switchingTrack.attrs, bufferedTrack.attrs)) {
15993
- targetBufferTime = pos;
15994
- }
15963
+ const loadPosition = this.getLoadPosition();
15964
+ const targetBufferTime = this.flushing ? loadPosition : bufferInfo.end;
15965
+ if (this.switchingTrack && media) {
15966
+ const pos = loadPosition;
15995
15967
  // if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime
15996
15968
  if (trackDetails.PTSKnown && pos < start) {
15997
15969
  // if everything is buffered from pos to start or if audio buffer upfront, let's seek to start
@@ -16003,7 +15975,7 @@ class AudioStreamController extends BaseStreamController {
16003
15975
  }
16004
15976
 
16005
15977
  // if buffer length is less than maxBufLen, or near the end, find a fragment to load
16006
- if (bufferLen >= maxBufLen && !switchingTrack && targetBufferTime < fragments[fragments.length - 1].start) {
15978
+ if (bufferLen >= maxBufLen && !this.switchingTrack && targetBufferTime < fragments[fragments.length - 1].start) {
16007
15979
  return;
16008
15980
  }
16009
15981
  let frag = this.getNextFragment(targetBufferTime, trackDetails);
@@ -16484,16 +16456,27 @@ class AudioStreamController extends BaseStreamController {
16484
16456
  }
16485
16457
  }
16486
16458
  flushAudioIfNeeded(switchingTrack) {
16487
- const {
16488
- media,
16489
- bufferedTrack
16490
- } = this;
16491
- const bufferedAttributes = bufferedTrack == null ? void 0 : bufferedTrack.attrs;
16492
- const switchAttributes = switchingTrack.attrs;
16493
- if (media && bufferedAttributes && (bufferedAttributes.CHANNELS !== switchAttributes.CHANNELS || bufferedTrack.name !== switchingTrack.name || bufferedTrack.lang !== switchingTrack.lang)) {
16494
- this.log('Switching audio track : flushing all audio');
16495
- super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
16496
- this.bufferedTrack = null;
16459
+ if (this.media && this.bufferedTrack) {
16460
+ const {
16461
+ name,
16462
+ lang,
16463
+ assocLang,
16464
+ characteristics,
16465
+ audioCodec,
16466
+ channels
16467
+ } = this.bufferedTrack;
16468
+ if (!matchesOption({
16469
+ name,
16470
+ lang,
16471
+ assocLang,
16472
+ characteristics,
16473
+ audioCodec,
16474
+ channels
16475
+ }, switchingTrack, audioMatchPredicate)) {
16476
+ this.log('Switching audio track : flushing all audio');
16477
+ super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
16478
+ this.bufferedTrack = null;
16479
+ }
16497
16480
  }
16498
16481
  }
16499
16482
  completeAudioSwitch(switchingTrack) {
@@ -16507,6 +16490,30 @@ class AudioStreamController extends BaseStreamController {
16507
16490
  }
16508
16491
  }
16509
16492
 
16493
+ function subtitleOptionsIdentical(trackList1, trackList2) {
16494
+ if (trackList1.length !== trackList2.length) {
16495
+ return false;
16496
+ }
16497
+ for (let i = 0; i < trackList1.length; i++) {
16498
+ if (!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) {
16499
+ return false;
16500
+ }
16501
+ }
16502
+ return true;
16503
+ }
16504
+ function mediaAttributesIdentical(attrs1, attrs2, customAttributes) {
16505
+ // Media options with the same rendition ID must be bit identical
16506
+ const stableRenditionId = attrs1['STABLE-RENDITION-ID'];
16507
+ if (stableRenditionId && !customAttributes) {
16508
+ return stableRenditionId === attrs2['STABLE-RENDITION-ID'];
16509
+ }
16510
+ // When rendition ID is not present, compare attributes
16511
+ return !(customAttributes || ['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED', 'ASSOC-LANGUAGE']).some(subtitleAttribute => attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]);
16512
+ }
16513
+ function subtitleTrackMatchesTextTrack(subtitleTrack, textTrack) {
16514
+ return textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && (!textTrack.language || textTrack.language.toLowerCase() === (subtitleTrack.lang || '').toLowerCase());
16515
+ }
16516
+
16510
16517
  class AudioTrackController extends BasePlaylistController {
16511
16518
  constructor(hls) {
16512
16519
  super(hls, '[audio-track-controller]');
@@ -22012,12 +22019,148 @@ class EMEController {
22012
22019
  this.mediaKeySessions = [];
22013
22020
  this.keyIdToKeySessionPromise = {};
22014
22021
  this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
22015
- this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
22016
- this.onWaitingForKey = this._onWaitingForKey.bind(this);
22017
22022
  this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
22018
22023
  this.log = logger.log.bind(logger, LOGGER_PREFIX);
22019
22024
  this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
22020
22025
  this.error = logger.error.bind(logger, LOGGER_PREFIX);
22026
+ this.onMediaEncrypted = event => {
22027
+ const {
22028
+ initDataType,
22029
+ initData
22030
+ } = event;
22031
+ const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
22032
+ this.debug(logMessage);
22033
+
22034
+ // Ignore event when initData is null
22035
+ if (initData === null) {
22036
+ return;
22037
+ }
22038
+ if (!this.keyFormatPromise) {
22039
+ let keySystems = Object.keys(this.keySystemAccessPromises);
22040
+ if (!keySystems.length) {
22041
+ keySystems = getKeySystemsForConfig(this.config);
22042
+ }
22043
+ const keyFormats = keySystems.map(keySystemDomainToKeySystemFormat).filter(k => !!k);
22044
+ this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
22045
+ }
22046
+ this.keyFormatPromise.then(keySystemFormat => {
22047
+ const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
22048
+ let keyId;
22049
+ let keySystemDomain;
22050
+ if (initDataType === 'sinf') {
22051
+ if (keySystem !== KeySystems.FAIRPLAY) {
22052
+ this.warn(`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
22053
+ return;
22054
+ }
22055
+ // Match sinf keyId to playlist skd://keyId=
22056
+ const json = bin2str(new Uint8Array(initData));
22057
+ try {
22058
+ const sinf = base64Decode(JSON.parse(json).sinf);
22059
+ const tenc = parseSinf(sinf);
22060
+ if (!tenc) {
22061
+ throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
22062
+ }
22063
+ keyId = tenc.subarray(8, 24);
22064
+ keySystemDomain = KeySystems.FAIRPLAY;
22065
+ } catch (error) {
22066
+ this.warn(`${logMessage} Failed to parse sinf: ${error}`);
22067
+ return;
22068
+ }
22069
+ } else {
22070
+ if (keySystem !== KeySystems.WIDEVINE && keySystem !== KeySystems.PLAYREADY) {
22071
+ this.warn(`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
22072
+ return;
22073
+ }
22074
+ // Support Widevine/PlayReady clear-lead key-session creation (otherwise depend on playlist keys)
22075
+ const psshResults = parseMultiPssh(initData);
22076
+ const psshInfos = psshResults.filter(pssh => !!pssh.systemId && keySystemIdToKeySystemDomain(pssh.systemId) === keySystem);
22077
+ if (psshInfos.length > 1) {
22078
+ this.warn(`${logMessage} Using first of ${psshInfos.length} pssh found for selected key-system ${keySystem}`);
22079
+ }
22080
+ const psshInfo = psshInfos[0];
22081
+ if (!psshInfo) {
22082
+ if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
22083
+ this.warn(`${logMessage} contains incomplete or invalid pssh data`);
22084
+ } else {
22085
+ this.log(`ignoring ${logMessage} for ${psshResults.map(pssh => keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`);
22086
+ }
22087
+ return;
22088
+ }
22089
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22090
+ if (psshInfo.version === 0 && psshInfo.data) {
22091
+ if (keySystemDomain === KeySystems.WIDEVINE) {
22092
+ const offset = psshInfo.data.length - 22;
22093
+ keyId = psshInfo.data.subarray(offset, offset + 16);
22094
+ } else if (keySystemDomain === KeySystems.PLAYREADY) {
22095
+ keyId = parsePlayReadyWRM(psshInfo.data);
22096
+ }
22097
+ }
22098
+ }
22099
+ if (!keySystemDomain || !keyId) {
22100
+ this.log(`Unable to handle ${logMessage} with key-system ${keySystem}`);
22101
+ return;
22102
+ }
22103
+ const keyIdHex = Hex.hexDump(keyId);
22104
+ const {
22105
+ keyIdToKeySessionPromise,
22106
+ mediaKeySessions
22107
+ } = this;
22108
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22109
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22110
+ // Match playlist key
22111
+ const keyContext = mediaKeySessions[i];
22112
+ const decryptdata = keyContext.decryptdata;
22113
+ if (!decryptdata.keyId) {
22114
+ continue;
22115
+ }
22116
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22117
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22118
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22119
+ if (decryptdata.pssh) {
22120
+ break;
22121
+ }
22122
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22123
+ decryptdata.pssh = new Uint8Array(initData);
22124
+ decryptdata.keyId = keyId;
22125
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22126
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22127
+ });
22128
+ keySessionContextPromise.catch(error => this.handleError(error));
22129
+ break;
22130
+ }
22131
+ }
22132
+ if (!keySessionContextPromise) {
22133
+ if (keySystemDomain !== keySystem) {
22134
+ this.log(`Ignoring "${logMessage}" with ${keySystemDomain} init data for selected key-system ${keySystem}`);
22135
+ return;
22136
+ }
22137
+ // "Clear-lead" (misc key not encountered in playlist)
22138
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22139
+ keySystem,
22140
+ mediaKeys
22141
+ }) => {
22142
+ var _keySystemToKeySystem;
22143
+ this.throwIfDestroyed();
22144
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22145
+ decryptdata.pssh = new Uint8Array(initData);
22146
+ decryptdata.keyId = keyId;
22147
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22148
+ this.throwIfDestroyed();
22149
+ const keySessionContext = this.createMediaKeySessionContext({
22150
+ decryptdata,
22151
+ keySystem,
22152
+ mediaKeys
22153
+ });
22154
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22155
+ });
22156
+ });
22157
+ keySessionContextPromise.catch(error => this.handleError(error));
22158
+ }
22159
+ });
22160
+ };
22161
+ this.onWaitingForKey = event => {
22162
+ this.log(`"${event.type}" event`);
22163
+ };
22021
22164
  this.hls = hls;
22022
22165
  this.config = hls.config;
22023
22166
  this.registerListeners();
@@ -22031,9 +22174,9 @@ class EMEController {
22031
22174
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
22032
22175
  config.drmSystems = config.drmSystemOptions = {};
22033
22176
  // @ts-ignore
22034
- this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22177
+ this.hls = this.config = this.keyIdToKeySessionPromise = null;
22035
22178
  // @ts-ignore
22036
- this.config = null;
22179
+ this.onMediaEncrypted = this.onWaitingForKey = null;
22037
22180
  }
22038
22181
  registerListeners() {
22039
22182
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -22061,7 +22204,13 @@ class EMEController {
22061
22204
  if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
22062
22205
  return widevineLicenseUrl;
22063
22206
  }
22064
- throw new Error(`no license server URL configured for key-system "${keySystem}"`);
22207
+ }
22208
+ getLicenseServerUrlOrThrow(keySystem) {
22209
+ const url = this.getLicenseServerUrl(keySystem);
22210
+ if (url === undefined) {
22211
+ throw new Error(`no license server URL configured for key-system "${keySystem}"`);
22212
+ }
22213
+ return url;
22065
22214
  }
22066
22215
  getServerCertificateUrl(keySystem) {
22067
22216
  const {
@@ -22297,111 +22446,6 @@ class EMEController {
22297
22446
  }
22298
22447
  return this.attemptKeySystemAccess(keySystemsToAttempt);
22299
22448
  }
22300
- _onMediaEncrypted(event) {
22301
- const {
22302
- initDataType,
22303
- initData
22304
- } = event;
22305
- const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
22306
- this.debug(logMessage);
22307
-
22308
- // Ignore event when initData is null
22309
- if (initData === null) {
22310
- return;
22311
- }
22312
- let keyId;
22313
- let keySystemDomain;
22314
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22315
- // Match sinf keyId to playlist skd://keyId=
22316
- const json = bin2str(new Uint8Array(initData));
22317
- try {
22318
- const sinf = base64Decode(JSON.parse(json).sinf);
22319
- const tenc = parseSinf(new Uint8Array(sinf));
22320
- if (!tenc) {
22321
- throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
22322
- }
22323
- keyId = tenc.subarray(8, 24);
22324
- keySystemDomain = KeySystems.FAIRPLAY;
22325
- } catch (error) {
22326
- this.warn(`${logMessage} Failed to parse sinf: ${error}`);
22327
- return;
22328
- }
22329
- } else {
22330
- // Support Widevine clear-lead key-session creation (otherwise depend on playlist keys)
22331
- const psshResults = parseMultiPssh(initData);
22332
- const psshInfo = psshResults.filter(pssh => pssh.systemId === KeySystemIds.WIDEVINE)[0];
22333
- if (!psshInfo) {
22334
- if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
22335
- this.warn(`${logMessage} contains incomplete or invalid pssh data`);
22336
- } else {
22337
- this.log(`ignoring ${logMessage} for ${psshResults.map(pssh => keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`);
22338
- }
22339
- return;
22340
- }
22341
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22342
- if (psshInfo.version === 0 && psshInfo.data) {
22343
- const offset = psshInfo.data.length - 22;
22344
- keyId = psshInfo.data.subarray(offset, offset + 16);
22345
- }
22346
- }
22347
- if (!keySystemDomain || !keyId) {
22348
- return;
22349
- }
22350
- const keyIdHex = Hex.hexDump(keyId);
22351
- const {
22352
- keyIdToKeySessionPromise,
22353
- mediaKeySessions
22354
- } = this;
22355
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22356
- for (let i = 0; i < mediaKeySessions.length; i++) {
22357
- // Match playlist key
22358
- const keyContext = mediaKeySessions[i];
22359
- const decryptdata = keyContext.decryptdata;
22360
- if (!decryptdata.keyId) {
22361
- continue;
22362
- }
22363
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22364
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22365
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22366
- if (decryptdata.pssh) {
22367
- break;
22368
- }
22369
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22370
- decryptdata.pssh = new Uint8Array(initData);
22371
- decryptdata.keyId = keyId;
22372
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22373
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22374
- });
22375
- break;
22376
- }
22377
- }
22378
- if (!keySessionContextPromise) {
22379
- // Clear-lead key (not encountered in playlist)
22380
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22381
- keySystem,
22382
- mediaKeys
22383
- }) => {
22384
- var _keySystemToKeySystem;
22385
- this.throwIfDestroyed();
22386
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22387
- decryptdata.pssh = new Uint8Array(initData);
22388
- decryptdata.keyId = keyId;
22389
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22390
- this.throwIfDestroyed();
22391
- const keySessionContext = this.createMediaKeySessionContext({
22392
- decryptdata,
22393
- keySystem,
22394
- mediaKeys
22395
- });
22396
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22397
- });
22398
- });
22399
- }
22400
- keySessionContextPromise.catch(error => this.handleError(error));
22401
- }
22402
- _onWaitingForKey(event) {
22403
- this.log(`"${event.type}" event`);
22404
- }
22405
22449
  attemptSetMediaKeys(keySystem, mediaKeys) {
22406
22450
  const queue = this.setMediaKeysQueue.slice();
22407
22451
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -22694,7 +22738,7 @@ class EMEController {
22694
22738
  requestLicense(keySessionContext, licenseChallenge) {
22695
22739
  const keyLoadPolicy = this.config.keyLoadPolicy.default;
22696
22740
  return new Promise((resolve, reject) => {
22697
- const url = this.getLicenseServerUrl(keySessionContext.keySystem);
22741
+ const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
22698
22742
  this.log(`Sending license request to URL: ${url}`);
22699
22743
  const xhr = new XMLHttpRequest();
22700
22744
  xhr.responseType = 'arraybuffer';
@@ -22764,6 +22808,8 @@ class EMEController {
22764
22808
 
22765
22809
  // keep reference of media
22766
22810
  this.media = media;
22811
+ media.removeEventListener('encrypted', this.onMediaEncrypted);
22812
+ media.removeEventListener('waitingforkey', this.onWaitingForKey);
22767
22813
  media.addEventListener('encrypted', this.onMediaEncrypted);
22768
22814
  media.addEventListener('waitingforkey', this.onWaitingForKey);
22769
22815
  }
@@ -26113,7 +26159,7 @@ class KeyLoader {
26113
26159
  }
26114
26160
  }
26115
26161
  load(frag) {
26116
- if (!frag.decryptdata && frag.encrypted && this.emeController) {
26162
+ if (!frag.decryptdata && frag.encrypted && this.emeController && this.config.emeEnabled) {
26117
26163
  // Multiple keys, but none selected, resolve in eme-controller
26118
26164
  return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => {
26119
26165
  return this.loadInternal(frag, keySystemFormat);
@@ -27801,7 +27847,7 @@ class Hls {
27801
27847
  * Get the video-dev/hls.js package version.
27802
27848
  */
27803
27849
  static get version() {
27804
- return "1.5.18";
27850
+ return "1.5.20";
27805
27851
  }
27806
27852
 
27807
27853
  /**