@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,488 @@
|
|
|
1
|
+
import { getId3Frames } from '@svta/common-media-library/id3/getId3Frames';
|
|
2
|
+
import { isId3TimestampFrame } from '@svta/common-media-library/id3/isId3TimestampFrame';
|
|
3
|
+
import { Events } from '../events';
|
|
4
|
+
import {
|
|
5
|
+
isDateRangeCueAttribute,
|
|
6
|
+
isSCTE35Attribute,
|
|
7
|
+
} from '../loader/date-range';
|
|
8
|
+
import { MetadataSchema } from '../types/demuxer';
|
|
9
|
+
import { hexToArrayBuffer } from '../utils/hex';
|
|
10
|
+
import { stringify } from '../utils/safe-json-stringify';
|
|
11
|
+
import { createTrackNode, removeCuesInRange } from '../utils/texttrack-utils';
|
|
12
|
+
import type { MediaFragment } from '../hls';
|
|
13
|
+
import type Hls from '../hls';
|
|
14
|
+
import type { DateRange } from '../loader/date-range';
|
|
15
|
+
import type { LevelDetails } from '../loader/level-details';
|
|
16
|
+
import type { ComponentAPI } from '../types/component-api';
|
|
17
|
+
import type {
|
|
18
|
+
BufferFlushingData,
|
|
19
|
+
FragParsingMetadataData,
|
|
20
|
+
LevelPTSUpdatedData,
|
|
21
|
+
LevelUpdatedData,
|
|
22
|
+
MediaAttachingData,
|
|
23
|
+
MediaDetachingData,
|
|
24
|
+
} from '../types/events';
|
|
25
|
+
|
|
26
|
+
declare global {
|
|
27
|
+
interface Window {
|
|
28
|
+
WebKitDataCue: VTTCue | void;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const MIN_CUE_DURATION = 0.25;
|
|
33
|
+
|
|
34
|
+
function getCueClass(): typeof VTTCue | typeof TextTrackCue | undefined {
|
|
35
|
+
if (typeof self === 'undefined') return undefined;
|
|
36
|
+
return (self.VTTCue as typeof VTTCue | undefined) || self.TextTrackCue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createCueWithDataFields(
|
|
40
|
+
Cue: typeof VTTCue | typeof TextTrackCue,
|
|
41
|
+
startTime: number,
|
|
42
|
+
endTime: number,
|
|
43
|
+
data: Object,
|
|
44
|
+
type?: string,
|
|
45
|
+
): VTTCue | TextTrackCue | undefined {
|
|
46
|
+
let cue = new Cue(startTime, endTime, '');
|
|
47
|
+
try {
|
|
48
|
+
(cue as any).value = data;
|
|
49
|
+
if (type) {
|
|
50
|
+
(cue as any).type = type;
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
cue = new Cue(
|
|
54
|
+
startTime,
|
|
55
|
+
endTime,
|
|
56
|
+
stringify(type ? { type, ...data } : data),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return cue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// VTTCue latest draft allows an infinite duration, fallback
|
|
63
|
+
// to MAX_VALUE if necessary
|
|
64
|
+
const MAX_CUE_ENDTIME = (() => {
|
|
65
|
+
const Cue = getCueClass();
|
|
66
|
+
try {
|
|
67
|
+
Cue && new Cue(0, Number.POSITIVE_INFINITY, '');
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return Number.MAX_VALUE;
|
|
70
|
+
}
|
|
71
|
+
return Number.POSITIVE_INFINITY;
|
|
72
|
+
})();
|
|
73
|
+
|
|
74
|
+
class ID3TrackController implements ComponentAPI {
|
|
75
|
+
private hls: Hls | null;
|
|
76
|
+
private id3Track: HTMLTrackElement | null = null;
|
|
77
|
+
private media: HTMLMediaElement | null = null;
|
|
78
|
+
private dateRangeCuesAppended: Record<
|
|
79
|
+
string,
|
|
80
|
+
| {
|
|
81
|
+
cues: Record<string, VTTCue | TextTrackCue | undefined>;
|
|
82
|
+
dateRange: DateRange;
|
|
83
|
+
durationKnown: boolean;
|
|
84
|
+
}
|
|
85
|
+
| undefined
|
|
86
|
+
> = {};
|
|
87
|
+
private removeCues: boolean = true;
|
|
88
|
+
private assetCue?: VTTCue | TextTrackCue;
|
|
89
|
+
|
|
90
|
+
constructor(hls) {
|
|
91
|
+
this.hls = hls;
|
|
92
|
+
this._registerListeners();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public destroy() {
|
|
96
|
+
this._unregisterListeners();
|
|
97
|
+
this.id3Track = null;
|
|
98
|
+
this.media = null;
|
|
99
|
+
this.dateRangeCuesAppended = {};
|
|
100
|
+
// @ts-ignore
|
|
101
|
+
this.hls = this.onEventCueEnter = null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private _registerListeners() {
|
|
105
|
+
const { hls } = this;
|
|
106
|
+
if (hls) {
|
|
107
|
+
hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
|
|
108
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
|
109
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
|
110
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
|
111
|
+
hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
|
|
112
|
+
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
|
113
|
+
hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
|
114
|
+
hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private _unregisterListeners() {
|
|
119
|
+
const { hls } = this;
|
|
120
|
+
if (hls) {
|
|
121
|
+
hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
|
|
122
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
|
123
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
|
124
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
|
125
|
+
hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
|
|
126
|
+
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
|
127
|
+
hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
|
128
|
+
hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private onEventCueEnter = () => {
|
|
133
|
+
if (!this.hls) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.hls.trigger(Events.EVENT_CUE_ENTER, {});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Add ID3 metatadata text track.
|
|
140
|
+
private onMediaAttaching(
|
|
141
|
+
event: Events.MEDIA_ATTACHING,
|
|
142
|
+
data: MediaAttachingData,
|
|
143
|
+
): void {
|
|
144
|
+
this.media = data.media;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private onMediaAttached() {
|
|
148
|
+
const details = this.hls?.latestLevelDetails;
|
|
149
|
+
if (details) {
|
|
150
|
+
this.updateDateRangeCues(details);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private onMediaDetaching(
|
|
155
|
+
event: Events.MEDIA_DETACHING,
|
|
156
|
+
data: MediaDetachingData,
|
|
157
|
+
) {
|
|
158
|
+
this.media = null;
|
|
159
|
+
const transferringMedia = !!data.transferMedia;
|
|
160
|
+
if (transferringMedia) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (this.id3Track) {
|
|
164
|
+
this.id3Track.remove();
|
|
165
|
+
this.id3Track = null;
|
|
166
|
+
}
|
|
167
|
+
this.dateRangeCuesAppended = {};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private onManifestLoading() {
|
|
171
|
+
this.dateRangeCuesAppended = {};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private createTrack(media: HTMLMediaElement): HTMLTrackElement {
|
|
175
|
+
return createTrackNode(media, 'metadata', 'id3', '', 'hidden');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private onFragParsingMetadata(
|
|
179
|
+
event: Events.FRAG_PARSING_METADATA,
|
|
180
|
+
data: FragParsingMetadataData,
|
|
181
|
+
) {
|
|
182
|
+
if (!this.media || !this.hls) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const { enableEmsgMetadataCues, enableID3MetadataCues } = this.hls.config;
|
|
187
|
+
if (!enableEmsgMetadataCues && !enableID3MetadataCues) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const { samples } = data;
|
|
192
|
+
|
|
193
|
+
// create track dynamically
|
|
194
|
+
if (!this.id3Track) {
|
|
195
|
+
this.id3Track = this.createTrack(this.media);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const Cue = getCueClass();
|
|
199
|
+
if (!Cue) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
for (let i = 0; i < samples.length; i++) {
|
|
204
|
+
const type = samples[i].type;
|
|
205
|
+
if (
|
|
206
|
+
(type === MetadataSchema.emsg && !enableEmsgMetadataCues) ||
|
|
207
|
+
!enableID3MetadataCues
|
|
208
|
+
) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const frames = getId3Frames(samples[i].data);
|
|
213
|
+
const startTime = samples[i].pts;
|
|
214
|
+
let endTime: number = startTime + samples[i].duration;
|
|
215
|
+
|
|
216
|
+
if (endTime > MAX_CUE_ENDTIME) {
|
|
217
|
+
endTime = MAX_CUE_ENDTIME;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const timeDiff = endTime - startTime;
|
|
221
|
+
if (timeDiff <= 0) {
|
|
222
|
+
endTime = startTime + MIN_CUE_DURATION;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (let j = 0; j < frames.length; j++) {
|
|
226
|
+
const frame = frames[j];
|
|
227
|
+
// Safari doesn't put the timestamp frame in the TextTrack
|
|
228
|
+
if (!isId3TimestampFrame(frame)) {
|
|
229
|
+
// add a bounds to any unbounded cues
|
|
230
|
+
this.updateId3CueEnds(startTime, type);
|
|
231
|
+
const cue = createCueWithDataFields(
|
|
232
|
+
Cue,
|
|
233
|
+
startTime,
|
|
234
|
+
endTime,
|
|
235
|
+
frame,
|
|
236
|
+
type,
|
|
237
|
+
);
|
|
238
|
+
if (cue) {
|
|
239
|
+
this.id3Track.track.addCue(cue);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private updateId3CueEnds(startTime: number, type: MetadataSchema) {
|
|
247
|
+
const cues = this.id3Track?.track.cues;
|
|
248
|
+
if (cues) {
|
|
249
|
+
for (let i = cues.length; i--; ) {
|
|
250
|
+
const cue = cues[i] as any;
|
|
251
|
+
if (
|
|
252
|
+
cue.type === type &&
|
|
253
|
+
cue.startTime < startTime &&
|
|
254
|
+
cue.endTime === MAX_CUE_ENDTIME
|
|
255
|
+
) {
|
|
256
|
+
cue.endTime = startTime;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private onBufferFlushing(
|
|
263
|
+
event: Events.BUFFER_FLUSHING,
|
|
264
|
+
{ startOffset, endOffset, type }: BufferFlushingData,
|
|
265
|
+
) {
|
|
266
|
+
const { id3Track, hls } = this;
|
|
267
|
+
if (!hls) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const {
|
|
272
|
+
config: { enableEmsgMetadataCues, enableID3MetadataCues },
|
|
273
|
+
} = hls;
|
|
274
|
+
if (id3Track && (enableEmsgMetadataCues || enableID3MetadataCues)) {
|
|
275
|
+
let predicate;
|
|
276
|
+
|
|
277
|
+
if (type === 'audio') {
|
|
278
|
+
predicate = (cue) =>
|
|
279
|
+
(cue as any).type === MetadataSchema.audioId3 &&
|
|
280
|
+
enableID3MetadataCues;
|
|
281
|
+
} else if (type === 'video') {
|
|
282
|
+
predicate = (cue) =>
|
|
283
|
+
(cue as any).type === MetadataSchema.emsg && enableEmsgMetadataCues;
|
|
284
|
+
} else {
|
|
285
|
+
predicate = (cue) =>
|
|
286
|
+
((cue as any).type === MetadataSchema.audioId3 &&
|
|
287
|
+
enableID3MetadataCues) ||
|
|
288
|
+
((cue as any).type === MetadataSchema.emsg && enableEmsgMetadataCues);
|
|
289
|
+
}
|
|
290
|
+
removeCuesInRange(id3Track.track, startOffset, endOffset, predicate);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private onLevelUpdated(
|
|
295
|
+
event: Events.LEVEL_UPDATED,
|
|
296
|
+
{ details }: LevelUpdatedData,
|
|
297
|
+
) {
|
|
298
|
+
this.updateDateRangeCues(details, true);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private onLevelPtsUpdated(
|
|
302
|
+
event: Events.LEVEL_PTS_UPDATED,
|
|
303
|
+
data: LevelPTSUpdatedData,
|
|
304
|
+
) {
|
|
305
|
+
if (Math.abs(data.drift) > 0.01) {
|
|
306
|
+
this.updateDateRangeCues(data.details);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private updateDateRangeCues(details: LevelDetails, removeOldCues?: true) {
|
|
311
|
+
if (!this.hls || !this.media) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const {
|
|
315
|
+
assetPlayerId,
|
|
316
|
+
timelineOffset,
|
|
317
|
+
enableDateRangeMetadataCues,
|
|
318
|
+
interstitialsController,
|
|
319
|
+
} = this.hls.config;
|
|
320
|
+
if (!enableDateRangeMetadataCues) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const Cue = getCueClass();
|
|
325
|
+
if (
|
|
326
|
+
__USE_INTERSTITIALS__ &&
|
|
327
|
+
assetPlayerId &&
|
|
328
|
+
timelineOffset &&
|
|
329
|
+
!interstitialsController
|
|
330
|
+
) {
|
|
331
|
+
const { fragmentStart, fragmentEnd } = details;
|
|
332
|
+
let cue = this.assetCue;
|
|
333
|
+
if (cue) {
|
|
334
|
+
cue.startTime = fragmentStart;
|
|
335
|
+
cue.endTime = fragmentEnd;
|
|
336
|
+
} else if (Cue) {
|
|
337
|
+
cue = this.assetCue = createCueWithDataFields(
|
|
338
|
+
Cue,
|
|
339
|
+
fragmentStart,
|
|
340
|
+
fragmentEnd,
|
|
341
|
+
{ assetPlayerId: this.hls.config.assetPlayerId },
|
|
342
|
+
'hlsjs.interstitial.asset',
|
|
343
|
+
);
|
|
344
|
+
if (cue) {
|
|
345
|
+
cue.id = assetPlayerId;
|
|
346
|
+
this.id3Track ||= this.createTrack(this.media);
|
|
347
|
+
this.id3Track.track.addCue(cue);
|
|
348
|
+
cue.addEventListener('enter', this.onEventCueEnter);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (!details.hasProgramDateTime) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const { id3Track } = this;
|
|
357
|
+
const { dateRanges } = details;
|
|
358
|
+
const ids = Object.keys(dateRanges);
|
|
359
|
+
let dateRangeCuesAppended = this.dateRangeCuesAppended;
|
|
360
|
+
// Remove cues from track not found in details.dateRanges
|
|
361
|
+
if (id3Track && removeOldCues) {
|
|
362
|
+
if (id3Track.track.cues?.length) {
|
|
363
|
+
const idsToRemove = Object.keys(dateRangeCuesAppended).filter(
|
|
364
|
+
(id) => !ids.includes(id),
|
|
365
|
+
);
|
|
366
|
+
for (let i = idsToRemove.length; i--; ) {
|
|
367
|
+
const id = idsToRemove[i];
|
|
368
|
+
const cues = dateRangeCuesAppended[id]?.cues;
|
|
369
|
+
delete dateRangeCuesAppended[id];
|
|
370
|
+
if (cues) {
|
|
371
|
+
Object.keys(cues).forEach((key) => {
|
|
372
|
+
const cue = cues[key];
|
|
373
|
+
if (cue) {
|
|
374
|
+
cue.removeEventListener('enter', this.onEventCueEnter);
|
|
375
|
+
try {
|
|
376
|
+
id3Track.track.removeCue(cue);
|
|
377
|
+
} catch (e) {
|
|
378
|
+
/* no-op */
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
dateRangeCuesAppended = this.dateRangeCuesAppended = {};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Exit if the playlist does not have Date Ranges or does not have Program Date Time
|
|
389
|
+
const lastFragment = details.fragments[details.fragments.length - 1] as
|
|
390
|
+
| MediaFragment
|
|
391
|
+
| undefined;
|
|
392
|
+
if (ids.length === 0 || !Number.isFinite(lastFragment?.programDateTime)) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
this.id3Track ||= this.createTrack(this.media);
|
|
397
|
+
|
|
398
|
+
for (let i = 0; i < ids.length; i++) {
|
|
399
|
+
const id = ids[i];
|
|
400
|
+
const dateRange = dateRanges[id]!;
|
|
401
|
+
const startTime = dateRange.startTime;
|
|
402
|
+
|
|
403
|
+
// Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT)
|
|
404
|
+
const appendedDateRangeCues = dateRangeCuesAppended[id];
|
|
405
|
+
const cues = appendedDateRangeCues?.cues || {};
|
|
406
|
+
let durationKnown = appendedDateRangeCues?.durationKnown || false;
|
|
407
|
+
let endTime = MAX_CUE_ENDTIME;
|
|
408
|
+
const { duration, endDate } = dateRange;
|
|
409
|
+
if (endDate && duration !== null) {
|
|
410
|
+
endTime = startTime + duration;
|
|
411
|
+
durationKnown = true;
|
|
412
|
+
} else if (dateRange.endOnNext && !durationKnown) {
|
|
413
|
+
const nextDateRangeWithSameClass = ids.reduce(
|
|
414
|
+
(candidateDateRange: DateRange | null, id) => {
|
|
415
|
+
if (id !== dateRange.id) {
|
|
416
|
+
const otherDateRange = dateRanges[id]!;
|
|
417
|
+
if (
|
|
418
|
+
otherDateRange.class === dateRange.class &&
|
|
419
|
+
otherDateRange.startDate > dateRange.startDate &&
|
|
420
|
+
(!candidateDateRange ||
|
|
421
|
+
dateRange.startDate < candidateDateRange.startDate)
|
|
422
|
+
) {
|
|
423
|
+
return otherDateRange;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return candidateDateRange;
|
|
427
|
+
},
|
|
428
|
+
null,
|
|
429
|
+
);
|
|
430
|
+
if (nextDateRangeWithSameClass) {
|
|
431
|
+
endTime = nextDateRangeWithSameClass.startTime;
|
|
432
|
+
durationKnown = true;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Create TextTrack Cues for each MetadataGroup Item (select DateRange attribute)
|
|
437
|
+
// This is to emulate Safari HLS playback handling of DateRange tags
|
|
438
|
+
const attributes = Object.keys(dateRange.attr);
|
|
439
|
+
for (let j = 0; j < attributes.length; j++) {
|
|
440
|
+
const key = attributes[j];
|
|
441
|
+
if (!isDateRangeCueAttribute(key)) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const cue = cues[key];
|
|
445
|
+
if (cue) {
|
|
446
|
+
if (durationKnown && !appendedDateRangeCues?.durationKnown) {
|
|
447
|
+
cue.endTime = endTime;
|
|
448
|
+
} else if (Math.abs(cue.startTime - startTime) > 0.01) {
|
|
449
|
+
cue.startTime = startTime;
|
|
450
|
+
cue.endTime = endTime;
|
|
451
|
+
}
|
|
452
|
+
} else if (Cue) {
|
|
453
|
+
let data = dateRange.attr[key];
|
|
454
|
+
if (isSCTE35Attribute(key)) {
|
|
455
|
+
data = hexToArrayBuffer(data);
|
|
456
|
+
}
|
|
457
|
+
const payload: any = { key, data };
|
|
458
|
+
const cue = createCueWithDataFields(
|
|
459
|
+
Cue,
|
|
460
|
+
startTime,
|
|
461
|
+
endTime,
|
|
462
|
+
payload,
|
|
463
|
+
MetadataSchema.dateRange,
|
|
464
|
+
);
|
|
465
|
+
if (cue) {
|
|
466
|
+
cue.id = id;
|
|
467
|
+
this.id3Track.track.addCue(cue);
|
|
468
|
+
cues[key] = cue;
|
|
469
|
+
if (__USE_INTERSTITIALS__ && interstitialsController) {
|
|
470
|
+
if (key === 'X-ASSET-LIST' || key === 'X-ASSET-URL') {
|
|
471
|
+
cue.addEventListener('enter', this.onEventCueEnter);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Keep track of processed DateRanges by ID for updating cues with new DateRange tag attributes
|
|
479
|
+
dateRangeCuesAppended[id] = {
|
|
480
|
+
cues,
|
|
481
|
+
dateRange,
|
|
482
|
+
durationKnown,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export default ID3TrackController;
|