@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,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
|
+
}
|