@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,487 @@
|
|
|
1
|
+
import { buildAbsoluteURL } from 'url-toolkit';
|
|
2
|
+
import { LoadStats } from './load-stats';
|
|
3
|
+
import { PlaylistLevelType } from '../types/loader';
|
|
4
|
+
import type { LevelKey } from './level-key';
|
|
5
|
+
import type {
|
|
6
|
+
FragmentLoaderContext,
|
|
7
|
+
KeyLoaderContext,
|
|
8
|
+
Loader,
|
|
9
|
+
} from '../types/loader';
|
|
10
|
+
import type { AttrList } from '../utils/attr-list';
|
|
11
|
+
import type { KeySystemFormats } from '../utils/mediakeys-helper';
|
|
12
|
+
|
|
13
|
+
export const enum ElementaryStreamTypes {
|
|
14
|
+
AUDIO = 'audio',
|
|
15
|
+
VIDEO = 'video',
|
|
16
|
+
AUDIOVIDEO = 'audiovideo',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ElementaryStreamInfo {
|
|
20
|
+
startPTS: number;
|
|
21
|
+
endPTS: number;
|
|
22
|
+
startDTS: number;
|
|
23
|
+
endDTS: number;
|
|
24
|
+
partial?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ElementaryStreams = Record<
|
|
28
|
+
ElementaryStreamTypes,
|
|
29
|
+
ElementaryStreamInfo | null
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
export type Base = {
|
|
33
|
+
url: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export class BaseSegment {
|
|
37
|
+
private _byteRange: [number, number] | null = null;
|
|
38
|
+
private _url: string | null = null;
|
|
39
|
+
private _stats: LoadStats | null = null;
|
|
40
|
+
private _streams: ElementaryStreams | null = null;
|
|
41
|
+
|
|
42
|
+
// baseurl is the URL to the playlist
|
|
43
|
+
public readonly base: Base;
|
|
44
|
+
|
|
45
|
+
// relurl is the portion of the URL that comes from inside the playlist.
|
|
46
|
+
public relurl?: string;
|
|
47
|
+
|
|
48
|
+
public algoRelurl?: string;
|
|
49
|
+
|
|
50
|
+
constructor(base: Base | string) {
|
|
51
|
+
if (typeof base === 'string') {
|
|
52
|
+
base = { url: base };
|
|
53
|
+
}
|
|
54
|
+
this.base = base;
|
|
55
|
+
makeEnumerable(this, 'stats');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
|
|
59
|
+
setByteRange(value: string, previous?: BaseSegment) {
|
|
60
|
+
const params = value.split('@', 2);
|
|
61
|
+
let start: number;
|
|
62
|
+
if (params.length === 1) {
|
|
63
|
+
start = previous?.byteRangeEndOffset || 0;
|
|
64
|
+
} else {
|
|
65
|
+
start = parseInt(params[1]);
|
|
66
|
+
}
|
|
67
|
+
this._byteRange = [start, parseInt(params[0]) + start];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get baseurl(): string {
|
|
71
|
+
return this.base.url;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get byteRange(): [number, number] | [] {
|
|
75
|
+
if (this._byteRange === null) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return this._byteRange;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get byteRangeStartOffset(): number | undefined {
|
|
83
|
+
return this.byteRange[0];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get byteRangeEndOffset(): number | undefined {
|
|
87
|
+
return this.byteRange[1];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get elementaryStreams(): ElementaryStreams {
|
|
91
|
+
if (this._streams === null) {
|
|
92
|
+
this._streams = {
|
|
93
|
+
[ElementaryStreamTypes.AUDIO]: null,
|
|
94
|
+
[ElementaryStreamTypes.VIDEO]: null,
|
|
95
|
+
[ElementaryStreamTypes.AUDIOVIDEO]: null,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return this._streams;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
set elementaryStreams(value: ElementaryStreams) {
|
|
102
|
+
this._streams = value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get hasStats(): boolean {
|
|
106
|
+
return this._stats !== null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get hasStreams(): boolean {
|
|
110
|
+
return this._streams !== null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get stats(): LoadStats {
|
|
114
|
+
if (this._stats === null) {
|
|
115
|
+
this._stats = new LoadStats();
|
|
116
|
+
}
|
|
117
|
+
return this._stats;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
set stats(value: LoadStats) {
|
|
121
|
+
this._stats = value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
get url(): string {
|
|
125
|
+
if (!this._url && this.baseurl && this.relurl) {
|
|
126
|
+
this._url = buildAbsoluteURL(this.baseurl, this.relurl, {
|
|
127
|
+
alwaysNormalize: true,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return this._url || '';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
set url(value: string) {
|
|
134
|
+
this._url = value;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
clearElementaryStreamInfo() {
|
|
138
|
+
const { elementaryStreams } = this;
|
|
139
|
+
elementaryStreams[ElementaryStreamTypes.AUDIO] = null;
|
|
140
|
+
elementaryStreams[ElementaryStreamTypes.VIDEO] = null;
|
|
141
|
+
elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface MediaFragment extends Fragment {
|
|
146
|
+
sn: number;
|
|
147
|
+
ref: MediaFragmentRef;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type MediaFragmentRef = {
|
|
151
|
+
base: Base;
|
|
152
|
+
start: number;
|
|
153
|
+
duration: number;
|
|
154
|
+
sn: number;
|
|
155
|
+
programDateTime: number | null;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export function isMediaFragment(frag: Fragment): frag is MediaFragment {
|
|
159
|
+
return frag.sn !== 'initSegment';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
|
|
164
|
+
*/
|
|
165
|
+
export class Fragment extends BaseSegment {
|
|
166
|
+
private _decryptdata: LevelKey | null = null;
|
|
167
|
+
private _programDateTime: number | null = null;
|
|
168
|
+
private _ref: MediaFragmentRef | null = null;
|
|
169
|
+
// Approximate bit rate of the fragment expressed in bits per second (bps) as indicated by the last EXT-X-BITRATE (kbps) tag
|
|
170
|
+
private _bitrate?: number;
|
|
171
|
+
|
|
172
|
+
public rawProgramDateTime: string | null = null;
|
|
173
|
+
public tagList: Array<string[]> = [];
|
|
174
|
+
|
|
175
|
+
// EXTINF has to be present for a m3u8 to be considered valid
|
|
176
|
+
public duration: number = 0;
|
|
177
|
+
// sn notates the sequence number for a segment, and if set to a string can be 'initSegment'
|
|
178
|
+
public sn: number | 'initSegment' = 0;
|
|
179
|
+
// levelkeys are the EXT-X-KEY tags that apply to this segment for decryption
|
|
180
|
+
// core difference from the private field _decryptdata is the lack of the initialized IV
|
|
181
|
+
// _decryptdata will set the IV for this segment based on the segment number in the fragment
|
|
182
|
+
public levelkeys?: { [key: string]: LevelKey | undefined };
|
|
183
|
+
// A string representing the fragment type
|
|
184
|
+
public readonly type: PlaylistLevelType;
|
|
185
|
+
// A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading
|
|
186
|
+
public loader: Loader<FragmentLoaderContext> | null = null;
|
|
187
|
+
// A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading
|
|
188
|
+
public keyLoader: Loader<KeyLoaderContext> | null = null;
|
|
189
|
+
// The level/track index to which the fragment belongs
|
|
190
|
+
public level: number = -1;
|
|
191
|
+
// The continuity counter of the fragment
|
|
192
|
+
public cc: number = 0;
|
|
193
|
+
// The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
|
|
194
|
+
public startPTS?: number;
|
|
195
|
+
// The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
|
|
196
|
+
public endPTS?: number;
|
|
197
|
+
// The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
|
|
198
|
+
public startDTS?: number;
|
|
199
|
+
// The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
|
|
200
|
+
public endDTS?: number;
|
|
201
|
+
// The start time of the fragment, as listed in the manifest. Updated after transmux complete.
|
|
202
|
+
public start: number = 0;
|
|
203
|
+
// The offset time (seconds) of the fragment from the start of the Playlist
|
|
204
|
+
public playlistOffset: number = 0;
|
|
205
|
+
// Set by `updateFragPTSDTS` in level-helper
|
|
206
|
+
public deltaPTS?: number;
|
|
207
|
+
// The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
|
|
208
|
+
public maxStartPTS?: number;
|
|
209
|
+
// The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
|
|
210
|
+
public minEndPTS?: number;
|
|
211
|
+
// Init Segment bytes (unset for media segments)
|
|
212
|
+
public data?: Uint8Array;
|
|
213
|
+
// A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered
|
|
214
|
+
public bitrateTest: boolean = false;
|
|
215
|
+
// #EXTINF segment title
|
|
216
|
+
public title: string | null = null;
|
|
217
|
+
// The Media Initialization Section for this segment
|
|
218
|
+
public initSegment: Fragment | null = null;
|
|
219
|
+
// Fragment is the last fragment in the media playlist
|
|
220
|
+
public endList?: boolean;
|
|
221
|
+
// Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded
|
|
222
|
+
public gap?: boolean;
|
|
223
|
+
// Deprecated
|
|
224
|
+
public urlId: number = 0;
|
|
225
|
+
|
|
226
|
+
constructor(type: PlaylistLevelType, base: Base | string) {
|
|
227
|
+
super(base);
|
|
228
|
+
this.type = type;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
get byteLength(): number | null {
|
|
232
|
+
if (this.hasStats) {
|
|
233
|
+
const total = this.stats.total;
|
|
234
|
+
if (total) {
|
|
235
|
+
return total;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (this.byteRange.length) {
|
|
239
|
+
const start = this.byteRange[0];
|
|
240
|
+
const end = this.byteRange[1];
|
|
241
|
+
if (Number.isFinite(start) && Number.isFinite(end)) {
|
|
242
|
+
return (end as number) - (start as number);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
get bitrate(): number | null {
|
|
249
|
+
if (this.byteLength) {
|
|
250
|
+
return (this.byteLength * 8) / this.duration;
|
|
251
|
+
}
|
|
252
|
+
if (this._bitrate) {
|
|
253
|
+
return this._bitrate;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
set bitrate(value: number) {
|
|
259
|
+
this._bitrate = value;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
get decryptdata(): LevelKey | null {
|
|
263
|
+
const { levelkeys } = this;
|
|
264
|
+
|
|
265
|
+
if (!levelkeys || levelkeys.NONE) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (levelkeys.identity) {
|
|
270
|
+
if (!this._decryptdata) {
|
|
271
|
+
this._decryptdata = levelkeys.identity.getDecryptData(this.sn);
|
|
272
|
+
}
|
|
273
|
+
} else if (!this._decryptdata?.keyId) {
|
|
274
|
+
const keyFormats = Object.keys(levelkeys);
|
|
275
|
+
if (keyFormats.length === 1) {
|
|
276
|
+
const levelKey = (this._decryptdata = levelkeys[keyFormats[0]] || null);
|
|
277
|
+
if (levelKey) {
|
|
278
|
+
this._decryptdata = levelKey.getDecryptData(this.sn, levelkeys);
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
// Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system.
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return this._decryptdata;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
get end(): number {
|
|
289
|
+
return this.start + this.duration;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
get endProgramDateTime() {
|
|
293
|
+
if (this.programDateTime === null) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const duration = !Number.isFinite(this.duration) ? 0 : this.duration;
|
|
298
|
+
|
|
299
|
+
return this.programDateTime + duration * 1000;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
get encrypted() {
|
|
303
|
+
// At the m3u8-parser level we need to add support for manifest signalled keyformats
|
|
304
|
+
// when we want the fragment to start reporting that it is encrypted.
|
|
305
|
+
// Currently, keyFormat will only be set for identity keys
|
|
306
|
+
if (this._decryptdata?.encrypted) {
|
|
307
|
+
return true;
|
|
308
|
+
} else if (this.levelkeys) {
|
|
309
|
+
const keyFormats = Object.keys(this.levelkeys);
|
|
310
|
+
const len = keyFormats.length;
|
|
311
|
+
if (len > 1 || (len === 1 && this.levelkeys[keyFormats[0]]?.encrypted)) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
get programDateTime(): number | null {
|
|
319
|
+
if (this._programDateTime === null && this.rawProgramDateTime) {
|
|
320
|
+
this.programDateTime = Date.parse(this.rawProgramDateTime);
|
|
321
|
+
}
|
|
322
|
+
return this._programDateTime;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
set programDateTime(value: number | null) {
|
|
326
|
+
if (!Number.isFinite(value)) {
|
|
327
|
+
this._programDateTime = this.rawProgramDateTime = null;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
this._programDateTime = value;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
get ref(): MediaFragmentRef | null {
|
|
334
|
+
if (!isMediaFragment(this)) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
if (!this._ref) {
|
|
338
|
+
this._ref = {
|
|
339
|
+
base: this.base,
|
|
340
|
+
start: this.start,
|
|
341
|
+
duration: this.duration,
|
|
342
|
+
sn: this.sn,
|
|
343
|
+
programDateTime: this.programDateTime,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return this._ref;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
addStart(value: number) {
|
|
350
|
+
this.setStart(this.start + value);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
setStart(value: number) {
|
|
354
|
+
this.start = value;
|
|
355
|
+
if (this._ref) {
|
|
356
|
+
this._ref.start = value;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
setDuration(value: number) {
|
|
361
|
+
this.duration = value;
|
|
362
|
+
if (this._ref) {
|
|
363
|
+
this._ref.duration = value;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
setKeyFormat(keyFormat: KeySystemFormats) {
|
|
368
|
+
const levelkeys = this.levelkeys;
|
|
369
|
+
if (levelkeys) {
|
|
370
|
+
const key = levelkeys[keyFormat];
|
|
371
|
+
if (key && !this._decryptdata?.keyId) {
|
|
372
|
+
this._decryptdata = key.getDecryptData(this.sn, levelkeys);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
abortRequests(): void {
|
|
378
|
+
this.loader?.abort();
|
|
379
|
+
this.keyLoader?.abort();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
setElementaryStreamInfo(
|
|
383
|
+
type: ElementaryStreamTypes,
|
|
384
|
+
startPTS: number,
|
|
385
|
+
endPTS: number,
|
|
386
|
+
startDTS: number,
|
|
387
|
+
endDTS: number,
|
|
388
|
+
partial: boolean = false,
|
|
389
|
+
) {
|
|
390
|
+
const { elementaryStreams } = this;
|
|
391
|
+
const info = elementaryStreams[type];
|
|
392
|
+
if (!info) {
|
|
393
|
+
elementaryStreams[type] = {
|
|
394
|
+
startPTS,
|
|
395
|
+
endPTS,
|
|
396
|
+
startDTS,
|
|
397
|
+
endDTS,
|
|
398
|
+
partial,
|
|
399
|
+
};
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
info.startPTS = Math.min(info.startPTS, startPTS);
|
|
404
|
+
info.endPTS = Math.max(info.endPTS, endPTS);
|
|
405
|
+
info.startDTS = Math.min(info.startDTS, startDTS);
|
|
406
|
+
info.endDTS = Math.max(info.endDTS, endDTS);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Object representing parsed data from an HLS Partial Segment. Found in {@link hls.js#LevelDetails.partList}.
|
|
412
|
+
*/
|
|
413
|
+
export class Part extends BaseSegment {
|
|
414
|
+
public readonly fragOffset: number = 0;
|
|
415
|
+
public readonly duration: number = 0;
|
|
416
|
+
public readonly gap: boolean = false;
|
|
417
|
+
public readonly independent: boolean = false;
|
|
418
|
+
public readonly relurl: string;
|
|
419
|
+
public readonly fragment: MediaFragment;
|
|
420
|
+
public readonly index: number;
|
|
421
|
+
|
|
422
|
+
constructor(
|
|
423
|
+
partAttrs: AttrList,
|
|
424
|
+
frag: MediaFragment,
|
|
425
|
+
base: Base | string,
|
|
426
|
+
index: number,
|
|
427
|
+
previous?: Part,
|
|
428
|
+
) {
|
|
429
|
+
super(base);
|
|
430
|
+
this.duration = partAttrs.decimalFloatingPoint('DURATION');
|
|
431
|
+
this.gap = partAttrs.bool('GAP');
|
|
432
|
+
this.independent = partAttrs.bool('INDEPENDENT');
|
|
433
|
+
this.relurl = partAttrs.enumeratedString('URI') as string;
|
|
434
|
+
this.fragment = frag;
|
|
435
|
+
this.index = index;
|
|
436
|
+
const byteRange = partAttrs.enumeratedString('BYTERANGE');
|
|
437
|
+
if (byteRange) {
|
|
438
|
+
this.setByteRange(byteRange, previous);
|
|
439
|
+
}
|
|
440
|
+
if (previous) {
|
|
441
|
+
this.fragOffset = previous.fragOffset + previous.duration;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
get start(): number {
|
|
446
|
+
return this.fragment.start + this.fragOffset;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
get end(): number {
|
|
450
|
+
return this.start + this.duration;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
get loaded(): boolean {
|
|
454
|
+
const { elementaryStreams } = this;
|
|
455
|
+
return !!(
|
|
456
|
+
elementaryStreams.audio ||
|
|
457
|
+
elementaryStreams.video ||
|
|
458
|
+
elementaryStreams.audiovideo ||
|
|
459
|
+
(this.fragment.type === PlaylistLevelType.SUBTITLE && this.stats.loaded)
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function getOwnPropertyDescriptorFromPrototypeChain(
|
|
465
|
+
object: Object | undefined,
|
|
466
|
+
property: string,
|
|
467
|
+
) {
|
|
468
|
+
const prototype = Object.getPrototypeOf(object);
|
|
469
|
+
if (prototype) {
|
|
470
|
+
const propertyDescriptor = Object.getOwnPropertyDescriptor(
|
|
471
|
+
prototype,
|
|
472
|
+
property,
|
|
473
|
+
);
|
|
474
|
+
if (propertyDescriptor) {
|
|
475
|
+
return propertyDescriptor;
|
|
476
|
+
}
|
|
477
|
+
return getOwnPropertyDescriptorFromPrototypeChain(prototype, property);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function makeEnumerable(object: Object, property: string) {
|
|
482
|
+
const d = getOwnPropertyDescriptorFromPrototypeChain(object, property);
|
|
483
|
+
if (d) {
|
|
484
|
+
d.enumerable = true;
|
|
485
|
+
Object.defineProperty(object, property, d);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AssetListJSON,
|
|
3
|
+
getInterstitialUrl,
|
|
4
|
+
type InterstitialEventWithAssetList,
|
|
5
|
+
} from './interstitial-event';
|
|
6
|
+
import { ErrorDetails, ErrorTypes } from '../errors';
|
|
7
|
+
import { Events } from '../events';
|
|
8
|
+
import type { InterstitialEvent } from './interstitial-event';
|
|
9
|
+
import type Hls from '../hls';
|
|
10
|
+
import type { ErrorData } from '../types/events';
|
|
11
|
+
import type {
|
|
12
|
+
Loader,
|
|
13
|
+
LoaderCallbacks,
|
|
14
|
+
LoaderConfiguration,
|
|
15
|
+
LoaderContext,
|
|
16
|
+
LoaderResponse,
|
|
17
|
+
LoaderStats,
|
|
18
|
+
} from '../types/loader';
|
|
19
|
+
import type { NullableNetworkDetails } from '../types/network-details';
|
|
20
|
+
|
|
21
|
+
export class AssetListLoader {
|
|
22
|
+
private hls: Hls;
|
|
23
|
+
|
|
24
|
+
constructor(hls: Hls) {
|
|
25
|
+
this.hls = hls;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
destroy() {
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
this.hls = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
loadAssetList(
|
|
34
|
+
interstitial: InterstitialEventWithAssetList,
|
|
35
|
+
hlsStartOffset: number | undefined,
|
|
36
|
+
): Loader<LoaderContext> | undefined {
|
|
37
|
+
const assetListUrl = interstitial.assetListUrl;
|
|
38
|
+
let url: URL;
|
|
39
|
+
try {
|
|
40
|
+
url = getInterstitialUrl(
|
|
41
|
+
assetListUrl,
|
|
42
|
+
this.hls.sessionId,
|
|
43
|
+
interstitial.baseUrl,
|
|
44
|
+
);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
const errorData = this.assignAssetListError(
|
|
47
|
+
interstitial,
|
|
48
|
+
ErrorDetails.ASSET_LIST_LOAD_ERROR,
|
|
49
|
+
error,
|
|
50
|
+
assetListUrl,
|
|
51
|
+
);
|
|
52
|
+
this.hls.trigger(Events.ERROR, errorData);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (hlsStartOffset && url.protocol !== 'data:') {
|
|
56
|
+
url.searchParams.set('_HLS_start_offset', '' + hlsStartOffset);
|
|
57
|
+
}
|
|
58
|
+
const config = this.hls.config;
|
|
59
|
+
const Loader = config.loader;
|
|
60
|
+
const loader = new Loader(config) as Loader<LoaderContext>;
|
|
61
|
+
const context: LoaderContext = {
|
|
62
|
+
responseType: 'json',
|
|
63
|
+
url: url.href,
|
|
64
|
+
};
|
|
65
|
+
const loadPolicy = config.interstitialAssetListLoadPolicy.default;
|
|
66
|
+
const loaderConfig: LoaderConfiguration = {
|
|
67
|
+
loadPolicy,
|
|
68
|
+
timeout: loadPolicy.maxLoadTimeMs,
|
|
69
|
+
maxRetry: 0,
|
|
70
|
+
retryDelay: 0,
|
|
71
|
+
maxRetryDelay: 0,
|
|
72
|
+
};
|
|
73
|
+
const callbacks: LoaderCallbacks<LoaderContext> = {
|
|
74
|
+
onSuccess: (
|
|
75
|
+
response: LoaderResponse,
|
|
76
|
+
stats: LoaderStats,
|
|
77
|
+
context: LoaderContext,
|
|
78
|
+
networkDetails: NullableNetworkDetails,
|
|
79
|
+
) => {
|
|
80
|
+
const assetListResponse = response.data as AssetListJSON;
|
|
81
|
+
const assets = assetListResponse?.ASSETS;
|
|
82
|
+
if (!Array.isArray(assets)) {
|
|
83
|
+
const errorData = this.assignAssetListError(
|
|
84
|
+
interstitial,
|
|
85
|
+
ErrorDetails.ASSET_LIST_PARSING_ERROR,
|
|
86
|
+
new Error(`Invalid interstitial asset list`),
|
|
87
|
+
context.url,
|
|
88
|
+
stats,
|
|
89
|
+
networkDetails,
|
|
90
|
+
);
|
|
91
|
+
this.hls.trigger(Events.ERROR, errorData);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
interstitial.assetListResponse = assetListResponse;
|
|
95
|
+
this.hls.trigger(Events.ASSET_LIST_LOADED, {
|
|
96
|
+
event: interstitial,
|
|
97
|
+
assetListResponse,
|
|
98
|
+
networkDetails,
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
onError: (
|
|
102
|
+
error: { code: number; text: string },
|
|
103
|
+
context: LoaderContext,
|
|
104
|
+
networkDetails: NullableNetworkDetails,
|
|
105
|
+
stats: LoaderStats,
|
|
106
|
+
) => {
|
|
107
|
+
const errorData = this.assignAssetListError(
|
|
108
|
+
interstitial,
|
|
109
|
+
ErrorDetails.ASSET_LIST_LOAD_ERROR,
|
|
110
|
+
new Error(
|
|
111
|
+
`Error loading X-ASSET-LIST: HTTP status ${error.code} ${error.text} (${context.url})`,
|
|
112
|
+
),
|
|
113
|
+
context.url,
|
|
114
|
+
stats,
|
|
115
|
+
networkDetails,
|
|
116
|
+
);
|
|
117
|
+
this.hls.trigger(Events.ERROR, errorData);
|
|
118
|
+
},
|
|
119
|
+
onTimeout: (
|
|
120
|
+
stats: LoaderStats,
|
|
121
|
+
context: LoaderContext,
|
|
122
|
+
networkDetails: NullableNetworkDetails,
|
|
123
|
+
) => {
|
|
124
|
+
const errorData = this.assignAssetListError(
|
|
125
|
+
interstitial,
|
|
126
|
+
ErrorDetails.ASSET_LIST_LOAD_TIMEOUT,
|
|
127
|
+
new Error(`Timeout loading X-ASSET-LIST (${context.url})`),
|
|
128
|
+
context.url,
|
|
129
|
+
stats,
|
|
130
|
+
networkDetails,
|
|
131
|
+
);
|
|
132
|
+
this.hls.trigger(Events.ERROR, errorData);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
loader.load(context, loaderConfig, callbacks);
|
|
136
|
+
this.hls.trigger(Events.ASSET_LIST_LOADING, {
|
|
137
|
+
event: interstitial,
|
|
138
|
+
});
|
|
139
|
+
return loader;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
assignAssetListError(
|
|
143
|
+
interstitial: InterstitialEvent,
|
|
144
|
+
details: ErrorDetails,
|
|
145
|
+
error: Error,
|
|
146
|
+
url: string,
|
|
147
|
+
stats?: LoaderStats,
|
|
148
|
+
networkDetails?: NullableNetworkDetails,
|
|
149
|
+
): ErrorData {
|
|
150
|
+
interstitial.error = error;
|
|
151
|
+
return {
|
|
152
|
+
type: ErrorTypes.NETWORK_ERROR,
|
|
153
|
+
details,
|
|
154
|
+
fatal: false,
|
|
155
|
+
interstitial,
|
|
156
|
+
url,
|
|
157
|
+
error,
|
|
158
|
+
networkDetails,
|
|
159
|
+
stats,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|