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.
@@ -1,5 +1,5 @@
1
1
  import BaseStreamController, { State } from './base-stream-controller';
2
- import { findFragWithCC, findNearestWithCC } from './fragment-finders';
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.nextLoadPosition = this.findSyncFrag(frag).start;
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.resetLoadingState();
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
- private findSyncFrag(mainFrag: MediaFragment): MediaFragment {
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 cc = mainFrag.cc;
185
- return (
186
- findNearestWithCC(trackDetails, cc, mainFrag) ||
187
- (trackDetails && findFragWithCC(trackDetails.fragments, cc)) ||
188
- mainFrag
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
- // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found
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
- clearWaitingFragment() {
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
- findFragWithCC,
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 && details.encryptedFragments.length) {
813
- this.keyLoader.loadClear(frag, details.encryptedFragments);
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, fragments);
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 = findFragWithCC(fragments, fragPrevious.cc);
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 selectKeySystemFormat(frag: Fragment): Promise<KeySystemFormats> {
388
- const keyFormats = Object.keys(frag.levelkeys || {}) as KeySystemFormats[];
389
- if (!this.keyFormatPromise) {
390
- this.log(
391
- `Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${
392
- frag.level
393
- }) key formats ${keyFormats.join(', ')}`,
394
- );
395
- this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
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
- private getKeyFormatPromise(
401
- keyFormats: KeySystemFormats[],
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
- fragment: MediaFragment,
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
- if (!fragment.relurl) {
236
- const { fragmentHint } = details;
237
- if (fragmentHint) {
238
- fragments = fragments.concat(fragmentHint);
239
- }
232
+ const { fragmentHint } = details;
233
+ if (fragmentHint) {
234
+ fragments = fragments.concat(fragmentHint);
240
235
  }
241
- return BinarySearch.search(fragments, (candidate) => {
242
- if (candidate.cc < cc || candidate.end <= start) {
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
- } else if (candidate.cc > cc || candidate.start >= end) {
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, assetId } = player;
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) {
@@ -1,7 +1,11 @@
1
1
  import { LoadError } from './fragment-loader';
2
2
  import { ErrorDetails, ErrorTypes } from '../errors';
3
- import type { HlsConfig } from '../config';
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
- ): void | Promise<void> {
94
- if (this.emeController && this.config.emeEnabled) {
95
- // access key-system with nearest key on start (loaidng frag is unencrypted)
96
- const { sn, cc } = loadingFrag;
97
- for (let i = 0; i < encryptedFragments.length; i++) {
98
- const frag = encryptedFragments[i];
99
- if (
100
- cc <= frag.cc &&
101
- (sn === 'initSegment' || frag.sn === 'initSegment' || sn < frag.sn)
102
- ) {
103
- this.emeController
104
- .selectKeySystemFormat(frag)
105
- .then((keySystemFormat) => {
106
- frag.setKeyFormat(keySystemFormat);
107
- });
108
- break;
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> {