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.js +342 -268
- package/dist/hls.js.d.ts +10 -6
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +112 -91
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +111 -90
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +292 -222
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +1 -1
- package/src/controller/audio-stream-controller.ts +4 -2
- package/src/controller/base-stream-controller.ts +9 -0
- package/src/controller/buffer-controller.ts +1 -0
- package/src/controller/eme-controller.ts +184 -124
- package/src/controller/stream-controller.ts +4 -1
- package/src/hls.ts +24 -17
- package/src/loader/key-loader.ts +6 -1
- package/src/loader/level-key.ts +6 -30
- package/src/remux/mp4-remuxer.ts +11 -7
- package/src/types/component-api.ts +2 -0
- package/src/utils/level-helper.ts +37 -38
- package/src/utils/mediakeys-helper.ts +34 -1
- package/src/utils/xhr-loader.ts +52 -49
@@ -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 = (
|
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
|
156
|
-
let
|
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
|
162
|
-
if (
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
?
|
202
|
-
:
|
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 =
|
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
|
-
|
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
|
+
}
|
package/src/utils/xhr-loader.ts
CHANGED
@@ -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
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
(
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
233
|
-
url: xhr.responseURL,
|
234
|
-
data: data,
|
235
|
-
code: status,
|
236
|
-
};
|
241
|
+
}
|
237
242
|
|
238
|
-
|
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
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
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
|
}
|