hls.js 1.5.17 → 1.5.19

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.17"}`);
414
+ exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.19"}`);
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
  }
@@ -9168,6 +9172,7 @@ class BaseStreamController extends TaskLoop {
9168
9172
  this.startFragRequested = false;
9169
9173
  this.decrypter = void 0;
9170
9174
  this.initPTS = [];
9175
+ this.buffering = true;
9171
9176
  this.onvseeking = null;
9172
9177
  this.onvended = null;
9173
9178
  this.logPrefix = '';
@@ -9207,6 +9212,12 @@ class BaseStreamController extends TaskLoop {
9207
9212
  this.clearNextTick();
9208
9213
  this.state = State.STOPPED;
9209
9214
  }
9215
+ pauseBuffering() {
9216
+ this.buffering = false;
9217
+ }
9218
+ resumeBuffering() {
9219
+ this.buffering = true;
9220
+ }
9210
9221
  _streamEnded(bufferInfo, levelDetails) {
9211
9222
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
9212
9223
  // of nothing loading/loaded return false
@@ -13624,19 +13635,23 @@ class MP4Remuxer {
13624
13635
  this.videoTrackConfig = undefined;
13625
13636
  }
13626
13637
  getVideoStartPts(videoSamples) {
13638
+ // Get the minimum PTS value relative to the first sample's PTS, normalized for 33-bit wrapping
13627
13639
  let rolloverDetected = false;
13640
+ const firstPts = videoSamples[0].pts;
13628
13641
  const startPTS = videoSamples.reduce((minPTS, sample) => {
13629
- const delta = sample.pts - minPTS;
13642
+ let pts = sample.pts;
13643
+ let delta = pts - minPTS;
13630
13644
  if (delta < -4294967296) {
13631
13645
  // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation
13632
13646
  rolloverDetected = true;
13633
- return normalizePts(minPTS, sample.pts);
13634
- } else if (delta > 0) {
13647
+ pts = normalizePts(pts, firstPts);
13648
+ delta = pts - minPTS;
13649
+ }
13650
+ if (delta > 0) {
13635
13651
  return minPTS;
13636
- } else {
13637
- return sample.pts;
13638
13652
  }
13639
- }, videoSamples[0].pts);
13653
+ return pts;
13654
+ }, firstPts);
13640
13655
  if (rolloverDetected) {
13641
13656
  logger.debug('PTS rollover detected');
13642
13657
  }
@@ -15934,12 +15949,13 @@ class AudioStreamController extends BaseStreamController {
15934
15949
  } = this;
15935
15950
  const config = hls.config;
15936
15951
 
15937
- // 1. if video not attached AND
15952
+ // 1. if buffering is suspended
15953
+ // 2. if video not attached AND
15938
15954
  // start fragment already requested OR start frag prefetch not enabled
15939
- // 2. if tracks or track not loaded and selected
15955
+ // 3. if tracks or track not loaded and selected
15940
15956
  // then exit loop
15941
15957
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
15942
- if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
15958
+ if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
15943
15959
  return;
15944
15960
  }
15945
15961
  const levelInfo = levels[trackId];
@@ -17993,6 +18009,7 @@ class BufferController {
17993
18009
  this.resetBuffer(type);
17994
18010
  });
17995
18011
  this._initSourceBuffer();
18012
+ this.hls.resumeBuffering();
17996
18013
  }
17997
18014
  resetBuffer(type) {
17998
18015
  const sb = this.sourceBuffer[type];
@@ -21999,12 +22016,148 @@ class EMEController {
21999
22016
  this.mediaKeySessions = [];
22000
22017
  this.keyIdToKeySessionPromise = {};
22001
22018
  this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
22002
- this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
22003
- this.onWaitingForKey = this._onWaitingForKey.bind(this);
22004
22019
  this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
22005
22020
  this.log = logger.log.bind(logger, LOGGER_PREFIX);
22006
22021
  this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
22007
22022
  this.error = logger.error.bind(logger, LOGGER_PREFIX);
22023
+ this.onMediaEncrypted = event => {
22024
+ const {
22025
+ initDataType,
22026
+ initData
22027
+ } = event;
22028
+ const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
22029
+ this.debug(logMessage);
22030
+
22031
+ // Ignore event when initData is null
22032
+ if (initData === null) {
22033
+ return;
22034
+ }
22035
+ if (!this.keyFormatPromise) {
22036
+ let keySystems = Object.keys(this.keySystemAccessPromises);
22037
+ if (!keySystems.length) {
22038
+ keySystems = getKeySystemsForConfig(this.config);
22039
+ }
22040
+ const keyFormats = keySystems.map(keySystemDomainToKeySystemFormat).filter(k => !!k);
22041
+ this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
22042
+ }
22043
+ this.keyFormatPromise.then(keySystemFormat => {
22044
+ const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
22045
+ let keyId;
22046
+ let keySystemDomain;
22047
+ if (initDataType === 'sinf') {
22048
+ if (keySystem !== KeySystems.FAIRPLAY) {
22049
+ this.warn(`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
22050
+ return;
22051
+ }
22052
+ // Match sinf keyId to playlist skd://keyId=
22053
+ const json = bin2str(new Uint8Array(initData));
22054
+ try {
22055
+ const sinf = base64Decode(JSON.parse(json).sinf);
22056
+ const tenc = parseSinf(sinf);
22057
+ if (!tenc) {
22058
+ throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
22059
+ }
22060
+ keyId = tenc.subarray(8, 24);
22061
+ keySystemDomain = KeySystems.FAIRPLAY;
22062
+ } catch (error) {
22063
+ this.warn(`${logMessage} Failed to parse sinf: ${error}`);
22064
+ return;
22065
+ }
22066
+ } else {
22067
+ if (keySystem !== KeySystems.WIDEVINE && keySystem !== KeySystems.PLAYREADY) {
22068
+ this.warn(`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
22069
+ return;
22070
+ }
22071
+ // Support Widevine/PlayReady clear-lead key-session creation (otherwise depend on playlist keys)
22072
+ const psshResults = parseMultiPssh(initData);
22073
+ const psshInfos = psshResults.filter(pssh => !!pssh.systemId && keySystemIdToKeySystemDomain(pssh.systemId) === keySystem);
22074
+ if (psshInfos.length > 1) {
22075
+ this.warn(`${logMessage} Using first of ${psshInfos.length} pssh found for selected key-system ${keySystem}`);
22076
+ }
22077
+ const psshInfo = psshInfos[0];
22078
+ if (!psshInfo) {
22079
+ if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
22080
+ this.warn(`${logMessage} contains incomplete or invalid pssh data`);
22081
+ } else {
22082
+ this.log(`ignoring ${logMessage} for ${psshResults.map(pssh => keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`);
22083
+ }
22084
+ return;
22085
+ }
22086
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22087
+ if (psshInfo.version === 0 && psshInfo.data) {
22088
+ if (keySystemDomain === KeySystems.WIDEVINE) {
22089
+ const offset = psshInfo.data.length - 22;
22090
+ keyId = psshInfo.data.subarray(offset, offset + 16);
22091
+ } else if (keySystemDomain === KeySystems.PLAYREADY) {
22092
+ keyId = parsePlayReadyWRM(psshInfo.data);
22093
+ }
22094
+ }
22095
+ }
22096
+ if (!keySystemDomain || !keyId) {
22097
+ this.log(`Unable to handle ${logMessage} with key-system ${keySystem}`);
22098
+ return;
22099
+ }
22100
+ const keyIdHex = Hex.hexDump(keyId);
22101
+ const {
22102
+ keyIdToKeySessionPromise,
22103
+ mediaKeySessions
22104
+ } = this;
22105
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22106
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22107
+ // Match playlist key
22108
+ const keyContext = mediaKeySessions[i];
22109
+ const decryptdata = keyContext.decryptdata;
22110
+ if (!decryptdata.keyId) {
22111
+ continue;
22112
+ }
22113
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22114
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22115
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22116
+ if (decryptdata.pssh) {
22117
+ break;
22118
+ }
22119
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22120
+ decryptdata.pssh = new Uint8Array(initData);
22121
+ decryptdata.keyId = keyId;
22122
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22123
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22124
+ });
22125
+ keySessionContextPromise.catch(error => this.handleError(error));
22126
+ break;
22127
+ }
22128
+ }
22129
+ if (!keySessionContextPromise) {
22130
+ if (keySystemDomain !== keySystem) {
22131
+ this.log(`Ignoring "${logMessage}" with ${keySystemDomain} init data for selected key-system ${keySystem}`);
22132
+ return;
22133
+ }
22134
+ // "Clear-lead" (misc key not encountered in playlist)
22135
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22136
+ keySystem,
22137
+ mediaKeys
22138
+ }) => {
22139
+ var _keySystemToKeySystem;
22140
+ this.throwIfDestroyed();
22141
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22142
+ decryptdata.pssh = new Uint8Array(initData);
22143
+ decryptdata.keyId = keyId;
22144
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22145
+ this.throwIfDestroyed();
22146
+ const keySessionContext = this.createMediaKeySessionContext({
22147
+ decryptdata,
22148
+ keySystem,
22149
+ mediaKeys
22150
+ });
22151
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22152
+ });
22153
+ });
22154
+ keySessionContextPromise.catch(error => this.handleError(error));
22155
+ }
22156
+ });
22157
+ };
22158
+ this.onWaitingForKey = event => {
22159
+ this.log(`"${event.type}" event`);
22160
+ };
22008
22161
  this.hls = hls;
22009
22162
  this.config = hls.config;
22010
22163
  this.registerListeners();
@@ -22018,9 +22171,9 @@ class EMEController {
22018
22171
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
22019
22172
  config.drmSystems = config.drmSystemOptions = {};
22020
22173
  // @ts-ignore
22021
- this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22174
+ this.hls = this.config = this.keyIdToKeySessionPromise = null;
22022
22175
  // @ts-ignore
22023
- this.config = null;
22176
+ this.onMediaEncrypted = this.onWaitingForKey = null;
22024
22177
  }
22025
22178
  registerListeners() {
22026
22179
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -22048,7 +22201,13 @@ class EMEController {
22048
22201
  if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
22049
22202
  return widevineLicenseUrl;
22050
22203
  }
22051
- throw new Error(`no license server URL configured for key-system "${keySystem}"`);
22204
+ }
22205
+ getLicenseServerUrlOrThrow(keySystem) {
22206
+ const url = this.getLicenseServerUrl(keySystem);
22207
+ if (url === undefined) {
22208
+ throw new Error(`no license server URL configured for key-system "${keySystem}"`);
22209
+ }
22210
+ return url;
22052
22211
  }
22053
22212
  getServerCertificateUrl(keySystem) {
22054
22213
  const {
@@ -22284,111 +22443,6 @@ class EMEController {
22284
22443
  }
22285
22444
  return this.attemptKeySystemAccess(keySystemsToAttempt);
22286
22445
  }
22287
- _onMediaEncrypted(event) {
22288
- const {
22289
- initDataType,
22290
- initData
22291
- } = event;
22292
- const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
22293
- this.debug(logMessage);
22294
-
22295
- // Ignore event when initData is null
22296
- if (initData === null) {
22297
- return;
22298
- }
22299
- let keyId;
22300
- let keySystemDomain;
22301
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22302
- // Match sinf keyId to playlist skd://keyId=
22303
- const json = bin2str(new Uint8Array(initData));
22304
- try {
22305
- const sinf = base64Decode(JSON.parse(json).sinf);
22306
- const tenc = parseSinf(new Uint8Array(sinf));
22307
- if (!tenc) {
22308
- throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
22309
- }
22310
- keyId = tenc.subarray(8, 24);
22311
- keySystemDomain = KeySystems.FAIRPLAY;
22312
- } catch (error) {
22313
- this.warn(`${logMessage} Failed to parse sinf: ${error}`);
22314
- return;
22315
- }
22316
- } else {
22317
- // Support Widevine clear-lead key-session creation (otherwise depend on playlist keys)
22318
- const psshResults = parseMultiPssh(initData);
22319
- const psshInfo = psshResults.filter(pssh => pssh.systemId === KeySystemIds.WIDEVINE)[0];
22320
- if (!psshInfo) {
22321
- if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
22322
- this.warn(`${logMessage} contains incomplete or invalid pssh data`);
22323
- } else {
22324
- this.log(`ignoring ${logMessage} for ${psshResults.map(pssh => keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`);
22325
- }
22326
- return;
22327
- }
22328
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22329
- if (psshInfo.version === 0 && psshInfo.data) {
22330
- const offset = psshInfo.data.length - 22;
22331
- keyId = psshInfo.data.subarray(offset, offset + 16);
22332
- }
22333
- }
22334
- if (!keySystemDomain || !keyId) {
22335
- return;
22336
- }
22337
- const keyIdHex = Hex.hexDump(keyId);
22338
- const {
22339
- keyIdToKeySessionPromise,
22340
- mediaKeySessions
22341
- } = this;
22342
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22343
- for (let i = 0; i < mediaKeySessions.length; i++) {
22344
- // Match playlist key
22345
- const keyContext = mediaKeySessions[i];
22346
- const decryptdata = keyContext.decryptdata;
22347
- if (!decryptdata.keyId) {
22348
- continue;
22349
- }
22350
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22351
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22352
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22353
- if (decryptdata.pssh) {
22354
- break;
22355
- }
22356
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22357
- decryptdata.pssh = new Uint8Array(initData);
22358
- decryptdata.keyId = keyId;
22359
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22360
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22361
- });
22362
- break;
22363
- }
22364
- }
22365
- if (!keySessionContextPromise) {
22366
- // Clear-lead key (not encountered in playlist)
22367
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22368
- keySystem,
22369
- mediaKeys
22370
- }) => {
22371
- var _keySystemToKeySystem;
22372
- this.throwIfDestroyed();
22373
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22374
- decryptdata.pssh = new Uint8Array(initData);
22375
- decryptdata.keyId = keyId;
22376
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22377
- this.throwIfDestroyed();
22378
- const keySessionContext = this.createMediaKeySessionContext({
22379
- decryptdata,
22380
- keySystem,
22381
- mediaKeys
22382
- });
22383
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22384
- });
22385
- });
22386
- }
22387
- keySessionContextPromise.catch(error => this.handleError(error));
22388
- }
22389
- _onWaitingForKey(event) {
22390
- this.log(`"${event.type}" event`);
22391
- }
22392
22446
  attemptSetMediaKeys(keySystem, mediaKeys) {
22393
22447
  const queue = this.setMediaKeysQueue.slice();
22394
22448
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -22681,7 +22735,7 @@ class EMEController {
22681
22735
  requestLicense(keySessionContext, licenseChallenge) {
22682
22736
  const keyLoadPolicy = this.config.keyLoadPolicy.default;
22683
22737
  return new Promise((resolve, reject) => {
22684
- const url = this.getLicenseServerUrl(keySessionContext.keySystem);
22738
+ const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
22685
22739
  this.log(`Sending license request to URL: ${url}`);
22686
22740
  const xhr = new XMLHttpRequest();
22687
22741
  xhr.responseType = 'arraybuffer';
@@ -22751,6 +22805,8 @@ class EMEController {
22751
22805
 
22752
22806
  // keep reference of media
22753
22807
  this.media = media;
22808
+ media.removeEventListener('encrypted', this.onMediaEncrypted);
22809
+ media.removeEventListener('waitingforkey', this.onWaitingForKey);
22754
22810
  media.addEventListener('encrypted', this.onMediaEncrypted);
22755
22811
  media.addEventListener('waitingforkey', this.onWaitingForKey);
22756
22812
  }
@@ -24704,47 +24760,51 @@ class XhrLoader {
24704
24760
  xhr.onprogress = null;
24705
24761
  const status = xhr.status;
24706
24762
  // http status between 200 to 299 are all successful
24707
- const useResponse = xhr.responseType !== 'text';
24708
- if (status >= 200 && status < 300 && (useResponse && xhr.response || xhr.responseText !== null)) {
24709
- stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
24710
- const data = useResponse ? xhr.response : xhr.responseText;
24711
- const len = xhr.responseType === 'arraybuffer' ? data.byteLength : data.length;
24712
- stats.loaded = stats.total = len;
24713
- stats.bwEstimate = stats.total * 8000 / (stats.loading.end - stats.loading.first);
24714
- if (!this.callbacks) {
24715
- return;
24716
- }
24717
- const onProgress = this.callbacks.onProgress;
24718
- if (onProgress) {
24719
- onProgress(stats, context, data, xhr);
24720
- }
24721
- if (!this.callbacks) {
24763
+ const useResponseText = xhr.responseType === 'text' ? xhr.responseText : null;
24764
+ if (status >= 200 && status < 300) {
24765
+ const data = useResponseText != null ? useResponseText : xhr.response;
24766
+ if (data != null) {
24767
+ stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
24768
+ const len = xhr.responseType === 'arraybuffer' ? data.byteLength : data.length;
24769
+ stats.loaded = stats.total = len;
24770
+ stats.bwEstimate = stats.total * 8000 / (stats.loading.end - stats.loading.first);
24771
+ if (!this.callbacks) {
24772
+ return;
24773
+ }
24774
+ const onProgress = this.callbacks.onProgress;
24775
+ if (onProgress) {
24776
+ onProgress(stats, context, data, xhr);
24777
+ }
24778
+ if (!this.callbacks) {
24779
+ return;
24780
+ }
24781
+ const _response = {
24782
+ url: xhr.responseURL,
24783
+ data: data,
24784
+ code: status
24785
+ };
24786
+ this.callbacks.onSuccess(_response, stats, context, xhr);
24722
24787
  return;
24723
24788
  }
24724
- const response = {
24725
- url: xhr.responseURL,
24726
- data: data,
24727
- code: status
24728
- };
24729
- this.callbacks.onSuccess(response, stats, context, xhr);
24789
+ }
24790
+
24791
+ // Handle bad status or nullish response
24792
+ const retryConfig = config.loadPolicy.errorRetry;
24793
+ const retryCount = stats.retry;
24794
+ // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
24795
+ const response = {
24796
+ url: context.url,
24797
+ data: undefined,
24798
+ code: status
24799
+ };
24800
+ if (shouldRetry(retryConfig, retryCount, false, response)) {
24801
+ this.retry(retryConfig);
24730
24802
  } else {
24731
- const retryConfig = config.loadPolicy.errorRetry;
24732
- const retryCount = stats.retry;
24733
- // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
24734
- const response = {
24735
- url: context.url,
24736
- data: undefined,
24737
- code: status
24738
- };
24739
- if (shouldRetry(retryConfig, retryCount, false, response)) {
24740
- this.retry(retryConfig);
24741
- } else {
24742
- logger.error(`${status} while loading ${context.url}`);
24743
- this.callbacks.onError({
24744
- code: status,
24745
- text: xhr.statusText
24746
- }, context, xhr, stats);
24747
- }
24803
+ logger.error(`${status} while loading ${context.url}`);
24804
+ this.callbacks.onError({
24805
+ code: status,
24806
+ text: xhr.statusText
24807
+ }, context, xhr, stats);
24748
24808
  }
24749
24809
  }
24750
24810
  }
@@ -26096,7 +26156,7 @@ class KeyLoader {
26096
26156
  }
26097
26157
  }
26098
26158
  load(frag) {
26099
- if (!frag.decryptdata && frag.encrypted && this.emeController) {
26159
+ if (!frag.decryptdata && frag.encrypted && this.emeController && this.config.emeEnabled) {
26100
26160
  // Multiple keys, but none selected, resolve in eme-controller
26101
26161
  return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => {
26102
26162
  return this.loadInternal(frag, keySystemFormat);
@@ -26797,7 +26857,7 @@ class StreamController extends BaseStreamController {
26797
26857
  if (this.altAudio && this.audioOnly) {
26798
26858
  return;
26799
26859
  }
26800
- const level = hls.nextLoadLevel;
26860
+ const level = this.buffering ? hls.nextLoadLevel : hls.loadLevel;
26801
26861
  if (!(levels != null && levels[level])) {
26802
26862
  return;
26803
26863
  }
@@ -26819,6 +26879,9 @@ class StreamController extends BaseStreamController {
26819
26879
  this.state = State.ENDED;
26820
26880
  return;
26821
26881
  }
26882
+ if (!this.buffering) {
26883
+ return;
26884
+ }
26822
26885
 
26823
26886
  // set next load level : this will trigger a playlist load if needed
26824
26887
  if (hls.loadLevel !== level && hls.manualLevel === -1) {
@@ -27781,7 +27844,7 @@ class Hls {
27781
27844
  * Get the video-dev/hls.js package version.
27782
27845
  */
27783
27846
  static get version() {
27784
- return "1.5.17";
27847
+ return "1.5.19";
27785
27848
  }
27786
27849
 
27787
27850
  /**
@@ -28062,9 +28125,13 @@ class Hls {
28062
28125
  startLoad(startPosition = -1) {
28063
28126
  logger.log(`startLoad(${startPosition})`);
28064
28127
  this.started = true;
28065
- this.networkControllers.forEach(controller => {
28066
- controller.startLoad(startPosition);
28067
- });
28128
+ this.resumeBuffering();
28129
+ for (let i = 0; i < this.networkControllers.length; i++) {
28130
+ this.networkControllers[i].startLoad(startPosition);
28131
+ if (!this.started || !this.networkControllers) {
28132
+ break;
28133
+ }
28134
+ }
28068
28135
  }
28069
28136
 
28070
28137
  /**
@@ -28073,32 +28140,35 @@ class Hls {
28073
28140
  stopLoad() {
28074
28141
  logger.log('stopLoad');
28075
28142
  this.started = false;
28076
- this.networkControllers.forEach(controller => {
28077
- controller.stopLoad();
28078
- });
28143
+ for (let i = 0; i < this.networkControllers.length; i++) {
28144
+ this.networkControllers[i].stopLoad();
28145
+ if (this.started || !this.networkControllers) {
28146
+ break;
28147
+ }
28148
+ }
28079
28149
  }
28080
28150
 
28081
28151
  /**
28082
- * Resumes stream controller segment loading if previously started.
28152
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
28083
28153
  */
28084
28154
  resumeBuffering() {
28085
- if (this.started) {
28086
- this.networkControllers.forEach(controller => {
28087
- if ('fragmentLoader' in controller) {
28088
- controller.startLoad(-1);
28089
- }
28090
- });
28091
- }
28155
+ logger.log(`resume buffering`);
28156
+ this.networkControllers.forEach(controller => {
28157
+ if (controller.resumeBuffering) {
28158
+ controller.resumeBuffering();
28159
+ }
28160
+ });
28092
28161
  }
28093
28162
 
28094
28163
  /**
28095
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
28164
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
28096
28165
  * This allows for media buffering to be paused without interupting playlist loading.
28097
28166
  */
28098
28167
  pauseBuffering() {
28168
+ logger.log(`pause buffering`);
28099
28169
  this.networkControllers.forEach(controller => {
28100
- if ('fragmentLoader' in controller) {
28101
- controller.stopLoad();
28170
+ if (controller.pauseBuffering) {
28171
+ controller.pauseBuffering();
28102
28172
  }
28103
28173
  });
28104
28174
  }