@zenvor/hls.js 1.0.0
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/LICENSE +28 -0
- package/README.md +472 -0
- package/dist/hls-demo.js +26995 -0
- package/dist/hls-demo.js.map +1 -0
- package/dist/hls.d.mts +4204 -0
- package/dist/hls.d.ts +4204 -0
- package/dist/hls.js +40050 -0
- package/dist/hls.js.d.ts +4204 -0
- package/dist/hls.js.map +1 -0
- package/dist/hls.light.js +27145 -0
- package/dist/hls.light.js.map +1 -0
- package/dist/hls.light.min.js +2 -0
- package/dist/hls.light.min.js.map +1 -0
- package/dist/hls.light.mjs +26392 -0
- package/dist/hls.light.mjs.map +1 -0
- package/dist/hls.min.js +2 -0
- package/dist/hls.min.js.map +1 -0
- package/dist/hls.mjs +38956 -0
- package/dist/hls.mjs.map +1 -0
- package/dist/hls.worker.js +2 -0
- package/dist/hls.worker.js.map +1 -0
- package/package.json +143 -0
- package/src/config.ts +794 -0
- package/src/controller/abr-controller.ts +1019 -0
- package/src/controller/algo-data-controller.ts +794 -0
- package/src/controller/audio-stream-controller.ts +1099 -0
- package/src/controller/audio-track-controller.ts +454 -0
- package/src/controller/base-playlist-controller.ts +438 -0
- package/src/controller/base-stream-controller.ts +2526 -0
- package/src/controller/buffer-controller.ts +2015 -0
- package/src/controller/buffer-operation-queue.ts +159 -0
- package/src/controller/cap-level-controller.ts +367 -0
- package/src/controller/cmcd-controller.ts +422 -0
- package/src/controller/content-steering-controller.ts +622 -0
- package/src/controller/eme-controller.ts +1617 -0
- package/src/controller/error-controller.ts +627 -0
- package/src/controller/fps-controller.ts +146 -0
- package/src/controller/fragment-finders.ts +256 -0
- package/src/controller/fragment-tracker.ts +567 -0
- package/src/controller/gap-controller.ts +719 -0
- package/src/controller/id3-track-controller.ts +488 -0
- package/src/controller/interstitial-player.ts +302 -0
- package/src/controller/interstitials-controller.ts +2895 -0
- package/src/controller/interstitials-schedule.ts +698 -0
- package/src/controller/latency-controller.ts +294 -0
- package/src/controller/level-controller.ts +776 -0
- package/src/controller/stream-controller.ts +1597 -0
- package/src/controller/subtitle-stream-controller.ts +508 -0
- package/src/controller/subtitle-track-controller.ts +617 -0
- package/src/controller/timeline-controller.ts +677 -0
- package/src/crypt/aes-crypto.ts +36 -0
- package/src/crypt/aes-decryptor.ts +339 -0
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +225 -0
- package/src/crypt/fast-aes-key.ts +39 -0
- package/src/define-plugin.d.ts +17 -0
- package/src/demux/audio/aacdemuxer.ts +126 -0
- package/src/demux/audio/ac3-demuxer.ts +170 -0
- package/src/demux/audio/adts.ts +249 -0
- package/src/demux/audio/base-audio-demuxer.ts +205 -0
- package/src/demux/audio/dolby.ts +21 -0
- package/src/demux/audio/mp3demuxer.ts +85 -0
- package/src/demux/audio/mpegaudio.ts +177 -0
- package/src/demux/chunk-cache.ts +42 -0
- package/src/demux/dummy-demuxed-track.ts +13 -0
- package/src/demux/inject-worker.ts +75 -0
- package/src/demux/mp4demuxer.ts +234 -0
- package/src/demux/sample-aes.ts +198 -0
- package/src/demux/transmuxer-interface.ts +449 -0
- package/src/demux/transmuxer-worker.ts +221 -0
- package/src/demux/transmuxer.ts +560 -0
- package/src/demux/tsdemuxer.ts +1256 -0
- package/src/demux/video/avc-video-parser.ts +401 -0
- package/src/demux/video/base-video-parser.ts +198 -0
- package/src/demux/video/exp-golomb.ts +153 -0
- package/src/demux/video/hevc-video-parser.ts +736 -0
- package/src/empty-es.js +5 -0
- package/src/empty.js +3 -0
- package/src/errors.ts +107 -0
- package/src/events.ts +548 -0
- package/src/exports-default.ts +3 -0
- package/src/exports-named.ts +81 -0
- package/src/hls.ts +1613 -0
- package/src/is-supported.ts +54 -0
- package/src/loader/date-range.ts +207 -0
- package/src/loader/fragment-loader.ts +403 -0
- package/src/loader/fragment.ts +487 -0
- package/src/loader/interstitial-asset-list.ts +162 -0
- package/src/loader/interstitial-event.ts +337 -0
- package/src/loader/key-loader.ts +439 -0
- package/src/loader/level-details.ts +203 -0
- package/src/loader/level-key.ts +259 -0
- package/src/loader/load-stats.ts +17 -0
- package/src/loader/m3u8-parser.ts +1072 -0
- package/src/loader/playlist-loader.ts +839 -0
- package/src/polyfills/number.ts +15 -0
- package/src/remux/aac-helper.ts +81 -0
- package/src/remux/mp4-generator.ts +1380 -0
- package/src/remux/mp4-remuxer.ts +1261 -0
- package/src/remux/passthrough-remuxer.ts +434 -0
- package/src/task-loop.ts +130 -0
- package/src/types/algo.ts +44 -0
- package/src/types/buffer.ts +105 -0
- package/src/types/component-api.ts +20 -0
- package/src/types/demuxer.ts +208 -0
- package/src/types/events.ts +574 -0
- package/src/types/fragment-tracker.ts +23 -0
- package/src/types/level.ts +268 -0
- package/src/types/loader.ts +198 -0
- package/src/types/media-playlist.ts +92 -0
- package/src/types/network-details.ts +3 -0
- package/src/types/remuxer.ts +104 -0
- package/src/types/track.ts +12 -0
- package/src/types/transmuxer.ts +46 -0
- package/src/types/tuples.ts +6 -0
- package/src/types/vtt.ts +11 -0
- package/src/utils/arrays.ts +22 -0
- package/src/utils/attr-list.ts +192 -0
- package/src/utils/binary-search.ts +46 -0
- package/src/utils/buffer-helper.ts +173 -0
- package/src/utils/cea-608-parser.ts +1413 -0
- package/src/utils/chunker.ts +41 -0
- package/src/utils/codecs.ts +314 -0
- package/src/utils/cues.ts +96 -0
- package/src/utils/discontinuities.ts +174 -0
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/error-helper.ts +95 -0
- package/src/utils/event-listener-helper.ts +16 -0
- package/src/utils/ewma-bandwidth-estimator.ts +97 -0
- package/src/utils/ewma.ts +43 -0
- package/src/utils/fetch-loader.ts +331 -0
- package/src/utils/global.ts +2 -0
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +67 -0
- package/src/utils/hex.ts +32 -0
- package/src/utils/imsc1-ttml-parser.ts +261 -0
- package/src/utils/keysystem-util.ts +45 -0
- package/src/utils/level-helper.ts +629 -0
- package/src/utils/logger.ts +120 -0
- package/src/utils/media-option-attributes.ts +49 -0
- package/src/utils/mediacapabilities-helper.ts +301 -0
- package/src/utils/mediakeys-helper.ts +210 -0
- package/src/utils/mediasource-helper.ts +37 -0
- package/src/utils/mp4-tools.ts +1473 -0
- package/src/utils/number.ts +3 -0
- package/src/utils/numeric-encoding-utils.ts +26 -0
- package/src/utils/output-filter.ts +46 -0
- package/src/utils/rendition-helper.ts +505 -0
- package/src/utils/safe-json-stringify.ts +22 -0
- package/src/utils/texttrack-utils.ts +164 -0
- package/src/utils/time-ranges.ts +17 -0
- package/src/utils/timescale-conversion.ts +46 -0
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +105 -0
- package/src/utils/vttcue.ts +384 -0
- package/src/utils/vttparser.ts +497 -0
- package/src/utils/webvtt-parser.ts +166 -0
- package/src/utils/xhr-loader.ts +337 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { ErrorDetails } from '../errors';
|
|
2
|
+
import { Events } from '../events';
|
|
3
|
+
import type { HlsConfig } from '../config';
|
|
4
|
+
import type Hls from '../hls';
|
|
5
|
+
import type { LevelDetails } from '../loader/level-details';
|
|
6
|
+
import type { ComponentAPI } from '../types/component-api';
|
|
7
|
+
import type {
|
|
8
|
+
ErrorData,
|
|
9
|
+
LevelUpdatedData,
|
|
10
|
+
MediaAttachingData,
|
|
11
|
+
} from '../types/events';
|
|
12
|
+
|
|
13
|
+
export default class LatencyController implements ComponentAPI {
|
|
14
|
+
private hls: Hls | null;
|
|
15
|
+
private readonly config: HlsConfig;
|
|
16
|
+
private media: HTMLMediaElement | null = null;
|
|
17
|
+
private currentTime: number = 0;
|
|
18
|
+
private stallCount: number = 0;
|
|
19
|
+
private _latency: number | null = null;
|
|
20
|
+
private _targetLatencyUpdated = false;
|
|
21
|
+
|
|
22
|
+
constructor(hls: Hls) {
|
|
23
|
+
this.hls = hls;
|
|
24
|
+
this.config = hls.config;
|
|
25
|
+
this.registerListeners();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private get levelDetails(): LevelDetails | null {
|
|
29
|
+
return this.hls?.latestLevelDetails || null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get latency(): number {
|
|
33
|
+
return this._latency || 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get maxLatency(): number {
|
|
37
|
+
const { config } = this;
|
|
38
|
+
if (config.liveMaxLatencyDuration !== undefined) {
|
|
39
|
+
return config.liveMaxLatencyDuration;
|
|
40
|
+
}
|
|
41
|
+
const levelDetails = this.levelDetails;
|
|
42
|
+
return levelDetails
|
|
43
|
+
? config.liveMaxLatencyDurationCount * levelDetails.targetduration
|
|
44
|
+
: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get targetLatency(): number | null {
|
|
48
|
+
const levelDetails = this.levelDetails;
|
|
49
|
+
if (levelDetails === null || this.hls === null) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const { holdBack, partHoldBack, targetduration } = levelDetails;
|
|
53
|
+
const { liveSyncDuration, liveSyncDurationCount, lowLatencyMode } =
|
|
54
|
+
this.config;
|
|
55
|
+
const userConfig = this.hls.userConfig;
|
|
56
|
+
let targetLatency = lowLatencyMode ? partHoldBack || holdBack : holdBack;
|
|
57
|
+
if (
|
|
58
|
+
this._targetLatencyUpdated ||
|
|
59
|
+
userConfig.liveSyncDuration ||
|
|
60
|
+
userConfig.liveSyncDurationCount ||
|
|
61
|
+
targetLatency === 0
|
|
62
|
+
) {
|
|
63
|
+
targetLatency =
|
|
64
|
+
liveSyncDuration !== undefined
|
|
65
|
+
? liveSyncDuration
|
|
66
|
+
: liveSyncDurationCount * targetduration;
|
|
67
|
+
}
|
|
68
|
+
const maxLiveSyncOnStallIncrease = targetduration;
|
|
69
|
+
return (
|
|
70
|
+
targetLatency +
|
|
71
|
+
Math.min(
|
|
72
|
+
this.stallCount * this.config.liveSyncOnStallIncrease,
|
|
73
|
+
maxLiveSyncOnStallIncrease,
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
set targetLatency(latency: number) {
|
|
79
|
+
this.stallCount = 0;
|
|
80
|
+
this.config.liveSyncDuration = latency;
|
|
81
|
+
this._targetLatencyUpdated = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get liveSyncPosition(): number | null {
|
|
85
|
+
const liveEdge = this.estimateLiveEdge();
|
|
86
|
+
const targetLatency = this.targetLatency;
|
|
87
|
+
if (liveEdge === null || targetLatency === null) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const levelDetails = this.levelDetails;
|
|
91
|
+
if (levelDetails === null) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const edge = levelDetails.edge;
|
|
95
|
+
const syncPosition = liveEdge - targetLatency - this.edgeStalled;
|
|
96
|
+
const min = edge - levelDetails.totalduration;
|
|
97
|
+
const max =
|
|
98
|
+
edge -
|
|
99
|
+
((this.config.lowLatencyMode && levelDetails.partTarget) ||
|
|
100
|
+
levelDetails.targetduration);
|
|
101
|
+
return Math.min(Math.max(min, syncPosition), max);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get drift(): number {
|
|
105
|
+
const levelDetails = this.levelDetails;
|
|
106
|
+
if (levelDetails === null) {
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
return levelDetails.drift;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get edgeStalled(): number {
|
|
113
|
+
const levelDetails = this.levelDetails;
|
|
114
|
+
if (levelDetails === null) {
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
const maxLevelUpdateAge =
|
|
118
|
+
((this.config.lowLatencyMode && levelDetails.partTarget) ||
|
|
119
|
+
levelDetails.targetduration) * 3;
|
|
120
|
+
return Math.max(levelDetails.age - maxLevelUpdateAge, 0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private get forwardBufferLength(): number {
|
|
124
|
+
const { media } = this;
|
|
125
|
+
const levelDetails = this.levelDetails;
|
|
126
|
+
if (!media || !levelDetails) {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
const bufferedRanges = media.buffered.length;
|
|
130
|
+
return (
|
|
131
|
+
(bufferedRanges
|
|
132
|
+
? media.buffered.end(bufferedRanges - 1)
|
|
133
|
+
: levelDetails.edge) - this.currentTime
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public destroy(): void {
|
|
138
|
+
this.unregisterListeners();
|
|
139
|
+
this.onMediaDetaching();
|
|
140
|
+
this.hls = null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private registerListeners() {
|
|
144
|
+
const { hls } = this;
|
|
145
|
+
if (!hls) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
|
149
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
|
150
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
|
151
|
+
hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
|
152
|
+
hls.on(Events.ERROR, this.onError, this);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private unregisterListeners() {
|
|
156
|
+
const { hls } = this;
|
|
157
|
+
if (!hls) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
|
161
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
|
162
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
|
163
|
+
hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
|
164
|
+
hls.off(Events.ERROR, this.onError, this);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private onMediaAttached(
|
|
168
|
+
event: Events.MEDIA_ATTACHED,
|
|
169
|
+
data: MediaAttachingData,
|
|
170
|
+
) {
|
|
171
|
+
this.media = data.media;
|
|
172
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private onMediaDetaching() {
|
|
176
|
+
if (this.media) {
|
|
177
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
|
178
|
+
this.media = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private onManifestLoading() {
|
|
183
|
+
this._latency = null;
|
|
184
|
+
this.stallCount = 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private onLevelUpdated(
|
|
188
|
+
event: Events.LEVEL_UPDATED,
|
|
189
|
+
{ details }: LevelUpdatedData,
|
|
190
|
+
) {
|
|
191
|
+
if (details.advanced) {
|
|
192
|
+
this.onTimeupdate();
|
|
193
|
+
}
|
|
194
|
+
if (!details.live && this.media) {
|
|
195
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private onError(event: Events.ERROR, data: ErrorData) {
|
|
200
|
+
if (data.details !== ErrorDetails.BUFFER_STALLED_ERROR) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this.stallCount++;
|
|
204
|
+
if (this.hls && this.levelDetails?.live) {
|
|
205
|
+
this.hls.logger.warn(
|
|
206
|
+
'[latency-controller]: Stall detected, adjusting target latency',
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private onTimeupdate = () => {
|
|
212
|
+
const { media } = this;
|
|
213
|
+
const levelDetails = this.levelDetails;
|
|
214
|
+
if (!media || !levelDetails) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.currentTime = media.currentTime;
|
|
218
|
+
|
|
219
|
+
const latency = this.computeLatency();
|
|
220
|
+
if (latency === null) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this._latency = latency;
|
|
224
|
+
|
|
225
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
|
226
|
+
const { lowLatencyMode, maxLiveSyncPlaybackRate } = this.config;
|
|
227
|
+
if (
|
|
228
|
+
!lowLatencyMode ||
|
|
229
|
+
maxLiveSyncPlaybackRate === 1 ||
|
|
230
|
+
!levelDetails.live
|
|
231
|
+
) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const targetLatency = this.targetLatency;
|
|
235
|
+
if (targetLatency === null) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const distanceFromTarget = latency - targetLatency;
|
|
239
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
|
240
|
+
// and more than one second from under-buffering.
|
|
241
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
|
242
|
+
const liveMinLatencyDuration = Math.min(
|
|
243
|
+
this.maxLatency,
|
|
244
|
+
targetLatency + levelDetails.targetduration,
|
|
245
|
+
);
|
|
246
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
|
247
|
+
|
|
248
|
+
if (
|
|
249
|
+
inLiveRange &&
|
|
250
|
+
distanceFromTarget > 0.05 &&
|
|
251
|
+
this.forwardBufferLength > 1
|
|
252
|
+
) {
|
|
253
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
|
254
|
+
const rate =
|
|
255
|
+
Math.round(
|
|
256
|
+
(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled))) *
|
|
257
|
+
20,
|
|
258
|
+
) / 20;
|
|
259
|
+
const playbackRate = Math.min(max, Math.max(1, rate));
|
|
260
|
+
this.changeMediaPlaybackRate(media, playbackRate);
|
|
261
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
|
262
|
+
this.changeMediaPlaybackRate(media, 1);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
private changeMediaPlaybackRate(
|
|
267
|
+
media: HTMLMediaElement,
|
|
268
|
+
playbackRate: number,
|
|
269
|
+
) {
|
|
270
|
+
if (media.playbackRate === playbackRate) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
this.hls?.logger.debug(
|
|
274
|
+
`[latency-controller]: latency=${this.latency.toFixed(3)}, targetLatency=${this.targetLatency?.toFixed(3)}, forwardBufferLength=${this.forwardBufferLength.toFixed(3)}: adjusting playback rate from ${media.playbackRate} to ${playbackRate}`,
|
|
275
|
+
);
|
|
276
|
+
media.playbackRate = playbackRate;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private estimateLiveEdge(): number | null {
|
|
280
|
+
const levelDetails = this.levelDetails;
|
|
281
|
+
if (levelDetails === null) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
return levelDetails.edge + levelDetails.age;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private computeLatency(): number | null {
|
|
288
|
+
const liveEdge = this.estimateLiveEdge();
|
|
289
|
+
if (liveEdge === null) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
return liveEdge - this.currentTime;
|
|
293
|
+
}
|
|
294
|
+
}
|