hls.js 1.6.0-beta.2.0.canary.10880 → 1.6.0-beta.2.0.canary.10883

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/package.json CHANGED
@@ -123,7 +123,7 @@
123
123
  "npm-run-all2": "6.2.6",
124
124
  "prettier": "3.4.2",
125
125
  "promise-polyfill": "8.3.0",
126
- "rollup": "4.29.2",
126
+ "rollup": "4.30.1",
127
127
  "rollup-plugin-istanbul": "5.0.0",
128
128
  "sauce-connect-launcher": "1.3.2",
129
129
  "selenium-webdriver": "4.27.0",
@@ -134,5 +134,5 @@
134
134
  "url-toolkit": "2.2.5",
135
135
  "wrangler": "3.99.0"
136
136
  },
137
- "version": "1.6.0-beta.2.0.canary.10880"
137
+ "version": "1.6.0-beta.2.0.canary.10883"
138
138
  }
package/src/config.ts CHANGED
@@ -319,6 +319,7 @@ export type HlsConfig = {
319
319
  progressive: boolean;
320
320
  lowLatencyMode: boolean;
321
321
  primarySessionId?: string;
322
+ detectStallWithCurrentTimeMs: number;
322
323
  } & ABRControllerConfig &
323
324
  BufferControllerConfig &
324
325
  CapLevelControllerConfig &
@@ -427,6 +428,7 @@ export const hlsDefaultConfig: HlsConfig = {
427
428
  progressive: false,
428
429
  lowLatencyMode: true,
429
430
  cmcd: undefined,
431
+ detectStallWithCurrentTimeMs: 1250,
430
432
  enableDateRangeMetadataCues: true,
431
433
  enableEmsgMetadataCues: true,
432
434
  enableEmsgKLVMetadata: false,
@@ -4,47 +4,41 @@ import { Events } from '../events';
4
4
  import { PlaylistLevelType } from '../types/loader';
5
5
  import { BufferHelper } from '../utils/buffer-helper';
6
6
  import { Logger } from '../utils/logger';
7
- import type { HlsConfig } from '../config';
8
7
  import type Hls from '../hls';
9
8
  import type { FragmentTracker } from './fragment-tracker';
10
9
  import type { Fragment } from '../loader/fragment';
11
10
  import type { LevelDetails } from '../loader/level-details';
12
11
  import type { BufferInfo } from '../utils/buffer-helper';
13
12
 
14
- export const STALL_MINIMUM_DURATION_MS = 250;
15
13
  export const MAX_START_GAP_JUMP = 2.0;
16
14
  export const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
17
15
  export const SKIP_BUFFER_RANGE_START = 0.05;
18
16
 
19
17
  export default class GapController extends Logger {
20
- private config: HlsConfig;
21
18
  private media: HTMLMediaElement | null = null;
22
- private fragmentTracker: FragmentTracker;
23
- private hls: Hls;
19
+ private fragmentTracker: FragmentTracker | null = null;
20
+ private hls: Hls | null = null;
24
21
  private nudgeRetry: number = 0;
25
22
  private stallReported: boolean = false;
26
23
  private stalled: number | null = null;
27
24
  private moved: boolean = false;
28
25
  private seeking: boolean = false;
29
26
  public ended: number = 0;
27
+ public waiting: number = 0;
30
28
 
31
29
  constructor(
32
- config: HlsConfig,
33
30
  media: HTMLMediaElement,
34
31
  fragmentTracker: FragmentTracker,
35
32
  hls: Hls,
36
33
  ) {
37
34
  super('gap-controller', hls.logger);
38
- this.config = config;
39
35
  this.media = media;
40
36
  this.fragmentTracker = fragmentTracker;
41
37
  this.hls = hls;
42
38
  }
43
39
 
44
40
  public destroy() {
45
- this.media = null;
46
- // @ts-ignore
47
- this.hls = this.fragmentTracker = null;
41
+ this.media = this.hls = this.fragmentTracker = null;
48
42
  }
49
43
 
50
44
  /**
@@ -59,8 +53,9 @@ export default class GapController extends Logger {
59
53
  levelDetails: LevelDetails | undefined,
60
54
  state: string,
61
55
  ) {
62
- const { config, media, stalled } = this;
63
- if (media === null) {
56
+ const { media, stalled } = this;
57
+
58
+ if (!media) {
64
59
  return;
65
60
  }
66
61
  const { currentTime, seeking } = media;
@@ -78,50 +73,44 @@ export default class GapController extends Logger {
78
73
  if (!seeking) {
79
74
  this.nudgeRetry = 0;
80
75
  }
81
- if (stalled !== null) {
82
- // The playhead is now moving, but was previously stalled
83
- if (this.stallReported) {
84
- const stalledDuration = self.performance.now() - stalled;
85
- this.warn(
86
- `playback not stuck anymore @${currentTime}, after ${Math.round(
87
- stalledDuration,
88
- )}ms`,
89
- );
90
- this.stallReported = false;
91
- }
92
- this.stalled = null;
76
+ if (this.waiting === 0) {
77
+ this.stallResolved(currentTime);
93
78
  }
94
79
  return;
95
80
  }
96
81
 
97
82
  // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
98
83
  if (beginSeek || seeked) {
99
- this.stalled = null;
84
+ if (seeked) {
85
+ this.stallResolved(currentTime);
86
+ }
100
87
  return;
101
88
  }
102
89
 
103
90
  // The playhead should not be moving
104
- if (
105
- (media.paused && !seeking) ||
106
- media.ended ||
107
- media.playbackRate === 0 ||
108
- !BufferHelper.getBuffered(media).length
109
- ) {
91
+ if ((media.paused && !seeking) || media.ended || media.playbackRate === 0) {
92
+ this.nudgeRetry = 0;
93
+ this.stallResolved(currentTime);
110
94
  // Fire MEDIA_ENDED to workaround event not being dispatched by browser
111
- if (!this.ended && media.ended) {
95
+ if (!this.ended && media.ended && this.hls) {
112
96
  this.ended = currentTime || 1;
113
97
  this.hls.trigger(Events.MEDIA_ENDED, {
114
98
  stalled: false,
115
99
  });
116
100
  }
101
+ return;
102
+ }
103
+
104
+ if (!BufferHelper.getBuffered(media).length) {
117
105
  this.nudgeRetry = 0;
118
106
  return;
119
107
  }
120
108
 
121
109
  const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
122
110
  const nextStart = bufferInfo.nextStart || 0;
111
+ const fragmentTracker = this.fragmentTracker;
123
112
 
124
- if (seeking) {
113
+ if (seeking && fragmentTracker) {
125
114
  // Waiting for seeking in a buffered range to complete
126
115
  const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
127
116
  // Next buffered range is too far ahead to jump to while still seeking
@@ -129,7 +118,7 @@ export default class GapController extends Logger {
129
118
  !nextStart ||
130
119
  (activeFrag && activeFrag.start <= currentTime) ||
131
120
  (nextStart - currentTime > MAX_START_GAP_JUMP &&
132
- !this.fragmentTracker.getPartialFragment(currentTime));
121
+ !fragmentTracker.getPartialFragment(currentTime));
133
122
  if (hasEnoughBuffer || noBufferGap) {
134
123
  return;
135
124
  }
@@ -139,7 +128,7 @@ export default class GapController extends Logger {
139
128
 
140
129
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
141
130
  // The addition poll gives the browser a chance to jump the gap for us
142
- if (!this.moved && this.stalled !== null) {
131
+ if (!this.moved && this.stalled !== null && fragmentTracker) {
143
132
  // There is no playable buffer (seeked, waiting for buffer)
144
133
  const isBuffered = bufferInfo.len > 0;
145
134
  if (!isBuffered && !nextStart) {
@@ -156,7 +145,7 @@ export default class GapController extends Logger {
156
145
  const maxStartGapJump = isLive
157
146
  ? levelDetails!.targetduration * 2
158
147
  : MAX_START_GAP_JUMP;
159
- const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
148
+ const partialOrGap = fragmentTracker.getPartialFragment(currentTime);
160
149
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
161
150
  if (!media.paused) {
162
151
  this._trySkipBufferHole(partialOrGap);
@@ -166,21 +155,36 @@ export default class GapController extends Logger {
166
155
  }
167
156
 
168
157
  // Start tracking stall time
158
+ const config = this.hls?.config;
159
+ if (!config) {
160
+ return;
161
+ }
162
+ const detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
169
163
  const tnow = self.performance.now();
164
+ const tWaiting = this.waiting;
170
165
  if (stalled === null) {
171
- this.stalled = tnow;
166
+ // Use time of recent "waiting" event
167
+ if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
168
+ this.stalled = tWaiting;
169
+ } else {
170
+ this.stalled = tnow;
171
+ }
172
172
  return;
173
173
  }
174
174
 
175
175
  const stalledDuration = tnow - stalled;
176
- if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
176
+ if (
177
+ !seeking &&
178
+ (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) &&
179
+ this.hls
180
+ ) {
177
181
  // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
178
182
  if (
179
183
  state === State.ENDED &&
180
184
  !levelDetails?.live &&
181
185
  Math.abs(currentTime - (levelDetails?.edge || 0)) < 1
182
186
  ) {
183
- if (stalledDuration < 1000 || this.ended) {
187
+ if (this.ended) {
184
188
  return;
185
189
  }
186
190
  this.ended = currentTime || 1;
@@ -191,7 +195,7 @@ export default class GapController extends Logger {
191
195
  }
192
196
  // Report stalling after trying to fix
193
197
  this._reportStall(bufferInfo);
194
- if (!this.media) {
198
+ if (!this.media || !this.hls) {
195
199
  return;
196
200
  }
197
201
  }
@@ -204,6 +208,25 @@ export default class GapController extends Logger {
204
208
  this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
205
209
  }
206
210
 
211
+ private stallResolved(currentTime: number) {
212
+ const stalled = this.stalled;
213
+ if (stalled && this.hls) {
214
+ this.stalled = null;
215
+ // The playhead is now moving, but was previously stalled
216
+ if (this.stallReported) {
217
+ const stalledDuration = self.performance.now() - stalled;
218
+ this.warn(
219
+ `playback not stuck anymore @${currentTime}, after ${Math.round(
220
+ stalledDuration,
221
+ )}ms`,
222
+ );
223
+ this.stallReported = false;
224
+ this.waiting = 0;
225
+ this.hls.trigger(Events.STALL_RESOLVED, {});
226
+ }
227
+ }
228
+ }
229
+
207
230
  /**
208
231
  * Detects and attempts to fix known buffer stalling issues.
209
232
  * @param bufferInfo - The properties of the current buffer.
@@ -214,10 +237,12 @@ export default class GapController extends Logger {
214
237
  bufferInfo: BufferInfo,
215
238
  stalledDurationMs: number,
216
239
  ) {
217
- const { config, fragmentTracker, media } = this;
218
- if (media === null) {
240
+ const { fragmentTracker, media } = this;
241
+ const config = this.hls?.config;
242
+ if (!media || !fragmentTracker || !config) {
219
243
  return;
220
244
  }
245
+
221
246
  const currentTime = media.currentTime;
222
247
 
223
248
  const partial = fragmentTracker.getPartialFragment(currentTime);
@@ -236,8 +261,11 @@ export default class GapController extends Logger {
236
261
  // we may just have to "nudge" the playlist as the browser decoding/rendering engine
237
262
  // needs to cross some sort of threshold covering all source-buffers content
238
263
  // to start playing properly.
264
+ const bufferedRanges = bufferInfo.buffered;
239
265
  if (
240
- (bufferInfo.len > config.maxBufferHole ||
266
+ ((bufferedRanges &&
267
+ bufferedRanges.length > 1 &&
268
+ bufferInfo.len > config.maxBufferHole) ||
241
269
  (bufferInfo.nextStart &&
242
270
  bufferInfo.nextStart - currentTime < config.maxBufferHole)) &&
243
271
  stalledDurationMs > config.highBufferWatchdogPeriod * 1000
@@ -245,9 +273,7 @@ export default class GapController extends Logger {
245
273
  this.warn('Trying to nudge playhead over buffer-hole');
246
274
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
247
275
  // We only try to jump the hole if it's under the configured size
248
- // Reset stalled so to rearm watchdog timer
249
- this.stalled = null;
250
- this._tryNudgeBuffer();
276
+ this._tryNudgeBuffer(bufferInfo);
251
277
  }
252
278
  }
253
279
 
@@ -257,8 +283,8 @@ export default class GapController extends Logger {
257
283
  * @private
258
284
  */
259
285
  private _reportStall(bufferInfo: BufferInfo) {
260
- const { hls, media, stallReported } = this;
261
- if (!stallReported && media) {
286
+ const { hls, media, stallReported, stalled } = this;
287
+ if (!stallReported && stalled !== null && media && hls) {
262
288
  // Report stalled error once
263
289
  this.stallReported = true;
264
290
  const error = new Error(
@@ -273,6 +299,8 @@ export default class GapController extends Logger {
273
299
  fatal: false,
274
300
  error,
275
301
  buffer: bufferInfo.len,
302
+ bufferInfo,
303
+ stalled: { start: stalled },
276
304
  });
277
305
  }
278
306
  }
@@ -283,8 +311,9 @@ export default class GapController extends Logger {
283
311
  * @private
284
312
  */
285
313
  private _trySkipBufferHole(partial: Fragment | null): number {
286
- const { config, hls, media } = this;
287
- if (media === null) {
314
+ const { fragmentTracker, media } = this;
315
+ const config = this.hls?.config;
316
+ if (!media || !fragmentTracker || !config) {
288
317
  return 0;
289
318
  }
290
319
 
@@ -301,7 +330,6 @@ export default class GapController extends Logger {
301
330
  if (gapLength > 0 && (bufferStarved || waiting)) {
302
331
  // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
303
332
  if (gapLength > config.maxBufferHole) {
304
- const { fragmentTracker } = this;
305
333
  let startGap = false;
306
334
  if (currentTime === 0) {
307
335
  const startFrag = fragmentTracker.getAppendedFrag(
@@ -345,19 +373,20 @@ export default class GapController extends Logger {
345
373
  `skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`,
346
374
  );
347
375
  this.moved = true;
348
- this.stalled = null;
349
376
  media.currentTime = targetTime;
350
- if (partial && !partial.gap) {
377
+ if (partial && !partial.gap && this.hls) {
351
378
  const error = new Error(
352
379
  `fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`,
353
380
  );
354
- hls.trigger(Events.ERROR, {
381
+ this.hls.trigger(Events.ERROR, {
355
382
  type: ErrorTypes.MEDIA_ERROR,
356
383
  details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
357
384
  fatal: false,
358
385
  error,
359
386
  reason: error.message,
360
387
  frag: partial,
388
+ buffer: bufferInfo.len,
389
+ bufferInfo,
361
390
  });
362
391
  }
363
392
  return targetTime;
@@ -370,10 +399,11 @@ export default class GapController extends Logger {
370
399
  * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
371
400
  * @private
372
401
  */
373
- private _tryNudgeBuffer() {
374
- const { config, hls, media, nudgeRetry } = this;
375
- if (media === null) {
376
- return;
402
+ private _tryNudgeBuffer(bufferInfo: BufferInfo) {
403
+ const { hls, media, nudgeRetry } = this;
404
+ const config = hls?.config;
405
+ if (!media || !config) {
406
+ return 0;
377
407
  }
378
408
  const currentTime = media.currentTime;
379
409
  this.nudgeRetry++;
@@ -391,6 +421,8 @@ export default class GapController extends Logger {
391
421
  details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
392
422
  error,
393
423
  fatal: false,
424
+ buffer: bufferInfo.len,
425
+ bufferInfo,
394
426
  });
395
427
  } else {
396
428
  const error = new Error(
@@ -402,6 +434,8 @@ export default class GapController extends Logger {
402
434
  details: ErrorDetails.BUFFER_STALLED_ERROR,
403
435
  error,
404
436
  fatal: true,
437
+ buffer: bufferInfo.len,
438
+ bufferInfo,
405
439
  });
406
440
  }
407
441
  }
@@ -123,7 +123,7 @@ export default class StreamController
123
123
 
124
124
  protected onHandlerDestroying() {
125
125
  // @ts-ignore
126
- this.onMediaPlaying = this.onMediaSeeked = null;
126
+ this.onMediaPlaying = this.onMediaSeeked = this.onMediaWaiting = null;
127
127
  this.unregisterListeners();
128
128
  super.onHandlerDestroying();
129
129
  }
@@ -535,10 +535,11 @@ export default class StreamController
535
535
  const media = data.media;
536
536
  media.removeEventListener('playing', this.onMediaPlaying);
537
537
  media.removeEventListener('seeked', this.onMediaSeeked);
538
+ media.removeEventListener('waiting', this.onMediaWaiting);
538
539
  media.addEventListener('playing', this.onMediaPlaying);
539
540
  media.addEventListener('seeked', this.onMediaSeeked);
541
+ media.addEventListener('waiting', this.onMediaWaiting);
540
542
  this.gapController = new GapController(
541
- this.config,
542
543
  media,
543
544
  this.fragmentTracker,
544
545
  this.hls,
@@ -553,6 +554,7 @@ export default class StreamController
553
554
  if (media) {
554
555
  media.removeEventListener('playing', this.onMediaPlaying);
555
556
  media.removeEventListener('seeked', this.onMediaSeeked);
557
+ media.removeEventListener('waiting', this.onMediaWaiting);
556
558
  }
557
559
  this.videoBuffer = null;
558
560
  this.fragPlaying = null;
@@ -568,11 +570,19 @@ export default class StreamController
568
570
  this._hasEnoughToStart = false;
569
571
  }
570
572
 
573
+ private onMediaWaiting = () => {
574
+ const gapController = this.gapController;
575
+ if (gapController) {
576
+ gapController.waiting = self.performance.now();
577
+ }
578
+ };
579
+
571
580
  private onMediaPlaying = () => {
572
581
  // tick to speed up FRAG_CHANGED triggering
573
582
  const gapController = this.gapController;
574
583
  if (gapController) {
575
584
  gapController.ended = 0;
585
+ gapController.waiting = 0;
576
586
  }
577
587
  this.tick();
578
588
  };
@@ -1119,7 +1129,7 @@ export default class StreamController
1119
1129
  let startPosition = this.startPosition;
1120
1130
  // only adjust currentTime if different from startPosition or if startPosition not buffered
1121
1131
  // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered
1122
- if (startPosition >= 0) {
1132
+ if (startPosition >= 0 && currentTime < startPosition) {
1123
1133
  if (media.seeking) {
1124
1134
  this.log(
1125
1135
  `could not seek to ${startPosition}, already seeking at ${currentTime}`,
package/src/events.ts CHANGED
@@ -78,6 +78,8 @@ export enum Events {
78
78
  MEDIA_DETACHED = 'hlsMediaDetached',
79
79
  // Fired when HTMLMediaElement dispatches "ended" event, or stalls at end of VOD program
80
80
  MEDIA_ENDED = 'hlsMediaEnded',
81
+ // Fired after playback stall is resolved with playing, seeked, or ended event following BUFFER_STALLED_ERROR
82
+ STALL_RESOLVED = 'hlsStallResolved',
81
83
  // Fired when the buffer is going to be reset
82
84
  BUFFER_RESET = 'hlsBufferReset',
83
85
  // Fired when we know about the codecs that we need buffers for to push into - data: {tracks : { container, codec, levelCodec, initSegment, metadata }}
@@ -244,6 +246,7 @@ export interface HlsListeners {
244
246
  event: Events.MEDIA_ENDED,
245
247
  data: MediaEndedData,
246
248
  ) => void;
249
+ [Events.STALL_RESOLVED]: (event: Events.STALL_RESOLVED, data: {}) => void;
247
250
  [Events.BUFFER_RESET]: (event: Events.BUFFER_RESET) => void;
248
251
  [Events.BUFFER_CODECS]: (
249
252
  event: Events.BUFFER_CODECS,
package/src/hls.ts CHANGED
@@ -54,7 +54,7 @@ import type {
54
54
  SubtitleSelectionOption,
55
55
  VideoSelectionOption,
56
56
  } from './types/media-playlist';
57
- import type { BufferInfo } from './utils/buffer-helper';
57
+ import type { BufferInfo, BufferTimeRange } from './utils/buffer-helper';
58
58
  import type EwmaBandWidthEstimator from './utils/ewma-bandwidth-estimator';
59
59
  import type { MediaDecodingInfo } from './utils/mediacapabilities-helper';
60
60
 
@@ -1195,6 +1195,7 @@ export type {
1195
1195
  HlsEventEmitter,
1196
1196
  HlsConfig,
1197
1197
  BufferInfo,
1198
+ BufferTimeRange,
1198
1199
  HdcpLevel,
1199
1200
  AbrController,
1200
1201
  AudioStreamController,
@@ -47,6 +47,7 @@ import type { LevelDetails } from '../loader/level-details';
47
47
  import type { LevelKey } from '../loader/level-key';
48
48
  import type { LoadStats } from '../loader/load-stats';
49
49
  import type { AttrList } from '../utils/attr-list';
50
+ import type { BufferInfo } from '../utils/buffer-helper';
50
51
 
51
52
  export interface MediaAttachingData {
52
53
  media: HTMLMediaElement;
@@ -313,6 +314,7 @@ export interface ErrorData {
313
314
  fatal: boolean;
314
315
  errorAction?: IErrorAction;
315
316
  buffer?: number;
317
+ bufferInfo?: BufferInfo;
316
318
  bytes?: number;
317
319
  chunkMeta?: ChunkMetadata;
318
320
  context?: PlaylistLoaderContext;
@@ -323,6 +325,7 @@ export interface ErrorData {
323
325
  levelRetry?: boolean;
324
326
  loader?: Loader<LoaderContext>;
325
327
  networkDetails?: any;
328
+ stalled?: { start: number };
326
329
  stats?: LoaderStats;
327
330
  mimeType?: string;
328
331
  reason?: string;
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { logger } from './logger';
10
10
 
11
- type BufferTimeRange = {
11
+ export type BufferTimeRange = {
12
12
  start: number;
13
13
  end: number;
14
14
  };
@@ -22,6 +22,7 @@ export type BufferInfo = {
22
22
  start: number;
23
23
  end: number;
24
24
  nextStart?: number;
25
+ buffered?: BufferTimeRange[];
25
26
  };
26
27
 
27
28
  const noopBuffered: TimeRanges = {
@@ -61,19 +62,14 @@ export class BufferHelper {
61
62
  return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
62
63
  }
63
64
  }
64
- return { len: 0, start: pos, end: pos, nextStart: undefined };
65
+ return { len: 0, start: pos, end: pos };
65
66
  }
66
67
 
67
68
  static bufferedInfo(
68
69
  buffered: BufferTimeRange[],
69
70
  pos: number,
70
71
  maxHoleDuration: number,
71
- ): {
72
- len: number;
73
- start: number;
74
- end: number;
75
- nextStart?: number;
76
- } {
72
+ ): BufferInfo {
77
73
  pos = Math.max(0, pos);
78
74
  // sort on buffer.start/smaller end (IE does not always return sorted buffered range)
79
75
  buffered.sort((a, b) => a.start - b.start || b.end - a.end);
@@ -136,6 +132,7 @@ export class BufferHelper {
136
132
  start: bufferStart || 0,
137
133
  end: bufferEnd || 0,
138
134
  nextStart: bufferStartNext,
135
+ buffered,
139
136
  };
140
137
  }
141
138