hls.js 1.5.6 → 1.5.7-0.canary.10015
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/README.md +1 -0
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2077 -1166
- package/dist/hls.js.d.ts +65 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1149 -858
- 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 +985 -695
- 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 +1758 -862
- 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 +21 -21
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +21 -20
- package/src/controller/audio-stream-controller.ts +15 -16
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +20 -8
- package/src/controller/base-stream-controller.ts +149 -33
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +1 -2
- package/src/controller/cmcd-controller.ts +27 -6
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +12 -18
- package/src/controller/stream-controller.ts +24 -31
- package/src/controller/subtitle-stream-controller.ts +13 -14
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +42 -34
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/loader/playlist-loader.ts +4 -5
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/codecs.ts +33 -4
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +54 -24
- package/src/utils/mp4-tools.ts +3 -1
@@ -1,5 +1,4 @@
|
|
1
1
|
import { Events } from '../events';
|
2
|
-
import { logger } from '../utils/logger';
|
3
2
|
import type { ComponentAPI } from '../types/component-api';
|
4
3
|
import type Hls from '../hls';
|
5
4
|
import type { MediaAttachingData } from '../types/events';
|
@@ -84,13 +83,13 @@ class FPSController implements ComponentAPI {
|
|
84
83
|
totalDroppedFrames: droppedFrames,
|
85
84
|
});
|
86
85
|
if (droppedFPS > 0) {
|
87
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
86
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
88
87
|
if (
|
89
88
|
currentDropped >
|
90
89
|
hls.config.fpsDroppedMonitoringThreshold * currentDecoded
|
91
90
|
) {
|
92
91
|
let currentLevel = hls.currentLevel;
|
93
|
-
logger.warn(
|
92
|
+
hls.logger.warn(
|
94
93
|
'drop FPS ratio greater than max allowed value for currentLevel: ' +
|
95
94
|
currentLevel,
|
96
95
|
);
|
@@ -1,20 +1,22 @@
|
|
1
|
-
import
|
1
|
+
import { State } from './base-stream-controller';
|
2
2
|
import { BufferHelper } from '../utils/buffer-helper';
|
3
3
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
4
4
|
import { PlaylistLevelType } from '../types/loader';
|
5
5
|
import { Events } from '../events';
|
6
|
-
import {
|
6
|
+
import { Logger } from '../utils/logger';
|
7
7
|
import type Hls from '../hls';
|
8
|
+
import type { BufferInfo } from '../utils/buffer-helper';
|
8
9
|
import type { HlsConfig } from '../config';
|
9
10
|
import type { Fragment } from '../loader/fragment';
|
10
11
|
import type { FragmentTracker } from './fragment-tracker';
|
12
|
+
import type { LevelDetails } from '../loader/level-details';
|
11
13
|
|
12
14
|
export const STALL_MINIMUM_DURATION_MS = 250;
|
13
15
|
export const MAX_START_GAP_JUMP = 2.0;
|
14
16
|
export const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
15
17
|
export const SKIP_BUFFER_RANGE_START = 0.05;
|
16
18
|
|
17
|
-
export default class GapController {
|
19
|
+
export default class GapController extends Logger {
|
18
20
|
private config: HlsConfig;
|
19
21
|
private media: HTMLMediaElement | null = null;
|
20
22
|
private fragmentTracker: FragmentTracker;
|
@@ -24,8 +26,15 @@ export default class GapController {
|
|
24
26
|
private stalled: number | null = null;
|
25
27
|
private moved: boolean = false;
|
26
28
|
private seeking: boolean = false;
|
29
|
+
private ended: number = 0;
|
27
30
|
|
28
|
-
constructor(
|
31
|
+
constructor(
|
32
|
+
config: HlsConfig,
|
33
|
+
media: HTMLMediaElement,
|
34
|
+
fragmentTracker: FragmentTracker,
|
35
|
+
hls: Hls,
|
36
|
+
) {
|
37
|
+
super('gap-controller', hls.logger);
|
29
38
|
this.config = config;
|
30
39
|
this.media = media;
|
31
40
|
this.fragmentTracker = fragmentTracker;
|
@@ -44,7 +53,12 @@ export default class GapController {
|
|
44
53
|
*
|
45
54
|
* @param lastCurrentTime - Previously read playhead position
|
46
55
|
*/
|
47
|
-
public poll(
|
56
|
+
public poll(
|
57
|
+
lastCurrentTime: number,
|
58
|
+
activeFrag: Fragment | null,
|
59
|
+
levelDetails: LevelDetails | undefined,
|
60
|
+
state: string,
|
61
|
+
) {
|
48
62
|
const { config, media, stalled } = this;
|
49
63
|
if (media === null) {
|
50
64
|
return;
|
@@ -57,6 +71,7 @@ export default class GapController {
|
|
57
71
|
|
58
72
|
// The playhead is moving, no-op
|
59
73
|
if (currentTime !== lastCurrentTime) {
|
74
|
+
this.ended = 0;
|
60
75
|
this.moved = true;
|
61
76
|
if (!seeking) {
|
62
77
|
this.nudgeRetry = 0;
|
@@ -65,7 +80,7 @@ export default class GapController {
|
|
65
80
|
// The playhead is now moving, but was previously stalled
|
66
81
|
if (this.stallReported) {
|
67
82
|
const stalledDuration = self.performance.now() - stalled;
|
68
|
-
|
83
|
+
this.warn(
|
69
84
|
`playback not stuck anymore @${currentTime}, after ${Math.round(
|
70
85
|
stalledDuration,
|
71
86
|
)}ms`,
|
@@ -128,12 +143,9 @@ export default class GapController {
|
|
128
143
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
129
144
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
130
145
|
// that begins over 1 target duration after the video start position.
|
131
|
-
const
|
132
|
-
? this.hls.levels[this.hls.currentLevel]
|
133
|
-
: null;
|
134
|
-
const isLive = level?.details?.live;
|
146
|
+
const isLive = !!levelDetails?.live;
|
135
147
|
const maxStartGapJump = isLive
|
136
|
-
?
|
148
|
+
? levelDetails!.targetduration * 2
|
137
149
|
: MAX_START_GAP_JUMP;
|
138
150
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
139
151
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
@@ -153,6 +165,21 @@ export default class GapController {
|
|
153
165
|
|
154
166
|
const stalledDuration = tnow - stalled;
|
155
167
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
168
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
169
|
+
if (
|
170
|
+
state === State.ENDED &&
|
171
|
+
!(levelDetails && levelDetails.live) &&
|
172
|
+
Math.abs(currentTime - (levelDetails?.edge || 0)) < 1
|
173
|
+
) {
|
174
|
+
if (stalledDuration < 1000 || this.ended) {
|
175
|
+
return;
|
176
|
+
}
|
177
|
+
this.ended = currentTime;
|
178
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
179
|
+
stalled: true,
|
180
|
+
});
|
181
|
+
return;
|
182
|
+
}
|
156
183
|
// Report stalling after trying to fix
|
157
184
|
this._reportStall(bufferInfo);
|
158
185
|
if (!this.media) {
|
@@ -206,7 +233,7 @@ export default class GapController {
|
|
206
233
|
bufferInfo.nextStart - currentTime < config.maxBufferHole)) &&
|
207
234
|
stalledDurationMs > config.highBufferWatchdogPeriod * 1000
|
208
235
|
) {
|
209
|
-
|
236
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
210
237
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
211
238
|
// We only try to jump the hole if it's under the configured size
|
212
239
|
// Reset stalled so to rearm watchdog timer
|
@@ -230,7 +257,7 @@ export default class GapController {
|
|
230
257
|
media.currentTime
|
231
258
|
} due to low buffer (${JSON.stringify(bufferInfo)})`,
|
232
259
|
);
|
233
|
-
|
260
|
+
this.warn(error.message);
|
234
261
|
hls.trigger(Events.ERROR, {
|
235
262
|
type: ErrorTypes.MEDIA_ERROR,
|
236
263
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -305,7 +332,7 @@ export default class GapController {
|
|
305
332
|
startTime + SKIP_BUFFER_RANGE_START,
|
306
333
|
currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS,
|
307
334
|
);
|
308
|
-
|
335
|
+
this.warn(
|
309
336
|
`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`,
|
310
337
|
);
|
311
338
|
this.moved = true;
|
@@ -348,7 +375,7 @@ export default class GapController {
|
|
348
375
|
const error = new Error(
|
349
376
|
`Nudging 'currentTime' from ${currentTime} to ${targetTime}`,
|
350
377
|
);
|
351
|
-
|
378
|
+
this.warn(error.message);
|
352
379
|
media.currentTime = targetTime;
|
353
380
|
hls.trigger(Events.ERROR, {
|
354
381
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -360,7 +387,7 @@ export default class GapController {
|
|
360
387
|
const error = new Error(
|
361
388
|
`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`,
|
362
389
|
);
|
363
|
-
|
390
|
+
this.error(error.message);
|
364
391
|
hls.trigger(Events.ERROR, {
|
365
392
|
type: ErrorTypes.MEDIA_ERROR,
|
366
393
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -6,7 +6,6 @@ import type {
|
|
6
6
|
LevelUpdatedData,
|
7
7
|
MediaAttachingData,
|
8
8
|
} from '../types/events';
|
9
|
-
import { logger } from '../utils/logger';
|
10
9
|
import type { ComponentAPI } from '../types/component-api';
|
11
10
|
import type Hls from '../hls';
|
12
11
|
import type { HlsConfig } from '../config';
|
@@ -19,7 +18,6 @@ export default class LatencyController implements ComponentAPI {
|
|
19
18
|
private currentTime: number = 0;
|
20
19
|
private stallCount: number = 0;
|
21
20
|
private _latency: number | null = null;
|
22
|
-
private timeupdateHandler = () => this.timeupdate();
|
23
21
|
|
24
22
|
constructor(hls: Hls) {
|
25
23
|
this.hls = hls;
|
@@ -126,7 +124,7 @@ export default class LatencyController implements ComponentAPI {
|
|
126
124
|
this.onMediaDetaching();
|
127
125
|
this.levelDetails = null;
|
128
126
|
// @ts-ignore
|
129
|
-
this.hls =
|
127
|
+
this.hls = null;
|
130
128
|
}
|
131
129
|
|
132
130
|
private registerListeners() {
|
@@ -150,12 +148,12 @@ export default class LatencyController implements ComponentAPI {
|
|
150
148
|
data: MediaAttachingData,
|
151
149
|
) {
|
152
150
|
this.media = data.media;
|
153
|
-
this.media.addEventListener('timeupdate', this.
|
151
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
154
152
|
}
|
155
153
|
|
156
154
|
private onMediaDetaching() {
|
157
155
|
if (this.media) {
|
158
|
-
this.media.removeEventListener('timeupdate', this.
|
156
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
159
157
|
this.media = null;
|
160
158
|
}
|
161
159
|
}
|
@@ -172,10 +170,10 @@ export default class LatencyController implements ComponentAPI {
|
|
172
170
|
) {
|
173
171
|
this.levelDetails = details;
|
174
172
|
if (details.advanced) {
|
175
|
-
this.
|
173
|
+
this.onTimeupdate();
|
176
174
|
}
|
177
175
|
if (!details.live && this.media) {
|
178
|
-
this.media.removeEventListener('timeupdate', this.
|
176
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
179
177
|
}
|
180
178
|
}
|
181
179
|
|
@@ -185,13 +183,13 @@ export default class LatencyController implements ComponentAPI {
|
|
185
183
|
}
|
186
184
|
this.stallCount++;
|
187
185
|
if (this.levelDetails?.live) {
|
188
|
-
logger.warn(
|
189
|
-
'[
|
186
|
+
this.hls.logger.warn(
|
187
|
+
'[latency-controller]: Stall detected, adjusting target latency',
|
190
188
|
);
|
191
189
|
}
|
192
190
|
}
|
193
191
|
|
194
|
-
private
|
192
|
+
private onTimeupdate = () => {
|
195
193
|
const { media, levelDetails } = this;
|
196
194
|
if (!media || !levelDetails) {
|
197
195
|
return;
|
@@ -242,7 +240,7 @@ export default class LatencyController implements ComponentAPI {
|
|
242
240
|
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
243
241
|
media.playbackRate = 1;
|
244
242
|
}
|
245
|
-
}
|
243
|
+
};
|
246
244
|
|
247
245
|
private estimateLiveEdge(): number | null {
|
248
246
|
const { levelDetails } = this;
|
@@ -27,8 +27,6 @@ import type Hls from '../hls';
|
|
27
27
|
import type { HlsUrlParameters, LevelParsed } from '../types/level';
|
28
28
|
import type { MediaPlaylist } from '../types/media-playlist';
|
29
29
|
|
30
|
-
let chromeOrFirefox: boolean;
|
31
|
-
|
32
30
|
export default class LevelController extends BasePlaylistController {
|
33
31
|
private _levels: Level[] = [];
|
34
32
|
private _firstLevel: number = -1;
|
@@ -45,7 +43,7 @@ export default class LevelController extends BasePlaylistController {
|
|
45
43
|
hls: Hls,
|
46
44
|
contentSteeringController: ContentSteeringController | null,
|
47
45
|
) {
|
48
|
-
super(hls, '
|
46
|
+
super(hls, 'level-controller');
|
49
47
|
this.steering = contentSteeringController;
|
50
48
|
this._registerListeners();
|
51
49
|
}
|
@@ -119,22 +117,12 @@ export default class LevelController extends BasePlaylistController {
|
|
119
117
|
|
120
118
|
data.levels.forEach((levelParsed: LevelParsed) => {
|
121
119
|
const attributes = levelParsed.attrs;
|
122
|
-
|
123
|
-
// erase audio codec info if browser does not support mp4a.40.34.
|
124
|
-
// demuxer will autodetect codec and fallback to mpeg/audio
|
125
120
|
let { audioCodec, videoCodec } = levelParsed;
|
126
|
-
if (audioCodec?.indexOf('mp4a.40.34') !== -1) {
|
127
|
-
chromeOrFirefox ||= /chrome|firefox/i.test(navigator.userAgent);
|
128
|
-
if (chromeOrFirefox) {
|
129
|
-
levelParsed.audioCodec = audioCodec = undefined;
|
130
|
-
}
|
131
|
-
}
|
132
|
-
|
133
121
|
if (audioCodec) {
|
134
|
-
|
135
|
-
|
136
|
-
preferManagedMediaSource
|
137
|
-
|
122
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
123
|
+
levelParsed.audioCodec = audioCodec =
|
124
|
+
getCodecCompatibleName(audioCodec, preferManagedMediaSource) ||
|
125
|
+
undefined;
|
138
126
|
}
|
139
127
|
|
140
128
|
if (videoCodec?.indexOf('avc1') === 0) {
|
@@ -568,7 +556,13 @@ export default class LevelController extends BasePlaylistController {
|
|
568
556
|
if (curLevel.fragmentError === 0) {
|
569
557
|
curLevel.loadError = 0;
|
570
558
|
}
|
571
|
-
|
559
|
+
// Ignore matching details populated by loading a Media Playlist directly
|
560
|
+
let previousDetails = curLevel.details;
|
561
|
+
if (previousDetails === data.details && previousDetails.advanced) {
|
562
|
+
previousDetails = undefined;
|
563
|
+
}
|
564
|
+
|
565
|
+
this.playlistLoaded(level, data, previousDetails);
|
572
566
|
} else if (data.deliveryDirectives?.skip) {
|
573
567
|
// received a delta playlist update that cannot be merged
|
574
568
|
details.deltaUpdateFailed = true;
|
@@ -49,8 +49,6 @@ export default class StreamController
|
|
49
49
|
private altAudio: boolean = false;
|
50
50
|
private audioOnly: boolean = false;
|
51
51
|
private fragPlaying: Fragment | null = null;
|
52
|
-
private onvplaying: EventListener | null = null;
|
53
|
-
private onvseeked: EventListener | null = null;
|
54
52
|
private fragLastKbps: number = 0;
|
55
53
|
private couldBacktrack: boolean = false;
|
56
54
|
private backtrackFragment: Fragment | null = null;
|
@@ -66,17 +64,15 @@ export default class StreamController
|
|
66
64
|
hls,
|
67
65
|
fragmentTracker,
|
68
66
|
keyLoader,
|
69
|
-
'
|
67
|
+
'stream-controller',
|
70
68
|
PlaylistLevelType.MAIN,
|
71
69
|
);
|
72
|
-
this.
|
70
|
+
this.registerListeners();
|
73
71
|
}
|
74
72
|
|
75
|
-
|
73
|
+
protected registerListeners() {
|
74
|
+
super.registerListeners();
|
76
75
|
const { hls } = this;
|
77
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
78
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
79
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
80
76
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
81
77
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
82
78
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
@@ -85,7 +81,6 @@ export default class StreamController
|
|
85
81
|
this.onFragLoadEmergencyAborted,
|
86
82
|
this,
|
87
83
|
);
|
88
|
-
hls.on(Events.ERROR, this.onError, this);
|
89
84
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
90
85
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
91
86
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -94,11 +89,9 @@ export default class StreamController
|
|
94
89
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
95
90
|
}
|
96
91
|
|
97
|
-
protected
|
92
|
+
protected unregisterListeners() {
|
93
|
+
super.unregisterListeners();
|
98
94
|
const { hls } = this;
|
99
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
100
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
101
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
102
95
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
103
96
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
104
97
|
hls.off(
|
@@ -106,7 +99,6 @@ export default class StreamController
|
|
106
99
|
this.onFragLoadEmergencyAborted,
|
107
100
|
this,
|
108
101
|
);
|
109
|
-
hls.off(Events.ERROR, this.onError, this);
|
110
102
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
111
103
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
112
104
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -116,7 +108,9 @@ export default class StreamController
|
|
116
108
|
}
|
117
109
|
|
118
110
|
protected onHandlerDestroying() {
|
119
|
-
|
111
|
+
// @ts-ignore
|
112
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
113
|
+
this.unregisterListeners();
|
120
114
|
super.onHandlerDestroying();
|
121
115
|
}
|
122
116
|
|
@@ -516,10 +510,8 @@ export default class StreamController
|
|
516
510
|
) {
|
517
511
|
super.onMediaAttached(event, data);
|
518
512
|
const media = data.media;
|
519
|
-
|
520
|
-
|
521
|
-
media.addEventListener('playing', this.onvplaying as EventListener);
|
522
|
-
media.addEventListener('seeked', this.onvseeked as EventListener);
|
513
|
+
media.addEventListener('playing', this.onMediaPlaying);
|
514
|
+
media.addEventListener('seeked', this.onMediaSeeked);
|
523
515
|
this.gapController = new GapController(
|
524
516
|
this.config,
|
525
517
|
media,
|
@@ -530,10 +522,9 @@ export default class StreamController
|
|
530
522
|
|
531
523
|
protected onMediaDetaching() {
|
532
524
|
const { media } = this;
|
533
|
-
if (media
|
534
|
-
media.removeEventListener('playing', this.
|
535
|
-
media.removeEventListener('seeked', this.
|
536
|
-
this.onvplaying = this.onvseeked = null;
|
525
|
+
if (media) {
|
526
|
+
media.removeEventListener('playing', this.onMediaPlaying);
|
527
|
+
media.removeEventListener('seeked', this.onMediaSeeked);
|
537
528
|
this.videoBuffer = null;
|
538
529
|
}
|
539
530
|
this.fragPlaying = null;
|
@@ -544,12 +535,12 @@ export default class StreamController
|
|
544
535
|
super.onMediaDetaching();
|
545
536
|
}
|
546
537
|
|
547
|
-
private onMediaPlaying() {
|
538
|
+
private onMediaPlaying = () => {
|
548
539
|
// tick to speed up FRAG_CHANGED triggering
|
549
540
|
this.tick();
|
550
|
-
}
|
541
|
+
};
|
551
542
|
|
552
|
-
private onMediaSeeked() {
|
543
|
+
private onMediaSeeked = () => {
|
553
544
|
const media = this.media;
|
554
545
|
const currentTime = media ? media.currentTime : null;
|
555
546
|
if (Number.isFinite(currentTime)) {
|
@@ -569,9 +560,9 @@ export default class StreamController
|
|
569
560
|
|
570
561
|
// tick to speed up FRAG_CHANGED triggering
|
571
562
|
this.tick();
|
572
|
-
}
|
563
|
+
};
|
573
564
|
|
574
|
-
|
565
|
+
protected onManifestLoading() {
|
575
566
|
// reset buffer on manifest loading
|
576
567
|
this.log('Trigger BUFFER_RESET');
|
577
568
|
this.hls.trigger(Events.BUFFER_RESET, undefined);
|
@@ -884,7 +875,7 @@ export default class StreamController
|
|
884
875
|
this.fragBufferedComplete(frag, part);
|
885
876
|
}
|
886
877
|
|
887
|
-
|
878
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
888
879
|
if (data.fatal) {
|
889
880
|
this.state = State.ERROR;
|
890
881
|
return;
|
@@ -942,8 +933,10 @@ export default class StreamController
|
|
942
933
|
|
943
934
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
944
935
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
945
|
-
const
|
946
|
-
|
936
|
+
const state = this.state;
|
937
|
+
const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
|
938
|
+
const levelDetails = this.getLevelDetails();
|
939
|
+
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
947
940
|
}
|
948
941
|
|
949
942
|
this.lastCurrentTime = media.currentTime;
|
@@ -9,6 +9,10 @@ import { PlaylistLevelType } from '../types/loader';
|
|
9
9
|
import { Level } from '../types/level';
|
10
10
|
import { subtitleOptionsIdentical } from '../utils/media-option-attributes';
|
11
11
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
12
|
+
import {
|
13
|
+
isFullSegmentEncryption,
|
14
|
+
getAesModeFromFullSegmentMethod,
|
15
|
+
} from '../utils/encryption-methods-util';
|
12
16
|
import type { NetworkComponentAPI } from '../types/component-api';
|
13
17
|
import type Hls from '../hls';
|
14
18
|
import type { FragmentTracker } from './fragment-tracker';
|
@@ -51,25 +55,22 @@ export class SubtitleStreamController
|
|
51
55
|
hls,
|
52
56
|
fragmentTracker,
|
53
57
|
keyLoader,
|
54
|
-
'
|
58
|
+
'subtitle-stream-controller',
|
55
59
|
PlaylistLevelType.SUBTITLE,
|
56
60
|
);
|
57
|
-
this.
|
61
|
+
this.registerListeners();
|
58
62
|
}
|
59
63
|
|
60
64
|
protected onHandlerDestroying() {
|
61
|
-
this.
|
65
|
+
this.unregisterListeners();
|
62
66
|
super.onHandlerDestroying();
|
63
67
|
this.mainDetails = null;
|
64
68
|
}
|
65
69
|
|
66
|
-
|
70
|
+
protected registerListeners() {
|
71
|
+
super.registerListeners();
|
67
72
|
const { hls } = this;
|
68
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
69
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
70
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
71
73
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
72
|
-
hls.on(Events.ERROR, this.onError, this);
|
73
74
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
74
75
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
75
76
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -78,13 +79,10 @@ export class SubtitleStreamController
|
|
78
79
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
79
80
|
}
|
80
81
|
|
81
|
-
|
82
|
+
protected unregisterListeners() {
|
83
|
+
super.unregisterListeners();
|
82
84
|
const { hls } = this;
|
83
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
84
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
85
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
86
85
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
87
|
-
hls.off(Events.ERROR, this.onError, this);
|
88
86
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
89
87
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
90
88
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -360,7 +358,7 @@ export class SubtitleStreamController
|
|
360
358
|
payload.byteLength > 0 &&
|
361
359
|
decryptData?.key &&
|
362
360
|
decryptData.iv &&
|
363
|
-
decryptData.method
|
361
|
+
isFullSegmentEncryption(decryptData.method)
|
364
362
|
) {
|
365
363
|
const startTime = performance.now();
|
366
364
|
// decrypt the subtitles
|
@@ -369,6 +367,7 @@ export class SubtitleStreamController
|
|
369
367
|
new Uint8Array(payload),
|
370
368
|
decryptData.key.buffer,
|
371
369
|
decryptData.iv.buffer,
|
370
|
+
getAesModeFromFullSegmentMethod(decryptData.method),
|
372
371
|
)
|
373
372
|
.catch((err) => {
|
374
373
|
hls.trigger(Events.ERROR, {
|
@@ -35,13 +35,14 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
35
35
|
private currentTrack: MediaPlaylist | null = null;
|
36
36
|
private selectDefaultTrack: boolean = true;
|
37
37
|
private queuedDefaultTrack: number = -1;
|
38
|
-
private asyncPollTrackChange: () => void = () => this.pollTrackChange(0);
|
39
38
|
private useTextTrackPolling: boolean = false;
|
40
39
|
private subtitlePollingInterval: number = -1;
|
41
40
|
private _subtitleDisplay: boolean = true;
|
42
41
|
|
42
|
+
private asyncPollTrackChange = () => this.pollTrackChange(0);
|
43
|
+
|
43
44
|
constructor(hls: Hls) {
|
44
|
-
super(hls, '
|
45
|
+
super(hls, 'subtitle-track-controller');
|
45
46
|
this.registerListeners();
|
46
47
|
}
|
47
48
|
|
@@ -50,7 +51,8 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
50
51
|
this.tracks.length = 0;
|
51
52
|
this.tracksInGroup.length = 0;
|
52
53
|
this.currentTrack = null;
|
53
|
-
|
54
|
+
// @ts-ignore
|
55
|
+
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
54
56
|
super.destroy();
|
55
57
|
}
|
56
58
|
|
@@ -28,7 +28,6 @@ import type {
|
|
28
28
|
BufferFlushingData,
|
29
29
|
FragLoadingData,
|
30
30
|
} from '../types/events';
|
31
|
-
import { logger } from '../utils/logger';
|
32
31
|
import type Hls from '../hls';
|
33
32
|
import type { ComponentAPI } from '../types/component-api';
|
34
33
|
import type { HlsConfig } from '../config';
|
@@ -136,17 +135,12 @@ export class TimelineController implements ComponentAPI {
|
|
136
135
|
}
|
137
136
|
|
138
137
|
private initCea608Parsers() {
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
)
|
143
|
-
|
144
|
-
|
145
|
-
const channel3 = new OutputFilter(this, 'textTrack3');
|
146
|
-
const channel4 = new OutputFilter(this, 'textTrack4');
|
147
|
-
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
148
|
-
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
149
|
-
}
|
138
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
139
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
140
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
141
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
142
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
143
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
150
144
|
}
|
151
145
|
|
152
146
|
public addCues(
|
@@ -410,7 +404,7 @@ export class TimelineController implements ComponentAPI {
|
|
410
404
|
.filter((t) => t !== null)
|
411
405
|
.map((t) => (t as TextTrack).label);
|
412
406
|
if (unusedTextTracks.length) {
|
413
|
-
logger.warn(
|
407
|
+
this.hls.logger.warn(
|
414
408
|
`Media element contains unused subtitle tracks: ${unusedTextTracks.join(
|
415
409
|
', ',
|
416
410
|
)}. Replace media element for each source to clear TextTracks and captions menu.`,
|
@@ -468,21 +462,19 @@ export class TimelineController implements ComponentAPI {
|
|
468
462
|
}
|
469
463
|
|
470
464
|
private onFragLoading(event: Events.FRAG_LOADING, data: FragLoadingData) {
|
471
|
-
this.initCea608Parsers();
|
472
|
-
const { cea608Parser1, cea608Parser2, lastCc, lastSn, lastPartIndex } =
|
473
|
-
this;
|
474
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
475
|
-
return;
|
476
|
-
}
|
477
465
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
478
|
-
if (data.frag.type === PlaylistLevelType.MAIN) {
|
466
|
+
if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
|
467
|
+
const { cea608Parser1, cea608Parser2, lastSn } = this;
|
468
|
+
if (!cea608Parser1 || !cea608Parser2) {
|
469
|
+
return;
|
470
|
+
}
|
479
471
|
const { cc, sn } = data.frag;
|
480
|
-
const partIndex = data
|
472
|
+
const partIndex = data.part?.index ?? -1;
|
481
473
|
if (
|
482
474
|
!(
|
483
475
|
sn === lastSn + 1 ||
|
484
|
-
(sn === lastSn && partIndex === lastPartIndex + 1) ||
|
485
|
-
cc === lastCc
|
476
|
+
(sn === lastSn && partIndex === this.lastPartIndex + 1) ||
|
477
|
+
cc === this.lastCc
|
486
478
|
)
|
487
479
|
) {
|
488
480
|
cea608Parser1.reset();
|
@@ -550,7 +542,7 @@ export class TimelineController implements ComponentAPI {
|
|
550
542
|
});
|
551
543
|
},
|
552
544
|
(error) => {
|
553
|
-
logger.log(`Failed to parse IMSC1: ${error}`);
|
545
|
+
hls.logger.log(`Failed to parse IMSC1: ${error}`);
|
554
546
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
555
547
|
success: false,
|
556
548
|
frag: frag,
|
@@ -597,7 +589,7 @@ export class TimelineController implements ComponentAPI {
|
|
597
589
|
this._fallbackToIMSC1(frag, payload);
|
598
590
|
}
|
599
591
|
// Something went wrong while parsing. Trigger event with success false.
|
600
|
-
logger.log(`Failed to parse VTT cue: ${error}`);
|
592
|
+
hls.logger.log(`Failed to parse VTT cue: ${error}`);
|
601
593
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
602
594
|
return;
|
603
595
|
}
|
@@ -669,9 +661,7 @@ export class TimelineController implements ComponentAPI {
|
|
669
661
|
event: Events.FRAG_PARSING_USERDATA,
|
670
662
|
data: FragParsingUserdataData,
|
671
663
|
) {
|
672
|
-
this.
|
673
|
-
const { cea608Parser1, cea608Parser2 } = this;
|
674
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
664
|
+
if (!this.enabled || !this.config.enableCEA708Captions) {
|
675
665
|
return;
|
676
666
|
}
|
677
667
|
const { frag, samples } = data;
|
@@ -686,9 +676,12 @@ export class TimelineController implements ComponentAPI {
|
|
686
676
|
for (let i = 0; i < samples.length; i++) {
|
687
677
|
const ccBytes = samples[i].bytes;
|
688
678
|
if (ccBytes) {
|
679
|
+
if (!this.cea608Parser1) {
|
680
|
+
this.initCea608Parsers();
|
681
|
+
}
|
689
682
|
const ccdatas = this.extractCea608Data(ccBytes);
|
690
|
-
cea608Parser1
|
691
|
-
cea608Parser2
|
683
|
+
this.cea608Parser1!.addData(samples[i].pts, ccdatas[0]);
|
684
|
+
this.cea608Parser2!.addData(samples[i].pts, ccdatas[1]);
|
692
685
|
}
|
693
686
|
}
|
694
687
|
}
|