From 907c983dadde11b8459ff51624991b782bc2fdda Mon Sep 17 00:00:00 2001 From: CyAaron Tai Nava Date: Fri, 9 Sep 2022 16:54:03 -0500 Subject: [PATCH 1/3] recording-audio-video-stream windows patch --- .../fluent-ffmpeg-multistream.js | 32 +++++ examples/record-audio-video-stream/server.js | 116 +++++++++--------- 2 files changed, 91 insertions(+), 57 deletions(-) create mode 100644 examples/record-audio-video-stream/fluent-ffmpeg-multistream.js diff --git a/examples/record-audio-video-stream/fluent-ffmpeg-multistream.js b/examples/record-audio-video-stream/fluent-ffmpeg-multistream.js new file mode 100644 index 0000000..3828b8c --- /dev/null +++ b/examples/record-audio-video-stream/fluent-ffmpeg-multistream.js @@ -0,0 +1,32 @@ +const net = require("net"); +const fs = require("fs"); +const os = require("os"); + +var counter = 0; +class UnixStream { + constructor(stream, onSocket) { + const path = `./${++counter}.sock`; + this.url = + os.platform() === "win32" ? "\\\\.\\pipe\\" + path : "unix:" + path; + + try { + fs.statSync(path); + fs.unlinkSync(path); + } catch (err) {} + const server = net.createServer(onSocket); + stream.on("finish", () => { + server.close(); + }); + server.listen(this.url); + } +} + +function StreamInput(stream) { + return new UnixStream(stream, (socket) => stream.pipe(socket)); +} +module.exports.StreamInput = StreamInput; + +function StreamOutput(stream) { + return new UnixStream(stream, (socket) => socket.pipe(stream)); +} +module.exports.StreamOutput = StreamOutput; diff --git a/examples/record-audio-video-stream/server.js b/examples/record-audio-video-stream/server.js index cf72285..16829f7 100644 --- a/examples/record-audio-video-stream/server.js +++ b/examples/record-audio-video-stream/server.js @@ -1,39 +1,43 @@ -'use strict'; +/* eslint-disable linebreak-style */ +/* eslint-disable new-cap */ +/* eslint-disable no-unused-vars */ +/* eslint-disable linebreak-style */ +"use strict"; -const { PassThrough } = require('stream') -const fs = require('fs') +const { PassThrough } = require("stream"); +const fs = require("fs"); -const { RTCAudioSink, RTCVideoSink } = require('wrtc').nonstandard; +const { RTCAudioSink, RTCVideoSink } = require("wrtc").nonstandard; -const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path; -const ffmpeg = require('fluent-ffmpeg'); +const ffmpegPath = require("@ffmpeg-installer/ffmpeg").path; +const ffmpeg = require("fluent-ffmpeg"); ffmpeg.setFfmpegPath(ffmpegPath); -const { StreamInput } = require('fluent-ffmpeg-multistream') +const { StreamInput } = require("./fluent-ffmpeg-multistream.js"); -const VIDEO_OUTPUT_SIZE = '320x240' -const VIDEO_OUTPUT_FILE = './recording.mp4' +const VIDEO_OUTPUT_SIZE = "320x240"; +const VIDEO_OUTPUT_FILE = "./recording.mp4"; let UID = 0; function beforeOffer(peerConnection) { - const audioTransceiver = peerConnection.addTransceiver('audio'); - const videoTransceiver = peerConnection.addTransceiver('video'); - + const audioTransceiver = peerConnection.addTransceiver("audio"); + const videoTransceiver = peerConnection.addTransceiver("video"); + const audioSink = new RTCAudioSink(audioTransceiver.receiver.track); const videoSink = new RTCVideoSink(videoTransceiver.receiver.track); const streams = []; - videoSink.addEventListener('frame', ({ frame: { width, height, data }}) => { - const size = width + 'x' + height; + videoSink.addEventListener("frame", ({ frame: { width, height, data } }) => { + const size = width + "x" + height; if (!streams[0] || (streams[0] && streams[0].size !== size)) { UID++; const stream = { - recordPath: './recording-' + size + '-' + UID + '.mp4', + recordPath: "./recording-" + size + "-" + UID + ".mp4", size, video: new PassThrough(), - audio: new PassThrough() + audio: new PassThrough(), }; const onAudioData = ({ samples: { buffer } }) => { @@ -42,15 +46,15 @@ function beforeOffer(peerConnection) { } }; - audioSink.addEventListener('data', onAudioData); + audioSink.addEventListener("data", onAudioData); - stream.audio.on('end', () => { - audioSink.removeEventListener('data', onAudioData); + stream.audio.on("end", () => { + audioSink.removeEventListener("data", onAudioData); }); streams.unshift(stream); - streams.forEach(item=>{ + streams.forEach((item) => { if (item !== stream && !item.end) { item.end = true; if (item.audio) { @@ -58,44 +62,44 @@ function beforeOffer(peerConnection) { } item.video.end(); } - }) - + }); + stream.proc = ffmpeg() - .addInput((new StreamInput(stream.video)).url) + .addInput(StreamInput(stream.video).url) .addInputOptions([ - '-f', 'rawvideo', - '-pix_fmt', 'yuv420p', - '-s', stream.size, - '-r', '30', + "-f", + "rawvideo", + "-pix_fmt", + "yuv420p", + "-s", + stream.size, + "-r", + "30", ]) - .addInput((new StreamInput(stream.audio)).url) - .addInputOptions([ - '-f s16le', - '-ar 48k', - '-ac 1', - ]) - .on('start', ()=>{ - console.log('Start recording >> ', stream.recordPath) + .addInput(StreamInput(stream.audio).url) + .addInputOptions(["-f s16le", "-ar 48k", "-ac 1"]) + .on("start", () => { + console.log("Start recording >> ", stream.recordPath); }) - .on('end', ()=>{ + .on("end", () => { stream.recordEnd = true; - console.log('Stop recording >> ', stream.recordPath) + console.log("Stop recording >> ", stream.recordPath); }) .size(VIDEO_OUTPUT_SIZE) .output(stream.recordPath); - stream.proc.run(); + stream.proc.run(); } streams[0].video.push(Buffer.from(data)); }); const { close } = peerConnection; - peerConnection.close = function() { + peerConnection.close = function () { audioSink.stop(); videoSink.stop(); - streams.forEach(({ audio, video, end, proc, recordPath })=>{ + streams.forEach(({ audio, video, end, proc, recordPath }) => { if (!end) { if (audio) { audio.end(); @@ -105,38 +109,36 @@ function beforeOffer(peerConnection) { }); let totalEnd = 0; - const timer = setInterval(()=>{ - streams.forEach(stream=>{ + const timer = setInterval(() => { + streams.forEach((stream) => { if (stream.recordEnd) { totalEnd++; if (totalEnd === streams.length) { clearTimeout(timer); const mergeProc = ffmpeg() - .on('start', ()=>{ - console.log('Start merging into ' + VIDEO_OUTPUT_FILE); + .on("start", () => { + console.log("Start merging into " + VIDEO_OUTPUT_FILE); }) - .on('end', ()=>{ - streams.forEach(({ recordPath })=>{ + .on("end", () => { + streams.forEach(({ recordPath }) => { fs.unlinkSync(recordPath); - }) - console.log('Merge end. You can play ' + VIDEO_OUTPUT_FILE); + }); + console.log("Merge end. You can play " + VIDEO_OUTPUT_FILE); }); - - streams.forEach(({ recordPath })=>{ - mergeProc.addInput(recordPath) + + streams.forEach(({ recordPath }) => { + mergeProc.addInput(recordPath); }); - - mergeProc - .output(VIDEO_OUTPUT_FILE) - .run(); + + mergeProc.output(VIDEO_OUTPUT_FILE).run(); } } }); - }, 1000) + }, 1000); return close.apply(this, arguments); - } + }; } module.exports = { beforeOffer }; From 103dd6ef7b43299122d9dcf98c125f001b424446 Mon Sep 17 00:00:00 2001 From: CyAaron Tai Nava Date: Fri, 9 Sep 2022 17:07:06 -0500 Subject: [PATCH 2/3] formatting --- .../fluent-ffmpeg-multistream.js | 10 +-- examples/record-audio-video-stream/server.js | 70 +++++++++---------- 2 files changed, 38 insertions(+), 42 deletions(-) diff --git a/examples/record-audio-video-stream/fluent-ffmpeg-multistream.js b/examples/record-audio-video-stream/fluent-ffmpeg-multistream.js index 3828b8c..245c121 100644 --- a/examples/record-audio-video-stream/fluent-ffmpeg-multistream.js +++ b/examples/record-audio-video-stream/fluent-ffmpeg-multistream.js @@ -1,20 +1,20 @@ -const net = require("net"); -const fs = require("fs"); -const os = require("os"); +const net = require('net'); +const fs = require('fs'); +const os = require('os'); var counter = 0; class UnixStream { constructor(stream, onSocket) { const path = `./${++counter}.sock`; this.url = - os.platform() === "win32" ? "\\\\.\\pipe\\" + path : "unix:" + path; + os.platform() === 'win32' ? '\\\\.\\pipe\\' + path : 'unix:' + path; try { fs.statSync(path); fs.unlinkSync(path); } catch (err) {} const server = net.createServer(onSocket); - stream.on("finish", () => { + stream.on('finish', () => { server.close(); }); server.listen(this.url); diff --git a/examples/record-audio-video-stream/server.js b/examples/record-audio-video-stream/server.js index 16829f7..38170ee 100644 --- a/examples/record-audio-video-stream/server.js +++ b/examples/record-audio-video-stream/server.js @@ -1,40 +1,36 @@ -/* eslint-disable linebreak-style */ -/* eslint-disable new-cap */ -/* eslint-disable no-unused-vars */ -/* eslint-disable linebreak-style */ -"use strict"; +'use strict'; -const { PassThrough } = require("stream"); -const fs = require("fs"); +const { PassThrough } = require('stream'); +const fs = require('fs'); -const { RTCAudioSink, RTCVideoSink } = require("wrtc").nonstandard; +const { RTCAudioSink, RTCVideoSink } = require('wrtc').nonstandard; -const ffmpegPath = require("@ffmpeg-installer/ffmpeg").path; -const ffmpeg = require("fluent-ffmpeg"); +const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path; +const ffmpeg = require('fluent-ffmpeg'); ffmpeg.setFfmpegPath(ffmpegPath); -const { StreamInput } = require("./fluent-ffmpeg-multistream.js"); +const { StreamInput } = require('./fluent-ffmpeg-multistream.js'); -const VIDEO_OUTPUT_SIZE = "320x240"; -const VIDEO_OUTPUT_FILE = "./recording.mp4"; +const VIDEO_OUTPUT_SIZE = '320x240'; +const VIDEO_OUTPUT_FILE = './recording.mp4'; let UID = 0; function beforeOffer(peerConnection) { - const audioTransceiver = peerConnection.addTransceiver("audio"); - const videoTransceiver = peerConnection.addTransceiver("video"); + const audioTransceiver = peerConnection.addTransceiver('audio'); + const videoTransceiver = peerConnection.addTransceiver('video'); const audioSink = new RTCAudioSink(audioTransceiver.receiver.track); const videoSink = new RTCVideoSink(videoTransceiver.receiver.track); const streams = []; - videoSink.addEventListener("frame", ({ frame: { width, height, data } }) => { - const size = width + "x" + height; + videoSink.addEventListener('frame', ({ frame: { width, height, data } }) => { + const size = width + 'x' + height; if (!streams[0] || (streams[0] && streams[0].size !== size)) { UID++; const stream = { - recordPath: "./recording-" + size + "-" + UID + ".mp4", + recordPath: './recording-' + size + '-' + UID + '.mp4', size, video: new PassThrough(), audio: new PassThrough(), @@ -46,10 +42,10 @@ function beforeOffer(peerConnection) { } }; - audioSink.addEventListener("data", onAudioData); + audioSink.addEventListener('data', onAudioData); - stream.audio.on("end", () => { - audioSink.removeEventListener("data", onAudioData); + stream.audio.on('end', () => { + audioSink.removeEventListener('data', onAudioData); }); streams.unshift(stream); @@ -67,23 +63,23 @@ function beforeOffer(peerConnection) { stream.proc = ffmpeg() .addInput(StreamInput(stream.video).url) .addInputOptions([ - "-f", - "rawvideo", - "-pix_fmt", - "yuv420p", - "-s", + '-f', + 'rawvideo', + '-pix_fmt', + 'yuv420p', + '-s', stream.size, - "-r", - "30", + '-r', + '30', ]) .addInput(StreamInput(stream.audio).url) - .addInputOptions(["-f s16le", "-ar 48k", "-ac 1"]) - .on("start", () => { - console.log("Start recording >> ", stream.recordPath); + .addInputOptions(['-f s16le', '-ar 48k', '-ac 1']) + .on('start', () => { + console.log('Start recording >> ', stream.recordPath); }) - .on("end", () => { + .on('end', () => { stream.recordEnd = true; - console.log("Stop recording >> ", stream.recordPath); + console.log('Stop recording >> ', stream.recordPath); }) .size(VIDEO_OUTPUT_SIZE) .output(stream.recordPath); @@ -117,14 +113,14 @@ function beforeOffer(peerConnection) { clearTimeout(timer); const mergeProc = ffmpeg() - .on("start", () => { - console.log("Start merging into " + VIDEO_OUTPUT_FILE); + .on('start', () => { + console.log('Start merging into ' + VIDEO_OUTPUT_FILE); }) - .on("end", () => { + .on('end', () => { streams.forEach(({ recordPath }) => { fs.unlinkSync(recordPath); }); - console.log("Merge end. You can play " + VIDEO_OUTPUT_FILE); + console.log('Merge end. You can play ' + VIDEO_OUTPUT_FILE); }); streams.forEach(({ recordPath }) => { From f149c7d164d37e70572f83695f390327df3b90cb Mon Sep 17 00:00:00 2001 From: CyAaron Tai Nava Date: Fri, 9 Sep 2022 17:14:18 -0500 Subject: [PATCH 3/3] formatting --- .gitignore | 1 + examples/record-audio-video-stream/server.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d5f19d8..2c68131 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules package-lock.json +recording.mp4 \ No newline at end of file diff --git a/examples/record-audio-video-stream/server.js b/examples/record-audio-video-stream/server.js index 38170ee..f953625 100644 --- a/examples/record-audio-video-stream/server.js +++ b/examples/record-audio-video-stream/server.js @@ -61,7 +61,7 @@ function beforeOffer(peerConnection) { }); stream.proc = ffmpeg() - .addInput(StreamInput(stream.video).url) + .addInput(new StreamInput(stream.video).url) .addInputOptions([ '-f', 'rawvideo', @@ -72,7 +72,7 @@ function beforeOffer(peerConnection) { '-r', '30', ]) - .addInput(StreamInput(stream.audio).url) + .addInput(new StreamInput(stream.audio).url) .addInputOptions(['-f s16le', '-ar 48k', '-ac 1']) .on('start', () => { console.log('Start recording >> ', stream.recordPath);