@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.
Files changed (159) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +472 -0
  3. package/dist/hls-demo.js +26995 -0
  4. package/dist/hls-demo.js.map +1 -0
  5. package/dist/hls.d.mts +4204 -0
  6. package/dist/hls.d.ts +4204 -0
  7. package/dist/hls.js +40050 -0
  8. package/dist/hls.js.d.ts +4204 -0
  9. package/dist/hls.js.map +1 -0
  10. package/dist/hls.light.js +27145 -0
  11. package/dist/hls.light.js.map +1 -0
  12. package/dist/hls.light.min.js +2 -0
  13. package/dist/hls.light.min.js.map +1 -0
  14. package/dist/hls.light.mjs +26392 -0
  15. package/dist/hls.light.mjs.map +1 -0
  16. package/dist/hls.min.js +2 -0
  17. package/dist/hls.min.js.map +1 -0
  18. package/dist/hls.mjs +38956 -0
  19. package/dist/hls.mjs.map +1 -0
  20. package/dist/hls.worker.js +2 -0
  21. package/dist/hls.worker.js.map +1 -0
  22. package/package.json +143 -0
  23. package/src/config.ts +794 -0
  24. package/src/controller/abr-controller.ts +1019 -0
  25. package/src/controller/algo-data-controller.ts +794 -0
  26. package/src/controller/audio-stream-controller.ts +1099 -0
  27. package/src/controller/audio-track-controller.ts +454 -0
  28. package/src/controller/base-playlist-controller.ts +438 -0
  29. package/src/controller/base-stream-controller.ts +2526 -0
  30. package/src/controller/buffer-controller.ts +2015 -0
  31. package/src/controller/buffer-operation-queue.ts +159 -0
  32. package/src/controller/cap-level-controller.ts +367 -0
  33. package/src/controller/cmcd-controller.ts +422 -0
  34. package/src/controller/content-steering-controller.ts +622 -0
  35. package/src/controller/eme-controller.ts +1617 -0
  36. package/src/controller/error-controller.ts +627 -0
  37. package/src/controller/fps-controller.ts +146 -0
  38. package/src/controller/fragment-finders.ts +256 -0
  39. package/src/controller/fragment-tracker.ts +567 -0
  40. package/src/controller/gap-controller.ts +719 -0
  41. package/src/controller/id3-track-controller.ts +488 -0
  42. package/src/controller/interstitial-player.ts +302 -0
  43. package/src/controller/interstitials-controller.ts +2895 -0
  44. package/src/controller/interstitials-schedule.ts +698 -0
  45. package/src/controller/latency-controller.ts +294 -0
  46. package/src/controller/level-controller.ts +776 -0
  47. package/src/controller/stream-controller.ts +1597 -0
  48. package/src/controller/subtitle-stream-controller.ts +508 -0
  49. package/src/controller/subtitle-track-controller.ts +617 -0
  50. package/src/controller/timeline-controller.ts +677 -0
  51. package/src/crypt/aes-crypto.ts +36 -0
  52. package/src/crypt/aes-decryptor.ts +339 -0
  53. package/src/crypt/decrypter-aes-mode.ts +4 -0
  54. package/src/crypt/decrypter.ts +225 -0
  55. package/src/crypt/fast-aes-key.ts +39 -0
  56. package/src/define-plugin.d.ts +17 -0
  57. package/src/demux/audio/aacdemuxer.ts +126 -0
  58. package/src/demux/audio/ac3-demuxer.ts +170 -0
  59. package/src/demux/audio/adts.ts +249 -0
  60. package/src/demux/audio/base-audio-demuxer.ts +205 -0
  61. package/src/demux/audio/dolby.ts +21 -0
  62. package/src/demux/audio/mp3demuxer.ts +85 -0
  63. package/src/demux/audio/mpegaudio.ts +177 -0
  64. package/src/demux/chunk-cache.ts +42 -0
  65. package/src/demux/dummy-demuxed-track.ts +13 -0
  66. package/src/demux/inject-worker.ts +75 -0
  67. package/src/demux/mp4demuxer.ts +234 -0
  68. package/src/demux/sample-aes.ts +198 -0
  69. package/src/demux/transmuxer-interface.ts +449 -0
  70. package/src/demux/transmuxer-worker.ts +221 -0
  71. package/src/demux/transmuxer.ts +560 -0
  72. package/src/demux/tsdemuxer.ts +1256 -0
  73. package/src/demux/video/avc-video-parser.ts +401 -0
  74. package/src/demux/video/base-video-parser.ts +198 -0
  75. package/src/demux/video/exp-golomb.ts +153 -0
  76. package/src/demux/video/hevc-video-parser.ts +736 -0
  77. package/src/empty-es.js +5 -0
  78. package/src/empty.js +3 -0
  79. package/src/errors.ts +107 -0
  80. package/src/events.ts +548 -0
  81. package/src/exports-default.ts +3 -0
  82. package/src/exports-named.ts +81 -0
  83. package/src/hls.ts +1613 -0
  84. package/src/is-supported.ts +54 -0
  85. package/src/loader/date-range.ts +207 -0
  86. package/src/loader/fragment-loader.ts +403 -0
  87. package/src/loader/fragment.ts +487 -0
  88. package/src/loader/interstitial-asset-list.ts +162 -0
  89. package/src/loader/interstitial-event.ts +337 -0
  90. package/src/loader/key-loader.ts +439 -0
  91. package/src/loader/level-details.ts +203 -0
  92. package/src/loader/level-key.ts +259 -0
  93. package/src/loader/load-stats.ts +17 -0
  94. package/src/loader/m3u8-parser.ts +1072 -0
  95. package/src/loader/playlist-loader.ts +839 -0
  96. package/src/polyfills/number.ts +15 -0
  97. package/src/remux/aac-helper.ts +81 -0
  98. package/src/remux/mp4-generator.ts +1380 -0
  99. package/src/remux/mp4-remuxer.ts +1261 -0
  100. package/src/remux/passthrough-remuxer.ts +434 -0
  101. package/src/task-loop.ts +130 -0
  102. package/src/types/algo.ts +44 -0
  103. package/src/types/buffer.ts +105 -0
  104. package/src/types/component-api.ts +20 -0
  105. package/src/types/demuxer.ts +208 -0
  106. package/src/types/events.ts +574 -0
  107. package/src/types/fragment-tracker.ts +23 -0
  108. package/src/types/level.ts +268 -0
  109. package/src/types/loader.ts +198 -0
  110. package/src/types/media-playlist.ts +92 -0
  111. package/src/types/network-details.ts +3 -0
  112. package/src/types/remuxer.ts +104 -0
  113. package/src/types/track.ts +12 -0
  114. package/src/types/transmuxer.ts +46 -0
  115. package/src/types/tuples.ts +6 -0
  116. package/src/types/vtt.ts +11 -0
  117. package/src/utils/arrays.ts +22 -0
  118. package/src/utils/attr-list.ts +192 -0
  119. package/src/utils/binary-search.ts +46 -0
  120. package/src/utils/buffer-helper.ts +173 -0
  121. package/src/utils/cea-608-parser.ts +1413 -0
  122. package/src/utils/chunker.ts +41 -0
  123. package/src/utils/codecs.ts +314 -0
  124. package/src/utils/cues.ts +96 -0
  125. package/src/utils/discontinuities.ts +174 -0
  126. package/src/utils/encryption-methods-util.ts +21 -0
  127. package/src/utils/error-helper.ts +95 -0
  128. package/src/utils/event-listener-helper.ts +16 -0
  129. package/src/utils/ewma-bandwidth-estimator.ts +97 -0
  130. package/src/utils/ewma.ts +43 -0
  131. package/src/utils/fetch-loader.ts +331 -0
  132. package/src/utils/global.ts +2 -0
  133. package/src/utils/hash.ts +10 -0
  134. package/src/utils/hdr.ts +67 -0
  135. package/src/utils/hex.ts +32 -0
  136. package/src/utils/imsc1-ttml-parser.ts +261 -0
  137. package/src/utils/keysystem-util.ts +45 -0
  138. package/src/utils/level-helper.ts +629 -0
  139. package/src/utils/logger.ts +120 -0
  140. package/src/utils/media-option-attributes.ts +49 -0
  141. package/src/utils/mediacapabilities-helper.ts +301 -0
  142. package/src/utils/mediakeys-helper.ts +210 -0
  143. package/src/utils/mediasource-helper.ts +37 -0
  144. package/src/utils/mp4-tools.ts +1473 -0
  145. package/src/utils/number.ts +3 -0
  146. package/src/utils/numeric-encoding-utils.ts +26 -0
  147. package/src/utils/output-filter.ts +46 -0
  148. package/src/utils/rendition-helper.ts +505 -0
  149. package/src/utils/safe-json-stringify.ts +22 -0
  150. package/src/utils/texttrack-utils.ts +164 -0
  151. package/src/utils/time-ranges.ts +17 -0
  152. package/src/utils/timescale-conversion.ts +46 -0
  153. package/src/utils/utf8-utils.ts +18 -0
  154. package/src/utils/variable-substitution.ts +105 -0
  155. package/src/utils/vttcue.ts +384 -0
  156. package/src/utils/vttparser.ts +497 -0
  157. package/src/utils/webvtt-parser.ts +166 -0
  158. package/src/utils/xhr-loader.ts +337 -0
  159. package/src/version.ts +1 -0
@@ -0,0 +1,41 @@
1
+ import { appendUint8Array } from './mp4-tools';
2
+
3
+ export default class Chunker {
4
+ private chunkSize: number;
5
+ public cache: Uint8Array | null = null;
6
+ constructor(chunkSize = Math.pow(2, 19)) {
7
+ this.chunkSize = chunkSize;
8
+ }
9
+
10
+ public push(data: Uint8Array): Array<Uint8Array> {
11
+ const { cache, chunkSize } = this;
12
+ const result: Array<Uint8Array> = [];
13
+
14
+ let temp: Uint8Array | null = null;
15
+ if (cache?.length) {
16
+ temp = appendUint8Array(cache, data);
17
+ this.cache = null;
18
+ } else {
19
+ temp = data;
20
+ }
21
+
22
+ if (temp.length < chunkSize) {
23
+ this.cache = temp;
24
+ return result;
25
+ }
26
+
27
+ if (temp.length > chunkSize) {
28
+ let offset = 0;
29
+ const len = temp.length;
30
+ while (offset < len - chunkSize) {
31
+ result.push(temp.slice(offset, offset + chunkSize));
32
+ offset += chunkSize;
33
+ }
34
+ this.cache = temp.slice(offset);
35
+ } else {
36
+ result.push(temp);
37
+ }
38
+
39
+ return result;
40
+ }
41
+ }
@@ -0,0 +1,314 @@
1
+ import { getMediaSource } from './mediasource-helper';
2
+ import { isHEVC } from './mp4-tools';
3
+
4
+ export const userAgentHevcSupportIsInaccurate = () => {
5
+ return /\(Windows.+Firefox\//i.test(navigator.userAgent);
6
+ };
7
+
8
+ // from http://mp4ra.org/codecs.html
9
+ // values indicate codec selection preference (lower is higher priority)
10
+ export const sampleEntryCodesISO = {
11
+ audio: {
12
+ a3ds: 1,
13
+ 'ac-3': 0.95,
14
+ 'ac-4': 1,
15
+ alac: 0.9,
16
+ alaw: 1,
17
+ dra1: 1,
18
+ 'dts+': 1,
19
+ 'dts-': 1,
20
+ dtsc: 1,
21
+ dtse: 1,
22
+ dtsh: 1,
23
+ 'ec-3': 0.9,
24
+ enca: 1,
25
+ fLaC: 0.9, // MP4-RA listed codec entry for FLAC
26
+ flac: 0.9, // legacy browser codec name for FLAC
27
+ FLAC: 0.9, // some manifests may list "FLAC" with Apple's tools
28
+ g719: 1,
29
+ g726: 1,
30
+ m4ae: 1,
31
+ mha1: 1,
32
+ mha2: 1,
33
+ mhm1: 1,
34
+ mhm2: 1,
35
+ mlpa: 1,
36
+ mp4a: 1,
37
+ 'raw ': 1,
38
+ Opus: 1,
39
+ opus: 1, // browsers expect this to be lowercase despite MP4RA says 'Opus'
40
+ samr: 1,
41
+ sawb: 1,
42
+ sawp: 1,
43
+ sevc: 1,
44
+ sqcp: 1,
45
+ ssmv: 1,
46
+ twos: 1,
47
+ ulaw: 1,
48
+ },
49
+ video: {
50
+ avc1: 1,
51
+ avc2: 1,
52
+ avc3: 1,
53
+ avc4: 1,
54
+ avcp: 1,
55
+ av01: 0.8,
56
+ dav1: 0.8,
57
+ drac: 1,
58
+ dva1: 1,
59
+ dvav: 1,
60
+ dvh1: 0.7,
61
+ dvhe: 0.7,
62
+ encv: 1,
63
+ hev1: 0.75,
64
+ hvc1: 0.75,
65
+ mjp2: 1,
66
+ mp4v: 1,
67
+ mvc1: 1,
68
+ mvc2: 1,
69
+ mvc3: 1,
70
+ mvc4: 1,
71
+ resv: 1,
72
+ rv60: 1,
73
+ s263: 1,
74
+ svc1: 1,
75
+ svc2: 1,
76
+ 'vc-1': 1,
77
+ vp08: 1,
78
+ vp09: 0.9,
79
+ },
80
+ text: {
81
+ stpp: 1,
82
+ wvtt: 1,
83
+ },
84
+ } as const;
85
+
86
+ export type CodecType = 'audio' | 'video';
87
+
88
+ export function isCodecType(codec: string, type: CodecType): boolean {
89
+ const typeCodes = sampleEntryCodesISO[type];
90
+ return !!typeCodes && !!typeCodes[codec.slice(0, 4)];
91
+ }
92
+
93
+ export function areCodecsMediaSourceSupported(
94
+ codecs: string,
95
+ type: CodecType,
96
+ preferManagedMediaSource = true,
97
+ ): boolean {
98
+ return !codecs
99
+ .split(',')
100
+ .some(
101
+ (codec) =>
102
+ !isCodecMediaSourceSupported(codec, type, preferManagedMediaSource),
103
+ );
104
+ }
105
+
106
+ function isCodecMediaSourceSupported(
107
+ codec: string,
108
+ type: CodecType,
109
+ preferManagedMediaSource = true,
110
+ ): boolean {
111
+ const MediaSource = getMediaSource(preferManagedMediaSource);
112
+ return MediaSource?.isTypeSupported(mimeTypeForCodec(codec, type)) ?? false;
113
+ }
114
+
115
+ export function mimeTypeForCodec(codec: string, type: CodecType): string {
116
+ return `${type}/mp4;codecs=${codec}`;
117
+ }
118
+
119
+ export function videoCodecPreferenceValue(
120
+ videoCodec: string | undefined,
121
+ ): number {
122
+ if (videoCodec) {
123
+ const fourCC = videoCodec.substring(0, 4);
124
+ return sampleEntryCodesISO.video[fourCC];
125
+ }
126
+ return 2;
127
+ }
128
+
129
+ export function codecsSetSelectionPreferenceValue(codecSet: string): number {
130
+ const limitedHevcSupport = userAgentHevcSupportIsInaccurate();
131
+ return codecSet.split(',').reduce((num, fourCC) => {
132
+ const lowerPriority = limitedHevcSupport && isHEVC(fourCC);
133
+ const preferenceValue = lowerPriority
134
+ ? 9
135
+ : sampleEntryCodesISO.video[fourCC];
136
+ if (preferenceValue) {
137
+ return (preferenceValue * 2 + num) / (num ? 3 : 2);
138
+ }
139
+ return (sampleEntryCodesISO.audio[fourCC] + num) / (num ? 2 : 1);
140
+ }, 0);
141
+ }
142
+
143
+ interface CodecNameCache {
144
+ flac?: string;
145
+ opus?: string;
146
+ }
147
+
148
+ const CODEC_COMPATIBLE_NAMES: CodecNameCache = {};
149
+
150
+ type LowerCaseCodecType = 'flac' | 'opus';
151
+
152
+ function getCodecCompatibleNameLower(
153
+ lowerCaseCodec: LowerCaseCodecType,
154
+ preferManagedMediaSource = true,
155
+ ): string {
156
+ if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
157
+ return CODEC_COMPATIBLE_NAMES[lowerCaseCodec]!;
158
+ }
159
+
160
+ const codecsToCheck = {
161
+ // Idealy fLaC and Opus would be first (spec-compliant) but
162
+ // some browsers will report that fLaC is supported then fail.
163
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
164
+ flac: ['flac', 'fLaC', 'FLAC'],
165
+ opus: ['opus', 'Opus'],
166
+ // Replace audio codec info if browser does not support mp4a.40.34,
167
+ // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
168
+ 'mp4a.40.34': ['mp3'],
169
+ }[lowerCaseCodec];
170
+
171
+ for (let i = 0; i < codecsToCheck.length; i++) {
172
+ if (
173
+ isCodecMediaSourceSupported(
174
+ codecsToCheck[i],
175
+ 'audio',
176
+ preferManagedMediaSource,
177
+ )
178
+ ) {
179
+ CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
180
+ return codecsToCheck[i];
181
+ } else if (
182
+ codecsToCheck[i] === 'mp3' &&
183
+ getMediaSource(preferManagedMediaSource)?.isTypeSupported('audio/mpeg')
184
+ ) {
185
+ return '';
186
+ }
187
+ }
188
+
189
+ return lowerCaseCodec;
190
+ }
191
+
192
+ const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
193
+ export function getCodecCompatibleName(
194
+ codec: string,
195
+ preferManagedMediaSource = true,
196
+ ): string {
197
+ return codec.replace(AUDIO_CODEC_REGEXP, (m) =>
198
+ getCodecCompatibleNameLower(
199
+ m.toLowerCase() as LowerCaseCodecType,
200
+ preferManagedMediaSource,
201
+ ),
202
+ );
203
+ }
204
+
205
+ export function replaceVideoCodec(
206
+ originalCodecs: string | undefined,
207
+ newVideoCodec: string | undefined,
208
+ ): string | undefined {
209
+ const codecs: string[] = [];
210
+ if (originalCodecs) {
211
+ const allCodecs = originalCodecs.split(',');
212
+ for (let i = 0; i < allCodecs.length; i++) {
213
+ if (!isCodecType(allCodecs[i], 'video')) {
214
+ codecs.push(allCodecs[i]);
215
+ }
216
+ }
217
+ }
218
+ if (newVideoCodec) {
219
+ codecs.push(newVideoCodec);
220
+ }
221
+ return codecs.join(',');
222
+ }
223
+
224
+ export function pickMostCompleteCodecName(
225
+ parsedCodec: string | undefined,
226
+ levelCodec: string | undefined,
227
+ ): string | undefined {
228
+ // Parsing of mp4a codecs strings in mp4-tools from media is incomplete as of d8c6c7a
229
+ // so use level codec is parsed codec is unavailable or incomplete
230
+ if (
231
+ parsedCodec &&
232
+ (parsedCodec.length > 4 ||
233
+ ['ac-3', 'ec-3', 'alac', 'fLaC', 'Opus'].indexOf(parsedCodec) !== -1)
234
+ ) {
235
+ if (
236
+ isCodecSupportedAsType(parsedCodec, 'audio') ||
237
+ isCodecSupportedAsType(parsedCodec, 'video')
238
+ ) {
239
+ return parsedCodec;
240
+ }
241
+ }
242
+ if (levelCodec) {
243
+ const levelCodecs = levelCodec.split(',');
244
+ if (levelCodecs.length > 1) {
245
+ if (parsedCodec) {
246
+ for (let i = levelCodecs.length; i--; ) {
247
+ if (levelCodecs[i].substring(0, 4) === parsedCodec.substring(0, 4)) {
248
+ return levelCodecs[i];
249
+ }
250
+ }
251
+ }
252
+ return levelCodecs[0];
253
+ }
254
+ }
255
+ return levelCodec || parsedCodec;
256
+ }
257
+
258
+ function isCodecSupportedAsType(codec: string, type: CodecType): boolean {
259
+ return isCodecType(codec, type) && isCodecMediaSourceSupported(codec, type);
260
+ }
261
+
262
+ export function convertAVC1ToAVCOTI(videoCodecs: string): string {
263
+ // Convert avc1 codec string from RFC-4281 to RFC-6381 for MediaSource.isTypeSupported
264
+ // Examples: avc1.66.30 to avc1.42001e and avc1.77.30,avc1.66.30 to avc1.4d001e,avc1.42001e.
265
+ const codecs = videoCodecs.split(',');
266
+ for (let i = 0; i < codecs.length; i++) {
267
+ const avcdata = codecs[i].split('.');
268
+ // only convert codec strings starting with avc1 (Examples: avc1.64001f,dvh1.05.07)
269
+ if (avcdata.length > 2 && avcdata[0] === 'avc1') {
270
+ codecs[i] = `avc1.${parseInt(avcdata[1]).toString(16)}${(
271
+ '000' + parseInt(avcdata[2]).toString(16)
272
+ ).slice(-4)}`;
273
+ }
274
+ }
275
+ return codecs.join(',');
276
+ }
277
+
278
+ export function fillInMissingAV01Params(videoCodec: string): string {
279
+ // Used to fill in incomplete AV1 playlist CODECS strings for mediaCapabilities.decodingInfo queries
280
+ if (videoCodec.startsWith('av01.')) {
281
+ const av1params = videoCodec.split('.');
282
+ const placeholders = ['0', '111', '01', '01', '01', '0'];
283
+ for (let i = av1params.length; i > 4 && i < 10; i++) {
284
+ av1params[i] = placeholders[i - 4];
285
+ }
286
+ return av1params.join('.');
287
+ }
288
+ return videoCodec;
289
+ }
290
+
291
+ export interface TypeSupported {
292
+ mpeg: boolean;
293
+ mp3: boolean;
294
+ ac3: boolean;
295
+ }
296
+
297
+ export function getM2TSSupportedAudioTypes(
298
+ preferManagedMediaSource: boolean,
299
+ ): TypeSupported {
300
+ const MediaSource = getMediaSource(preferManagedMediaSource) || {
301
+ isTypeSupported: () => false,
302
+ };
303
+ return {
304
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
305
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
306
+ ac3: __USE_M2TS_ADVANCED_CODECS__
307
+ ? MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
308
+ : false,
309
+ };
310
+ }
311
+
312
+ export function getCodecsForMimeType(mimeType: string): string {
313
+ return mimeType.replace(/^.+codecs=["']?([^"']+).*$/, '$1');
314
+ }
@@ -0,0 +1,96 @@
1
+ import { addCueToTrack } from './texttrack-utils';
2
+ import { fixLineBreaks } from './vttparser';
3
+ import { generateCueId } from './webvtt-parser';
4
+ import type { CaptionScreen, Row } from './cea-608-parser';
5
+
6
+ const WHITESPACE_CHAR = /\s/;
7
+
8
+ export interface CuesInterface {
9
+ newCue(
10
+ track: TextTrack | null,
11
+ startTime: number,
12
+ endTime: number,
13
+ captionScreen: CaptionScreen,
14
+ ): VTTCue[];
15
+ }
16
+
17
+ const Cues: CuesInterface = {
18
+ newCue(
19
+ track: TextTrack | null,
20
+ startTime: number,
21
+ endTime: number,
22
+ captionScreen: CaptionScreen,
23
+ ): VTTCue[] {
24
+ const result: VTTCue[] = [];
25
+ let row: Row;
26
+ // the type data states this is VTTCue, but it can potentially be a TextTrackCue on old browsers
27
+ let cue: VTTCue;
28
+ let indenting: boolean;
29
+ let indent: number;
30
+ let text: string;
31
+ const Cue = (self.VTTCue || self.TextTrackCue) as any;
32
+
33
+ for (let r = 0; r < captionScreen.rows.length; r++) {
34
+ row = captionScreen.rows[r];
35
+ indenting = true;
36
+ indent = 0;
37
+ text = '';
38
+
39
+ if (!row.isEmpty()) {
40
+ for (let c = 0; c < row.chars.length; c++) {
41
+ if (WHITESPACE_CHAR.test(row.chars[c].uchar) && indenting) {
42
+ indent++;
43
+ } else {
44
+ text += row.chars[c].uchar;
45
+ indenting = false;
46
+ }
47
+ }
48
+ // To be used for cleaning-up orphaned roll-up captions
49
+ row.cueStartTime = startTime;
50
+
51
+ // Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE
52
+ if (startTime === endTime) {
53
+ endTime += 0.0001;
54
+ }
55
+
56
+ if (indent >= 16) {
57
+ indent--;
58
+ } else {
59
+ indent++;
60
+ }
61
+
62
+ const cueText = fixLineBreaks(text.trim());
63
+ const id = generateCueId(startTime, endTime, cueText);
64
+
65
+ // If this cue already exists in the track do not push it
66
+ if (!track?.cues?.getCueById(id)) {
67
+ cue = new Cue(startTime, endTime, cueText);
68
+ cue.id = id;
69
+ cue.line = r + 1;
70
+ cue.align = 'left';
71
+ // Clamp the position between 10 and 80 percent (CEA-608 PAC indent code)
72
+ // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608
73
+ // Firefox throws an exception and captions break with out of bounds 0-100 values
74
+ cue.position = 10 + Math.min(80, Math.floor((indent * 8) / 32) * 10);
75
+ result.push(cue);
76
+ }
77
+ }
78
+ }
79
+ if (track && result.length) {
80
+ // Sort bottom cues in reverse order so that they render in line order when overlapping in Chrome
81
+ result.sort((cueA, cueB) => {
82
+ if (cueA.line === 'auto' || cueB.line === 'auto') {
83
+ return 0;
84
+ }
85
+ if (cueA.line > 8 && cueB.line > 8) {
86
+ return cueB.line - cueA.line;
87
+ }
88
+ return cueA.line - cueB.line;
89
+ });
90
+ result.forEach((cue) => addCueToTrack(track, cue));
91
+ }
92
+ return result;
93
+ },
94
+ };
95
+
96
+ export default Cues;
@@ -0,0 +1,174 @@
1
+ import { adjustSliding } from './level-helper';
2
+ import type { ILogger } from './logger';
3
+ import type { Fragment } from '../loader/fragment';
4
+ import type { LevelDetails } from '../loader/level-details';
5
+
6
+ export function findFirstFragWithCC(
7
+ fragments: Fragment[],
8
+ cc: number,
9
+ ): Fragment | null {
10
+ for (let i = 0, len = fragments.length; i < len; i++) {
11
+ if (fragments[i]?.cc === cc) {
12
+ return fragments[i];
13
+ }
14
+ }
15
+ return null;
16
+ }
17
+
18
+ export function shouldAlignOnDiscontinuities(
19
+ refDetails: LevelDetails | undefined,
20
+ details: LevelDetails,
21
+ ): refDetails is LevelDetails & boolean {
22
+ if (refDetails) {
23
+ if (
24
+ details.startCC < refDetails.endCC &&
25
+ details.endCC > refDetails.startCC
26
+ ) {
27
+ return true;
28
+ }
29
+ }
30
+ return false;
31
+ }
32
+
33
+ function adjustFragmentStart(frag: Fragment, sliding: number) {
34
+ const start = frag.start + sliding;
35
+ frag.startPTS = start;
36
+ frag.setStart(start);
37
+ frag.endPTS = start + frag.duration;
38
+ }
39
+
40
+ export function adjustSlidingStart(sliding: number, details: LevelDetails) {
41
+ // Update segments
42
+ const fragments = details.fragments;
43
+ for (let i = 0, len = fragments.length; i < len; i++) {
44
+ adjustFragmentStart(fragments[i], sliding);
45
+ }
46
+ // Update LL-HLS parts at the end of the playlist
47
+ if (details.fragmentHint) {
48
+ adjustFragmentStart(details.fragmentHint, sliding);
49
+ }
50
+ details.alignedSliding = true;
51
+ }
52
+
53
+ /**
54
+ * Using the parameters of the last level, this function computes PTS' of the new fragments so that they form a
55
+ * contiguous stream with the last fragments.
56
+ * The PTS of a fragment lets Hls.js know where it fits into a stream - by knowing every PTS, we know which fragment to
57
+ * download at any given time. PTS is normally computed when the fragment is demuxed, so taking this step saves us time
58
+ * and an extra download.
59
+ * @param lastLevel
60
+ * @param details
61
+ */
62
+ export function alignStream(
63
+ switchDetails: LevelDetails | undefined,
64
+ details: LevelDetails,
65
+ logger: ILogger,
66
+ ) {
67
+ if (!switchDetails) {
68
+ return;
69
+ }
70
+ alignDiscontinuities(details, switchDetails, logger);
71
+ if (!details.alignedSliding) {
72
+ // If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level.
73
+ // Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same
74
+ // discontinuity sequence.
75
+ alignMediaPlaylistByPDT(details, switchDetails, logger);
76
+ }
77
+ if (!details.alignedSliding && !details.skippedSegments) {
78
+ // Try to align on sn so that we pick a better start fragment.
79
+ // Do not perform this on playlists with delta updates as this is only to align levels on switch
80
+ // and adjustSliding only adjusts fragments after skippedSegments.
81
+ adjustSliding(switchDetails, details, false, logger);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Ajust the start of fragments in `details` by the difference in time between fragments of the latest
87
+ * shared discontinuity sequence change.
88
+ * @param lastLevel - The details of the last loaded level
89
+ * @param details - The details of the new level
90
+ */
91
+ export function alignDiscontinuities(
92
+ details: LevelDetails,
93
+ refDetails: LevelDetails | undefined,
94
+ logger: ILogger,
95
+ ) {
96
+ if (!shouldAlignOnDiscontinuities(refDetails, details)) {
97
+ return;
98
+ }
99
+ const targetCC = Math.min(refDetails.endCC, details.endCC);
100
+ const refFrag = findFirstFragWithCC(refDetails.fragments, targetCC);
101
+ const frag = findFirstFragWithCC(details.fragments, targetCC);
102
+ if (!refFrag || !frag) {
103
+ return;
104
+ }
105
+ const delta = refFrag.start - frag.start;
106
+ logger.log(
107
+ `Aligning playlists using dicontinuity sequence ${targetCC} (diff: ${delta})`,
108
+ );
109
+ adjustSlidingStart(delta, details);
110
+ }
111
+
112
+ /**
113
+ * Ensures appropriate time-alignment between renditions based on PDT.
114
+ * This function assumes the timelines represented in `refDetails` are accurate, including the PDTs
115
+ * for the last discontinuity sequence number shared by both playlists when present,
116
+ * and uses the "wallclock"/PDT timeline as a cross-reference to `details`, adjusting the presentation
117
+ * times/timelines of `details` accordingly.
118
+ * Given the asynchronous nature of fetches and initial loads of live `main` and audio/subtitle tracks,
119
+ * the primary purpose of this function is to ensure the "local timelines" of audio/subtitle tracks
120
+ * are aligned to the main/video timeline, using PDT as the cross-reference/"anchor" that should
121
+ * be consistent across playlists, per the HLS spec.
122
+ * @param details - The details of the rendition you'd like to time-align (e.g. an audio rendition).
123
+ * @param refDetails - The details of the reference rendition with start and PDT times for alignment.
124
+ */
125
+ export function alignMediaPlaylistByPDT(
126
+ details: LevelDetails,
127
+ refDetails: LevelDetails,
128
+ logger: ILogger,
129
+ ) {
130
+ if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) {
131
+ return;
132
+ }
133
+
134
+ const fragments = details.fragments;
135
+ const refFragments = refDetails.fragments;
136
+ if (!fragments.length || !refFragments.length) {
137
+ return;
138
+ }
139
+
140
+ // Calculate a delta to apply to all fragments according to the delta in PDT times and start times
141
+ // of a fragment in the reference details, and a fragment in the target details of the same discontinuity.
142
+ // If a fragment of the same discontinuity was not found use the middle fragment of both.
143
+ let refFrag: Fragment | null | undefined;
144
+ let frag: Fragment | null | undefined;
145
+ const targetCC = Math.min(refDetails.endCC, details.endCC);
146
+ if (refDetails.startCC < targetCC && details.startCC < targetCC) {
147
+ refFrag = findFirstFragWithCC(refFragments, targetCC);
148
+ frag = findFirstFragWithCC(fragments, targetCC);
149
+ }
150
+ if (!refFrag || !frag) {
151
+ refFrag = refFragments[Math.floor(refFragments.length / 2)];
152
+ frag =
153
+ findFirstFragWithCC(fragments, refFrag.cc) ||
154
+ fragments[Math.floor(fragments.length / 2)];
155
+ }
156
+ const refPDT = refFrag.programDateTime;
157
+ const targetPDT = frag.programDateTime;
158
+ if (!refPDT || !targetPDT) {
159
+ return;
160
+ }
161
+
162
+ const dateDifference = (targetPDT - refPDT) / 1000;
163
+ if (Math.abs(dateDifference) > Math.max(60, details.totalduration)) {
164
+ // Do not align on PDT if ranges differ significantly
165
+ logger.log(
166
+ `Cannot align playlists using PDT without overlap (${Math.abs(dateDifference)} > ${details.totalduration})`,
167
+ );
168
+ return;
169
+ }
170
+
171
+ const delta = dateDifference - (frag.start - refFrag.start);
172
+ logger.log(`Aligning playlists using PDT (diff: ${delta})`);
173
+ adjustSlidingStart(delta, details);
174
+ }
@@ -0,0 +1,21 @@
1
+ import { DecrypterAesMode } from '../crypt/decrypter-aes-mode';
2
+
3
+ export function isFullSegmentEncryption(method: string): boolean {
4
+ return (
5
+ method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR'
6
+ );
7
+ }
8
+
9
+ export function getAesModeFromFullSegmentMethod(
10
+ method: string,
11
+ ): DecrypterAesMode {
12
+ switch (method) {
13
+ case 'AES-128':
14
+ case 'AES-256':
15
+ return DecrypterAesMode.cbc;
16
+ case 'AES-256-CTR':
17
+ return DecrypterAesMode.ctr;
18
+ default:
19
+ throw new Error(`invalid full segment method ${method}`);
20
+ }
21
+ }