jmuxer 2.0.7 → 2.1.1
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 +29 -36
- package/dist/jmuxer.js +2770 -2020
- package/dist/jmuxer.min.js +1 -1
- package/eslint.config.mjs +21 -0
- package/package.json +20 -28
- package/rollup.config.js +13 -4
- package/src/controller/remux.js +63 -46
- package/src/jmuxer.js +40 -145
- package/src/parsers/aac.js +11 -38
- package/src/parsers/h264.js +106 -99
- package/src/parsers/h265.js +489 -0
- package/src/remuxer/aac.js +63 -7
- package/src/remuxer/base.js +2 -1
- package/src/remuxer/h264.js +176 -16
- package/src/remuxer/h265.js +295 -0
- package/src/util/debug.js +3 -5
- package/src/util/event.js +2 -2
- package/src/util/mp4-generator.js +124 -9
- package/src/util/utils.js +20 -0
- package/.idea/codeStyles/Project.xml +0 -13
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/jmuxer.iml +0 -9
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
- package/karma.conf.js +0 -77
- package/loader-thumb.jpg +0 -0
- package/packet-format.png +0 -0
- package/src/util/nalu.js +0 -72
- package/test/event.js +0 -30
- package/test/parser.js +0 -35
- package/test/utils.js +0 -19
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
|
}
|
|
@@ -429,7 +324,7 @@ export default class JMuxer extends Event {
|
|
|
429
324
|
URL.revokeObjectURL(this.url);
|
|
430
325
|
// this.createBuffer();
|
|
431
326
|
if (typeof this.options.onReady === 'function') {
|
|
432
|
-
this.options.onReady.call(null, this.isReset);
|
|
327
|
+
this.options.onReady.call(null, this.isReset, this.mediaSource);
|
|
433
328
|
}
|
|
434
329
|
}
|
|
435
330
|
|
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
|
}
|
package/src/parsers/h264.js
CHANGED
|
@@ -1,56 +1,42 @@
|
|
|
1
1
|
import { ExpGolomb } from '../util/exp-golomb.js';
|
|
2
|
-
import { NALU } from '../util/nalu.js';
|
|
3
2
|
import * as debug from '../util/debug';
|
|
4
3
|
|
|
4
|
+
// spec https://www.itu.int/rec/T-REC-H.264/
|
|
5
|
+
|
|
5
6
|
export class H264Parser {
|
|
6
7
|
|
|
7
8
|
static extractNALu(buffer) {
|
|
8
9
|
let i = 0,
|
|
9
10
|
length = buffer.byteLength,
|
|
10
|
-
value,
|
|
11
|
-
state = 0,
|
|
12
11
|
result = [],
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
lastIndex = 0,
|
|
13
|
+
zeroCount = 0;
|
|
15
14
|
|
|
16
15
|
while (i < length) {
|
|
17
|
-
value = buffer[i++];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
case 2:
|
|
33
|
-
case 3:
|
|
34
|
-
if (value === 0) {
|
|
35
|
-
state = 3;
|
|
36
|
-
} else if (value === 1 && i < length) {
|
|
37
|
-
if (lastIndex != i - state -1) {
|
|
38
|
-
result.push(buffer.subarray(lastIndex, i - state -1));
|
|
39
|
-
}
|
|
40
|
-
lastIndex = i;
|
|
41
|
-
state = 0;
|
|
42
|
-
} else {
|
|
43
|
-
state = 0;
|
|
44
|
-
}
|
|
45
|
-
break;
|
|
46
|
-
default:
|
|
47
|
-
break;
|
|
16
|
+
let value = buffer[i++];
|
|
17
|
+
|
|
18
|
+
if (value === 0) {
|
|
19
|
+
zeroCount++;
|
|
20
|
+
} else if (value === 1 && zeroCount >= 2) {
|
|
21
|
+
let startCodeLength = zeroCount + 1;
|
|
22
|
+
|
|
23
|
+
if (lastIndex !== i - startCodeLength) {
|
|
24
|
+
result.push(buffer.subarray(lastIndex, i - startCodeLength));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
lastIndex = i;
|
|
28
|
+
zeroCount = 0;
|
|
29
|
+
} else {
|
|
30
|
+
zeroCount = 0;
|
|
48
31
|
}
|
|
49
32
|
}
|
|
50
33
|
|
|
34
|
+
// Remaining data after last start code
|
|
35
|
+
let left = null;
|
|
51
36
|
if (lastIndex < length) {
|
|
52
37
|
left = buffer.subarray(lastIndex, length);
|
|
53
38
|
}
|
|
39
|
+
|
|
54
40
|
return [result, left];
|
|
55
41
|
}
|
|
56
42
|
|
|
@@ -235,9 +221,11 @@ export class H264Parser {
|
|
|
235
221
|
let fixedFrameRate = decoder.readBoolean();
|
|
236
222
|
let frameDuration = timeScale / (2 * unitsInTick);
|
|
237
223
|
|
|
238
|
-
if (fixedFrameRate) {
|
|
239
|
-
|
|
240
|
-
}
|
|
224
|
+
// if (fixedFrameRate) {
|
|
225
|
+
// fps = frameDuration;
|
|
226
|
+
// }
|
|
227
|
+
// Return the fps value even if fixedFrameRate is not set
|
|
228
|
+
fps = frameDuration;
|
|
241
229
|
}
|
|
242
230
|
}
|
|
243
231
|
return {
|
|
@@ -246,76 +234,95 @@ export class H264Parser {
|
|
|
246
234
|
height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - ((frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset)),
|
|
247
235
|
};
|
|
248
236
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export class NALU264 {
|
|
241
|
+
static get NDR() { return 1; }
|
|
242
|
+
static get IDR() { return 5; }
|
|
243
|
+
static get SEI() { return 6; }
|
|
244
|
+
static get SPS() { return 7; }
|
|
245
|
+
static get PPS() { return 8; }
|
|
246
|
+
static get AUD() { return 9; }
|
|
247
|
+
|
|
248
|
+
static get TYPES() {
|
|
249
|
+
return {
|
|
250
|
+
[NALU264.IDR]: 'IDR',
|
|
251
|
+
[NALU264.SEI]: 'SEI',
|
|
252
|
+
[NALU264.SPS]: 'SPS',
|
|
253
|
+
[NALU264.PPS]: 'PPS',
|
|
254
|
+
[NALU264.NDR]: 'NDR',
|
|
255
|
+
[NALU264.AUD]: 'AUD',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
constructor(data) {
|
|
260
|
+
this.payload = data;
|
|
261
|
+
this.nri = (this.payload[0] & 0x60) >> 5; // nal_ref_idc
|
|
262
|
+
this.nalUnitType = this.payload[0] & 0x1f;
|
|
263
|
+
this._sliceType = null;
|
|
264
|
+
this._isFirstSlice = false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
toString() {
|
|
268
|
+
return `${NALU264.TYPES[this.type()] || 'UNKNOWN'}: NRI: ${this.getNri()}`;
|
|
255
269
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
this.
|
|
270
|
+
|
|
271
|
+
getNri() {
|
|
272
|
+
return this.nri;
|
|
259
273
|
}
|
|
260
274
|
|
|
261
|
-
|
|
262
|
-
|
|
275
|
+
type() {
|
|
276
|
+
return this.nalUnitType;
|
|
277
|
+
}
|
|
263
278
|
|
|
264
|
-
|
|
265
|
-
this.
|
|
266
|
-
|
|
267
|
-
this.track.sps = [new Uint8Array(sps)];
|
|
268
|
-
this.track.codec = 'avc1.';
|
|
279
|
+
get isKeyframe() {
|
|
280
|
+
return this.nalUnitType === NALU264.IDR;
|
|
281
|
+
}
|
|
269
282
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
var h = codecarray.getUint8(i).toString(16);
|
|
273
|
-
if (h.length < 2) {
|
|
274
|
-
h = '0' + h;
|
|
275
|
-
}
|
|
276
|
-
this.track.codec += h;
|
|
277
|
-
}
|
|
283
|
+
get isVCL() {
|
|
284
|
+
return this.nalUnitType == NALU264.IDR || this.nalUnitType == NALU264.NDR;
|
|
278
285
|
}
|
|
279
286
|
|
|
280
|
-
|
|
281
|
-
|
|
287
|
+
parseHeader() {
|
|
288
|
+
let decoder = new ExpGolomb(this.getPayload());
|
|
289
|
+
// skip NALu type
|
|
290
|
+
decoder.readUByte();
|
|
291
|
+
this._isFirstSlice = decoder.readUEG() === 0;
|
|
292
|
+
this._sliceType = decoder.readUEG();
|
|
282
293
|
}
|
|
283
294
|
|
|
284
|
-
|
|
285
|
-
if (!
|
|
295
|
+
get isFirstSlice() {
|
|
296
|
+
if (!this._isFirstSlice) {
|
|
297
|
+
this.parseHeader();
|
|
298
|
+
}
|
|
299
|
+
return this._isFirstSlice;
|
|
300
|
+
}
|
|
286
301
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
case NALU.NDR:
|
|
291
|
-
push = true;
|
|
292
|
-
break;
|
|
293
|
-
case NALU.PPS:
|
|
294
|
-
if (!this.track.pps) {
|
|
295
|
-
this.parsePPS(unit.getPayload());
|
|
296
|
-
if (!this.remuxer.readyToDecode && this.track.pps && this.track.sps) {
|
|
297
|
-
this.remuxer.readyToDecode = true;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
push = true;
|
|
301
|
-
break;
|
|
302
|
-
case NALU.SPS:
|
|
303
|
-
if (!this.track.sps) {
|
|
304
|
-
this.parseSPS(unit.getPayload());
|
|
305
|
-
if (!this.remuxer.readyToDecode && this.track.pps && this.track.sps) {
|
|
306
|
-
this.remuxer.readyToDecode = true;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
push = true;
|
|
310
|
-
break;
|
|
311
|
-
case NALU.AUD:
|
|
312
|
-
debug.log('AUD - ignoing');
|
|
313
|
-
break;
|
|
314
|
-
case NALU.SEI:
|
|
315
|
-
debug.log('SEI - ignoing');
|
|
316
|
-
break;
|
|
317
|
-
default:
|
|
302
|
+
get sliceType() {
|
|
303
|
+
if (!this._sliceType) {
|
|
304
|
+
this.parseHeader();
|
|
318
305
|
}
|
|
319
|
-
return
|
|
306
|
+
return this._sliceType;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
getPayload() {
|
|
310
|
+
return this.payload;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
getPayloadSize() {
|
|
314
|
+
return this.payload.byteLength;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
getSize() {
|
|
318
|
+
return 4 + this.getPayloadSize();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
getData() {
|
|
322
|
+
const result = new Uint8Array(this.getSize());
|
|
323
|
+
const view = new DataView(result.buffer);
|
|
324
|
+
view.setUint32(0, this.getSize() - 4);
|
|
325
|
+
result.set(this.getPayload(), 4);
|
|
326
|
+
return result;
|
|
320
327
|
}
|
|
321
328
|
}
|