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.
@@ -8,7 +8,12 @@ import { LevelDetails } from '../loader/level-details';
8
8
  import type { Level } from '../types/level';
9
9
  import { DateRange } from '../loader/date-range';
10
10
 
11
- type FragmentIntersection = (oldFrag: Fragment, newFrag: Fragment) => void;
11
+ type FragmentIntersection = (
12
+ oldFrag: Fragment,
13
+ newFrag: Fragment,
14
+ newFragIndex: number,
15
+ newFragments: Fragment[],
16
+ ) => void;
12
17
  type PartIntersection = (oldPart: Part, newPart: Part) => void;
13
18
 
14
19
  export function updatePTS(
@@ -106,7 +111,7 @@ export function updateFragPTSDTS(
106
111
  if (!details || sn < details.startSN || sn > details.endSN) {
107
112
  return 0;
108
113
  }
109
- let i;
114
+ let i: number;
110
115
  const fragIdx = sn - details.startSN;
111
116
  const fragments = details.fragments;
112
117
  // update frag reference in fragments array
@@ -152,18 +157,19 @@ export function mergeDetails(
152
157
  delete oldDetails.fragmentHint.endPTS;
153
158
  }
154
159
  // check if old/new playlists have fragments in common
155
- // loop through overlapping SN and update startPTS , cc, and duration if any found
156
- let ccOffset = 0;
157
- let PTSFrag;
160
+ // loop through overlapping SN and update startPTS, cc, and duration if any found
161
+ let PTSFrag: Fragment | undefined;
158
162
  mapFragmentIntersection(
159
163
  oldDetails,
160
164
  newDetails,
161
- (oldFrag: Fragment, newFrag: Fragment) => {
162
- if (oldFrag.relurl) {
163
- // Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts.
164
- // It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end
165
- // of the playlist.
166
- ccOffset = oldFrag.cc - newFrag.cc;
165
+ (oldFrag, newFrag, newFragIndex, newFragments) => {
166
+ if (newDetails.skippedSegments) {
167
+ if (newFrag.cc !== oldFrag.cc) {
168
+ const ccOffset = oldFrag.cc - newFrag.cc;
169
+ for (let i = newFragIndex; i < newFragments.length; i++) {
170
+ newFragments[i].cc += ccOffset;
171
+ }
172
+ }
167
173
  }
168
174
  if (
169
175
  Number.isFinite(oldFrag.startPTS) &&
@@ -196,10 +202,11 @@ export function mergeDetails(
196
202
  },
197
203
  );
198
204
 
205
+ const newFragments = newDetails.fragments;
199
206
  if (currentInitSegment) {
200
207
  const fragmentsToCheck = newDetails.fragmentHint
201
- ? newDetails.fragments.concat(newDetails.fragmentHint)
202
- : newDetails.fragments;
208
+ ? newFragments.concat(newDetails.fragmentHint)
209
+ : newFragments;
203
210
  fragmentsToCheck.forEach((frag) => {
204
211
  if (
205
212
  frag &&
@@ -212,34 +219,26 @@ export function mergeDetails(
212
219
  }
213
220
 
214
221
  if (newDetails.skippedSegments) {
215
- newDetails.deltaUpdateFailed = newDetails.fragments.some((frag) => !frag);
222
+ newDetails.deltaUpdateFailed = newFragments.some((frag) => !frag);
216
223
  if (newDetails.deltaUpdateFailed) {
217
224
  logger.warn(
218
225
  '[level-helper] Previous playlist missing segments skipped in delta playlist',
219
226
  );
220
227
  for (let i = newDetails.skippedSegments; i--; ) {
221
- newDetails.fragments.shift();
228
+ newFragments.shift();
229
+ }
230
+ newDetails.startSN = newFragments[0].sn as number;
231
+ } else {
232
+ if (newDetails.canSkipDateRanges) {
233
+ newDetails.dateRanges = mergeDateRanges(
234
+ oldDetails.dateRanges,
235
+ newDetails.dateRanges,
236
+ newDetails.recentlyRemovedDateranges,
237
+ );
222
238
  }
223
- newDetails.startSN = newDetails.fragments[0].sn as number;
224
- newDetails.startCC = newDetails.fragments[0].cc;
225
- } else if (newDetails.canSkipDateRanges) {
226
- newDetails.dateRanges = mergeDateRanges(
227
- oldDetails.dateRanges,
228
- newDetails.dateRanges,
229
- newDetails.recentlyRemovedDateranges,
230
- );
231
- }
232
- }
233
-
234
- const newFragments = newDetails.fragments;
235
- if (ccOffset) {
236
- logger.warn('discontinuity sliding from playlist, take drift into account');
237
- for (let i = 0; i < newFragments.length; i++) {
238
- newFragments[i].cc += ccOffset;
239
239
  }
240
- }
241
- if (newDetails.skippedSegments) {
242
240
  newDetails.startCC = newDetails.fragments[0].cc;
241
+ newDetails.endCC = newFragments[newFragments.length - 1].cc;
243
242
  }
244
243
 
245
244
  // Merge parts
@@ -257,10 +256,10 @@ export function mergeDetails(
257
256
  updateFragPTSDTS(
258
257
  newDetails,
259
258
  PTSFrag,
260
- PTSFrag.startPTS,
261
- PTSFrag.endPTS,
262
- PTSFrag.startDTS,
263
- PTSFrag.endDTS,
259
+ PTSFrag.startPTS as number,
260
+ PTSFrag.endPTS as number,
261
+ PTSFrag.startDTS as number,
262
+ PTSFrag.endDTS as number,
264
263
  );
265
264
  } else {
266
265
  // ensure that delta is within oldFragments range
@@ -371,7 +370,7 @@ export function mapFragmentIntersection(
371
370
  newFrag = newDetails.fragments[i] = oldFrag;
372
371
  }
373
372
  if (oldFrag && newFrag) {
374
- intersectionFn(oldFrag, newFrag);
373
+ intersectionFn(oldFrag, newFrag, i, newFrags);
375
374
  }
376
375
  }
377
376
  }
@@ -1,5 +1,7 @@
1
- import type { DRMSystemOptions, EMEControllerConfig } from '../config';
2
1
  import { optionalSelf } from './global';
2
+ import { changeEndianness } from './keysystem-util';
3
+ import { base64Decode } from './numeric-encoding-utils';
4
+ import type { DRMSystemOptions, EMEControllerConfig } from '../config';
3
5
 
4
6
  /**
5
7
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess
@@ -163,3 +165,34 @@ function createMediaKeySystemConfigurations(
163
165
 
164
166
  return [baseConfig];
165
167
  }
168
+
169
+ export function parsePlayReadyWRM(keyBytes: Uint8Array): Uint8Array | null {
170
+ const keyBytesUtf16 = new Uint16Array(
171
+ keyBytes.buffer,
172
+ keyBytes.byteOffset,
173
+ keyBytes.byteLength / 2,
174
+ );
175
+ const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
176
+
177
+ // Parse Playready WRMHeader XML
178
+ const xmlKeyBytes = keyByteStr.substring(
179
+ keyByteStr.indexOf('<'),
180
+ keyByteStr.length,
181
+ );
182
+ const parser = new DOMParser();
183
+ const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
184
+ const keyData = xmlDoc.getElementsByTagName('KID')[0];
185
+ if (keyData) {
186
+ const keyId = keyData.childNodes[0]
187
+ ? keyData.childNodes[0].nodeValue
188
+ : keyData.getAttribute('VALUE');
189
+ if (keyId) {
190
+ const keyIdArray = base64Decode(keyId).subarray(0, 16);
191
+ // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
192
+ // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
193
+ changeEndianness(keyIdArray);
194
+ return keyIdArray;
195
+ }
196
+ }
197
+ return null;
198
+ }
@@ -203,59 +203,62 @@ class XhrLoader implements Loader<LoaderContext> {
203
203
  xhr.onprogress = null;
204
204
  const status = xhr.status;
205
205
  // http status between 200 to 299 are all successful
206
- const useResponse = xhr.responseType !== 'text';
207
- if (
208
- status >= 200 &&
209
- status < 300 &&
210
- ((useResponse && xhr.response) || xhr.responseText !== null)
211
- ) {
212
- stats.loading.end = Math.max(
213
- self.performance.now(),
214
- stats.loading.first,
215
- );
216
- const data = useResponse ? xhr.response : xhr.responseText;
217
- const len =
218
- xhr.responseType === 'arraybuffer' ? data.byteLength : data.length;
219
- stats.loaded = stats.total = len;
220
- stats.bwEstimate =
221
- (stats.total * 8000) / (stats.loading.end - stats.loading.first);
222
- if (!this.callbacks) {
223
- return;
224
- }
225
- const onProgress = this.callbacks.onProgress;
226
- if (onProgress) {
227
- onProgress(stats, context, data, xhr);
228
- }
229
- if (!this.callbacks) {
206
+ const useResponseText =
207
+ xhr.responseType === 'text' ? xhr.responseText : null;
208
+ if (status >= 200 && status < 300) {
209
+ const data = useResponseText ?? xhr.response;
210
+ if (data != null) {
211
+ stats.loading.end = Math.max(
212
+ self.performance.now(),
213
+ stats.loading.first,
214
+ );
215
+ const len =
216
+ xhr.responseType === 'arraybuffer'
217
+ ? data.byteLength
218
+ : data.length;
219
+ stats.loaded = stats.total = len;
220
+ stats.bwEstimate =
221
+ (stats.total * 8000) / (stats.loading.end - stats.loading.first);
222
+ if (!this.callbacks) {
223
+ return;
224
+ }
225
+ const onProgress = this.callbacks.onProgress;
226
+ if (onProgress) {
227
+ onProgress(stats, context, data, xhr);
228
+ }
229
+ if (!this.callbacks) {
230
+ return;
231
+ }
232
+ const response: LoaderResponse = {
233
+ url: xhr.responseURL,
234
+ data: data,
235
+ code: status,
236
+ };
237
+
238
+ this.callbacks.onSuccess(response, stats, context, xhr);
230
239
  return;
231
240
  }
232
- const response: LoaderResponse = {
233
- url: xhr.responseURL,
234
- data: data,
235
- code: status,
236
- };
241
+ }
237
242
 
238
- this.callbacks.onSuccess(response, stats, context, xhr);
243
+ // Handle bad status or nullish response
244
+ const retryConfig = config.loadPolicy.errorRetry;
245
+ const retryCount = stats.retry;
246
+ // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
247
+ const response: LoaderResponse = {
248
+ url: context.url,
249
+ data: undefined,
250
+ code: status,
251
+ };
252
+ if (shouldRetry(retryConfig, retryCount, false, response)) {
253
+ this.retry(retryConfig);
239
254
  } else {
240
- const retryConfig = config.loadPolicy.errorRetry;
241
- const retryCount = stats.retry;
242
- // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
243
- const response: LoaderResponse = {
244
- url: context.url,
245
- data: undefined,
246
- code: status,
247
- };
248
- if (shouldRetry(retryConfig, retryCount, false, response)) {
249
- this.retry(retryConfig);
250
- } else {
251
- logger.error(`${status} while loading ${context.url}`);
252
- this.callbacks!.onError(
253
- { code: status, text: xhr.statusText },
254
- context,
255
- xhr,
256
- stats,
257
- );
258
- }
255
+ logger.error(`${status} while loading ${context.url}`);
256
+ this.callbacks!.onError(
257
+ { code: status, text: xhr.statusText },
258
+ context,
259
+ xhr,
260
+ stats,
261
+ );
259
262
  }
260
263
  }
261
264
  }