@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,126 @@
1
+ /**
2
+ * AAC demuxer
3
+ */
4
+ import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
5
+ import * as ADTS from './adts';
6
+ import BaseAudioDemuxer from './base-audio-demuxer';
7
+ import * as MpegAudio from './mpegaudio';
8
+ import SampleAesDecrypter from '../sample-aes';
9
+ import type { HlsConfig } from '../../config';
10
+ import type { HlsEventEmitter } from '../../events';
11
+ import type {
12
+ AACAudioSample,
13
+ DemuxedAudioTrack,
14
+ DemuxerResult,
15
+ KeyData,
16
+ } from '../../types/demuxer';
17
+ import type { ILogger } from '../../utils/logger';
18
+
19
+ class AACDemuxer extends BaseAudioDemuxer {
20
+ private readonly observer: HlsEventEmitter;
21
+ private readonly config: HlsConfig;
22
+ private sampleAes: SampleAesDecrypter | null = null;
23
+
24
+ constructor(observer: HlsEventEmitter, config) {
25
+ super();
26
+ this.observer = observer;
27
+ this.config = config;
28
+ }
29
+
30
+ resetInitSegment(
31
+ initSegment: Uint8Array | undefined,
32
+ audioCodec: string | undefined,
33
+ videoCodec: string | undefined,
34
+ trackDuration: number,
35
+ ) {
36
+ super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
37
+ this._audioTrack = {
38
+ container: 'audio/adts',
39
+ type: 'audio',
40
+ id: 2,
41
+ pid: -1,
42
+ sequenceNumber: 0,
43
+ segmentCodec: 'aac',
44
+ samples: [],
45
+ manifestCodec: audioCodec,
46
+ duration: trackDuration,
47
+ inputTimeScale: 90000,
48
+ dropped: 0,
49
+ };
50
+ }
51
+
52
+ // Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS
53
+ static probe(data: Uint8Array | undefined, logger: ILogger): boolean {
54
+ if (!data) {
55
+ return false;
56
+ }
57
+
58
+ // Check for the ADTS sync word
59
+ // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
60
+ // Layer bits (position 14 and 15) in header should be always 0 for ADTS
61
+ // More info https://wiki.multimedia.cx/index.php?title=ADTS
62
+ const id3Data = getId3Data(data, 0);
63
+ let offset = id3Data?.length || 0;
64
+
65
+ if (MpegAudio.probe(data, offset)) {
66
+ return false;
67
+ }
68
+
69
+ for (let length = data.length; offset < length; offset++) {
70
+ if (ADTS.probe(data, offset)) {
71
+ logger.log('ADTS sync word found !');
72
+ return true;
73
+ }
74
+ }
75
+ return false;
76
+ }
77
+
78
+ canParse(data, offset) {
79
+ return ADTS.canParse(data, offset);
80
+ }
81
+
82
+ appendFrame(track: DemuxedAudioTrack, data: Uint8Array, offset: number) {
83
+ ADTS.initTrackConfig(
84
+ track,
85
+ this.observer,
86
+ data,
87
+ offset,
88
+ track.manifestCodec,
89
+ );
90
+ const frame = ADTS.appendFrame(
91
+ track,
92
+ data,
93
+ offset,
94
+ this.basePTS as number,
95
+ this.frameIndex,
96
+ );
97
+ if (frame?.missing === 0) {
98
+ return frame;
99
+ }
100
+ }
101
+
102
+ demuxSampleAes(
103
+ data: Uint8Array,
104
+ keyData: KeyData,
105
+ timeOffset: number,
106
+ ): Promise<DemuxerResult> {
107
+ const demuxResult = this.demux(data, timeOffset);
108
+ const sampleAes = (this.sampleAes = new SampleAesDecrypter(
109
+ this.observer,
110
+ this.config,
111
+ keyData,
112
+ ));
113
+
114
+ return new Promise((resolve) => {
115
+ sampleAes.decryptAacSamples(
116
+ demuxResult.audioTrack.samples as AACAudioSample[],
117
+ 0,
118
+ () => {
119
+ resolve(demuxResult);
120
+ },
121
+ );
122
+ });
123
+ }
124
+ }
125
+
126
+ export default AACDemuxer;
@@ -0,0 +1,170 @@
1
+ import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
2
+ import { getId3Timestamp } from '@svta/common-media-library/id3/getId3Timestamp';
3
+ import BaseAudioDemuxer from './base-audio-demuxer';
4
+ import { getAudioBSID } from './dolby';
5
+ import type { HlsEventEmitter } from '../../events';
6
+ import type { AudioFrame, DemuxedAudioTrack } from '../../types/demuxer';
7
+
8
+ export class AC3Demuxer extends BaseAudioDemuxer {
9
+ private readonly observer: HlsEventEmitter;
10
+
11
+ constructor(observer: HlsEventEmitter) {
12
+ super();
13
+ this.observer = observer;
14
+ }
15
+
16
+ resetInitSegment(
17
+ initSegment: Uint8Array | undefined,
18
+ audioCodec: string | undefined,
19
+ videoCodec: string | undefined,
20
+ trackDuration: number,
21
+ ) {
22
+ super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
23
+ this._audioTrack = {
24
+ container: 'audio/ac-3',
25
+ type: 'audio',
26
+ id: 2,
27
+ pid: -1,
28
+ sequenceNumber: 0,
29
+ segmentCodec: 'ac3',
30
+ samples: [],
31
+ manifestCodec: audioCodec,
32
+ duration: trackDuration,
33
+ inputTimeScale: 90000,
34
+ dropped: 0,
35
+ };
36
+ }
37
+
38
+ canParse(data: Uint8Array, offset: number): boolean {
39
+ return offset + 64 < data.length;
40
+ }
41
+
42
+ appendFrame(
43
+ track: DemuxedAudioTrack,
44
+ data: Uint8Array,
45
+ offset: number,
46
+ ): AudioFrame | void {
47
+ const frameLength = appendFrame(
48
+ track,
49
+ data,
50
+ offset,
51
+ this.basePTS as number,
52
+ this.frameIndex,
53
+ );
54
+ if (frameLength !== -1) {
55
+ const sample = track.samples[track.samples.length - 1];
56
+ return { sample, length: frameLength, missing: 0 };
57
+ }
58
+ }
59
+
60
+ static probe(data: Uint8Array | undefined): boolean {
61
+ if (!data) {
62
+ return false;
63
+ }
64
+
65
+ const id3Data = getId3Data(data, 0);
66
+ if (!id3Data) {
67
+ return false;
68
+ }
69
+
70
+ // look for the ac-3 sync bytes
71
+ const offset = id3Data.length;
72
+ if (
73
+ data[offset] === 0x0b &&
74
+ data[offset + 1] === 0x77 &&
75
+ getId3Timestamp(id3Data) !== undefined &&
76
+ // check the bsid to confirm ac-3
77
+ getAudioBSID(data, offset) < 16
78
+ ) {
79
+ return true;
80
+ }
81
+ return false;
82
+ }
83
+ }
84
+
85
+ export function appendFrame(
86
+ track: DemuxedAudioTrack,
87
+ data: Uint8Array,
88
+ start: number,
89
+ pts: number,
90
+ frameIndex: number,
91
+ ): number {
92
+ if (start + 8 > data.length) {
93
+ return -1; // not enough bytes left
94
+ }
95
+
96
+ if (data[start] !== 0x0b || data[start + 1] !== 0x77) {
97
+ return -1; // invalid magic
98
+ }
99
+
100
+ // get sample rate
101
+ const samplingRateCode = data[start + 4] >> 6;
102
+ if (samplingRateCode >= 3) {
103
+ return -1; // invalid sampling rate
104
+ }
105
+
106
+ const samplingRateMap = [48000, 44100, 32000];
107
+ const sampleRate = samplingRateMap[samplingRateCode];
108
+
109
+ // get frame size
110
+ const frameSizeCode = data[start + 4] & 0x3f;
111
+ const frameSizeMap = [
112
+ 64, 69, 96, 64, 70, 96, 80, 87, 120, 80, 88, 120, 96, 104, 144, 96, 105,
113
+ 144, 112, 121, 168, 112, 122, 168, 128, 139, 192, 128, 140, 192, 160, 174,
114
+ 240, 160, 175, 240, 192, 208, 288, 192, 209, 288, 224, 243, 336, 224, 244,
115
+ 336, 256, 278, 384, 256, 279, 384, 320, 348, 480, 320, 349, 480, 384, 417,
116
+ 576, 384, 418, 576, 448, 487, 672, 448, 488, 672, 512, 557, 768, 512, 558,
117
+ 768, 640, 696, 960, 640, 697, 960, 768, 835, 1152, 768, 836, 1152, 896, 975,
118
+ 1344, 896, 976, 1344, 1024, 1114, 1536, 1024, 1115, 1536, 1152, 1253, 1728,
119
+ 1152, 1254, 1728, 1280, 1393, 1920, 1280, 1394, 1920,
120
+ ];
121
+
122
+ const frameLength = frameSizeMap[frameSizeCode * 3 + samplingRateCode] * 2;
123
+ if (start + frameLength > data.length) {
124
+ return -1;
125
+ }
126
+
127
+ // get channel count
128
+ const channelMode = data[start + 6] >> 5;
129
+ let skipCount = 0;
130
+ if (channelMode === 2) {
131
+ skipCount += 2;
132
+ } else {
133
+ if (channelMode & 1 && channelMode !== 1) {
134
+ skipCount += 2;
135
+ }
136
+ if (channelMode & 4) {
137
+ skipCount += 2;
138
+ }
139
+ }
140
+
141
+ const lfeon =
142
+ (((data[start + 6] << 8) | data[start + 7]) >> (12 - skipCount)) & 1;
143
+
144
+ const channelsMap = [2, 1, 2, 3, 3, 4, 4, 5];
145
+ const channelCount = channelsMap[channelMode] + lfeon;
146
+
147
+ // build dac3 box
148
+ const bsid = data[start + 5] >> 3;
149
+ const bsmod = data[start + 5] & 7;
150
+
151
+ const config = new Uint8Array([
152
+ (samplingRateCode << 6) | (bsid << 1) | (bsmod >> 2),
153
+ ((bsmod & 3) << 6) |
154
+ (channelMode << 3) |
155
+ (lfeon << 2) |
156
+ (frameSizeCode >> 4),
157
+ (frameSizeCode << 4) & 0xe0,
158
+ ]);
159
+
160
+ const frameDuration = (1536 / sampleRate) * 90000;
161
+ const stamp = pts + frameIndex * frameDuration;
162
+ const unit = data.subarray(start, start + frameLength);
163
+
164
+ track.config = config;
165
+ track.channelCount = channelCount;
166
+ track.samplerate = sampleRate;
167
+ track.samples.push({ unit, pts: stamp });
168
+
169
+ return frameLength;
170
+ }
@@ -0,0 +1,249 @@
1
+ /**
2
+ * ADTS parser helper
3
+ * @link https://wiki.multimedia.cx/index.php?title=ADTS
4
+ */
5
+ import { ErrorDetails, ErrorTypes } from '../../errors';
6
+ import { Events } from '../../events';
7
+ import { logger } from '../../utils/logger';
8
+ import type { HlsEventEmitter } from '../../events';
9
+ import type {
10
+ AudioFrame,
11
+ AudioSample,
12
+ DemuxedAudioTrack,
13
+ } from '../../types/demuxer';
14
+
15
+ type AudioConfig = {
16
+ config: [number, number];
17
+ samplerate: number;
18
+ channelCount: number;
19
+ codec: string;
20
+ parsedCodec: string;
21
+ manifestCodec: string | undefined;
22
+ };
23
+
24
+ type FrameHeader = {
25
+ headerLength: number;
26
+ frameLength: number;
27
+ };
28
+
29
+ export function getAudioConfig(
30
+ observer: HlsEventEmitter,
31
+ data: Uint8Array,
32
+ offset: number,
33
+ manifestCodec: string | undefined,
34
+ ): AudioConfig | void {
35
+ const adtsSamplingRates = [
36
+ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025,
37
+ 8000, 7350,
38
+ ];
39
+ const byte2 = data[offset + 2];
40
+ const adtsSamplingIndex = (byte2 >> 2) & 0xf;
41
+ if (adtsSamplingIndex > 12) {
42
+ const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
43
+ observer.emit(Events.ERROR, Events.ERROR, {
44
+ type: ErrorTypes.MEDIA_ERROR,
45
+ details: ErrorDetails.FRAG_PARSING_ERROR,
46
+ fatal: true,
47
+ error,
48
+ reason: error.message,
49
+ });
50
+ return;
51
+ }
52
+ // MPEG-4 Audio Object Type (profile_ObjectType+1)
53
+ const adtsObjectType = ((byte2 >> 6) & 0x3) + 1;
54
+ const channelCount = ((data[offset + 3] >> 6) & 0x3) | ((byte2 & 1) << 2);
55
+ const codec = 'mp4a.40.' + adtsObjectType;
56
+ /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
57
+ ISO/IEC 14496-3 - Table 1.13 — Syntax of AudioSpecificConfig()
58
+ Audio Profile / Audio Object Type
59
+ 0: Null
60
+ 1: AAC Main
61
+ 2: AAC LC (Low Complexity)
62
+ 3: AAC SSR (Scalable Sample Rate)
63
+ 4: AAC LTP (Long Term Prediction)
64
+ 5: SBR (Spectral Band Replication)
65
+ 6: AAC Scalable
66
+ sampling freq
67
+ 0: 96000 Hz
68
+ 1: 88200 Hz
69
+ 2: 64000 Hz
70
+ 3: 48000 Hz
71
+ 4: 44100 Hz
72
+ 5: 32000 Hz
73
+ 6: 24000 Hz
74
+ 7: 22050 Hz
75
+ 8: 16000 Hz
76
+ 9: 12000 Hz
77
+ 10: 11025 Hz
78
+ 11: 8000 Hz
79
+ 12: 7350 Hz
80
+ 13: Reserved
81
+ 14: Reserved
82
+ 15: frequency is written explictly
83
+ Channel Configurations
84
+ These are the channel configurations:
85
+ 0: Defined in AOT Specifc Config
86
+ 1: 1 channel: front-center
87
+ 2: 2 channels: front-left, front-right
88
+ */
89
+ // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1
90
+ const samplerate = adtsSamplingRates[adtsSamplingIndex];
91
+ let aacSampleIndex = adtsSamplingIndex;
92
+ if (adtsObjectType === 5 || adtsObjectType === 29) {
93
+ // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
94
+ // there is a factor 2 between frame sample rate and output sample rate
95
+ // multiply frequency by 2 (see table above, equivalent to substract 3)
96
+ aacSampleIndex -= 3;
97
+ }
98
+ const config: [number, number] = [
99
+ (adtsObjectType << 3) | ((aacSampleIndex & 0x0e) >> 1),
100
+ ((aacSampleIndex & 0x01) << 7) | (channelCount << 3),
101
+ ];
102
+ logger.log(
103
+ `manifest codec:${manifestCodec}, parsed codec:${codec}, channels:${channelCount}, rate:${samplerate} (ADTS object type:${adtsObjectType} sampling index:${adtsSamplingIndex})`,
104
+ );
105
+ return {
106
+ config,
107
+ samplerate,
108
+ channelCount,
109
+ codec,
110
+ parsedCodec: codec,
111
+ manifestCodec,
112
+ };
113
+ }
114
+
115
+ export function isHeaderPattern(data: Uint8Array, offset: number): boolean {
116
+ return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0;
117
+ }
118
+
119
+ export function getHeaderLength(data: Uint8Array, offset: number): number {
120
+ return data[offset + 1] & 0x01 ? 7 : 9;
121
+ }
122
+
123
+ export function getFullFrameLength(data: Uint8Array, offset: number): number {
124
+ return (
125
+ ((data[offset + 3] & 0x03) << 11) |
126
+ (data[offset + 4] << 3) |
127
+ ((data[offset + 5] & 0xe0) >>> 5)
128
+ );
129
+ }
130
+
131
+ export function canGetFrameLength(data: Uint8Array, offset: number): boolean {
132
+ return offset + 5 < data.length;
133
+ }
134
+
135
+ export function isHeader(data: Uint8Array, offset: number): boolean {
136
+ // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
137
+ // Layer bits (position 14 and 15) in header should be always 0 for ADTS
138
+ // More info https://wiki.multimedia.cx/index.php?title=ADTS
139
+ return offset + 1 < data.length && isHeaderPattern(data, offset);
140
+ }
141
+
142
+ export function canParse(data: Uint8Array, offset: number): boolean {
143
+ return (
144
+ canGetFrameLength(data, offset) &&
145
+ isHeaderPattern(data, offset) &&
146
+ getFullFrameLength(data, offset) <= data.length - offset
147
+ );
148
+ }
149
+
150
+ export function probe(data: Uint8Array, offset: number): boolean {
151
+ // same as isHeader but we also check that ADTS frame follows last ADTS frame
152
+ // or end of data is reached
153
+ if (isHeader(data, offset)) {
154
+ // ADTS header Length
155
+ const headerLength = getHeaderLength(data, offset);
156
+ if (offset + headerLength >= data.length) {
157
+ return false;
158
+ }
159
+ // ADTS frame Length
160
+ const frameLength = getFullFrameLength(data, offset);
161
+ if (frameLength <= headerLength) {
162
+ return false;
163
+ }
164
+
165
+ const newOffset = offset + frameLength;
166
+ return newOffset === data.length || isHeader(data, newOffset);
167
+ }
168
+ return false;
169
+ }
170
+
171
+ export function initTrackConfig(
172
+ track: DemuxedAudioTrack,
173
+ observer: HlsEventEmitter,
174
+ data: Uint8Array,
175
+ offset: number,
176
+ audioCodec: string | undefined,
177
+ ) {
178
+ if (!track.samplerate) {
179
+ const config = getAudioConfig(observer, data, offset, audioCodec);
180
+ if (!config) {
181
+ return;
182
+ }
183
+ Object.assign(track, config);
184
+ }
185
+ }
186
+
187
+ export function getFrameDuration(samplerate: number): number {
188
+ return (1024 * 90000) / samplerate;
189
+ }
190
+
191
+ export function parseFrameHeader(
192
+ data: Uint8Array,
193
+ offset: number,
194
+ ): FrameHeader | void {
195
+ // The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header
196
+ const headerLength = getHeaderLength(data, offset);
197
+ if (offset + headerLength <= data.length) {
198
+ // retrieve frame size
199
+ const frameLength = getFullFrameLength(data, offset) - headerLength;
200
+ if (frameLength > 0) {
201
+ // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}`);
202
+ return { headerLength, frameLength };
203
+ }
204
+ }
205
+ }
206
+
207
+ export function appendFrame(
208
+ track: DemuxedAudioTrack,
209
+ data: Uint8Array,
210
+ offset: number,
211
+ pts: number,
212
+ frameIndex: number,
213
+ ): AudioFrame {
214
+ const frameDuration = getFrameDuration(track.samplerate as number);
215
+ const stamp = pts + frameIndex * frameDuration;
216
+ const header = parseFrameHeader(data, offset);
217
+ let unit: Uint8Array;
218
+ if (header) {
219
+ const { frameLength, headerLength } = header;
220
+ const length = headerLength + frameLength;
221
+ const missing = Math.max(0, offset + length - data.length);
222
+ // logger.log(`AAC frame ${frameIndex}, pts:${stamp} length@offset/total: ${frameLength}@${offset+headerLength}/${data.byteLength} missing: ${missing}`);
223
+ if (missing) {
224
+ unit = new Uint8Array(length - headerLength);
225
+ unit.set(data.subarray(offset + headerLength, data.length), 0);
226
+ } else {
227
+ unit = data.subarray(offset + headerLength, offset + length);
228
+ }
229
+
230
+ const sample: AudioSample = {
231
+ unit,
232
+ pts: stamp,
233
+ };
234
+ if (!missing) {
235
+ track.samples.push(sample as AudioSample);
236
+ }
237
+
238
+ return { sample, length, missing };
239
+ }
240
+ // overflow incomplete header
241
+ const length = data.length - offset;
242
+ unit = new Uint8Array(length);
243
+ unit.set(data.subarray(offset, data.length), 0);
244
+ const sample: AudioSample = {
245
+ unit,
246
+ pts: stamp,
247
+ };
248
+ return { sample, length, missing: -1 };
249
+ }