@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,508 @@
|
|
|
1
|
+
import BaseStreamController, { State } from './base-stream-controller';
|
|
2
|
+
import { FragmentState } from './fragment-tracker';
|
|
3
|
+
import { ErrorDetails, ErrorTypes } from '../errors';
|
|
4
|
+
import { Events } from '../events';
|
|
5
|
+
import {
|
|
6
|
+
type Fragment,
|
|
7
|
+
isMediaFragment,
|
|
8
|
+
type MediaFragment,
|
|
9
|
+
} from '../loader/fragment';
|
|
10
|
+
import { Level } from '../types/level';
|
|
11
|
+
import { PlaylistLevelType } from '../types/loader';
|
|
12
|
+
import { BufferHelper } from '../utils/buffer-helper';
|
|
13
|
+
import { alignStream } from '../utils/discontinuities';
|
|
14
|
+
import {
|
|
15
|
+
getAesModeFromFullSegmentMethod,
|
|
16
|
+
isFullSegmentEncryption,
|
|
17
|
+
} from '../utils/encryption-methods-util';
|
|
18
|
+
import { subtitleOptionsIdentical } from '../utils/media-option-attributes';
|
|
19
|
+
import type { FragmentTracker } from './fragment-tracker';
|
|
20
|
+
import type Hls from '../hls';
|
|
21
|
+
import type KeyLoader from '../loader/key-loader';
|
|
22
|
+
import type { LevelDetails } from '../loader/level-details';
|
|
23
|
+
import type { NetworkComponentAPI } from '../types/component-api';
|
|
24
|
+
import type {
|
|
25
|
+
BufferFlushingData,
|
|
26
|
+
ErrorData,
|
|
27
|
+
FragLoadedData,
|
|
28
|
+
LevelLoadedData,
|
|
29
|
+
MediaDetachingData,
|
|
30
|
+
SubtitleFragProcessed,
|
|
31
|
+
SubtitleTracksUpdatedData,
|
|
32
|
+
TrackLoadedData,
|
|
33
|
+
TrackSwitchedData,
|
|
34
|
+
} from '../types/events';
|
|
35
|
+
import type { Bufferable } from '../utils/buffer-helper';
|
|
36
|
+
|
|
37
|
+
const TICK_INTERVAL = 500; // how often to tick in ms
|
|
38
|
+
|
|
39
|
+
interface TimeRange {
|
|
40
|
+
start: number;
|
|
41
|
+
end: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class SubtitleStreamController
|
|
45
|
+
extends BaseStreamController
|
|
46
|
+
implements NetworkComponentAPI
|
|
47
|
+
{
|
|
48
|
+
private currentTrackId: number = -1;
|
|
49
|
+
private tracksBuffered: Array<TimeRange[] | undefined> = [];
|
|
50
|
+
private mainDetails: LevelDetails | null = null;
|
|
51
|
+
|
|
52
|
+
constructor(
|
|
53
|
+
hls: Hls,
|
|
54
|
+
fragmentTracker: FragmentTracker,
|
|
55
|
+
keyLoader: KeyLoader,
|
|
56
|
+
) {
|
|
57
|
+
super(
|
|
58
|
+
hls,
|
|
59
|
+
fragmentTracker,
|
|
60
|
+
keyLoader,
|
|
61
|
+
'subtitle-stream-controller',
|
|
62
|
+
PlaylistLevelType.SUBTITLE,
|
|
63
|
+
);
|
|
64
|
+
this.registerListeners();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected onHandlerDestroying() {
|
|
68
|
+
this.unregisterListeners();
|
|
69
|
+
super.onHandlerDestroying();
|
|
70
|
+
this.mainDetails = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected registerListeners() {
|
|
74
|
+
super.registerListeners();
|
|
75
|
+
const { hls } = this;
|
|
76
|
+
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
|
77
|
+
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
|
78
|
+
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
|
79
|
+
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
|
80
|
+
hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
|
|
81
|
+
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected unregisterListeners() {
|
|
85
|
+
super.unregisterListeners();
|
|
86
|
+
const { hls } = this;
|
|
87
|
+
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
|
88
|
+
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
|
89
|
+
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
|
90
|
+
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
|
91
|
+
hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
|
|
92
|
+
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
startLoad(startPosition: number, skipSeekToStartPosition?: boolean) {
|
|
96
|
+
this.stopLoad();
|
|
97
|
+
this.state = State.IDLE;
|
|
98
|
+
|
|
99
|
+
this.setInterval(TICK_INTERVAL);
|
|
100
|
+
|
|
101
|
+
this.nextLoadPosition = this.lastCurrentTime =
|
|
102
|
+
startPosition + this.timelineOffset;
|
|
103
|
+
this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
|
|
104
|
+
|
|
105
|
+
this.tick();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected onManifestLoading() {
|
|
109
|
+
super.onManifestLoading();
|
|
110
|
+
this.mainDetails = null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
protected onMediaDetaching(
|
|
114
|
+
event: Events.MEDIA_DETACHING,
|
|
115
|
+
data: MediaDetachingData,
|
|
116
|
+
) {
|
|
117
|
+
this.tracksBuffered = [];
|
|
118
|
+
super.onMediaDetaching(event, data);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
|
122
|
+
this.mainDetails = data.details;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private onSubtitleFragProcessed(
|
|
126
|
+
event: Events.SUBTITLE_FRAG_PROCESSED,
|
|
127
|
+
data: SubtitleFragProcessed,
|
|
128
|
+
) {
|
|
129
|
+
const { frag, part, success } = data;
|
|
130
|
+
if (!success) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const buffered = this.tracksBuffered[this.currentTrackId];
|
|
135
|
+
if (!buffered) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Create/update a buffered array matching the interface used by BufferHelper.bufferedInfo
|
|
140
|
+
// so we can re-use the logic used to detect how much has been buffered
|
|
141
|
+
let timeRange: TimeRange | undefined;
|
|
142
|
+
const start = (part || frag).start;
|
|
143
|
+
for (let i = 0; i < buffered.length; i++) {
|
|
144
|
+
if (start >= buffered[i].start && start <= buffered[i].end) {
|
|
145
|
+
timeRange = buffered[i];
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const end = start + (part || frag).duration;
|
|
151
|
+
if (timeRange) {
|
|
152
|
+
timeRange.end = end;
|
|
153
|
+
} else {
|
|
154
|
+
timeRange = { start, end };
|
|
155
|
+
buffered.push(timeRange);
|
|
156
|
+
}
|
|
157
|
+
if (!part || end >= frag.end) {
|
|
158
|
+
this.fragmentTracker.fragBuffered(frag as MediaFragment);
|
|
159
|
+
if (!this.fragContextChanged(frag)) {
|
|
160
|
+
if (isMediaFragment(frag)) {
|
|
161
|
+
this.fragPrevious = frag;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
this.fragBufferedComplete(frag, part);
|
|
165
|
+
if (this.media) {
|
|
166
|
+
this.tickImmediate();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private onBufferFlushing(
|
|
172
|
+
event: Events.BUFFER_FLUSHING,
|
|
173
|
+
data: BufferFlushingData,
|
|
174
|
+
) {
|
|
175
|
+
const { startOffset, endOffset } = data;
|
|
176
|
+
if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
|
|
177
|
+
const endOffsetSubtitles = endOffset - 1;
|
|
178
|
+
if (endOffsetSubtitles <= 0) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
data.endOffsetSubtitles = Math.max(0, endOffsetSubtitles);
|
|
182
|
+
this.tracksBuffered.forEach((buffered) => {
|
|
183
|
+
if (!buffered) return;
|
|
184
|
+
for (let i = 0; i < buffered.length; ) {
|
|
185
|
+
if (buffered[i].end <= endOffsetSubtitles) {
|
|
186
|
+
buffered.shift();
|
|
187
|
+
continue;
|
|
188
|
+
} else if (buffered[i].start < endOffsetSubtitles) {
|
|
189
|
+
buffered[i].start = endOffsetSubtitles;
|
|
190
|
+
} else {
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
i++;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
this.fragmentTracker.removeFragmentsInRange(
|
|
197
|
+
startOffset,
|
|
198
|
+
endOffsetSubtitles,
|
|
199
|
+
PlaylistLevelType.SUBTITLE,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// If something goes wrong, proceed to next frag, if we were processing one.
|
|
205
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
|
206
|
+
const frag = data.frag;
|
|
207
|
+
|
|
208
|
+
if (frag?.type === PlaylistLevelType.SUBTITLE) {
|
|
209
|
+
if (data.details === ErrorDetails.FRAG_GAP) {
|
|
210
|
+
this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
|
|
211
|
+
}
|
|
212
|
+
if (this.fragCurrent) {
|
|
213
|
+
this.fragCurrent.abortRequests();
|
|
214
|
+
}
|
|
215
|
+
if (this.state !== State.STOPPED) {
|
|
216
|
+
this.state = State.IDLE;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Got all new subtitle levels.
|
|
222
|
+
private onSubtitleTracksUpdated(
|
|
223
|
+
event: Events.SUBTITLE_TRACKS_UPDATED,
|
|
224
|
+
{ subtitleTracks }: SubtitleTracksUpdatedData,
|
|
225
|
+
) {
|
|
226
|
+
if (this.levels && subtitleOptionsIdentical(this.levels, subtitleTracks)) {
|
|
227
|
+
this.levels = subtitleTracks.map(
|
|
228
|
+
(mediaPlaylist) => new Level(mediaPlaylist),
|
|
229
|
+
);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
this.tracksBuffered = [];
|
|
233
|
+
this.levels = subtitleTracks.map((mediaPlaylist) => {
|
|
234
|
+
const level = new Level(mediaPlaylist);
|
|
235
|
+
this.tracksBuffered[level.id] = [];
|
|
236
|
+
return level;
|
|
237
|
+
});
|
|
238
|
+
this.fragmentTracker.removeFragmentsInRange(
|
|
239
|
+
0,
|
|
240
|
+
Number.POSITIVE_INFINITY,
|
|
241
|
+
PlaylistLevelType.SUBTITLE,
|
|
242
|
+
);
|
|
243
|
+
this.fragPrevious = null;
|
|
244
|
+
this.mediaBuffer = null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private onSubtitleTrackSwitch(
|
|
248
|
+
event: Events.SUBTITLE_TRACK_SWITCH,
|
|
249
|
+
data: TrackSwitchedData,
|
|
250
|
+
) {
|
|
251
|
+
this.currentTrackId = data.id;
|
|
252
|
+
|
|
253
|
+
if (!this.levels?.length || this.currentTrackId === -1) {
|
|
254
|
+
this.clearInterval();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check if track has the necessary details to load fragments
|
|
259
|
+
const currentTrack = this.levels[this.currentTrackId] as Level | undefined;
|
|
260
|
+
if (!currentTrack?.details) {
|
|
261
|
+
this.mediaBuffer = null;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
this.mediaBuffer = this.mediaBufferTimeRanges;
|
|
265
|
+
if (this.state !== State.STOPPED) {
|
|
266
|
+
this.setInterval(TICK_INTERVAL);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Got a new set of subtitle fragments.
|
|
271
|
+
private onSubtitleTrackLoaded(
|
|
272
|
+
event: Events.SUBTITLE_TRACK_LOADED,
|
|
273
|
+
data: TrackLoadedData,
|
|
274
|
+
) {
|
|
275
|
+
const { currentTrackId, levels } = this;
|
|
276
|
+
const { details: newDetails, id: trackId } = data;
|
|
277
|
+
if (!levels) {
|
|
278
|
+
this.warn(`Subtitle tracks were reset while loading level ${trackId}`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const track = levels[trackId] as Level | undefined;
|
|
282
|
+
if (trackId >= levels.length || !track) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
this.log(
|
|
286
|
+
`Subtitle track ${trackId} loaded [${newDetails.startSN},${
|
|
287
|
+
newDetails.endSN
|
|
288
|
+
}]${
|
|
289
|
+
newDetails.lastPartSn
|
|
290
|
+
? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]`
|
|
291
|
+
: ''
|
|
292
|
+
},duration:${newDetails.totalduration}`,
|
|
293
|
+
);
|
|
294
|
+
this.mediaBuffer = this.mediaBufferTimeRanges;
|
|
295
|
+
|
|
296
|
+
const mainDetails = this.mainDetails;
|
|
297
|
+
let sliding = 0;
|
|
298
|
+
if (newDetails.live || track.details?.live) {
|
|
299
|
+
if (newDetails.deltaUpdateFailed) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (!mainDetails) {
|
|
303
|
+
this.startFragRequested = false;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (track.details) {
|
|
307
|
+
sliding = this.alignPlaylists(
|
|
308
|
+
newDetails,
|
|
309
|
+
track.details,
|
|
310
|
+
this.levelLastLoaded?.details,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
if (!newDetails.alignedSliding) {
|
|
314
|
+
// line up live playlist with main so that fragments in range are loaded
|
|
315
|
+
alignStream(mainDetails, newDetails, this);
|
|
316
|
+
sliding = newDetails.fragmentStart;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
track.details = newDetails;
|
|
321
|
+
this.levelLastLoaded = track;
|
|
322
|
+
|
|
323
|
+
// compute start position if we are aligned with the main playlist
|
|
324
|
+
if (mainDetails && !this.startFragRequested) {
|
|
325
|
+
this.setStartPosition(mainDetails, sliding);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (trackId !== currentTrackId) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
this.hls.trigger(Events.SUBTITLE_TRACK_UPDATED, {
|
|
333
|
+
details: newDetails,
|
|
334
|
+
id: trackId,
|
|
335
|
+
groupId: data.groupId,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// trigger handler right now
|
|
339
|
+
this.tickImmediate();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
_handleFragmentLoadComplete(fragLoadedData: FragLoadedData) {
|
|
343
|
+
const { frag, payload } = fragLoadedData;
|
|
344
|
+
const decryptData = frag.decryptdata;
|
|
345
|
+
const hls = this.hls;
|
|
346
|
+
|
|
347
|
+
if (this.fragContextChanged(frag)) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// check to see if the payload needs to be decrypted
|
|
351
|
+
if (
|
|
352
|
+
payload &&
|
|
353
|
+
payload.byteLength > 0 &&
|
|
354
|
+
decryptData?.key &&
|
|
355
|
+
decryptData.iv &&
|
|
356
|
+
isFullSegmentEncryption(decryptData.method)
|
|
357
|
+
) {
|
|
358
|
+
const startTime = performance.now();
|
|
359
|
+
// decrypt the subtitles
|
|
360
|
+
this.decrypter
|
|
361
|
+
.decrypt(
|
|
362
|
+
new Uint8Array(payload),
|
|
363
|
+
decryptData.key.buffer,
|
|
364
|
+
decryptData.iv.buffer,
|
|
365
|
+
getAesModeFromFullSegmentMethod(decryptData.method),
|
|
366
|
+
)
|
|
367
|
+
.catch((err) => {
|
|
368
|
+
hls.trigger(Events.ERROR, {
|
|
369
|
+
type: ErrorTypes.MEDIA_ERROR,
|
|
370
|
+
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
|
371
|
+
fatal: false,
|
|
372
|
+
error: err,
|
|
373
|
+
reason: err.message,
|
|
374
|
+
frag,
|
|
375
|
+
});
|
|
376
|
+
throw err;
|
|
377
|
+
})
|
|
378
|
+
.then((decryptedData) => {
|
|
379
|
+
const endTime = performance.now();
|
|
380
|
+
hls.trigger(Events.FRAG_DECRYPTED, {
|
|
381
|
+
frag,
|
|
382
|
+
payload: decryptedData,
|
|
383
|
+
stats: {
|
|
384
|
+
tstart: startTime,
|
|
385
|
+
tdecrypt: endTime,
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
})
|
|
389
|
+
.catch((err) => {
|
|
390
|
+
this.warn(`${err.name}: ${err.message}`);
|
|
391
|
+
this.state = State.IDLE;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
doTick() {
|
|
397
|
+
if (this.state === State.IDLE) {
|
|
398
|
+
if (
|
|
399
|
+
!this.media &&
|
|
400
|
+
!this.primaryPrefetch &&
|
|
401
|
+
(this.startFragRequested || !this.config.startFragPrefetch)
|
|
402
|
+
) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const { currentTrackId, levels } = this;
|
|
406
|
+
const track = levels?.[currentTrackId];
|
|
407
|
+
const trackDetails = track?.details;
|
|
408
|
+
if (
|
|
409
|
+
!trackDetails ||
|
|
410
|
+
this.waitForLive(track) ||
|
|
411
|
+
this.waitForCdnTuneIn(trackDetails)
|
|
412
|
+
) {
|
|
413
|
+
this.startFragRequested = false;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const { config } = this;
|
|
417
|
+
const currentTime = this.getLoadPosition();
|
|
418
|
+
const bufferedInfo = BufferHelper.bufferedInfo(
|
|
419
|
+
this.tracksBuffered[this.currentTrackId] || [],
|
|
420
|
+
currentTime,
|
|
421
|
+
config.maxBufferHole,
|
|
422
|
+
);
|
|
423
|
+
const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
|
|
424
|
+
const maxBufLen =
|
|
425
|
+
this.hls.maxBufferLength + trackDetails.levelTargetDuration;
|
|
426
|
+
|
|
427
|
+
if (bufferLen > maxBufLen || (bufferLen && !this.buffering)) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
let frag = this.getNextFragment(currentTime, trackDetails);
|
|
431
|
+
if (!frag) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
// Load earlier fragment in same discontinuity to make up for misaligned playlists and cues that extend beyond end of segment
|
|
435
|
+
if (isMediaFragment(frag)) {
|
|
436
|
+
const curSNIdx = frag.sn - trackDetails.startSN;
|
|
437
|
+
const prevFrag = trackDetails.fragments[curSNIdx - 1] as
|
|
438
|
+
| MediaFragment
|
|
439
|
+
| undefined;
|
|
440
|
+
if (
|
|
441
|
+
prevFrag &&
|
|
442
|
+
prevFrag.cc === frag.cc &&
|
|
443
|
+
!trackDetails.partList?.length &&
|
|
444
|
+
this.fragmentTracker.getState(prevFrag) === FragmentState.NOT_LOADED
|
|
445
|
+
) {
|
|
446
|
+
frag = prevFrag;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
this.loadFragment(frag, track, targetBufferTime);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
protected loadFragment(
|
|
454
|
+
frag: Fragment,
|
|
455
|
+
level: Level,
|
|
456
|
+
targetBufferTime: number,
|
|
457
|
+
) {
|
|
458
|
+
// Check if fragment is not loaded
|
|
459
|
+
const fragState = this.fragmentTracker.getState(frag);
|
|
460
|
+
if (
|
|
461
|
+
fragState === FragmentState.NOT_LOADED ||
|
|
462
|
+
fragState === FragmentState.PARTIAL
|
|
463
|
+
) {
|
|
464
|
+
if (!isMediaFragment(frag)) {
|
|
465
|
+
this._loadInitSegment(frag, level);
|
|
466
|
+
} else {
|
|
467
|
+
super.loadFragment(frag, level, targetBufferTime);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
get mediaBufferTimeRanges(): Bufferable {
|
|
473
|
+
return new BufferableInstance(
|
|
474
|
+
this.tracksBuffered[this.currentTrackId] || [],
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
class BufferableInstance implements Bufferable {
|
|
480
|
+
public readonly buffered: TimeRanges;
|
|
481
|
+
|
|
482
|
+
constructor(timeranges: TimeRange[]) {
|
|
483
|
+
const getRange = (
|
|
484
|
+
name: 'start' | 'end',
|
|
485
|
+
index: number,
|
|
486
|
+
length: number,
|
|
487
|
+
): number => {
|
|
488
|
+
index = index >>> 0;
|
|
489
|
+
if (index > length - 1) {
|
|
490
|
+
throw new DOMException(
|
|
491
|
+
`Failed to execute '${name}' on 'TimeRanges': The index provided (${index}) is greater than the maximum bound (${length})`,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
return timeranges[index][name];
|
|
495
|
+
};
|
|
496
|
+
this.buffered = {
|
|
497
|
+
get length() {
|
|
498
|
+
return timeranges.length;
|
|
499
|
+
},
|
|
500
|
+
end(index: number): number {
|
|
501
|
+
return getRange('end', index, timeranges.length);
|
|
502
|
+
},
|
|
503
|
+
start(index: number): number {
|
|
504
|
+
return getRange('start', index, timeranges.length);
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
}
|