hls.js 1.6.0-beta.2.0.canary.10882 → 1.6.0-beta.2.0.canary.10884
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 +21 -1
- package/dist/hls.d.ts +21 -1
- package/dist/hls.js +130 -98
- package/dist/hls.js.d.ts +21 -1
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +130 -98
- 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 +123 -93
- 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 +123 -93
- 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/config.ts +2 -0
- package/src/controller/gap-controller.ts +92 -58
- package/src/controller/stream-controller.ts +13 -3
- package/src/events.ts +3 -0
- package/src/hls.ts +2 -1
- package/src/loader/level-details.ts +0 -2
- package/src/loader/m3u8-parser.ts +1 -1
- package/src/types/events.ts +3 -0
- package/src/utils/buffer-helper.ts +5 -8
- package/src/utils/level-helper.ts +29 -35
package/package.json
CHANGED
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 {
|
63
|
-
|
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 (
|
82
|
-
|
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
|
-
|
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
|
-
|
106
|
-
|
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
|
-
!
|
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 =
|
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
|
-
|
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 (
|
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 (
|
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 {
|
218
|
-
|
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
|
-
(
|
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
|
-
|
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 {
|
287
|
-
|
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 {
|
375
|
-
|
376
|
-
|
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,
|
@@ -25,7 +25,6 @@ export class LevelDetails {
|
|
25
25
|
public advancedDateTime?: number;
|
26
26
|
public updated: boolean = true;
|
27
27
|
public advanced: boolean = true;
|
28
|
-
public availabilityDelay?: number; // Manifest reload synchronization
|
29
28
|
public misses: number = 0;
|
30
29
|
public startCC: number = 0;
|
31
30
|
public startSN: number = 0;
|
@@ -87,7 +86,6 @@ export class LevelDetails {
|
|
87
86
|
} else {
|
88
87
|
this.misses = previous.misses + 1;
|
89
88
|
}
|
90
|
-
this.availabilityDelay = previous.availabilityDelay;
|
91
89
|
}
|
92
90
|
|
93
91
|
get hasProgramDateTime(): boolean {
|
package/src/types/events.ts
CHANGED
@@ -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
|
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
|
|