hls.js 1.6.3-0.canary.11253 → 1.6.3-0.canary.11255
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.d.mts +8 -4
- package/dist/hls.d.ts +8 -4
- package/dist/hls.js +220 -166
- package/dist/hls.js.d.ts +8 -4
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +72 -31
- 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 +69 -30
- 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 +127 -84
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/package.json +1 -1
- package/src/config.ts +2 -0
- package/src/controller/audio-stream-controller.ts +39 -31
- package/src/controller/base-stream-controller.ts +17 -6
- package/src/controller/eme-controller.ts +47 -19
- package/src/controller/fragment-finders.ts +18 -14
- package/src/controller/interstitials-controller.ts +1 -1
- package/src/loader/key-loader.ts +42 -17
@@ -1,5 +1,5 @@
|
|
1
1
|
import BaseStreamController, { State } from './base-stream-controller';
|
2
|
-
import {
|
2
|
+
import { findNearestWithCC } from './fragment-finders';
|
3
3
|
import { FragmentState } from './fragment-tracker';
|
4
4
|
import ChunkCache from '../demux/chunk-cache';
|
5
5
|
import TransmuxerInterface from '../demux/transmuxer-interface';
|
@@ -161,32 +161,56 @@ class AudioStreamController
|
|
161
161
|
(!waitingData && !this.loadingParts) ||
|
162
162
|
(waitingData && waitingData.frag.cc !== cc)
|
163
163
|
) {
|
164
|
-
this.
|
164
|
+
this.syncWithAnchor(frag, waitingData?.frag);
|
165
165
|
}
|
166
|
-
this.tick();
|
167
166
|
} else if (
|
168
167
|
!this.hls.hasEnoughToStart &&
|
169
168
|
inFlightFrag &&
|
170
169
|
inFlightFrag.cc !== cc
|
171
170
|
) {
|
172
|
-
this.startFragRequested = false;
|
173
|
-
this.nextLoadPosition = this.findSyncFrag(frag).start;
|
174
171
|
inFlightFrag.abortRequests();
|
175
|
-
this.
|
172
|
+
this.syncWithAnchor(frag, inFlightFrag);
|
176
173
|
} else if (this.state === State.IDLE) {
|
177
174
|
this.tick();
|
178
175
|
}
|
179
176
|
}
|
180
177
|
}
|
181
178
|
|
182
|
-
|
179
|
+
protected getLoadPosition(): number {
|
180
|
+
if (!this.startFragRequested && this.nextLoadPosition >= 0) {
|
181
|
+
return this.nextLoadPosition;
|
182
|
+
}
|
183
|
+
return super.getLoadPosition();
|
184
|
+
}
|
185
|
+
|
186
|
+
private syncWithAnchor(
|
187
|
+
mainAnchor: MediaFragment,
|
188
|
+
waitingToAppend: Fragment | undefined,
|
189
|
+
) {
|
190
|
+
// Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found
|
191
|
+
const mainFragLoading = this.mainFragLoading?.frag || null;
|
192
|
+
if (waitingToAppend) {
|
193
|
+
if (mainFragLoading?.cc === waitingToAppend.cc) {
|
194
|
+
// Wait for loading frag to complete and INIT_PTS_FOUND
|
195
|
+
return;
|
196
|
+
}
|
197
|
+
}
|
198
|
+
const targetDiscontinuity = (mainFragLoading || mainAnchor).cc;
|
183
199
|
const trackDetails = this.getLevelDetails();
|
184
|
-
const
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
200
|
+
const pos = this.getLoadPosition();
|
201
|
+
const syncFrag = findNearestWithCC(trackDetails, targetDiscontinuity, pos);
|
202
|
+
// Only stop waiting for audioFrag.cc if an audio segment of the same discontinuity domain (cc) is found
|
203
|
+
if (syncFrag) {
|
204
|
+
this.log(
|
205
|
+
`Waiting fragment cc (${waitingToAppend?.cc}) cancelled because video is at cc ${mainAnchor.cc}`,
|
206
|
+
);
|
207
|
+
this.startFragRequested = false;
|
208
|
+
this.nextLoadPosition = syncFrag.start;
|
209
|
+
this.resetLoadingState();
|
210
|
+
if (this.state === State.IDLE) {
|
211
|
+
this.doTickIdle();
|
212
|
+
}
|
213
|
+
}
|
190
214
|
}
|
191
215
|
|
192
216
|
startLoad(startPosition: number, skipSeekToStartPosition?: boolean) {
|
@@ -265,12 +289,7 @@ class AudioStreamController
|
|
265
289
|
super._handleFragmentLoadComplete(data);
|
266
290
|
}
|
267
291
|
} else if (mainAnchor && mainAnchor.cc !== waitingData.frag.cc) {
|
268
|
-
|
269
|
-
this.log(
|
270
|
-
`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${mainAnchor.cc}`,
|
271
|
-
);
|
272
|
-
this.nextLoadPosition = this.findSyncFrag(mainAnchor).start;
|
273
|
-
this.clearWaitingFragment();
|
292
|
+
this.syncWithAnchor(mainAnchor, waitingData.frag);
|
274
293
|
}
|
275
294
|
} else {
|
276
295
|
this.state = State.IDLE;
|
@@ -281,23 +300,12 @@ class AudioStreamController
|
|
281
300
|
this.onTickEnd();
|
282
301
|
}
|
283
302
|
|
284
|
-
|
303
|
+
protected resetLoadingState() {
|
285
304
|
const waitingData = this.waitingData;
|
286
305
|
if (waitingData) {
|
287
|
-
if (!this.hls.hasEnoughToStart) {
|
288
|
-
// Load overlapping fragment on start when discontinuity start times are not aligned
|
289
|
-
this.startFragRequested = false;
|
290
|
-
}
|
291
306
|
this.fragmentTracker.removeFragment(waitingData.frag);
|
292
307
|
this.waitingData = null;
|
293
|
-
if (this.state !== State.STOPPED) {
|
294
|
-
this.state = State.IDLE;
|
295
|
-
}
|
296
308
|
}
|
297
|
-
}
|
298
|
-
|
299
|
-
protected resetLoadingState() {
|
300
|
-
this.clearWaitingFragment();
|
301
309
|
super.resetLoadingState();
|
302
310
|
}
|
303
311
|
|
@@ -2,7 +2,7 @@ import { ErrorActionFlags, NetworkErrorAction } from './error-controller';
|
|
2
2
|
import {
|
3
3
|
findFragmentByPDT,
|
4
4
|
findFragmentByPTS,
|
5
|
-
|
5
|
+
findNearestWithCC,
|
6
6
|
} from './fragment-finders';
|
7
7
|
import { FragmentState } from './fragment-tracker';
|
8
8
|
import Decrypter from '../crypt/decrypter';
|
@@ -31,6 +31,7 @@ import {
|
|
31
31
|
getPartWith,
|
32
32
|
updateFragPTSDTS,
|
33
33
|
} from '../utils/level-helper';
|
34
|
+
import { getKeySystemsForConfig } from '../utils/mediakeys-helper';
|
34
35
|
import { appendUint8Array } from '../utils/mp4-tools';
|
35
36
|
import TimeRanges from '../utils/time-ranges';
|
36
37
|
import type { FragmentTracker } from './fragment-tracker';
|
@@ -809,8 +810,14 @@ export default class BaseStreamController
|
|
809
810
|
new Error(`frag load aborted, context changed in KEY_LOADING`),
|
810
811
|
);
|
811
812
|
}
|
812
|
-
} else if (!frag.encrypted
|
813
|
-
this.keyLoader.loadClear(
|
813
|
+
} else if (!frag.encrypted) {
|
814
|
+
keyLoadingPromise = this.keyLoader.loadClear(
|
815
|
+
frag,
|
816
|
+
details.encryptedFragments,
|
817
|
+
);
|
818
|
+
if (keyLoadingPromise) {
|
819
|
+
this.log(`[eme] blocking frag load until media-keys acquired`);
|
820
|
+
}
|
814
821
|
}
|
815
822
|
|
816
823
|
const fragPrevious = this.fragPrevious;
|
@@ -1298,7 +1305,7 @@ export default class BaseStreamController
|
|
1298
1305
|
this.log(`LL-Part loading ON for initial live fragment`);
|
1299
1306
|
this.loadingParts = true;
|
1300
1307
|
}
|
1301
|
-
frag = this.getInitialLiveFragment(levelDetails
|
1308
|
+
frag = this.getInitialLiveFragment(levelDetails);
|
1302
1309
|
const mainStart = this.hls.startPosition;
|
1303
1310
|
const liveSyncPosition = this.hls.liveSyncPosition;
|
1304
1311
|
const startPosition = frag
|
@@ -1506,8 +1513,8 @@ export default class BaseStreamController
|
|
1506
1513
|
*/
|
1507
1514
|
protected getInitialLiveFragment(
|
1508
1515
|
levelDetails: LevelDetails,
|
1509
|
-
fragments: MediaFragment[],
|
1510
1516
|
): MediaFragment | null {
|
1517
|
+
const fragments = levelDetails.fragments;
|
1511
1518
|
const fragPrevious = this.fragPrevious;
|
1512
1519
|
let frag: MediaFragment | null = null;
|
1513
1520
|
if (fragPrevious) {
|
@@ -1543,7 +1550,11 @@ export default class BaseStreamController
|
|
1543
1550
|
// It's important to stay within the continuity range if available; otherwise the fragments in the playlist
|
1544
1551
|
// will have the wrong start times
|
1545
1552
|
if (!frag) {
|
1546
|
-
frag =
|
1553
|
+
frag = findNearestWithCC(
|
1554
|
+
levelDetails,
|
1555
|
+
fragPrevious.cc,
|
1556
|
+
fragPrevious.end,
|
1557
|
+
);
|
1547
1558
|
if (frag) {
|
1548
1559
|
this.log(
|
1549
1560
|
`Live playlist, switching playlist, load frag with same CC: ${frag.sn}`,
|
@@ -52,6 +52,7 @@ interface KeySystemAccessPromises {
|
|
52
52
|
keySystemAccess: Promise<MediaKeySystemAccess>;
|
53
53
|
mediaKeys?: Promise<MediaKeys>;
|
54
54
|
certificate?: Promise<BufferSource | void>;
|
55
|
+
hasMediaKeys?: boolean;
|
55
56
|
}
|
56
57
|
|
57
58
|
export interface MediaKeySessionContext {
|
@@ -285,6 +286,7 @@ class EMEController extends Logger implements ComponentAPI {
|
|
285
286
|
.createMediaKeys()
|
286
287
|
.then((mediaKeys) => {
|
287
288
|
this.log(`Media-keys created for "${keySystem}"`);
|
289
|
+
keySystemAccessPromises.hasMediaKeys = true;
|
288
290
|
return certificateRequest.then((certificate) => {
|
289
291
|
if (certificate) {
|
290
292
|
return this.setMediaKeysServerCertificate(
|
@@ -384,29 +386,29 @@ class EMEController extends Logger implements ComponentAPI {
|
|
384
386
|
return keySession.update(data);
|
385
387
|
}
|
386
388
|
|
387
|
-
public
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
)
|
395
|
-
|
396
|
-
}
|
397
|
-
return this.keyFormatPromise;
|
389
|
+
public getSelectedKeySystemFormats(): KeySystemFormats[] {
|
390
|
+
return (Object.keys(this.keySystemAccessPromises) as KeySystems[])
|
391
|
+
.map((keySystem) => ({
|
392
|
+
keySystem,
|
393
|
+
hasMediaKeys: this.keySystemAccessPromises[keySystem].hasMediaKeys,
|
394
|
+
}))
|
395
|
+
.filter(({ hasMediaKeys }) => !!hasMediaKeys)
|
396
|
+
.map(({ keySystem }) => keySystemToKeySystemFormat(keySystem))
|
397
|
+
.filter((keySystem): keySystem is KeySystemFormats => !!keySystem);
|
398
398
|
}
|
399
399
|
|
400
|
-
|
401
|
-
|
400
|
+
public getKeySystemAccess(keySystemsToAttempt: KeySystems[]): Promise<void> {
|
401
|
+
return this.getKeySystemSelectionPromise(keySystemsToAttempt).then(
|
402
|
+
({ keySystem, mediaKeys }) => {
|
403
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys);
|
404
|
+
},
|
405
|
+
);
|
406
|
+
}
|
407
|
+
|
408
|
+
public selectKeySystem(
|
409
|
+
keySystemsToAttempt: KeySystems[],
|
402
410
|
): Promise<KeySystemFormats> {
|
403
411
|
return new Promise((resolve, reject) => {
|
404
|
-
const keySystemsInConfig = getKeySystemsForConfig(this.config);
|
405
|
-
const keySystemsToAttempt = keyFormats
|
406
|
-
.map(keySystemFormatToKeySystemDomain)
|
407
|
-
.filter(
|
408
|
-
(value) => !!value && keySystemsInConfig.indexOf(value) !== -1,
|
409
|
-
) as any as KeySystems[];
|
410
412
|
return this.getKeySystemSelectionPromise(keySystemsToAttempt)
|
411
413
|
.then(({ keySystem }) => {
|
412
414
|
const keySystemFormat = keySystemToKeySystemFormat(keySystem);
|
@@ -422,6 +424,32 @@ class EMEController extends Logger implements ComponentAPI {
|
|
422
424
|
});
|
423
425
|
}
|
424
426
|
|
427
|
+
public selectKeySystemFormat(frag: Fragment): Promise<KeySystemFormats> {
|
428
|
+
const keyFormats = Object.keys(frag.levelkeys || {}) as KeySystemFormats[];
|
429
|
+
if (!this.keyFormatPromise) {
|
430
|
+
this.log(
|
431
|
+
`Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${
|
432
|
+
frag.level
|
433
|
+
}) key formats ${keyFormats.join(', ')}`,
|
434
|
+
);
|
435
|
+
this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
|
436
|
+
}
|
437
|
+
return this.keyFormatPromise;
|
438
|
+
}
|
439
|
+
|
440
|
+
private getKeyFormatPromise(
|
441
|
+
keyFormats: KeySystemFormats[],
|
442
|
+
): Promise<KeySystemFormats> {
|
443
|
+
const keySystemsInConfig = getKeySystemsForConfig(this.config);
|
444
|
+
const keySystemsToAttempt = keyFormats
|
445
|
+
.map(keySystemFormatToKeySystemDomain)
|
446
|
+
.filter(
|
447
|
+
(value) => !!value && keySystemsInConfig.indexOf(value) !== -1,
|
448
|
+
) as any as KeySystems[];
|
449
|
+
|
450
|
+
return this.selectKeySystem(keySystemsToAttempt);
|
451
|
+
}
|
452
|
+
|
425
453
|
public loadKey(data: KeyLoadedData): Promise<MediaKeySessionContext> {
|
426
454
|
const decryptdata = data.keyInfo.decryptdata;
|
427
455
|
|
@@ -33,7 +33,6 @@ export function findFragmentByPDT(
|
|
33
33
|
return null;
|
34
34
|
}
|
35
35
|
|
36
|
-
maxFragLookUpTolerance = maxFragLookUpTolerance || 0;
|
37
36
|
for (let seg = 0; seg < fragments.length; ++seg) {
|
38
37
|
const frag = fragments[seg];
|
39
38
|
if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) {
|
@@ -225,28 +224,33 @@ export function findFragWithCC(
|
|
225
224
|
export function findNearestWithCC(
|
226
225
|
details: LevelDetails | undefined,
|
227
226
|
cc: number,
|
228
|
-
|
227
|
+
pos: number,
|
229
228
|
): MediaFragment | null {
|
230
229
|
if (details) {
|
231
230
|
if (details.startCC <= cc && details.endCC >= cc) {
|
232
|
-
const start = fragment.start;
|
233
|
-
const end = fragment.end;
|
234
231
|
let fragments = details.fragments;
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
fragments = fragments.concat(fragmentHint);
|
239
|
-
}
|
232
|
+
const { fragmentHint } = details;
|
233
|
+
if (fragmentHint) {
|
234
|
+
fragments = fragments.concat(fragmentHint);
|
240
235
|
}
|
241
|
-
|
242
|
-
|
236
|
+
let closest: MediaFragment | undefined;
|
237
|
+
BinarySearch.search(fragments, (candidate) => {
|
238
|
+
if (candidate.cc < cc) {
|
239
|
+
return 1;
|
240
|
+
}
|
241
|
+
if (candidate.cc > cc) {
|
242
|
+
return -1;
|
243
|
+
}
|
244
|
+
closest = candidate;
|
245
|
+
if (candidate.end <= pos) {
|
243
246
|
return 1;
|
244
|
-
}
|
247
|
+
}
|
248
|
+
if (candidate.start > pos) {
|
245
249
|
return -1;
|
246
|
-
} else {
|
247
|
-
return 0;
|
248
250
|
}
|
251
|
+
return 0;
|
249
252
|
});
|
253
|
+
return closest || null;
|
250
254
|
}
|
251
255
|
}
|
252
256
|
return null;
|
@@ -2437,7 +2437,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
2437
2437
|
}
|
2438
2438
|
|
2439
2439
|
private bufferAssetPlayer(player: HlsAssetPlayer, media: HTMLMediaElement) {
|
2440
|
-
const { interstitial, assetItem
|
2440
|
+
const { interstitial, assetItem } = player;
|
2441
2441
|
const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier);
|
2442
2442
|
const item = this.schedule.items?.[scheduleIndex];
|
2443
2443
|
if (!item) {
|
package/src/loader/key-loader.ts
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
import { LoadError } from './fragment-loader';
|
2
2
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
3
|
-
import
|
3
|
+
import {
|
4
|
+
getKeySystemsForConfig,
|
5
|
+
keySystemFormatToKeySystemDomain,
|
6
|
+
} from '../utils/mediakeys-helper';
|
4
7
|
import type { LevelKey } from './level-key';
|
8
|
+
import type { HlsConfig } from '../config';
|
5
9
|
import type EMEController from '../controller/eme-controller';
|
6
10
|
import type { MediaKeySessionContext } from '../controller/eme-controller';
|
7
11
|
import type { Fragment } from '../loader/fragment';
|
@@ -90,25 +94,46 @@ export default class KeyLoader implements ComponentAPI {
|
|
90
94
|
loadClear(
|
91
95
|
loadingFrag: Fragment,
|
92
96
|
encryptedFragments: Fragment[],
|
93
|
-
):
|
94
|
-
if (
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
) {
|
103
|
-
|
104
|
-
|
105
|
-
.
|
106
|
-
|
107
|
-
|
108
|
-
|
97
|
+
): null | Promise<void> {
|
98
|
+
if (
|
99
|
+
this.emeController &&
|
100
|
+
this.config.emeEnabled &&
|
101
|
+
!this.emeController.getSelectedKeySystemFormats().length
|
102
|
+
) {
|
103
|
+
// access key-system with nearest key on start (loading frag is unencrypted)
|
104
|
+
if (encryptedFragments.length) {
|
105
|
+
const { sn, cc } = loadingFrag;
|
106
|
+
for (let i = 0; i < encryptedFragments.length; i++) {
|
107
|
+
const frag = encryptedFragments[i];
|
108
|
+
if (
|
109
|
+
cc <= frag.cc &&
|
110
|
+
(sn === 'initSegment' || frag.sn === 'initSegment' || sn < frag.sn)
|
111
|
+
) {
|
112
|
+
return this.emeController
|
113
|
+
.selectKeySystemFormat(frag)
|
114
|
+
.then((keySystemFormat) => {
|
115
|
+
frag.setKeyFormat(keySystemFormat);
|
116
|
+
if (
|
117
|
+
this.emeController &&
|
118
|
+
this.config.requireKeySystemAccessOnStart
|
119
|
+
) {
|
120
|
+
const keySystem =
|
121
|
+
keySystemFormatToKeySystemDomain(keySystemFormat);
|
122
|
+
if (keySystem) {
|
123
|
+
return this.emeController.getKeySystemAccess([keySystem]);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
});
|
127
|
+
}
|
128
|
+
}
|
129
|
+
} else if (this.config.requireKeySystemAccessOnStart) {
|
130
|
+
const keySystemsInConfig = getKeySystemsForConfig(this.config);
|
131
|
+
if (keySystemsInConfig.length) {
|
132
|
+
return this.emeController.getKeySystemAccess(keySystemsInConfig);
|
109
133
|
}
|
110
134
|
}
|
111
135
|
}
|
136
|
+
return null;
|
112
137
|
}
|
113
138
|
|
114
139
|
load(frag: Fragment): Promise<KeyLoadedData> {
|