feat: try implement djs voice + video (v12)
This commit is contained in:
272
src/client/voice/player/MediaPlayer.js
Normal file
272
src/client/voice/player/MediaPlayer.js
Normal file
@@ -0,0 +1,272 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
Credit: https://github.com/dank074/Discord-video-stream
|
||||
The use of video streaming in this library is an incomplete implementation with many bugs, primarily aimed at lazy users.
|
||||
The video streaming features in this library are sourced from https://github.com/dank074/Discord-video-stream.
|
||||
|
||||
Please use the @dank074/discord-video-stream library to access all advanced and professional features,
|
||||
along with comprehensive support. I will not actively fix bugs related to streaming and encourage everyone to
|
||||
use https://github.com/dank074/Discord-video-stream for stable and smooth streaming.
|
||||
|
||||
To reiterate: This is an incomplete implementation of the library https://github.com/dank074/Discord-video-stream.
|
||||
|
||||
Thanks to dank074 and longnguyen2004 for implementing new codecs (H264, H265).
|
||||
Thanks to mrjvs for discovering how Discord transmits data and the VP8 codec.
|
||||
|
||||
Please use the @dank074/discord-video-stream library for the best support.
|
||||
*/
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const { Readable: ReadableStream } = require('stream');
|
||||
const prism = require('prism-media');
|
||||
const { H264NalSplitter } = require('./processing/AnnexBNalSplitter');
|
||||
const { IvfTransformer } = require('./processing/IvfSplitter');
|
||||
const { H264Dispatcher } = require('../dispatcher/AnnexBDispatcher');
|
||||
const AudioDispatcher = require('../dispatcher/AudioDispatcher');
|
||||
const { VP8Dispatcher } = require('../dispatcher/VPxDispatcher');
|
||||
|
||||
const FFMPEG_ARGUMENTS = ['-analyzeduration', '0', '-loglevel', '0', '-f', 's16le', '-ar', '48000', '-ac', '2'];
|
||||
|
||||
/**
|
||||
* Player for a Voice Connection.
|
||||
* @private
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
class MediaPlayer extends EventEmitter {
|
||||
constructor(voiceConnection) {
|
||||
super();
|
||||
|
||||
this.dispatcher = null;
|
||||
|
||||
this.videoDispatcher = null;
|
||||
/**
|
||||
* The voice connection that the player serves
|
||||
* @type {VoiceConnection}
|
||||
*/
|
||||
this.voiceConnection = voiceConnection;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.destroyDispatcher();
|
||||
this.destroyVideoDispatcher();
|
||||
}
|
||||
|
||||
destroyDispatcher() {
|
||||
if (this.dispatcher) {
|
||||
this.dispatcher.destroy();
|
||||
this.dispatcher = null;
|
||||
}
|
||||
}
|
||||
|
||||
destroyVideoDispatcher() {
|
||||
if (this.videoDispatcher) {
|
||||
this.videoDispatcher.destroy();
|
||||
this.videoDispatcher = null;
|
||||
}
|
||||
}
|
||||
|
||||
playUnknown(input, options, streams = {}) {
|
||||
this.destroyDispatcher();
|
||||
|
||||
const isStream = input instanceof ReadableStream;
|
||||
|
||||
const args = isStream ? FFMPEG_ARGUMENTS.slice() : ['-i', input, ...FFMPEG_ARGUMENTS];
|
||||
if (options.seek) args.unshift('-ss', String(options.seek));
|
||||
|
||||
const ffmpeg = new prism.FFmpeg({ args });
|
||||
streams.ffmpeg = ffmpeg;
|
||||
if (isStream) {
|
||||
streams.input = input;
|
||||
input.pipe(ffmpeg);
|
||||
}
|
||||
return this.playPCMStream(ffmpeg, options, streams);
|
||||
}
|
||||
|
||||
playPCMStream(stream, options, streams = {}) {
|
||||
this.destroyDispatcher();
|
||||
const opus = (streams.opus = new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 }));
|
||||
if (options && options.volume === false) {
|
||||
stream.pipe(opus);
|
||||
return this.playOpusStream(opus, options, streams);
|
||||
}
|
||||
streams.volume = new prism.VolumeTransformer({ type: 's16le', volume: options ? options.volume : 1 });
|
||||
stream.pipe(streams.volume).pipe(opus);
|
||||
return this.playOpusStream(opus, options, streams);
|
||||
}
|
||||
|
||||
playOpusStream(stream, options, streams = {}) {
|
||||
this.destroyDispatcher();
|
||||
streams.opus = stream;
|
||||
if (options.volume !== false && !streams.input) {
|
||||
streams.input = stream;
|
||||
const decoder = new prism.opus.Decoder({ channels: 2, rate: 48000, frameSize: 960 });
|
||||
streams.volume = new prism.VolumeTransformer({ type: 's16le', volume: options ? options.volume : 1 });
|
||||
streams.opus = stream
|
||||
.pipe(decoder)
|
||||
.pipe(streams.volume)
|
||||
.pipe(new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 }));
|
||||
}
|
||||
const dispatcher = this.createDispatcher(options, streams);
|
||||
streams.opus.pipe(dispatcher);
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
playUnknownVideo(input, options = {}) {
|
||||
this.destroyVideoDispatcher();
|
||||
|
||||
const isStream = input instanceof ReadableStream;
|
||||
|
||||
if (!options?.fps) options.fps = 30;
|
||||
|
||||
const args = [
|
||||
'-i',
|
||||
'-',
|
||||
'-analyzeduration',
|
||||
'0',
|
||||
'-flags',
|
||||
'low_delay',
|
||||
'-quality',
|
||||
'realtime',
|
||||
'-r',
|
||||
`${options?.fps}`,
|
||||
];
|
||||
|
||||
if (!isStream) {
|
||||
args[1] = input;
|
||||
}
|
||||
|
||||
if (options?.hwAccel === true) {
|
||||
args.unshift('-hwaccel', 'auto');
|
||||
}
|
||||
|
||||
if (options.seek) args.unshift('-ss', String(options.seek));
|
||||
|
||||
// Get stream type
|
||||
if (this.voiceConnection.videoCodec == 'VP8') {
|
||||
args.push('-f', 'ivf', '-deadline', 'realtime', '-c:v', options?.copy ? 'copy' : 'libvpx', '-speed', '5');
|
||||
}
|
||||
|
||||
if (this.voiceConnection.videoCodec == 'H264') {
|
||||
args.push(
|
||||
'-c:v',
|
||||
options?.copy ? 'copy' : 'libx264',
|
||||
'-f',
|
||||
'h264',
|
||||
'-tune',
|
||||
'zerolatency',
|
||||
'-pix_fmt',
|
||||
'yuv420p',
|
||||
'-preset',
|
||||
options?.preset || 'faster',
|
||||
'-profile:v',
|
||||
'baseline',
|
||||
'-g',
|
||||
`${options?.fps}`,
|
||||
'-x264-params',
|
||||
`keyint=${options?.fps}:min-keyint=${options?.fps}`,
|
||||
'-bf',
|
||||
'0',
|
||||
'-bsf:v',
|
||||
'h264_metadata=aud=insert',
|
||||
);
|
||||
}
|
||||
|
||||
if (options?.inputFFmpegArgs) {
|
||||
args.unshift(...options.inputFFmpegArgs);
|
||||
}
|
||||
|
||||
if (options?.outputFFmpegArgs) {
|
||||
args.push(...options.outputFFmpegArgs);
|
||||
}
|
||||
|
||||
const ffmpeg = new prism.FFmpeg({ args });
|
||||
const streams = { ffmpeg };
|
||||
|
||||
if (isStream) {
|
||||
streams.input = input;
|
||||
input.pipe(ffmpeg);
|
||||
}
|
||||
|
||||
this.emit('debug', `[ffmpeg] Spawn process with args:\n${args.join(' ')}`);
|
||||
|
||||
ffmpeg.process.stderr.on('data', data => {
|
||||
this.emit('debug', `[ffmpeg]: ${data.toString()}`);
|
||||
});
|
||||
|
||||
switch (this.voiceConnection.videoCodec) {
|
||||
case 'VP8': {
|
||||
return this.playIvfVideo(ffmpeg, options, streams);
|
||||
}
|
||||
case 'H264': {
|
||||
return this.playAnnexBVideo(ffmpeg, options, streams, 'H264');
|
||||
}
|
||||
default: {
|
||||
throw new Error('Invalid codec');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playIvfVideo(stream, options, streams) {
|
||||
this.destroyVideoDispatcher();
|
||||
const videoStream = new IvfTransformer();
|
||||
stream.pipe(videoStream);
|
||||
streams.video = videoStream;
|
||||
const dispatcher = this.createVideoDispatcher(options, streams);
|
||||
videoStream.pipe(dispatcher);
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
playAnnexBVideo(stream, options, streams, type) {
|
||||
this.destroyVideoDispatcher();
|
||||
const videoStream = new H264NalSplitter();
|
||||
stream.pipe(videoStream);
|
||||
streams.video = videoStream;
|
||||
const dispatcher = this.createVideoDispatcher(options, streams);
|
||||
videoStream.pipe(dispatcher);
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
createDispatcher(options, streams) {
|
||||
this.destroyDispatcher();
|
||||
const dispatcher = (this.dispatcher = new AudioDispatcher(this, options, streams));
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create
|
||||
* @private
|
||||
* @param {Object} options any
|
||||
* @param {Object} streams any
|
||||
* @returns {VideoDispatcher}
|
||||
*/
|
||||
createVideoDispatcher(options, streams) {
|
||||
this.destroyVideoDispatcher();
|
||||
switch (this.voiceConnection.videoCodec) {
|
||||
case 'VP8': {
|
||||
const dispatcher = (this.videoDispatcher = new VP8Dispatcher(
|
||||
this,
|
||||
options?.highWaterMark,
|
||||
streams,
|
||||
options?.fps,
|
||||
));
|
||||
return dispatcher;
|
||||
}
|
||||
case 'H264': {
|
||||
const dispatcher = (this.videoDispatcher = new H264Dispatcher(
|
||||
this,
|
||||
options?.highWaterMark,
|
||||
streams,
|
||||
options?.fps,
|
||||
));
|
||||
return dispatcher;
|
||||
}
|
||||
default: {
|
||||
throw new Error('Invalid codec');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MediaPlayer;
|
||||
244
src/client/voice/player/processing/AnnexBNalSplitter.js
Normal file
244
src/client/voice/player/processing/AnnexBNalSplitter.js
Normal file
@@ -0,0 +1,244 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
Credit: https://github.com/dank074/Discord-video-stream
|
||||
The use of video streaming in this library is an incomplete implementation with many bugs, primarily aimed at lazy users.
|
||||
The video streaming features in this library are sourced from https://github.com/dank074/Discord-video-stream.
|
||||
|
||||
Please use the @dank074/discord-video-stream library to access all advanced and professional features,
|
||||
along with comprehensive support. I will not actively fix bugs related to streaming and encourage everyone to
|
||||
use https://github.com/dank074/Discord-video-stream for stable and smooth streaming.
|
||||
|
||||
To reiterate: This is an incomplete implementation of the library https://github.com/dank074/Discord-video-stream.
|
||||
|
||||
Thanks to dank074 and longnguyen2004 for implementing new codecs (H264, H265).
|
||||
Thanks to mrjvs for discovering how Discord transmits data and the VP8 codec.
|
||||
|
||||
Please use the @dank074/discord-video-stream library for the best support.
|
||||
*/
|
||||
|
||||
const { Buffer } = require('buffer');
|
||||
const { Transform } = require('stream');
|
||||
|
||||
const H264NalUnitTypes = {
|
||||
Unspecified: 0,
|
||||
CodedSliceNonIDR: 1,
|
||||
CodedSlicePartitionA: 2,
|
||||
CodedSlicePartitionB: 3,
|
||||
CodedSlicePartitionC: 4,
|
||||
CodedSliceIdr: 5,
|
||||
SEI: 6,
|
||||
SPS: 7,
|
||||
PPS: 8,
|
||||
AccessUnitDelimiter: 9,
|
||||
EndOfSequence: 10,
|
||||
EndOfStream: 11,
|
||||
FillerData: 12,
|
||||
SEIExtenstion: 13,
|
||||
PrefixNalUnit: 14,
|
||||
SubsetSPS: 15,
|
||||
};
|
||||
|
||||
const H265NalUnitTypes = {
|
||||
TRAIL_N: 0,
|
||||
TRAIL_R: 1,
|
||||
TSA_N: 2,
|
||||
TSA_R: 3,
|
||||
STSA_N: 4,
|
||||
STSA_R: 5,
|
||||
RADL_N: 6,
|
||||
RADL_R: 7,
|
||||
RASL_N: 8,
|
||||
RASL_R: 9,
|
||||
RSV_VCL_N10: 10,
|
||||
RSV_VCL_R11: 11,
|
||||
RSV_VCL_N12: 12,
|
||||
RSV_VCL_R13: 13,
|
||||
RSV_VCL_N14: 14,
|
||||
RSV_VCL_R15: 15,
|
||||
BLA_W_LP: 16,
|
||||
BLA_W_RADL: 17,
|
||||
BLA_N_LP: 18,
|
||||
IDR_W_RADL: 19,
|
||||
IDR_N_LP: 20,
|
||||
CRA_NUT: 21,
|
||||
RSV_IRAP_VCL22: 22,
|
||||
RSV_IRAP_VCL23: 23,
|
||||
RSV_VCL24: 24,
|
||||
RSV_VCL25: 25,
|
||||
RSV_VCL26: 26,
|
||||
RSV_VCL27: 27,
|
||||
RSV_VCL28: 28,
|
||||
RSV_VCL29: 29,
|
||||
RSV_VCL30: 30,
|
||||
RSV_VCL31: 31,
|
||||
VPS_NUT: 32,
|
||||
SPS_NUT: 33,
|
||||
PPS_NUT: 34,
|
||||
AUD_NUT: 35,
|
||||
EOS_NUT: 36,
|
||||
EOB_NUT: 37,
|
||||
FD_NUT: 38,
|
||||
PREFIX_SEI_NUT: 39,
|
||||
SUFFIX_SEI_NUT: 40,
|
||||
RSV_NVCL41: 41,
|
||||
RSV_NVCL42: 42,
|
||||
RSV_NVCL43: 43,
|
||||
RSV_NVCL44: 44,
|
||||
RSV_NVCL45: 45,
|
||||
RSV_NVCL46: 46,
|
||||
RSV_NVCL47: 47,
|
||||
UNSPEC48: 48,
|
||||
UNSPEC49: 49,
|
||||
UNSPEC50: 50,
|
||||
UNSPEC51: 51,
|
||||
UNSPEC52: 52,
|
||||
UNSPEC53: 53,
|
||||
UNSPEC54: 54,
|
||||
UNSPEC55: 55,
|
||||
UNSPEC56: 56,
|
||||
UNSPEC57: 57,
|
||||
UNSPEC58: 58,
|
||||
UNSPEC59: 59,
|
||||
UNSPEC60: 60,
|
||||
UNSPEC61: 61,
|
||||
UNSPEC62: 62,
|
||||
UNSPEC63: 63,
|
||||
};
|
||||
|
||||
const H264Helpers = {
|
||||
getUnitType(frame) {
|
||||
return frame[0] & 0x1f;
|
||||
},
|
||||
splitHeader(frame) {
|
||||
return [frame.subarray(0, 1), frame.subarray(1)];
|
||||
},
|
||||
isAUD(unitType) {
|
||||
return unitType === H264NalUnitTypes.AccessUnitDelimiter;
|
||||
},
|
||||
};
|
||||
|
||||
const H265Helpers = {
|
||||
getUnitType(frame) {
|
||||
return (frame[0] >> 1) & 0x3f;
|
||||
},
|
||||
splitHeader(frame) {
|
||||
return [frame.subarray(0, 2), frame.subarray(2)];
|
||||
},
|
||||
isAUD(unitType) {
|
||||
return unitType === H265NalUnitTypes.AUD_NUT;
|
||||
},
|
||||
};
|
||||
|
||||
const emptyBuffer = Buffer.allocUnsafe(0);
|
||||
const epbPrefix = Buffer.from([0x00, 0x00, 0x03]);
|
||||
const nalSuffix = Buffer.from([0x00, 0x00, 0x01]);
|
||||
|
||||
class AnnexBNalSplitter extends Transform {
|
||||
constructor(nalFunctions) {
|
||||
super();
|
||||
this._buffer = null;
|
||||
this._accessUnit = [];
|
||||
this._nalFunctions = nalFunctions;
|
||||
}
|
||||
|
||||
rbsp(data) {
|
||||
const newData = Buffer.allocUnsafe(data.length);
|
||||
let newLength = 0;
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const epbsPos = data.indexOf(epbPrefix);
|
||||
if (epbsPos === -1) {
|
||||
data.copy(newData, newLength);
|
||||
newLength += data.length;
|
||||
break;
|
||||
}
|
||||
const copyRange = data[epbsPos + 3] <= 0x03 ? epbsPos + 2 : epbsPos + 3;
|
||||
data.copy(newData, newLength, 0, copyRange);
|
||||
newLength += copyRange;
|
||||
data = data.subarray(epbsPos + 3);
|
||||
}
|
||||
|
||||
return newData.subarray(0, newLength);
|
||||
}
|
||||
|
||||
findNalStart(buf) {
|
||||
const pos = buf.indexOf(nalSuffix);
|
||||
if (pos === -1) return null;
|
||||
return pos > 0 && buf[pos - 1] === 0 ? { index: pos - 1, length: 4 } : { index: pos, length: 3 };
|
||||
}
|
||||
|
||||
processFrame(frame) {
|
||||
if (frame.length === 0) return;
|
||||
|
||||
const unitType = this._nalFunctions.getUnitType(frame);
|
||||
if (this._nalFunctions.isAUD(unitType) && this._accessUnit.length > 0) {
|
||||
const sizeOfAccessUnit = this._accessUnit.reduce((acc, nalu) => acc + nalu.length + 4, 0);
|
||||
const accessUnitBuf = Buffer.allocUnsafe(sizeOfAccessUnit);
|
||||
|
||||
let offset = 0;
|
||||
this._accessUnit.forEach(nalu => {
|
||||
accessUnitBuf.writeUint32BE(nalu.length, offset);
|
||||
offset += 4;
|
||||
nalu.copy(accessUnitBuf, offset);
|
||||
offset += nalu.length;
|
||||
});
|
||||
|
||||
this.push(accessUnitBuf);
|
||||
this._accessUnit = [];
|
||||
} else {
|
||||
this._accessUnit.push(this.removeEpbs(frame, unitType));
|
||||
}
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, callback) {
|
||||
let nalStart = this.findNalStart(chunk);
|
||||
if (!this._buffer) {
|
||||
if (!nalStart) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
chunk = chunk.subarray(nalStart.index + nalStart.length);
|
||||
this._buffer = emptyBuffer;
|
||||
}
|
||||
|
||||
chunk = Buffer.concat([this._buffer, chunk]);
|
||||
while ((nalStart = this.findNalStart(chunk))) {
|
||||
const frame = chunk.subarray(0, nalStart.index);
|
||||
this.processFrame(frame);
|
||||
chunk = chunk.subarray(nalStart.index + nalStart.length);
|
||||
}
|
||||
this._buffer = chunk;
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
class H264NalSplitter extends AnnexBNalSplitter {
|
||||
constructor() {
|
||||
super(H264Helpers);
|
||||
}
|
||||
|
||||
removeEpbs(frame, unitType) {
|
||||
return unitType === H264NalUnitTypes.SPS || unitType === H264NalUnitTypes.SEI ? this.rbsp(frame) : frame;
|
||||
}
|
||||
}
|
||||
|
||||
class H265NalSplitter extends AnnexBNalSplitter {
|
||||
constructor() {
|
||||
super(H265Helpers);
|
||||
}
|
||||
|
||||
removeEpbs(frame) {
|
||||
return frame; // No specific processing for H265
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
H264NalUnitTypes,
|
||||
H265NalUnitTypes,
|
||||
H264Helpers,
|
||||
H265Helpers,
|
||||
H264NalSplitter,
|
||||
H265NalSplitter,
|
||||
};
|
||||
106
src/client/voice/player/processing/IvfSplitter.js
Normal file
106
src/client/voice/player/processing/IvfSplitter.js
Normal file
@@ -0,0 +1,106 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
Credit: https://github.com/dank074/Discord-video-stream
|
||||
The use of video streaming in this library is an incomplete implementation with many bugs, primarily aimed at lazy users.
|
||||
The video streaming features in this library are sourced from https://github.com/dank074/Discord-video-stream.
|
||||
|
||||
Please use the @dank074/discord-video-stream library to access all advanced and professional features,
|
||||
along with comprehensive support. I will not actively fix bugs related to streaming and encourage everyone to
|
||||
use https://github.com/dank074/Discord-video-stream for stable and smooth streaming.
|
||||
|
||||
To reiterate: This is an incomplete implementation of the library https://github.com/dank074/Discord-video-stream.
|
||||
|
||||
Thanks to dank074 and longnguyen2004 for implementing new codecs (H264, H265).
|
||||
Thanks to mrjvs for discovering how Discord transmits data and the VP8 codec.
|
||||
|
||||
Please use the @dank074/discord-video-stream library for the best support.
|
||||
*/
|
||||
|
||||
const { Buffer } = require('buffer');
|
||||
const { Transform } = require('stream');
|
||||
|
||||
class IvfTransformer extends Transform {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.headerSize = 32;
|
||||
this.frameHeaderSize = 12;
|
||||
|
||||
this.header = null;
|
||||
this.buf = null;
|
||||
this.retFullFrame = options && options.fullframe ? options.fullframe : false;
|
||||
}
|
||||
|
||||
_parseHeader(header) {
|
||||
this.header = {
|
||||
signature: header.subarray(0, 4).toString(),
|
||||
version: header.readUIntLE(4, 2),
|
||||
headerLength: header.readUIntLE(6, 2),
|
||||
codec: header.subarray(8, 12).toString(),
|
||||
width: header.readUIntLE(12, 2),
|
||||
height: header.readUIntLE(14, 2),
|
||||
timeDenominator: header.readUIntLE(16, 4),
|
||||
timeNumerator: header.readUIntLE(20, 4),
|
||||
frameCount: header.readUIntLE(24, 4),
|
||||
};
|
||||
}
|
||||
|
||||
_getFrameSize(buf) {
|
||||
return buf.readUIntLE(0, 4);
|
||||
}
|
||||
|
||||
_parseFrame(frame) {
|
||||
const size = this._getFrameSize(frame);
|
||||
|
||||
if (this.retFullFrame) return this.push(frame.subarray(0, 12 + size));
|
||||
|
||||
const out = {
|
||||
size: size,
|
||||
timestamp: frame.readBigUInt64LE(4),
|
||||
data: frame.subarray(12, 12 + size),
|
||||
};
|
||||
|
||||
return this.push(out.data);
|
||||
}
|
||||
|
||||
_appendChunkToBuf(chunk) {
|
||||
if (this.buf) this.buf = Buffer.concat([this.buf, chunk]);
|
||||
else this.buf = chunk;
|
||||
}
|
||||
|
||||
_updateBufLen(size) {
|
||||
if (this.buf.length > size) this.buf = this.buf.subarray(size, this.buf.length);
|
||||
else this.buf = null;
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, callback) {
|
||||
this._appendChunkToBuf(chunk);
|
||||
|
||||
if (!this.header) {
|
||||
if (this.buf.length >= this.headerSize) {
|
||||
this._parseHeader(this.buf.subarray(0, this.headerSize));
|
||||
this._updateBufLen(this.headerSize);
|
||||
} else {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (this.buf && this.buf.length >= this.frameHeaderSize) {
|
||||
const size = this._getFrameSize(this.buf) + this.frameHeaderSize;
|
||||
|
||||
if (this.buf.length >= size) {
|
||||
this._parseFrame(this.buf.subarray(0, size));
|
||||
this._updateBufLen(size);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
IvfTransformer,
|
||||
};
|
||||
Reference in New Issue
Block a user