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/fluent-ffmpeg-multistream.js b/examples/record-audio-video-stream/fluent-ffmpeg-multistream.js new file mode 100644 index 0000000..245c121 --- /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..f953625 100644 --- a/examples/record-audio-video-stream/server.js +++ b/examples/record-audio-video-stream/server.js @@ -1,30 +1,30 @@ '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 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 audioSink = new RTCAudioSink(audioTransceiver.receiver.track); const videoSink = new RTCVideoSink(videoTransceiver.receiver.track); const streams = []; - videoSink.addEventListener('frame', ({ frame: { width, height, data }}) => { + videoSink.addEventListener('frame', ({ frame: { width, height, data } }) => { const size = width + 'x' + height; if (!streams[0] || (streams[0] && streams[0].size !== size)) { UID++; @@ -33,7 +33,7 @@ function beforeOffer(peerConnection) { recordPath: './recording-' + size + '-' + UID + '.mp4', size, video: new PassThrough(), - audio: new PassThrough() + audio: new PassThrough(), }; const onAudioData = ({ samples: { buffer } }) => { @@ -50,7 +50,7 @@ function beforeOffer(peerConnection) { streams.unshift(stream); - streams.forEach(item=>{ + streams.forEach((item) => { if (item !== stream && !item.end) { item.end = true; if (item.audio) { @@ -58,44 +58,44 @@ function beforeOffer(peerConnection) { } item.video.end(); } - }) - + }); + stream.proc = ffmpeg() - .addInput((new StreamInput(stream.video)).url) + .addInput(new 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(new 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 +105,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', ()=>{ + .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); }); - - 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 };