hls.js 1.6.0-beta.2.0.canary.10924 → 1.6.0-beta.2.0.canary.10926
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 +68 -30
- package/dist/hls.d.ts +68 -30
- package/dist/hls.js +684 -496
- package/dist/hls.js.d.ts +68 -30
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +3882 -3693
- 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 +1140 -954
- 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 +684 -499
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/package.json +1 -1
- package/src/config.ts +15 -9
- package/src/controller/abr-controller.ts +2 -2
- package/src/controller/base-stream-controller.ts +16 -12
- package/src/controller/buffer-controller.ts +19 -22
- package/src/controller/error-controller.ts +2 -2
- package/src/controller/fragment-tracker.ts +1 -1
- package/src/controller/gap-controller.ts +273 -38
- package/src/controller/interstitials-controller.ts +14 -11
- package/src/controller/level-controller.ts +4 -0
- package/src/controller/stream-controller.ts +26 -73
- package/src/hls.ts +57 -3
- package/src/utils/buffer-helper.ts +35 -13
- package/src/utils/event-listener-helper.ts +16 -0
- package/src/utils/rendition-helper.ts +1 -1
@@ -1,44 +1,149 @@
|
|
1
1
|
import { State } from './base-stream-controller';
|
2
2
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
3
3
|
import { Events } from '../events';
|
4
|
+
import TaskLoop from '../task-loop';
|
4
5
|
import { PlaylistLevelType } from '../types/loader';
|
5
6
|
import { BufferHelper } from '../utils/buffer-helper';
|
6
|
-
import {
|
7
|
+
import {
|
8
|
+
addEventListener,
|
9
|
+
removeEventListener,
|
10
|
+
} from '../utils/event-listener-helper';
|
11
|
+
import type { InFlightData } from './base-stream-controller';
|
12
|
+
import type { InFlightFragments } from '../hls';
|
7
13
|
import type Hls from '../hls';
|
8
14
|
import type { FragmentTracker } from './fragment-tracker';
|
9
|
-
import type { Fragment } from '../loader/fragment';
|
10
|
-
import type {
|
15
|
+
import type { Fragment, MediaFragment } from '../loader/fragment';
|
16
|
+
import type { SourceBufferName } from '../types/buffer';
|
17
|
+
import type {
|
18
|
+
BufferAppendedData,
|
19
|
+
MediaAttachedData,
|
20
|
+
MediaDetachingData,
|
21
|
+
} from '../types/events';
|
11
22
|
import type { BufferInfo } from '../utils/buffer-helper';
|
12
23
|
|
13
24
|
export const MAX_START_GAP_JUMP = 2.0;
|
14
25
|
export const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
15
26
|
export const SKIP_BUFFER_RANGE_START = 0.05;
|
27
|
+
const TICK_INTERVAL = 100;
|
16
28
|
|
17
|
-
export default class GapController extends
|
18
|
-
private media: HTMLMediaElement | null = null;
|
19
|
-
private fragmentTracker: FragmentTracker | null = null;
|
29
|
+
export default class GapController extends TaskLoop {
|
20
30
|
private hls: Hls | null = null;
|
31
|
+
private fragmentTracker: FragmentTracker | null = null;
|
32
|
+
private media: HTMLMediaElement | null = null;
|
33
|
+
private mediaSource?: MediaSource;
|
34
|
+
|
21
35
|
private nudgeRetry: number = 0;
|
22
36
|
private stallReported: boolean = false;
|
23
37
|
private stalled: number | null = null;
|
24
38
|
private moved: boolean = false;
|
25
39
|
private seeking: boolean = false;
|
40
|
+
private buffered: Partial<Record<SourceBufferName, TimeRanges>> = {};
|
41
|
+
|
42
|
+
private lastCurrentTime: number = 0;
|
26
43
|
public ended: number = 0;
|
27
44
|
public waiting: number = 0;
|
28
45
|
|
29
|
-
constructor(
|
30
|
-
media: HTMLMediaElement,
|
31
|
-
fragmentTracker: FragmentTracker,
|
32
|
-
hls: Hls,
|
33
|
-
) {
|
46
|
+
constructor(hls: Hls, fragmentTracker: FragmentTracker) {
|
34
47
|
super('gap-controller', hls.logger);
|
35
|
-
this.media = media;
|
36
|
-
this.fragmentTracker = fragmentTracker;
|
37
48
|
this.hls = hls;
|
49
|
+
this.fragmentTracker = fragmentTracker;
|
50
|
+
this.registerListeners();
|
51
|
+
}
|
52
|
+
|
53
|
+
private registerListeners() {
|
54
|
+
const { hls } = this;
|
55
|
+
if (hls) {
|
56
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
57
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
58
|
+
hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
private unregisterListeners() {
|
63
|
+
const { hls } = this;
|
64
|
+
if (hls) {
|
65
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
66
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
67
|
+
hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
|
68
|
+
}
|
38
69
|
}
|
39
70
|
|
40
71
|
public destroy() {
|
72
|
+
super.destroy();
|
73
|
+
this.unregisterListeners();
|
41
74
|
this.media = this.hls = this.fragmentTracker = null;
|
75
|
+
this.mediaSource = undefined;
|
76
|
+
}
|
77
|
+
|
78
|
+
private onMediaAttached(
|
79
|
+
event: Events.MEDIA_ATTACHED,
|
80
|
+
data: MediaAttachedData,
|
81
|
+
) {
|
82
|
+
this.setInterval(TICK_INTERVAL);
|
83
|
+
this.mediaSource = data.mediaSource;
|
84
|
+
const media = (this.media = data.media);
|
85
|
+
addEventListener(media, 'playing', this.onMediaPlaying);
|
86
|
+
addEventListener(media, 'waiting', this.onMediaWaiting);
|
87
|
+
addEventListener(media, 'ended', this.onMediaEnded);
|
88
|
+
}
|
89
|
+
|
90
|
+
private onMediaDetaching(
|
91
|
+
event: Events.MEDIA_DETACHING,
|
92
|
+
data: MediaDetachingData,
|
93
|
+
) {
|
94
|
+
this.clearInterval();
|
95
|
+
const { media } = this;
|
96
|
+
if (media) {
|
97
|
+
removeEventListener(media, 'playing', this.onMediaPlaying);
|
98
|
+
removeEventListener(media, 'waiting', this.onMediaWaiting);
|
99
|
+
removeEventListener(media, 'ended', this.onMediaEnded);
|
100
|
+
this.media = null;
|
101
|
+
}
|
102
|
+
this.mediaSource = undefined;
|
103
|
+
}
|
104
|
+
|
105
|
+
private onBufferAppended(
|
106
|
+
event: Events.BUFFER_APPENDED,
|
107
|
+
data: BufferAppendedData,
|
108
|
+
) {
|
109
|
+
this.buffered = data.timeRanges;
|
110
|
+
}
|
111
|
+
|
112
|
+
private onMediaPlaying = () => {
|
113
|
+
this.ended = 0;
|
114
|
+
this.waiting = 0;
|
115
|
+
};
|
116
|
+
|
117
|
+
private onMediaWaiting = () => {
|
118
|
+
if (this.media?.seeking) {
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
this.waiting = self.performance.now();
|
122
|
+
this.tick();
|
123
|
+
};
|
124
|
+
|
125
|
+
private onMediaEnded = () => {
|
126
|
+
if (this.hls) {
|
127
|
+
// ended is set when triggering MEDIA_ENDED so that we do not trigger it again on stall or on tick with media.ended
|
128
|
+
this.ended = this.media?.currentTime || 1;
|
129
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
130
|
+
stalled: false,
|
131
|
+
});
|
132
|
+
}
|
133
|
+
};
|
134
|
+
|
135
|
+
public get hasBuffered(): boolean {
|
136
|
+
return Object.keys(this.buffered).length > 0;
|
137
|
+
}
|
138
|
+
|
139
|
+
public tick() {
|
140
|
+
if (!this.media?.readyState || !this.hasBuffered) {
|
141
|
+
return;
|
142
|
+
}
|
143
|
+
|
144
|
+
const currentTime = this.media.currentTime;
|
145
|
+
this.poll(currentTime, this.lastCurrentTime);
|
146
|
+
this.lastCurrentTime = currentTime;
|
42
147
|
}
|
43
148
|
|
44
149
|
/**
|
@@ -47,20 +152,20 @@ export default class GapController extends Logger {
|
|
47
152
|
*
|
48
153
|
* @param lastCurrentTime - Previously read playhead position
|
49
154
|
*/
|
50
|
-
public poll(
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
) {
|
155
|
+
public poll(currentTime: number, lastCurrentTime: number) {
|
156
|
+
const config = this.hls?.config;
|
157
|
+
if (!config) {
|
158
|
+
return;
|
159
|
+
}
|
56
160
|
const { media, stalled } = this;
|
57
|
-
|
58
161
|
if (!media) {
|
59
162
|
return;
|
60
163
|
}
|
61
|
-
const {
|
164
|
+
const { seeking } = media;
|
62
165
|
const seeked = this.seeking && !seeking;
|
63
166
|
const beginSeek = !this.seeking && seeking;
|
167
|
+
const pausedEndedOrHalted =
|
168
|
+
(media.paused && !seeking) || media.ended || media.playbackRate === 0;
|
64
169
|
|
65
170
|
this.seeking = seeking;
|
66
171
|
|
@@ -72,6 +177,14 @@ export default class GapController extends Logger {
|
|
72
177
|
this.moved = true;
|
73
178
|
if (!seeking) {
|
74
179
|
this.nudgeRetry = 0;
|
180
|
+
// When crossing between buffered video time ranges, but not audio, flush pipeline with seek (Chrome)
|
181
|
+
if (
|
182
|
+
config.nudgeOnVideoHole &&
|
183
|
+
!pausedEndedOrHalted &&
|
184
|
+
currentTime > lastCurrentTime
|
185
|
+
) {
|
186
|
+
this.nudgeOnVideoHole(currentTime, lastCurrentTime);
|
187
|
+
}
|
75
188
|
}
|
76
189
|
if (this.waiting === 0) {
|
77
190
|
this.stallResolved(currentTime);
|
@@ -88,7 +201,7 @@ export default class GapController extends Logger {
|
|
88
201
|
}
|
89
202
|
|
90
203
|
// The playhead should not be moving
|
91
|
-
if (
|
204
|
+
if (pausedEndedOrHalted) {
|
92
205
|
this.nudgeRetry = 0;
|
93
206
|
this.stallResolved(currentTime);
|
94
207
|
// Fire MEDIA_ENDED to workaround event not being dispatched by browser
|
@@ -106,28 +219,36 @@ export default class GapController extends Logger {
|
|
106
219
|
return;
|
107
220
|
}
|
108
221
|
|
222
|
+
// Resolve stalls at buffer holes using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
109
223
|
const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
|
110
224
|
const nextStart = bufferInfo.nextStart || 0;
|
111
225
|
const fragmentTracker = this.fragmentTracker;
|
112
226
|
|
113
|
-
if (seeking && fragmentTracker) {
|
227
|
+
if (seeking && fragmentTracker && this.hls) {
|
228
|
+
// Is there a fragment loading/parsing/appending before currentTime?
|
229
|
+
const inFlightDependency = getInFlightDependency(
|
230
|
+
this.hls.inFlightFragments,
|
231
|
+
currentTime,
|
232
|
+
);
|
233
|
+
|
114
234
|
// Waiting for seeking in a buffered range to complete
|
115
235
|
const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
|
116
236
|
// Next buffered range is too far ahead to jump to while still seeking
|
117
|
-
const
|
237
|
+
const noBufferHole =
|
118
238
|
!nextStart ||
|
119
|
-
|
239
|
+
inFlightDependency ||
|
120
240
|
(nextStart - currentTime > MAX_START_GAP_JUMP &&
|
121
241
|
!fragmentTracker.getPartialFragment(currentTime));
|
122
|
-
if (hasEnoughBuffer ||
|
242
|
+
if (hasEnoughBuffer || noBufferHole) {
|
123
243
|
return;
|
124
244
|
}
|
125
|
-
// Reset moved state when seeking to a point in or before a gap
|
245
|
+
// Reset moved state when seeking to a point in or before a gap/hole
|
126
246
|
this.moved = false;
|
127
247
|
}
|
128
248
|
|
129
249
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
130
250
|
// The addition poll gives the browser a chance to jump the gap for us
|
251
|
+
const levelDetails = this.hls?.latestLevelDetails;
|
131
252
|
if (!this.moved && this.stalled !== null && fragmentTracker) {
|
132
253
|
// There is no playable buffer (seeked, waiting for buffer)
|
133
254
|
const isBuffered = bufferInfo.len > 0;
|
@@ -155,10 +276,6 @@ export default class GapController extends Logger {
|
|
155
276
|
}
|
156
277
|
|
157
278
|
// Start tracking stall time
|
158
|
-
const config = this.hls?.config;
|
159
|
-
if (!config) {
|
160
|
-
return;
|
161
|
-
}
|
162
279
|
const detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
|
163
280
|
const tnow = self.performance.now();
|
164
281
|
const tWaiting = this.waiting;
|
@@ -180,7 +297,7 @@ export default class GapController extends Logger {
|
|
180
297
|
) {
|
181
298
|
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
182
299
|
if (
|
183
|
-
|
300
|
+
this.mediaSource?.readyState === 'ended' &&
|
184
301
|
!levelDetails?.live &&
|
185
302
|
Math.abs(currentTime - (levelDetails?.edge || 0)) < 1
|
186
303
|
) {
|
@@ -215,7 +332,7 @@ export default class GapController extends Logger {
|
|
215
332
|
// The playhead is now moving, but was previously stalled
|
216
333
|
if (this.stallReported) {
|
217
334
|
const stalledDuration = self.performance.now() - stalled;
|
218
|
-
this.
|
335
|
+
this.log(
|
219
336
|
`playback not stuck anymore @${currentTime}, after ${Math.round(
|
220
337
|
stalledDuration,
|
221
338
|
)}ms`,
|
@@ -227,6 +344,81 @@ export default class GapController extends Logger {
|
|
227
344
|
}
|
228
345
|
}
|
229
346
|
|
347
|
+
private nudgeOnVideoHole(currentTime: number, lastCurrentTime: number) {
|
348
|
+
// Chrome will play one second past a hole in video buffered time ranges without rendering any video from the subsequent range and then stall as long as audio is buffered:
|
349
|
+
// https://github.com/video-dev/hls.js/issues/5631
|
350
|
+
// https://issues.chromium.org/issues/40280613#comment10
|
351
|
+
// Detect the potential for this situation and proactively seek to flush the video pipeline once the playhead passes the start of the video hole.
|
352
|
+
// When there are audio and video buffers and currentTime is past the end of the first video buffered range...
|
353
|
+
const videoSourceBuffered = this.buffered.video;
|
354
|
+
if (
|
355
|
+
this.hls &&
|
356
|
+
this.media &&
|
357
|
+
this.fragmentTracker &&
|
358
|
+
this.buffered.audio?.length &&
|
359
|
+
videoSourceBuffered &&
|
360
|
+
videoSourceBuffered.length > 1 &&
|
361
|
+
currentTime > videoSourceBuffered.end(0)
|
362
|
+
) {
|
363
|
+
// and audio is buffered at the playhead
|
364
|
+
const audioBufferInfo = BufferHelper.bufferedInfo(
|
365
|
+
BufferHelper.timeRangesToArray(this.buffered.audio),
|
366
|
+
currentTime,
|
367
|
+
0,
|
368
|
+
);
|
369
|
+
if (audioBufferInfo.len > 1 && lastCurrentTime >= audioBufferInfo.start) {
|
370
|
+
const videoTimes = BufferHelper.timeRangesToArray(videoSourceBuffered);
|
371
|
+
const lastBufferedIndex = BufferHelper.bufferedInfo(
|
372
|
+
videoTimes,
|
373
|
+
lastCurrentTime,
|
374
|
+
0,
|
375
|
+
).bufferedIndex;
|
376
|
+
// nudge when crossing into another video buffered range (hole).
|
377
|
+
if (
|
378
|
+
lastBufferedIndex > -1 &&
|
379
|
+
lastBufferedIndex < videoTimes.length - 1
|
380
|
+
) {
|
381
|
+
const bufferedIndex = BufferHelper.bufferedInfo(
|
382
|
+
videoTimes,
|
383
|
+
currentTime,
|
384
|
+
0,
|
385
|
+
).bufferedIndex;
|
386
|
+
const holeStart = videoTimes[lastBufferedIndex].end;
|
387
|
+
const holeEnd = videoTimes[lastBufferedIndex + 1].start;
|
388
|
+
if (
|
389
|
+
(bufferedIndex === -1 || bufferedIndex > lastBufferedIndex) &&
|
390
|
+
holeEnd - holeStart < 1 && // `maxBufferHole` may be too small and setting it to 0 should not disable this feature
|
391
|
+
currentTime - holeStart < 2
|
392
|
+
) {
|
393
|
+
const error = new Error(
|
394
|
+
`nudging playhead to flush pipeline after video hole. currentTime: ${currentTime} hole: ${holeStart} -> ${holeEnd} buffered index: ${bufferedIndex}`,
|
395
|
+
);
|
396
|
+
this.warn(error.message);
|
397
|
+
// Magic number to flush the pipeline without interuption to audio playback:
|
398
|
+
this.media.currentTime += 0.000001;
|
399
|
+
const frag =
|
400
|
+
this.fragmentTracker.getPartialFragment(currentTime) || undefined;
|
401
|
+
const bufferInfo = BufferHelper.bufferInfo(
|
402
|
+
this.media,
|
403
|
+
currentTime,
|
404
|
+
0,
|
405
|
+
);
|
406
|
+
this.hls.trigger(Events.ERROR, {
|
407
|
+
type: ErrorTypes.MEDIA_ERROR,
|
408
|
+
details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
|
409
|
+
fatal: false,
|
410
|
+
error,
|
411
|
+
reason: error.message,
|
412
|
+
frag,
|
413
|
+
buffer: bufferInfo.len,
|
414
|
+
bufferInfo,
|
415
|
+
});
|
416
|
+
}
|
417
|
+
}
|
418
|
+
}
|
419
|
+
}
|
420
|
+
}
|
421
|
+
|
230
422
|
/**
|
231
423
|
* Detects and attempts to fix known buffer stalling issues.
|
232
424
|
* @param bufferInfo - The properties of the current buffer.
|
@@ -268,7 +460,8 @@ export default class GapController extends Logger {
|
|
268
460
|
bufferInfo.len > config.maxBufferHole) ||
|
269
461
|
(bufferInfo.nextStart &&
|
270
462
|
bufferInfo.nextStart - currentTime < config.maxBufferHole)) &&
|
271
|
-
stalledDurationMs > config.highBufferWatchdogPeriod * 1000
|
463
|
+
(stalledDurationMs > config.highBufferWatchdogPeriod * 1000 ||
|
464
|
+
this.waiting)
|
272
465
|
) {
|
273
466
|
this.warn('Trying to nudge playhead over buffer-hole');
|
274
467
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
@@ -310,7 +503,7 @@ export default class GapController extends Logger {
|
|
310
503
|
* @param partial - The partial fragment found at the current time (where playback is stalling).
|
311
504
|
* @private
|
312
505
|
*/
|
313
|
-
private _trySkipBufferHole(partial:
|
506
|
+
private _trySkipBufferHole(partial: MediaFragment | null): number {
|
314
507
|
const { fragmentTracker, media } = this;
|
315
508
|
const config = this.hls?.config;
|
316
509
|
if (!media || !fragmentTracker || !config) {
|
@@ -322,7 +515,7 @@ export default class GapController extends Logger {
|
|
322
515
|
const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
|
323
516
|
const startTime =
|
324
517
|
currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
|
325
|
-
if (startTime) {
|
518
|
+
if (startTime && this.hls) {
|
326
519
|
const bufferStarved = bufferInfo.len <= config.maxBufferHole;
|
327
520
|
const waiting =
|
328
521
|
bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
|
@@ -348,6 +541,19 @@ export default class GapController extends Logger {
|
|
348
541
|
PlaylistLevelType.MAIN,
|
349
542
|
);
|
350
543
|
if (startProvisioned) {
|
544
|
+
// Do not seek when selected variant playlist is unloaded
|
545
|
+
if (!this.hls.loadLevelObj?.details) {
|
546
|
+
return 0;
|
547
|
+
}
|
548
|
+
// Do not seek when required fragments are inflight or appending
|
549
|
+
const inFlightDependency = getInFlightDependency(
|
550
|
+
this.hls.inFlightFragments,
|
551
|
+
startTime,
|
552
|
+
);
|
553
|
+
if (inFlightDependency) {
|
554
|
+
return 0;
|
555
|
+
}
|
556
|
+
// Do not seek if we can't walk tracked fragments to end of gap
|
351
557
|
let moreToLoad = false;
|
352
558
|
let pos = startProvisioned.end;
|
353
559
|
while (pos < startTime) {
|
@@ -374,7 +580,7 @@ export default class GapController extends Logger {
|
|
374
580
|
);
|
375
581
|
this.moved = true;
|
376
582
|
media.currentTime = targetTime;
|
377
|
-
if (
|
583
|
+
if (!partial?.gap) {
|
378
584
|
const error = new Error(
|
379
585
|
`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`,
|
380
586
|
);
|
@@ -384,7 +590,7 @@ export default class GapController extends Logger {
|
|
384
590
|
fatal: false,
|
385
591
|
error,
|
386
592
|
reason: error.message,
|
387
|
-
frag: partial,
|
593
|
+
frag: partial || undefined,
|
388
594
|
buffer: bufferInfo.len,
|
389
595
|
bufferInfo,
|
390
596
|
});
|
@@ -440,3 +646,32 @@ export default class GapController extends Logger {
|
|
440
646
|
}
|
441
647
|
}
|
442
648
|
}
|
649
|
+
|
650
|
+
function getInFlightDependency(
|
651
|
+
inFlightFragments: InFlightFragments,
|
652
|
+
currentTime: number,
|
653
|
+
): Fragment | null {
|
654
|
+
const main = inFlight(inFlightFragments.main);
|
655
|
+
if (main && main.start <= currentTime) {
|
656
|
+
return main;
|
657
|
+
}
|
658
|
+
const audio = inFlight(inFlightFragments.audio);
|
659
|
+
if (audio && audio.start <= currentTime) {
|
660
|
+
return audio;
|
661
|
+
}
|
662
|
+
return null;
|
663
|
+
}
|
664
|
+
|
665
|
+
function inFlight(inFlightData: InFlightData | undefined): Fragment | null {
|
666
|
+
if (!inFlightData) {
|
667
|
+
return null;
|
668
|
+
}
|
669
|
+
switch (inFlightData.state) {
|
670
|
+
case State.IDLE:
|
671
|
+
case State.STOPPED:
|
672
|
+
case State.ENDED:
|
673
|
+
case State.ERROR:
|
674
|
+
return null;
|
675
|
+
}
|
676
|
+
return inFlightData.frag;
|
677
|
+
}
|
@@ -22,6 +22,10 @@ import {
|
|
22
22
|
TimelineOccupancy,
|
23
23
|
} from '../loader/interstitial-event';
|
24
24
|
import { BufferHelper } from '../utils/buffer-helper';
|
25
|
+
import {
|
26
|
+
addEventListener,
|
27
|
+
removeEventListener,
|
28
|
+
} from '../utils/event-listener-helper';
|
25
29
|
import { hash } from '../utils/hash';
|
26
30
|
import { Logger } from '../utils/logger';
|
27
31
|
import { isCompatibleTrackChange } from '../utils/mediasource-helper';
|
@@ -226,17 +230,17 @@ export default class InterstitialsController
|
|
226
230
|
}
|
227
231
|
|
228
232
|
private onDestroying() {
|
229
|
-
const media = this.primaryMedia;
|
233
|
+
const media = this.primaryMedia || this.media;
|
230
234
|
if (media) {
|
231
235
|
this.removeMediaListeners(media);
|
232
236
|
}
|
233
237
|
}
|
234
238
|
|
235
239
|
private removeMediaListeners(media: HTMLMediaElement) {
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
+
removeEventListener(media, 'play', this.onPlay);
|
241
|
+
removeEventListener(media, 'pause', this.onPause);
|
242
|
+
removeEventListener(media, 'seeking', this.onSeeking);
|
243
|
+
removeEventListener(media, 'timeupdate', this.onTimeupdate);
|
240
244
|
}
|
241
245
|
|
242
246
|
private onMediaAttaching(
|
@@ -244,11 +248,10 @@ export default class InterstitialsController
|
|
244
248
|
data: MediaAttachingData,
|
245
249
|
) {
|
246
250
|
const media = (this.media = data.media);
|
247
|
-
this.
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
media.addEventListener('pause', this.onPause);
|
251
|
+
addEventListener(media, 'seeking', this.onSeeking);
|
252
|
+
addEventListener(media, 'timeupdate', this.onTimeupdate);
|
253
|
+
addEventListener(media, 'play', this.onPlay);
|
254
|
+
addEventListener(media, 'pause', this.onPause);
|
252
255
|
}
|
253
256
|
|
254
257
|
private onMediaAttached(
|
@@ -1918,7 +1921,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
|
|
1918
1921
|
const userConfig = primary.userConfig;
|
1919
1922
|
let videoPreference = userConfig.videoPreference;
|
1920
1923
|
const currentLevel =
|
1921
|
-
primary.
|
1924
|
+
primary.loadLevelObj || primary.levels[primary.currentLevel];
|
1922
1925
|
if (videoPreference || currentLevel) {
|
1923
1926
|
videoPreference = Object.assign({}, videoPreference);
|
1924
1927
|
if (currentLevel.videoCodec) {
|
@@ -389,6 +389,10 @@ export default class LevelController extends BasePlaylistController {
|
|
389
389
|
return this._levels;
|
390
390
|
}
|
391
391
|
|
392
|
+
get loadLevelObj(): Level | null {
|
393
|
+
return this.currentLevel;
|
394
|
+
}
|
395
|
+
|
392
396
|
get level(): number {
|
393
397
|
return this.currentLevelIndex;
|
394
398
|
}
|