@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,205 @@
1
+ import { canParseId3 } from '@svta/common-media-library/id3/canParseId3';
2
+ import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
3
+ import { getId3Timestamp } from '@svta/common-media-library/id3/getId3Timestamp';
4
+ import {
5
+ type AudioFrame,
6
+ type DemuxedAudioTrack,
7
+ type DemuxedMetadataTrack,
8
+ type DemuxedUserdataTrack,
9
+ type DemuxedVideoTrackBase,
10
+ type Demuxer,
11
+ type DemuxerResult,
12
+ type KeyData,
13
+ MetadataSchema,
14
+ } from '../../types/demuxer';
15
+ import { appendUint8Array } from '../../utils/mp4-tools';
16
+ import { dummyTrack } from '../dummy-demuxed-track';
17
+ import type {
18
+ RationalTimestamp,
19
+ TimestampOffset,
20
+ } from '../../utils/timescale-conversion';
21
+
22
+ class BaseAudioDemuxer implements Demuxer {
23
+ protected _audioTrack?: DemuxedAudioTrack;
24
+ protected _id3Track?: DemuxedMetadataTrack;
25
+ protected frameIndex: number = 0;
26
+ protected cachedData: Uint8Array | null = null;
27
+ protected basePTS: number | null = null;
28
+ protected initPTS: TimestampOffset | null = null;
29
+ protected lastPTS: number | null = null;
30
+
31
+ resetInitSegment(
32
+ initSegment: Uint8Array | undefined,
33
+ audioCodec: string | undefined,
34
+ videoCodec: string | undefined,
35
+ trackDuration: number,
36
+ ) {
37
+ this._id3Track = {
38
+ type: 'id3',
39
+ id: 3,
40
+ pid: -1,
41
+ inputTimeScale: 90000,
42
+ sequenceNumber: 0,
43
+ samples: [],
44
+ dropped: 0,
45
+ };
46
+ }
47
+
48
+ resetTimeStamp(deaultTimestamp: TimestampOffset | null) {
49
+ this.initPTS = deaultTimestamp;
50
+ this.resetContiguity();
51
+ }
52
+
53
+ resetContiguity(): void {
54
+ this.basePTS = null;
55
+ this.lastPTS = null;
56
+ this.frameIndex = 0;
57
+ }
58
+
59
+ canParse(data: Uint8Array, offset: number): boolean {
60
+ return false;
61
+ }
62
+
63
+ appendFrame(
64
+ track: DemuxedAudioTrack,
65
+ data: Uint8Array,
66
+ offset: number,
67
+ ): AudioFrame | void {}
68
+
69
+ // feed incoming data to the front of the parsing pipeline
70
+ demux(data: Uint8Array, timeOffset: number): DemuxerResult {
71
+ if (this.cachedData) {
72
+ data = appendUint8Array(this.cachedData, data);
73
+ this.cachedData = null;
74
+ }
75
+
76
+ let id3Data: Uint8Array | undefined = getId3Data(data, 0);
77
+ let offset = id3Data ? id3Data.length : 0;
78
+ let lastDataIndex;
79
+ const track = this._audioTrack as DemuxedAudioTrack;
80
+ const id3Track = this._id3Track as DemuxedMetadataTrack;
81
+ const timestamp = id3Data ? getId3Timestamp(id3Data) : undefined;
82
+ const length = data.length;
83
+
84
+ if (
85
+ this.basePTS === null ||
86
+ (this.frameIndex === 0 && Number.isFinite(timestamp))
87
+ ) {
88
+ this.basePTS = initPTSFn(timestamp, timeOffset, this.initPTS);
89
+ this.lastPTS = this.basePTS;
90
+ }
91
+
92
+ if (this.lastPTS === null) {
93
+ this.lastPTS = this.basePTS;
94
+ }
95
+
96
+ // more expressive than alternative: id3Data?.length
97
+ if (id3Data && id3Data.length > 0) {
98
+ id3Track.samples.push({
99
+ pts: this.lastPTS,
100
+ dts: this.lastPTS,
101
+ data: id3Data,
102
+ type: MetadataSchema.audioId3,
103
+ duration: Number.POSITIVE_INFINITY,
104
+ });
105
+ }
106
+
107
+ while (offset < length) {
108
+ if (this.canParse(data, offset)) {
109
+ const frame = this.appendFrame(track, data, offset);
110
+ if (frame) {
111
+ this.frameIndex++;
112
+ this.lastPTS = frame.sample.pts;
113
+ offset += frame.length;
114
+ lastDataIndex = offset;
115
+ } else {
116
+ offset = length;
117
+ }
118
+ } else if (canParseId3(data, offset)) {
119
+ // after a canParse, a call to getId3Data *should* always returns some data
120
+ id3Data = getId3Data(data, offset)!;
121
+ id3Track.samples.push({
122
+ pts: this.lastPTS,
123
+ dts: this.lastPTS,
124
+ data: id3Data,
125
+ type: MetadataSchema.audioId3,
126
+ duration: Number.POSITIVE_INFINITY,
127
+ });
128
+ offset += id3Data.length;
129
+ lastDataIndex = offset;
130
+ } else {
131
+ offset++;
132
+ }
133
+ if (offset === length && lastDataIndex !== length) {
134
+ const partialData = data.slice(lastDataIndex);
135
+ if (this.cachedData) {
136
+ this.cachedData = appendUint8Array(this.cachedData, partialData);
137
+ } else {
138
+ this.cachedData = partialData;
139
+ }
140
+ }
141
+ }
142
+
143
+ return {
144
+ audioTrack: track,
145
+ videoTrack: dummyTrack() as DemuxedVideoTrackBase,
146
+ id3Track,
147
+ textTrack: dummyTrack() as DemuxedUserdataTrack,
148
+ };
149
+ }
150
+
151
+ demuxSampleAes(
152
+ data: Uint8Array,
153
+ keyData: KeyData,
154
+ timeOffset: number,
155
+ ): Promise<DemuxerResult> {
156
+ return Promise.reject(
157
+ new Error(
158
+ `[${this}] This demuxer does not support Sample-AES decryption`,
159
+ ),
160
+ );
161
+ }
162
+
163
+ flush(timeOffset: number): DemuxerResult {
164
+ // Parse cache in case of remaining frames.
165
+ const cachedData = this.cachedData;
166
+ if (cachedData) {
167
+ this.cachedData = null;
168
+ this.demux(cachedData, 0);
169
+ }
170
+
171
+ return {
172
+ audioTrack: this._audioTrack as DemuxedAudioTrack,
173
+ videoTrack: dummyTrack() as DemuxedVideoTrackBase,
174
+ id3Track: this._id3Track as DemuxedMetadataTrack,
175
+ textTrack: dummyTrack() as DemuxedUserdataTrack,
176
+ };
177
+ }
178
+
179
+ destroy() {
180
+ this.cachedData = null;
181
+ // @ts-ignore
182
+ this._audioTrack = this._id3Track = undefined;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Initialize PTS
188
+ * <p>
189
+ * use timestamp unless it is undefined, NaN or Infinity
190
+ * </p>
191
+ */
192
+ export const initPTSFn = (
193
+ timestamp: number | undefined,
194
+ timeOffset: number,
195
+ initPTS: RationalTimestamp | null,
196
+ ): number => {
197
+ if (Number.isFinite(timestamp as number)) {
198
+ return timestamp! * 90;
199
+ }
200
+ const init90kHz = initPTS
201
+ ? (initPTS.baseTime * 90000) / initPTS.timescale
202
+ : 0;
203
+ return timeOffset * 90000 + init90kHz;
204
+ };
205
+ export default BaseAudioDemuxer;
@@ -0,0 +1,21 @@
1
+ export const getAudioBSID = (data: Uint8Array, offset: number): number => {
2
+ // check the bsid to confirm ac-3 | ec-3
3
+ let bsid = 0;
4
+ let numBits = 5;
5
+ offset += numBits;
6
+ const temp = new Uint32Array(1); // unsigned 32 bit for temporary storage
7
+ const mask = new Uint32Array(1); // unsigned 32 bit mask value
8
+ const byte = new Uint8Array(1); // unsigned 8 bit for temporary storage
9
+ while (numBits > 0) {
10
+ byte[0] = data[offset];
11
+ // read remaining bits, upto 8 bits at a time
12
+ const bits = Math.min(numBits, 8);
13
+ const shift = 8 - bits;
14
+ mask[0] = (0xff000000 >>> (24 + shift)) << shift;
15
+ temp[0] = (byte[0] & mask[0]) >> shift;
16
+ bsid = !bsid ? temp[0] : (bsid << bits) | temp[0];
17
+ offset += 1;
18
+ numBits -= bits;
19
+ }
20
+ return bsid;
21
+ };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * MP3 demuxer
3
+ */
4
+ import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
5
+ import { getId3Timestamp } from '@svta/common-media-library/id3/getId3Timestamp';
6
+ import BaseAudioDemuxer from './base-audio-demuxer';
7
+ import { getAudioBSID } from './dolby';
8
+ import * as MpegAudio from './mpegaudio';
9
+ import { logger } from '../../utils/logger';
10
+
11
+ class MP3Demuxer extends BaseAudioDemuxer {
12
+ resetInitSegment(
13
+ initSegment: Uint8Array | undefined,
14
+ audioCodec: string | undefined,
15
+ videoCodec: string | undefined,
16
+ trackDuration: number,
17
+ ) {
18
+ super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
19
+ this._audioTrack = {
20
+ container: 'audio/mpeg',
21
+ type: 'audio',
22
+ id: 2,
23
+ pid: -1,
24
+ sequenceNumber: 0,
25
+ segmentCodec: 'mp3',
26
+ samples: [],
27
+ manifestCodec: audioCodec,
28
+ duration: trackDuration,
29
+ inputTimeScale: 90000,
30
+ dropped: 0,
31
+ };
32
+ }
33
+
34
+ static probe(data: Uint8Array | undefined): boolean {
35
+ if (!data) {
36
+ return false;
37
+ }
38
+
39
+ // check if data contains ID3 timestamp and MPEG sync word
40
+ // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
41
+ // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
42
+ // More info http://www.mp3-tech.org/programmer/frame_header.html
43
+ const id3Data = getId3Data(data, 0);
44
+ let offset = id3Data?.length || 0;
45
+
46
+ // Check for ac-3|ec-3 sync bytes and return false if present
47
+ if (
48
+ id3Data &&
49
+ data[offset] === 0x0b &&
50
+ data[offset + 1] === 0x77 &&
51
+ getId3Timestamp(id3Data) !== undefined &&
52
+ // check the bsid to confirm ac-3 or ec-3 (not mp3)
53
+ getAudioBSID(data, offset) <= 16
54
+ ) {
55
+ return false;
56
+ }
57
+
58
+ for (let length = data.length; offset < length; offset++) {
59
+ if (MpegAudio.probe(data, offset)) {
60
+ logger.log('MPEG Audio sync word found !');
61
+ return true;
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+
67
+ canParse(data, offset) {
68
+ return MpegAudio.canParse(data, offset);
69
+ }
70
+
71
+ appendFrame(track, data, offset) {
72
+ if (this.basePTS === null) {
73
+ return;
74
+ }
75
+ return MpegAudio.appendFrame(
76
+ track,
77
+ data,
78
+ offset,
79
+ this.basePTS,
80
+ this.frameIndex,
81
+ );
82
+ }
83
+ }
84
+
85
+ export default MP3Demuxer;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * MPEG parser helper
3
+ */
4
+ import type { DemuxedAudioTrack } from '../../types/demuxer';
5
+
6
+ let chromeVersion: number | null = null;
7
+
8
+ const BitratesMap = [
9
+ 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56,
10
+ 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80,
11
+ 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144,
12
+ 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144,
13
+ 160,
14
+ ];
15
+
16
+ const SamplingRateMap = [
17
+ 44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000,
18
+ ];
19
+
20
+ const SamplesCoefficients = [
21
+ // MPEG 2.5
22
+ [
23
+ 0, // Reserved
24
+ 72, // Layer3
25
+ 144, // Layer2
26
+ 12, // Layer1
27
+ ],
28
+ // Reserved
29
+ [
30
+ 0, // Reserved
31
+ 0, // Layer3
32
+ 0, // Layer2
33
+ 0, // Layer1
34
+ ],
35
+ // MPEG 2
36
+ [
37
+ 0, // Reserved
38
+ 72, // Layer3
39
+ 144, // Layer2
40
+ 12, // Layer1
41
+ ],
42
+ // MPEG 1
43
+ [
44
+ 0, // Reserved
45
+ 144, // Layer3
46
+ 144, // Layer2
47
+ 12, // Layer1
48
+ ],
49
+ ];
50
+
51
+ const BytesInSlot = [
52
+ 0, // Reserved
53
+ 1, // Layer3
54
+ 1, // Layer2
55
+ 4, // Layer1
56
+ ];
57
+
58
+ export function appendFrame(
59
+ track: DemuxedAudioTrack,
60
+ data: Uint8Array,
61
+ offset: number,
62
+ pts: number,
63
+ frameIndex: number,
64
+ ) {
65
+ // Using http://www.datavoyage.com/mpgscript/mpeghdr.htm as a reference
66
+ if (offset + 24 > data.length) {
67
+ return;
68
+ }
69
+
70
+ const header = parseHeader(data, offset);
71
+ if (header && offset + header.frameLength <= data.length) {
72
+ const frameDuration = (header.samplesPerFrame * 90000) / header.sampleRate;
73
+ const stamp = pts + frameIndex * frameDuration;
74
+ const sample = {
75
+ unit: data.subarray(offset, offset + header.frameLength),
76
+ pts: stamp,
77
+ dts: stamp,
78
+ };
79
+
80
+ track.config = [];
81
+ track.channelCount = header.channelCount;
82
+ track.samplerate = header.sampleRate;
83
+ track.samples.push(sample);
84
+
85
+ return { sample, length: header.frameLength, missing: 0 };
86
+ }
87
+ }
88
+
89
+ export function parseHeader(data: Uint8Array, offset: number) {
90
+ const mpegVersion = (data[offset + 1] >> 3) & 3;
91
+ const mpegLayer = (data[offset + 1] >> 1) & 3;
92
+ const bitRateIndex = (data[offset + 2] >> 4) & 15;
93
+ const sampleRateIndex = (data[offset + 2] >> 2) & 3;
94
+ if (
95
+ mpegVersion !== 1 &&
96
+ bitRateIndex !== 0 &&
97
+ bitRateIndex !== 15 &&
98
+ sampleRateIndex !== 3
99
+ ) {
100
+ const paddingBit = (data[offset + 2] >> 1) & 1;
101
+ const channelMode = data[offset + 3] >> 6;
102
+ const columnInBitrates =
103
+ mpegVersion === 3 ? 3 - mpegLayer : mpegLayer === 3 ? 3 : 4;
104
+ const bitRate =
105
+ BitratesMap[columnInBitrates * 14 + bitRateIndex - 1] * 1000;
106
+ const columnInSampleRates =
107
+ mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2;
108
+ const sampleRate =
109
+ SamplingRateMap[columnInSampleRates * 3 + sampleRateIndex];
110
+ const channelCount = channelMode === 3 ? 1 : 2; // If bits of channel mode are `11` then it is a single channel (Mono)
111
+ const sampleCoefficient = SamplesCoefficients[mpegVersion][mpegLayer];
112
+ const bytesInSlot = BytesInSlot[mpegLayer];
113
+ const samplesPerFrame = sampleCoefficient * 8 * bytesInSlot;
114
+ const frameLength =
115
+ Math.floor((sampleCoefficient * bitRate) / sampleRate + paddingBit) *
116
+ bytesInSlot;
117
+
118
+ if (chromeVersion === null) {
119
+ const userAgent = navigator.userAgent || '';
120
+ const result = userAgent.match(/Chrome\/(\d+)/i);
121
+ chromeVersion = result ? parseInt(result[1]) : 0;
122
+ }
123
+ const needChromeFix = !!chromeVersion && chromeVersion <= 87;
124
+
125
+ if (
126
+ needChromeFix &&
127
+ mpegLayer === 2 &&
128
+ bitRate >= 224000 &&
129
+ channelMode === 0
130
+ ) {
131
+ // Work around bug in Chromium by setting channelMode to dual-channel (01) instead of stereo (00)
132
+ data[offset + 3] = data[offset + 3] | 0x80;
133
+ }
134
+
135
+ return { sampleRate, channelCount, frameLength, samplesPerFrame };
136
+ }
137
+ }
138
+
139
+ export function isHeaderPattern(data: Uint8Array, offset: number): boolean {
140
+ return (
141
+ data[offset] === 0xff &&
142
+ (data[offset + 1] & 0xe0) === 0xe0 &&
143
+ (data[offset + 1] & 0x06) !== 0x00
144
+ );
145
+ }
146
+
147
+ export function isHeader(data: Uint8Array, offset: number): boolean {
148
+ // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
149
+ // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
150
+ // More info http://www.mp3-tech.org/programmer/frame_header.html
151
+ return offset + 1 < data.length && isHeaderPattern(data, offset);
152
+ }
153
+
154
+ export function canParse(data: Uint8Array, offset: number): boolean {
155
+ const headerSize = 4;
156
+
157
+ return isHeaderPattern(data, offset) && headerSize <= data.length - offset;
158
+ }
159
+
160
+ export function probe(data: Uint8Array, offset: number): boolean {
161
+ // same as isHeader but we also check that MPEG frame follows last MPEG frame
162
+ // or end of data is reached
163
+ if (offset + 1 < data.length && isHeaderPattern(data, offset)) {
164
+ // MPEG header Length
165
+ const headerLength = 4;
166
+ // MPEG frame Length
167
+ const header = parseHeader(data, offset);
168
+ let frameLength = headerLength;
169
+ if (header?.frameLength) {
170
+ frameLength = header.frameLength;
171
+ }
172
+
173
+ const newOffset = offset + frameLength;
174
+ return newOffset === data.length || isHeader(data, newOffset);
175
+ }
176
+ return false;
177
+ }
@@ -0,0 +1,42 @@
1
+ export default class ChunkCache {
2
+ private chunks: Array<Uint8Array<ArrayBuffer>> = [];
3
+ public dataLength: number = 0;
4
+
5
+ push(chunk: Uint8Array<ArrayBuffer>) {
6
+ this.chunks.push(chunk);
7
+ this.dataLength += chunk.length;
8
+ }
9
+
10
+ flush(): Uint8Array<ArrayBuffer> {
11
+ const { chunks, dataLength } = this;
12
+ let result: Uint8Array<ArrayBuffer>;
13
+ if (!chunks.length) {
14
+ return new Uint8Array(0);
15
+ } else if (chunks.length === 1) {
16
+ result = chunks[0];
17
+ } else {
18
+ result = concatUint8Arrays(chunks, dataLength);
19
+ }
20
+ this.reset();
21
+ return result;
22
+ }
23
+
24
+ reset() {
25
+ this.chunks.length = 0;
26
+ this.dataLength = 0;
27
+ }
28
+ }
29
+
30
+ function concatUint8Arrays(
31
+ chunks: Array<Uint8Array<ArrayBuffer>>,
32
+ dataLength: number,
33
+ ): Uint8Array<ArrayBuffer> {
34
+ const result = new Uint8Array(dataLength);
35
+ let offset = 0;
36
+ for (let i = 0; i < chunks.length; i++) {
37
+ const chunk = chunks[i];
38
+ result.set(chunk, offset);
39
+ offset += chunk.length;
40
+ }
41
+ return result;
42
+ }
@@ -0,0 +1,13 @@
1
+ import type { DemuxedTrack } from '../types/demuxer';
2
+
3
+ export function dummyTrack(type = '', inputTimeScale = 90000): DemuxedTrack {
4
+ return {
5
+ type,
6
+ id: -1,
7
+ pid: -1,
8
+ inputTimeScale,
9
+ sequenceNumber: -1,
10
+ samples: [],
11
+ dropped: 0,
12
+ };
13
+ }
@@ -0,0 +1,75 @@
1
+ // ensure the worker ends up in the bundle
2
+ // If the worker should not be included this gets aliased to empty.js
3
+ import './transmuxer-worker';
4
+ import { version } from '../version';
5
+
6
+ const workerStore: Record<string, WorkerContext> = {};
7
+
8
+ export function hasUMDWorker(): boolean {
9
+ return typeof __HLS_WORKER_BUNDLE__ === 'function';
10
+ }
11
+
12
+ export type WorkerContext = {
13
+ worker: Worker;
14
+ objectURL?: string;
15
+ scriptURL?: string;
16
+ clientCount: number;
17
+ };
18
+
19
+ export function injectWorker(): WorkerContext {
20
+ const workerContext = workerStore[version];
21
+ if (workerContext) {
22
+ workerContext.clientCount++;
23
+ return workerContext;
24
+ }
25
+ const blob = new self.Blob(
26
+ [
27
+ `var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(${__HLS_WORKER_BUNDLE__.toString()})(true);`,
28
+ ],
29
+ {
30
+ type: 'text/javascript',
31
+ },
32
+ );
33
+ const objectURL = self.URL.createObjectURL(blob);
34
+ const worker = new self.Worker(objectURL);
35
+ const result = {
36
+ worker,
37
+ objectURL,
38
+ clientCount: 1,
39
+ };
40
+ workerStore[version] = result;
41
+ return result;
42
+ }
43
+
44
+ export function loadWorker(path: string): WorkerContext {
45
+ const workerContext = workerStore[path];
46
+ if (workerContext) {
47
+ workerContext.clientCount++;
48
+ return workerContext;
49
+ }
50
+ const scriptURL = new self.URL(path, self.location.href).href;
51
+ const worker = new self.Worker(scriptURL);
52
+ const result = {
53
+ worker,
54
+ scriptURL,
55
+ clientCount: 1,
56
+ };
57
+ workerStore[path] = result;
58
+ return result;
59
+ }
60
+
61
+ export function removeWorkerFromStore(path?: string | null) {
62
+ const workerContext = workerStore[path || version];
63
+ if (workerContext) {
64
+ const clientCount = workerContext.clientCount--;
65
+ if (clientCount === 1) {
66
+ const { worker, objectURL } = workerContext;
67
+ delete workerStore[path || version];
68
+ if (objectURL) {
69
+ // revoke the Object URL that was used to create transmuxer worker, so as not to leak it
70
+ self.URL.revokeObjectURL(objectURL);
71
+ }
72
+ worker.terminate();
73
+ }
74
+ }
75
+ }