jmuxer 2.0.7 → 2.1.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/README.md +24 -33
- package/dist/jmuxer.min.js +3416 -1
- package/package.json +11 -6
- package/src/controller/remux.js +63 -46
- package/src/jmuxer.js +39 -144
- package/src/parsers/aac.js +11 -38
- package/src/parsers/h264.js +101 -96
- package/src/parsers/h265.js +303 -0
- package/src/remuxer/aac.js +63 -7
- package/src/remuxer/base.js +2 -1
- package/src/remuxer/h264.js +163 -7
- package/src/remuxer/h265.js +288 -0
- package/src/util/debug.js +3 -3
- package/src/util/event.js +2 -2
- package/src/util/mp4-generator.js +124 -9
- package/loader-thumb.jpg +0 -0
- package/packet-format.png +0 -0
- package/src/util/nalu.js +0 -72
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jmuxer",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "jMuxer - a simple javascript mp4 muxer for non-standard streaming communications protocol",
|
|
5
5
|
"main": "dist/jmuxer.min.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "rollup -c",
|
|
8
|
-
"
|
|
8
|
+
"watch": "rollup -c -w",
|
|
9
|
+
"server": "cd example && nodemon server.mjs",
|
|
10
|
+
"dev": "concurrently --names \"BUILD,SERVER\" -c \"bgBlue.bold,bgMagenta.bold\" \"npm run watch\" \"npm run server\"",
|
|
9
11
|
"pro": "NODE_ENV=production rollup -c",
|
|
10
12
|
"lint": "eslint -c .eslintrc.json ./src/",
|
|
11
13
|
"test": "karma start karma.conf.js"
|
|
@@ -42,17 +44,20 @@
|
|
|
42
44
|
"@rollup/pluginutils": "^4.0.0",
|
|
43
45
|
"caniuse-lite": "^1.0.30001223",
|
|
44
46
|
"chai": "^4.2.0",
|
|
47
|
+
"concurrently": "^9.2.1",
|
|
45
48
|
"eslint": "^8.2.0",
|
|
49
|
+
"express": "^5.1.0",
|
|
50
|
+
"express-ws": "^5.0.2",
|
|
46
51
|
"karma": "^6.3.2",
|
|
47
52
|
"karma-chai": "^0.1.0",
|
|
48
53
|
"karma-chrome-launcher": "^3.1.0",
|
|
49
54
|
"karma-mocha": "^2.0.1",
|
|
50
55
|
"karma-rollup-preprocessor": "^7.0.5",
|
|
51
|
-
"mocha": "^
|
|
52
|
-
"
|
|
56
|
+
"mocha": "^11.7.2",
|
|
57
|
+
"msgpack-lite": "^0.1.26",
|
|
58
|
+
"nodemon": "^3.1.10",
|
|
53
59
|
"path": "^0.12.7",
|
|
54
60
|
"rollup": "^2.59.0",
|
|
55
|
-
"rollup-plugin-terser": "^7.0.2"
|
|
56
|
-
"ws": "^8.2.3"
|
|
61
|
+
"rollup-plugin-terser": "^7.0.2"
|
|
57
62
|
}
|
|
58
63
|
}
|
package/src/controller/remux.js
CHANGED
|
@@ -2,38 +2,49 @@ import * as debug from '../util/debug';
|
|
|
2
2
|
import { MP4 } from '../util/mp4-generator.js';
|
|
3
3
|
import { AACRemuxer } from '../remuxer/aac.js';
|
|
4
4
|
import { H264Remuxer } from '../remuxer/h264.js';
|
|
5
|
+
import { H265Remuxer } from '../remuxer/h265.js';
|
|
5
6
|
import { appendByteArray, secToTime } from '../util/utils.js';
|
|
6
7
|
import Event from '../util/event';
|
|
7
8
|
|
|
8
9
|
export default class RemuxController extends Event {
|
|
9
10
|
|
|
10
|
-
constructor(env) {
|
|
11
|
+
constructor(env, live, videoCodec, frameDuration) {
|
|
11
12
|
super('remuxer');
|
|
13
|
+
this.videoCodec = videoCodec;
|
|
14
|
+
this.frameDuration = frameDuration;
|
|
12
15
|
this.initialized = false;
|
|
13
|
-
this.trackTypes = [];
|
|
14
16
|
this.tracks = {};
|
|
15
17
|
this.seq = 1;
|
|
16
18
|
this.env = env;
|
|
17
19
|
this.timescale = 1000;
|
|
18
|
-
this.mediaDuration = 0;
|
|
19
|
-
this.aacParser = null;
|
|
20
|
+
this.mediaDuration = live ? 0xffffffff : 0;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
addTrack(type) {
|
|
23
24
|
if (type === 'video' || type === 'both') {
|
|
24
|
-
this.
|
|
25
|
-
|
|
25
|
+
if (this.videoCodec == 'H265') {
|
|
26
|
+
this.tracks.video = new H265Remuxer(this.timescale, this.mediaDuration, this.frameDuration);
|
|
27
|
+
} else {
|
|
28
|
+
this.tracks.video = new H264Remuxer(this.timescale, this.mediaDuration, this.frameDuration);
|
|
29
|
+
}
|
|
30
|
+
this.tracks.video.on('outOfData', () => {
|
|
31
|
+
this.dispatch('missingVideoFrames');
|
|
32
|
+
});
|
|
33
|
+
this.tracks.video.on('keyframePosition', (time) => {
|
|
34
|
+
this.dispatch('keyframePosition', time);
|
|
35
|
+
});
|
|
26
36
|
}
|
|
27
37
|
if (type === 'audio' || type === 'both') {
|
|
28
|
-
const aacRemuxer = new AACRemuxer(this.timescale);
|
|
29
|
-
this.aacParser = aacRemuxer.getAacParser();
|
|
38
|
+
const aacRemuxer = new AACRemuxer(this.timescale, this.mediaDuration, this.frameDuration);
|
|
30
39
|
this.tracks.audio = aacRemuxer;
|
|
31
|
-
this.
|
|
40
|
+
this.tracks.video.on('outOfData', () => {
|
|
41
|
+
this.dispatch('missingAudioFrames');
|
|
42
|
+
});
|
|
32
43
|
}
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
reset() {
|
|
36
|
-
for (
|
|
47
|
+
for (const type in this.tracks) {
|
|
37
48
|
this.tracks[type].resetTrack();
|
|
38
49
|
}
|
|
39
50
|
this.initialized = false;
|
|
@@ -46,41 +57,40 @@ export default class RemuxController extends Event {
|
|
|
46
57
|
|
|
47
58
|
flush() {
|
|
48
59
|
if (!this.initialized) {
|
|
49
|
-
if (this.isReady())
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
this.dispatch('buffer', data);
|
|
72
|
-
let duration = secToTime(track.dts / this.timescale);
|
|
73
|
-
debug.log(`put segment (${type}): dts: ${track.dts} frames: ${track.mp4track.samples.length} second: ${duration}`);
|
|
74
|
-
track.flush();
|
|
75
|
-
this.seq++;
|
|
60
|
+
if (!this.isReady()) return;
|
|
61
|
+
|
|
62
|
+
this.dispatch('ready');
|
|
63
|
+
this.initSegment();
|
|
64
|
+
this.initialized = true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const type in this.tracks) {
|
|
68
|
+
let track = this.tracks[type];
|
|
69
|
+
let pay = track.getPayload();
|
|
70
|
+
if (pay && pay.byteLength) {
|
|
71
|
+
const moof = MP4.moof(this.seq, track.dts, track.mp4track);
|
|
72
|
+
const mdat = MP4.mdat(pay);
|
|
73
|
+
let payload = appendByteArray(moof, mdat);
|
|
74
|
+
let data = {
|
|
75
|
+
type: type,
|
|
76
|
+
payload: payload,
|
|
77
|
+
dts: track.dts
|
|
78
|
+
};
|
|
79
|
+
if (type === 'video') {
|
|
80
|
+
data.fps = track.mp4track.fps;
|
|
76
81
|
}
|
|
82
|
+
this.dispatch('buffer', data);
|
|
83
|
+
let duration = secToTime(track.dts / this.timescale);
|
|
84
|
+
debug.log(`put segment (${type}): dts: ${track.dts} frames: ${track.mp4track.samples.length} second: ${duration}`);
|
|
85
|
+
track.flush();
|
|
86
|
+
this.seq++;
|
|
77
87
|
}
|
|
78
88
|
}
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
initSegment() {
|
|
82
92
|
let tracks = [];
|
|
83
|
-
for (
|
|
93
|
+
for (const type in this.tracks) {
|
|
84
94
|
let track = this.tracks[type];
|
|
85
95
|
if (this.env == 'browser') {
|
|
86
96
|
let data = {
|
|
@@ -103,20 +113,27 @@ export default class RemuxController extends Event {
|
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
isReady() {
|
|
106
|
-
for (
|
|
116
|
+
for (const type in this.tracks) {
|
|
107
117
|
if (!this.tracks[type].readyToDecode || !this.tracks[type].samples.length) return false;
|
|
108
118
|
}
|
|
109
119
|
return true;
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.tracks[type].remux(frames);
|
|
118
|
-
}
|
|
122
|
+
feed(data) {
|
|
123
|
+
let remux = false;
|
|
124
|
+
|
|
125
|
+
if (data.video && this.tracks.video) {
|
|
126
|
+
remux |= this.tracks.video.feed(data.video, data.duration, data.compositionTimeOffset);
|
|
119
127
|
}
|
|
128
|
+
if (data.audio && this.tracks.audio) {
|
|
129
|
+
remux |= this.tracks.audio.feed(data.audio, data.duration);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!remux) {
|
|
133
|
+
debug.error('Input object must have video and/or audio property. Make sure it is a valid typed array');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
120
137
|
this.flush();
|
|
121
138
|
}
|
|
122
139
|
}
|
package/src/jmuxer.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import * as debug from './util/debug';
|
|
2
|
-
import { NALU } from './util/nalu.js';
|
|
3
|
-
import { appendByteArray } from './util/utils.js';
|
|
4
|
-
import { H264Parser } from './parsers/h264.js';
|
|
5
|
-
import { AACParser } from './parsers/aac.js';
|
|
6
2
|
import Event from './util/event';
|
|
7
3
|
import RemuxController from './controller/remux.js';
|
|
8
4
|
import BufferController from './controller/buffer.js';
|
|
@@ -19,6 +15,7 @@ export default class JMuxer extends Event {
|
|
|
19
15
|
let defaults = {
|
|
20
16
|
node: '',
|
|
21
17
|
mode: 'both', // both, audio, video
|
|
18
|
+
videoCodec: 'H264', // H264, H265
|
|
22
19
|
flushingTime: 500,
|
|
23
20
|
maxDelay: 500,
|
|
24
21
|
clearBuffer: true,
|
|
@@ -28,20 +25,24 @@ export default class JMuxer extends Event {
|
|
|
28
25
|
onReady: function() {}, // function called when MSE is ready to accept frames
|
|
29
26
|
onData: function() {}, // function called when data is ready to be sent
|
|
30
27
|
onError: function() {}, // function called when jmuxer encounters any buffer related errors
|
|
28
|
+
onUnsupportedCodec: function() {}, // function called when a codec is not supported by the browser
|
|
31
29
|
onMissingVideoFrames: function () {}, // function called when jmuxer encounters any missing video frames
|
|
32
30
|
onMissingAudioFrames: function () {}, // function called when jmuxer encounters any missing audio frames
|
|
31
|
+
onKeyframePosition: function () {}, // function called when a keyframe is detected thus the provided time is seekable
|
|
32
|
+
onLoggerLog: console.log,
|
|
33
|
+
onLoggerErr: console.error,
|
|
33
34
|
};
|
|
34
35
|
this.options = Object.assign({}, defaults, options);
|
|
35
36
|
this.env = typeof process === 'object' && typeof window === 'undefined' ? 'node' : 'browser';
|
|
36
37
|
if (this.options.debug) {
|
|
37
|
-
debug.setLogger();
|
|
38
|
+
debug.setLogger(this.options.onLoggerLog, this.options.onLoggerErr);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
if (!this.options.fps) {
|
|
41
42
|
this.options.fps = 30;
|
|
42
43
|
}
|
|
43
44
|
this.frameDuration = (1000 / this.options.fps) | 0;
|
|
44
|
-
this.remuxController = new RemuxController(this.env);
|
|
45
|
+
this.remuxController = new RemuxController(this.env, options.live, this.options.videoCodec, this.frameDuration);
|
|
45
46
|
this.remuxController.addTrack(this.options.mode);
|
|
46
47
|
|
|
47
48
|
this.initData();
|
|
@@ -52,12 +53,34 @@ export default class JMuxer extends Event {
|
|
|
52
53
|
this.remuxController.on('ready', this.createBuffer.bind(this));
|
|
53
54
|
this.initBrowser();
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
this.remuxController.on('missingVideoFrames', () => {
|
|
58
|
+
if (typeof this.options.onMissingVideoFrames === 'function') {
|
|
59
|
+
this.options.onMissingVideoFrames.call(null);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
this.remuxController.on('missingAudioFrames', () => {
|
|
63
|
+
if (typeof this.options.onMissingAudioFrames === 'function') {
|
|
64
|
+
this.options.onMissingAudioFrames.call(null);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (this.clearBuffer) {
|
|
68
|
+
// this is used to know when keyframes are,
|
|
69
|
+
// to essentially know which specific times are seekable
|
|
70
|
+
this.remuxController.on('keyframePosition', time => {
|
|
71
|
+
this.kfPosition.push(time);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (typeof this.options.onKeyframePosition === 'function') {
|
|
75
|
+
this.remuxController.on('keyframePosition', time => {
|
|
76
|
+
this.options.onKeyframePosition.call(null, time);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
55
79
|
}
|
|
56
80
|
|
|
57
81
|
initData() {
|
|
58
82
|
this.lastCleaningTime = Date.now();
|
|
59
83
|
this.kfPosition = [];
|
|
60
|
-
this.kfCounter = 0;
|
|
61
84
|
this.pendingUnits = {};
|
|
62
85
|
this.remainingData = new Uint8Array();
|
|
63
86
|
this.startInterval();
|
|
@@ -134,143 +157,11 @@ export default class JMuxer extends Event {
|
|
|
134
157
|
}
|
|
135
158
|
|
|
136
159
|
feed(data) {
|
|
137
|
-
let remux = false,
|
|
138
|
-
slices,
|
|
139
|
-
left,
|
|
140
|
-
duration,
|
|
141
|
-
chunks = {
|
|
142
|
-
video: [],
|
|
143
|
-
audio: []
|
|
144
|
-
};
|
|
145
|
-
|
|
146
160
|
if (!data || !this.remuxController) return;
|
|
147
|
-
|
|
161
|
+
|
|
162
|
+
data.duration = data.duration ? parseInt(data.duration) : 0;
|
|
148
163
|
|
|
149
|
-
|
|
150
|
-
data.video = appendByteArray(this.remainingData, data.video);
|
|
151
|
-
[slices, left] = H264Parser.extractNALu(data.video);
|
|
152
|
-
this.remainingData = left || new Uint8Array();
|
|
153
|
-
|
|
154
|
-
if (slices.length > 0) {
|
|
155
|
-
chunks.video = this.getVideoFrames(slices, duration, data.compositionTimeOffset);
|
|
156
|
-
remux = true;
|
|
157
|
-
} else {
|
|
158
|
-
debug.error('Failed to extract any NAL units from video data:', left);
|
|
159
|
-
if (typeof this.options.onMissingVideoFrames === 'function') {
|
|
160
|
-
this.options.onMissingVideoFrames.call(null, data);
|
|
161
|
-
}
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (data.audio) {
|
|
166
|
-
slices = AACParser.extractAAC(data.audio);
|
|
167
|
-
if (slices.length > 0) {
|
|
168
|
-
chunks.audio = this.getAudioFrames(slices, duration);
|
|
169
|
-
remux = true;
|
|
170
|
-
} else {
|
|
171
|
-
debug.error('Failed to extract audio data from:', data.audio);
|
|
172
|
-
if (typeof this.options.onMissingAudioFrames === 'function') {
|
|
173
|
-
this.options.onMissingAudioFrames.call(null, data);
|
|
174
|
-
}
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (!remux) {
|
|
179
|
-
debug.error('Input object must have video and/or audio property. Make sure it is a valid typed array');
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
this.remuxController.remux(chunks);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
getVideoFrames(nalus, duration, compositionTimeOffset) {
|
|
186
|
-
let units = [],
|
|
187
|
-
frames = [],
|
|
188
|
-
fd = 0,
|
|
189
|
-
tt = 0,
|
|
190
|
-
keyFrame = false,
|
|
191
|
-
vcl = false;
|
|
192
|
-
if (this.pendingUnits.units) {
|
|
193
|
-
units = this.pendingUnits.units;
|
|
194
|
-
vcl = this.pendingUnits.vcl;
|
|
195
|
-
keyFrame = this.pendingUnits.keyFrame;
|
|
196
|
-
this.pendingUnits = {};
|
|
197
|
-
}
|
|
198
|
-
for (let nalu of nalus) {
|
|
199
|
-
let unit = new NALU(nalu);
|
|
200
|
-
if (unit.type() === NALU.IDR || unit.type() === NALU.NDR) {
|
|
201
|
-
H264Parser.parseHeader(unit);
|
|
202
|
-
}
|
|
203
|
-
if (units.length && vcl && (unit.isfmb || !unit.isvcl)) {
|
|
204
|
-
frames.push({
|
|
205
|
-
units,
|
|
206
|
-
keyFrame
|
|
207
|
-
});
|
|
208
|
-
units = [];
|
|
209
|
-
keyFrame = false;
|
|
210
|
-
vcl = false;
|
|
211
|
-
}
|
|
212
|
-
units.push(unit);
|
|
213
|
-
keyFrame = keyFrame || unit.isKeyframe();
|
|
214
|
-
vcl = vcl || unit.isvcl;
|
|
215
|
-
}
|
|
216
|
-
if (units.length) {
|
|
217
|
-
// lets keep indecisive nalus as pending in case of fixed fps
|
|
218
|
-
if (!duration) {
|
|
219
|
-
this.pendingUnits = {
|
|
220
|
-
units,
|
|
221
|
-
keyFrame,
|
|
222
|
-
vcl
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
else if (vcl) {
|
|
226
|
-
frames.push({
|
|
227
|
-
units,
|
|
228
|
-
keyFrame
|
|
229
|
-
});
|
|
230
|
-
} else {
|
|
231
|
-
let last = frames.length - 1;
|
|
232
|
-
if (last >= 0) {
|
|
233
|
-
frames[last].units = frames[last].units.concat(units);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
fd = duration ? duration / frames.length | 0 : this.frameDuration;
|
|
238
|
-
tt = duration ? (duration - (fd * frames.length)) : 0;
|
|
239
|
-
|
|
240
|
-
frames.map((frame) => {
|
|
241
|
-
frame.duration = fd;
|
|
242
|
-
frame.compositionTimeOffset = compositionTimeOffset;
|
|
243
|
-
if (tt > 0) {
|
|
244
|
-
frame.duration++;
|
|
245
|
-
tt--;
|
|
246
|
-
}
|
|
247
|
-
this.kfCounter++;
|
|
248
|
-
if (frame.keyFrame && this.options.clearBuffer) {
|
|
249
|
-
this.kfPosition.push((this.kfCounter * fd) / 1000);
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
debug.log(`jmuxer: No. of frames of the last chunk: ${frames.length}`);
|
|
253
|
-
return frames;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
getAudioFrames(aacFrames, duration) {
|
|
257
|
-
let frames = [],
|
|
258
|
-
fd = 0,
|
|
259
|
-
tt = 0;
|
|
260
|
-
|
|
261
|
-
for (let units of aacFrames) {
|
|
262
|
-
frames.push({ units });
|
|
263
|
-
}
|
|
264
|
-
fd = duration ? duration / frames.length | 0 : this.frameDuration;
|
|
265
|
-
tt = duration ? (duration - (fd * frames.length)) : 0;
|
|
266
|
-
frames.map((frame) => {
|
|
267
|
-
frame.duration = fd;
|
|
268
|
-
if (tt > 0) {
|
|
269
|
-
frame.duration++;
|
|
270
|
-
tt--;
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
return frames;
|
|
164
|
+
this.remuxController.feed(data);
|
|
274
165
|
}
|
|
275
166
|
|
|
276
167
|
destroy() {
|
|
@@ -324,7 +215,10 @@ export default class JMuxer extends Event {
|
|
|
324
215
|
for (let type in this.remuxController.tracks) {
|
|
325
216
|
let track = this.remuxController.tracks[type];
|
|
326
217
|
if (!JMuxer.isSupported(`${type}/mp4; codecs="${track.mp4track.codec}"`)) {
|
|
327
|
-
debug.error(
|
|
218
|
+
debug.error(`Browser does not support codec: ${type}/mp4; codecs="${track.mp4track.codec}"`);
|
|
219
|
+
if (typeof this.options.onUnsupportedCodec === 'function') {
|
|
220
|
+
this.options.onUnsupportedCodec.call(null, track.mp4track.codec);
|
|
221
|
+
}
|
|
328
222
|
return false;
|
|
329
223
|
}
|
|
330
224
|
let sb = this.mediaSource.addSourceBuffer(`${type}/mp4; codecs="${track.mp4track.codec}"`);
|
|
@@ -353,7 +247,8 @@ export default class JMuxer extends Event {
|
|
|
353
247
|
if (this.node.buffered && this.node.buffered.length > 0 && !this.node.seeking) {
|
|
354
248
|
const end = this.node.buffered.end(0);
|
|
355
249
|
if (end - this.node.currentTime > (this.options.maxDelay / 1000)) {
|
|
356
|
-
|
|
250
|
+
debug.log('delay');
|
|
251
|
+
if (this.node.paused) this.node.play().catch(debug.error);
|
|
357
252
|
this.node.currentTime = end - 0.001;
|
|
358
253
|
}
|
|
359
254
|
}
|
package/src/parsers/aac.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as debug from '../util/debug';
|
|
2
2
|
|
|
3
3
|
export class AACParser {
|
|
4
|
-
static aacHeader;
|
|
5
4
|
|
|
6
5
|
static get samplingRateMap() {
|
|
7
6
|
return [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
|
@@ -22,56 +21,30 @@ export class AACParser {
|
|
|
22
21
|
static extractAAC(buffer) {
|
|
23
22
|
let i = 0,
|
|
24
23
|
length = buffer.byteLength,
|
|
25
|
-
|
|
24
|
+
slices = [],
|
|
26
25
|
headerLength,
|
|
27
26
|
frameLength;
|
|
28
27
|
|
|
29
28
|
if (!AACParser.isAACPattern(buffer)) {
|
|
30
29
|
debug.error('Invalid ADTS audio format');
|
|
31
|
-
return
|
|
30
|
+
return {
|
|
31
|
+
valid: false,
|
|
32
|
+
};
|
|
32
33
|
}
|
|
33
34
|
headerLength = AACParser.getHeaderLength(buffer);
|
|
34
|
-
|
|
35
|
-
AACParser.aacHeader = buffer.subarray(0, headerLength);
|
|
36
|
-
}
|
|
35
|
+
const header = buffer.subarray(0, headerLength);
|
|
37
36
|
|
|
38
37
|
while (i < length) {
|
|
39
38
|
frameLength = AACParser.getFrameLength(buffer);
|
|
40
|
-
|
|
39
|
+
slices.push(buffer.subarray(headerLength, frameLength));
|
|
41
40
|
buffer = buffer.slice(frameLength);
|
|
42
41
|
i += frameLength;
|
|
43
42
|
}
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.track = remuxer.mp4track;
|
|
43
|
+
return {
|
|
44
|
+
valid: true,
|
|
45
|
+
header,
|
|
46
|
+
slices,
|
|
47
|
+
};
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
setAACConfig() {
|
|
53
|
-
let objectType,
|
|
54
|
-
sampleIndex,
|
|
55
|
-
channelCount,
|
|
56
|
-
config = new Uint8Array(2),
|
|
57
|
-
headerData = AACParser.aacHeader;
|
|
58
|
-
|
|
59
|
-
if (!headerData) return;
|
|
60
|
-
|
|
61
|
-
objectType = ((headerData[2] & 0xC0) >>> 6) + 1;
|
|
62
|
-
sampleIndex = ((headerData[2] & 0x3C) >>> 2);
|
|
63
|
-
channelCount = ((headerData[2] & 0x01) << 2);
|
|
64
|
-
channelCount |= ((headerData[3] & 0xC0) >>> 6);
|
|
65
|
-
|
|
66
|
-
/* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config */
|
|
67
|
-
config[0] = objectType << 3;
|
|
68
|
-
config[0] |= (sampleIndex & 0x0E) >> 1;
|
|
69
|
-
config[1] |= (sampleIndex & 0x01) << 7;
|
|
70
|
-
config[1] |= channelCount << 3;
|
|
71
|
-
|
|
72
|
-
this.track.codec = 'mp4a.40.' + objectType;
|
|
73
|
-
this.track.channelCount = channelCount;
|
|
74
|
-
this.track.config = config;
|
|
75
|
-
this.remuxer.readyToDecode = true;
|
|
76
|
-
}
|
|
77
50
|
}
|