chore: clean code + add options
This commit is contained in:
@@ -46,7 +46,9 @@ client.on('ready', async client => {
|
|||||||
const input = 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
|
const input = 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
|
||||||
// Play
|
// Play
|
||||||
const dispatcher = stream.playVideo(input, {
|
const dispatcher = stream.playVideo(input, {
|
||||||
fps: 30,
|
fps: 60,
|
||||||
|
bitrate: 4000,
|
||||||
|
resolution: '1080',
|
||||||
});
|
});
|
||||||
const dispatcher2 = stream.playAudio(input);
|
const dispatcher2 = stream.playAudio(input);
|
||||||
dispatcher.on('start', () => {
|
dispatcher.on('start', () => {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class AnnexBDispatcher extends VideoDispatcher {
|
|||||||
|
|
||||||
codecCallback(frame) {
|
codecCallback(frame) {
|
||||||
let accessUnit = frame;
|
let accessUnit = frame;
|
||||||
const nalus = [];
|
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
|
||||||
// Extract NALUs from the access unit
|
// Extract NALUs from the access unit
|
||||||
@@ -37,25 +36,19 @@ class AnnexBDispatcher extends VideoDispatcher {
|
|||||||
const naluSize = accessUnit.readUInt32BE(offset);
|
const naluSize = accessUnit.readUInt32BE(offset);
|
||||||
offset += 4;
|
offset += 4;
|
||||||
const nalu = accessUnit.subarray(offset, offset + naluSize);
|
const nalu = accessUnit.subarray(offset, offset + naluSize);
|
||||||
nalus.push(nalu);
|
const isLastNal = offset + naluSize >= accessUnit.length;
|
||||||
offset += naluSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
nalus.forEach((nalu, index) => {
|
|
||||||
const isLastNal = index === nalus.length - 1;
|
|
||||||
|
|
||||||
if (nalu.length <= this.mtu) {
|
if (nalu.length <= this.mtu) {
|
||||||
// If NALU size is within MTU, send it directly
|
// If NALU size is within MTU, send it directly
|
||||||
this._playChunk(Buffer.concat([this.createHeaderExtension(), nalu]), index + 1 === nalus.length);
|
this._playChunk(Buffer.concat([this.createHeaderExtension(), nalu]), isLastNal);
|
||||||
} else {
|
} else {
|
||||||
// If NALU size exceeds MTU, fragment it
|
// If NALU size exceeds MTU, fragment it
|
||||||
const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu);
|
const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu);
|
||||||
const dataFragments = this.partitionVideoData(naluData);
|
const dataFragments = this.partitionVideoData(naluData);
|
||||||
|
|
||||||
dataFragments.forEach((data, fragmentIndex) => {
|
for (let fragmentIndex = 0; fragmentIndex < dataFragments.length; fragmentIndex++) {
|
||||||
|
const data = dataFragments[fragmentIndex];
|
||||||
const isFirstPacket = fragmentIndex === 0;
|
const isFirstPacket = fragmentIndex === 0;
|
||||||
const isFinalPacket = fragmentIndex === dataFragments.length - 1;
|
const isFinalPacket = fragmentIndex === dataFragments.length - 1;
|
||||||
const markerBit = isLastNal && isFinalPacket; // Is last packet ?
|
|
||||||
|
|
||||||
this._playChunk(
|
this._playChunk(
|
||||||
Buffer.concat([
|
Buffer.concat([
|
||||||
@@ -63,11 +56,12 @@ class AnnexBDispatcher extends VideoDispatcher {
|
|||||||
this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader),
|
this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader),
|
||||||
data,
|
data,
|
||||||
]),
|
]),
|
||||||
markerBit,
|
isLastNal && isFinalPacket,
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
});
|
offset += naluSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -316,12 +316,12 @@ class BaseDispatcher extends Writable {
|
|||||||
_createPacket(buffer, isLastPacket = false) {
|
_createPacket(buffer, isLastPacket = false) {
|
||||||
// Header
|
// Header
|
||||||
const packetBuffer = Buffer.alloc(12);
|
const packetBuffer = Buffer.alloc(12);
|
||||||
packetBuffer[0] = (2 << 6) | ((this.extensionEnabled ? 1 : 0) << 4);
|
packetBuffer[0] = this.extensionEnabled ? 0x90 : 0x80; // 0b10000000 | ((this.extensionEnabled ? 1 : 0) << 4);
|
||||||
packetBuffer[1] = this.payloadType;
|
packetBuffer[1] = this.payloadType;
|
||||||
|
|
||||||
if (this.extensionEnabled) {
|
if (this.extensionEnabled) {
|
||||||
if (isLastPacket) {
|
if (isLastPacket) {
|
||||||
packetBuffer[1] |= 0b10000000;
|
packetBuffer[1] |= 0x80;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,27 +25,24 @@ class VP8Dispatcher extends VideoDispatcher {
|
|||||||
super(player, highWaterMark, streams, fps);
|
super(player, highWaterMark, streams, fps);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeChunk(buffer, i) {
|
makeChunk(buffer, isFirstFrame) {
|
||||||
// Make frame
|
// Make frame
|
||||||
const headerExtensionBuf = this.createHeaderExtension();
|
const headerExtensionBuf = this.createHeaderExtension();
|
||||||
// Vp8 payload descriptor
|
// Vp8 payload descriptor
|
||||||
const payloadDescriptorBuf = Buffer.alloc(2);
|
const payloadDescriptorBuf = Buffer.alloc(2);
|
||||||
payloadDescriptorBuf[0] = 0x80;
|
payloadDescriptorBuf[0] = isFirstFrame ? 0x90 : 0x80; // Mark S bit, indicates start of frame: payloadDescriptorBuf[0] |= 0b00010000;
|
||||||
payloadDescriptorBuf[1] = 0x80;
|
payloadDescriptorBuf[1] = 0x80;
|
||||||
if (i == 0) {
|
|
||||||
payloadDescriptorBuf[0] |= 0b00010000; // Mark S bit, indicates start of frame
|
|
||||||
}
|
|
||||||
// Vp8 pictureid payload extension
|
// Vp8 pictureid payload extension
|
||||||
const pictureIdBuf = Buffer.alloc(2);
|
const pictureIdBuf = Buffer.alloc(2);
|
||||||
pictureIdBuf.writeUIntBE(this.count, 0, 2);
|
pictureIdBuf.writeUintBE(this.count, 0, 2);
|
||||||
pictureIdBuf[0] |= 0b10000000;
|
pictureIdBuf[0] |= 0x80;
|
||||||
return Buffer.concat([headerExtensionBuf, payloadDescriptorBuf, pictureIdBuf, buffer]);
|
return Buffer.concat([headerExtensionBuf, payloadDescriptorBuf, pictureIdBuf, buffer]);
|
||||||
}
|
}
|
||||||
|
|
||||||
codecCallback(chunk) {
|
codecCallback(chunk) {
|
||||||
const chunkSplit = this.partitionVideoData(chunk);
|
const chunkSplit = this.partitionVideoData(chunk);
|
||||||
for (let i = 0; i < chunkSplit.length; i++) {
|
for (let i = 0; i < chunkSplit.length; i++) {
|
||||||
this._playChunk(this.makeChunk(chunkSplit[i], i), i + 1 === chunkSplit.length);
|
this._playChunk(this.makeChunk(chunkSplit[i], i == 0), i + 1 === chunkSplit.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,14 @@ class MediaPlayer extends EventEmitter {
|
|||||||
`${options?.fps}`,
|
`${options?.fps}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (options?.resolution && options?.resolution !== 'maximum') {
|
||||||
|
args.push('-vf', `scale=-1:${options.resolution}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.bitrate && typeof options?.bitrate === 'number') {
|
||||||
|
args.push('-b:v', `${options?.bitrate}K`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isStream) {
|
if (!isStream) {
|
||||||
args[1] = input;
|
args[1] = input;
|
||||||
}
|
}
|
||||||
@@ -144,28 +152,28 @@ class MediaPlayer extends EventEmitter {
|
|||||||
|
|
||||||
// Get stream type
|
// Get stream type
|
||||||
if (this.voiceConnection.videoCodec == 'VP8') {
|
if (this.voiceConnection.videoCodec == 'VP8') {
|
||||||
args.push('-f', 'ivf', '-deadline', 'realtime', '-c:v', options?.copy ? 'copy' : 'libvpx');
|
args.push('-f', 'ivf', '-deadline', 'realtime', '-c:v', 'libvpx');
|
||||||
// Remove '-speed', '5' bc bad quality
|
// Remove '-speed', '5' bc bad quality
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.voiceConnection.videoCodec == 'H264') {
|
if (this.voiceConnection.videoCodec == 'H264') {
|
||||||
args.push(
|
args.push(
|
||||||
'-c:v',
|
'-c:v',
|
||||||
options?.copy ? 'copy' : 'libx264',
|
'libx264',
|
||||||
'-f',
|
'-f',
|
||||||
'h264',
|
'h264',
|
||||||
'-tune',
|
'-tune',
|
||||||
'zerolatency',
|
'zerolatency',
|
||||||
'-pix_fmt',
|
// '-pix_fmt',
|
||||||
'yuv420p',
|
// 'yuv420p',
|
||||||
'-preset',
|
'-preset',
|
||||||
options?.preset || 'faster',
|
options?.presetH26x || 'faster',
|
||||||
'-profile:v',
|
'-profile:v',
|
||||||
'baseline',
|
'baseline',
|
||||||
'-g',
|
// '-g',
|
||||||
`${options?.fps}`,
|
// `${options?.fps}`,
|
||||||
'-x264-params',
|
// '-x264-params',
|
||||||
`keyint=${options?.fps}:min-keyint=${options?.fps}`,
|
// `keyint=${options?.fps}:min-keyint=${options?.fps}`,
|
||||||
'-bf',
|
'-bf',
|
||||||
'0',
|
'0',
|
||||||
'-bsf:v',
|
'-bsf:v',
|
||||||
|
|||||||
@@ -79,17 +79,19 @@ class PlayInterface {
|
|||||||
* @typedef {Object} VideoOptions
|
* @typedef {Object} VideoOptions
|
||||||
* @property {number} [seek=0] The time to seek to, will be ignored when playing `ogg/opus` or `webm/opus` streams
|
* @property {number} [seek=0] The time to seek to, will be ignored when playing `ogg/opus` or `webm/opus` streams
|
||||||
* @property {number} [fps=30] Video fps
|
* @property {number} [fps=30] Video fps
|
||||||
* @property {boolean} [copy=false] Copy codec ?
|
* @property {'maximum' | '480' | '720' | '1080' | '1440'} [resolution='maximum'] Resoluion (Height)
|
||||||
* @property {number} [highWaterMark=12] The maximum number of opus packets to make and store before they are
|
* @property {number} [highWaterMark=12] The maximum number of opus packets to make and store before they are
|
||||||
* actually needed. See https://nodejs.org/en/docs/guides/backpressuring-in-streams/. Setting this value to
|
* actually needed. See https://nodejs.org/en/docs/guides/backpressuring-in-streams/. Setting this value to
|
||||||
* 1 means that changes in volume will be more instant.
|
* 1 means that changes in volume will be more instant.
|
||||||
* @property {'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast' | 'medium' | 'slow' | 'slower' | 'veryslow'} [preset='veryfast'] ffmpeg preset
|
* @property {'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast' | 'medium' | 'slow' | 'slower' | 'veryslow'} [presetH26x='veryfast'] ffmpeg preset h254 / h265
|
||||||
* @property {boolean} [hwAccel=false] Enables hardware accelerated video decoding. Enabling this option might result in an exception
|
* @property {boolean} [hwAccel=false] Enables hardware accelerated video decoding. Enabling this option might result in an exception
|
||||||
* being thrown by Ffmpeg process if your system does not support hardware acceleration
|
* being thrown by Ffmpeg process if your system does not support hardware acceleration
|
||||||
* @property {string[]} [inputFFmpegArgs] input ffmpeg
|
* @property {string[]} [inputFFmpegArgs] input ffmpeg
|
||||||
* Ex: ['-config1', 'value1', '-config2', 'value2']
|
* Ex: ['-config1', 'value1', '-config2', 'value2']
|
||||||
* @property {string[]} [outputFFmpegArgs] output ffmpeg
|
* @property {string[]} [outputFFmpegArgs] output ffmpeg
|
||||||
* Ex: ['-config1', 'value1', '-config2', 'value2']
|
* Ex: ['-config1', 'value1', '-config2', 'value2']
|
||||||
|
* @property {number|'auto'} [bitrate=2000] The bitrate (quality) of the video in kbps.
|
||||||
|
* If set to 'auto', ffmpeg will automatically select
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
5
typings/index.d.ts
vendored
5
typings/index.d.ts
vendored
@@ -932,10 +932,11 @@ export interface VideoOptions {
|
|||||||
highWaterMark?: number;
|
highWaterMark?: number;
|
||||||
fps?: number;
|
fps?: number;
|
||||||
hwAccel?: boolean;
|
hwAccel?: boolean;
|
||||||
preset?: 'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast' | 'medium' | 'slow' | 'slower' | 'veryslow';
|
presetH26x?: 'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast' | 'medium' | 'slow' | 'slower' | 'veryslow';
|
||||||
copy?: boolean;
|
|
||||||
inputFFmpegArgs?: string[];
|
inputFFmpegArgs?: string[];
|
||||||
outputFFmpegArgs?: string[];
|
outputFFmpegArgs?: string[];
|
||||||
|
resolution?: 'maximum' | '480' | '720' | '1080' | '1440';
|
||||||
|
bitrate?: number | 'auto';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BaseDispatcher extends Writable {
|
export class BaseDispatcher extends Writable {
|
||||||
|
|||||||
Reference in New Issue
Block a user