diff options
| -rw-r--r-- | README.md | 16 | ||||
| -rw-r--r-- | background.js | 224 | ||||
| -rwxr-xr-x | dependencies_check.sh | 3 | ||||
| -rw-r--r-- | export.js | 26 | ||||
| -rw-r--r-- | ffmpegServer.js | 16 | ||||
| -rw-r--r-- | liveJoin.js | 25 | ||||
| -rw-r--r-- | liveRTMP.js | 20 |
7 files changed, 208 insertions, 122 deletions
@@ -10,7 +10,17 @@ Bigbluebutton recordings export to `webm` or `mp4` & live broadcasting. This is ### Dependencies 1. xvfb (`apt install xvfb`) -2. npm modules listed in package.json +2. Google Chrome stable +3. npm modules listed in package.json + +The latest Google Chrome stable build should be used. + +```sh +curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add +echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list +apt-get -y update +apt-get -y install google-chrome-stable +``` ### Usage @@ -71,10 +81,10 @@ node liveRTMP.js "https://BBB_HOST/bigbluebutton/api/join?meetingId=MEETING_ID.. Check the process of websocket server, `ffmpeg` should start sending data to RTMP server. **Note:** -If you do nothing in meeting room that time `ffmpeg` may exit with error. Actually I don't have much experience on `ffmpeg` to resolve those problems. Please contribute your experience. +If you do nothing in meeting room that time `ffmpeg` may exit with error & will try to reconnect again. Actually I don't have much experience on `ffmpeg` to resolve those problems. Please contribute your experience. ### How it will work? -When you will run the command that time `chromium` browser will be open in background & visit the link & perform screen recording. So, if you have set 10 seconds then it will record 10 seconds only. Later it will give you file as webm or mp4. +When you will run the command that time `Chrome` browser will be open in background & visit the link & perform screen recording. So, if you have set 10 seconds then it will record 10 seconds only. Later it will give you file as webm or mp4. **Note: It will use extra CPU to process chrome & ffmpeg.** diff --git a/background.js b/background.js index 253e81e..5895f19 100644 --- a/background.js +++ b/background.js @@ -4,110 +4,144 @@ let recorder = null; let filename = null; let ws; let liveSteam = false; +let ffmpegServer; +let doDownload = true; + chrome.runtime.onConnect.addListener(port => { - port.onMessage.addListener(msg => { - console.log(msg); - switch (msg.type) { - case 'SET_EXPORT_PATH': - filename = msg.filename - break - case 'FFMPEG_SERVER': - let ffmpegServer = msg.ffmpegServer - ws = new WebSocket(ffmpegServer); - liveSteam = true; - break - case 'REC_STOP': - recorder.stop() - break - case 'REC_START': - if(liveSteam){ - recorder.start(1000); - }else{ - recorder.start(); - } - break - case 'REC_CLIENT_PLAY': - if(recorder){ - return - } - const tab = port.sender.tab - tab.url = msg.data.url - chrome.desktopCapture.chooseDesktopMedia(['tab', 'audio'], streamId => { - // Get the stream - navigator.webkitGetUserMedia({ - audio: { - mandatory: { - chromeMediaSource: 'system' - } - }, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: streamId, - minWidth: 1280, - maxWidth: 1280, - minHeight: 720, - maxHeight: 720, - minFrameRate: 60, - } - } - }, stream => { - var chunks = []; - recorder = new MediaRecorder(stream, { - videoBitsPerSecond: 2500000, - ignoreMutedMedia: true, - mimeType: 'video/webm' - }); - recorder.ondataavailable = function (event) { - if (event.data.size > 0) { - chunks.push(event.data); - if(liveSteam){ - ws.send(event.data); - } + port.onMessage.addListener(msg => { + console.log(msg); + switch (msg.type) { + + case 'SET_EXPORT_PATH': + filename = msg.filename + break + + case 'FFMPEG_SERVER': + ffmpegServer = msg.ffmpegServer + startWebsock(); + break + + case 'REC_STOP': + doDownload = true; + recorder.stop() + break + + case 'REC_START': + if (liveSteam) { + recorder.start(1000); + } else { + recorder.start(); } - }; + break - recorder.onstop = function () { - if(liveSteam){ - ws.close(); + case 'REC_CLIENT_PLAY': + if (recorder) { + return } + const tab = port.sender.tab + tab.url = msg.data.url + chrome.desktopCapture.chooseDesktopMedia(['tab', 'audio'], streamId => { + // Get the stream + navigator.webkitGetUserMedia({ + audio: { + mandatory: { + chromeMediaSource: 'system' + } + }, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: streamId, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720, + minFrameRate: 60, + } + } + }, stream => { + var chunks = []; + recorder = new MediaRecorder(stream, { + videoBitsPerSecond: 2500000, + ignoreMutedMedia: true, + mimeType: 'video/webm;codecs=h264' + }); + recorder.ondataavailable = function (event) { + if (event.data.size > 0) { + chunks.push(event.data); + if (liveSteam) { + ws.send(event.data); + } + } + }; - var superBuffer = new Blob(chunks, { - type: 'video/webm' - }); + recorder.onstop = function () { + if (liveSteam) { + ws.close(); + } - var url = URL.createObjectURL(superBuffer); - // var a = document.createElement('a'); - // document.body.appendChild(a); - // a.style = 'display: none'; - // a.href = url; - // a.download = 'test.webm'; - // a.click(); + if(!doDownload){ + chunks = []; + return; + } - chrome.downloads.download({ - url: url, - filename: filename - }, ()=>{ - }); - } + var superBuffer = new Blob(chunks, { + type: 'video/webm' + }); - }, error => console.log('Unable to get user media', error)) - }) - break - default: - console.log('Unrecognized message', msg) - } - }) + var url = URL.createObjectURL(superBuffer); + // var a = document.createElement('a'); + // document.body.appendChild(a); + // a.style = 'display: none'; + // a.href = url; + // a.download = 'test.webm'; + // a.click(); - chrome.downloads.onChanged.addListener(function(delta) { - if (!delta.state ||(delta.state.current != 'complete')) { - return; - } - try{ - port.postMessage({downloadComplete: true}) - } - catch(e){} - }); + chrome.downloads.download({ + url: url, + filename: filename + }, () => { + }); + } + + }, error => console.log('Unable to get user media', error)) + }) + break + default: + console.log('Unrecognized message', msg) + } + }) + + chrome.downloads.onChanged.addListener(function (delta) { + if (!delta.state || (delta.state.current != 'complete')) { + return; + } + try { + port.postMessage({ downloadComplete: true }) + } + catch (e) { } + }); }) + +function startWebsock() { + + ws = new WebSocket(ffmpegServer); + liveSteam = true; + + ws.onmessage = function (e) { + console.log(e.data); + + if (e.data == "ffmpegClosed") { + + doDownload = false; + recorder.stop(); + + setTimeout(function () { + startWebsock(); + recorder.start(1000); + }, 500) + } + } +} diff --git a/dependencies_check.sh b/dependencies_check.sh index 5f60992..b734df3 100755 --- a/dependencies_check.sh +++ b/dependencies_check.sh @@ -37,7 +37,8 @@ libappindicator1 libnss3 lsb-release xdg-utils -wget" +wget +xvfb" declare -a neededPackages @@ -2,7 +2,9 @@ const puppeteer = require('puppeteer'); const Xvfb = require('xvfb'); var exec = require('child_process').exec; const fs = require('fs'); -const homedir = require('os').homedir(); +const os = require('os'); +const homedir = os.homedir(); +const platform = os.platform(); var xvfb = new Xvfb({ silent: true, @@ -26,16 +28,25 @@ var options = { ], } +if(platform == "linux"){ + options.executablePath = "/usr/bin/google-chrome" +}else if(platform == "darwin"){ + options.executablePath = "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" +} + + async function main() { try{ - xvfb.startSync() + if(platform == "linux"){ + xvfb.startSync() + } var url = process.argv[2], exportname = process.argv[3], duration = process.argv[4], convert = process.argv[5] - if(!url){ url = 'http://tobiasahlin.com/spinkit/' } - if(!exportname){ exportname = 'spinner.webm' } + if(!url){ url = 'https://www.mynaparrot.com/' } + if(!exportname){ exportname = 'export.webm' } if(!duration){ duration = 10 } if(!convert){ convert = false } @@ -76,7 +87,10 @@ async function main() { await page.waitForSelector('html.downloadComplete', {timeout: 0}) await page.close() await browser.close() - xvfb.stopSync() + + if(platform == "linux"){ + xvfb.stopSync() + } if(convert){ convertAndCopy(exportname) @@ -107,7 +121,7 @@ function convertAndCopy(filename){ console.log(copyTo); console.log(copyFrom); - var cmd = "ffmpeg -y -i '" + copyFrom + "' -preset veryfast -movflags faststart -profile:v high -level 4.2 -max_muxing_queue_size 9999 -vf mpdecimate -vsync vfr '" + copyTo + "'"; + var cmd = "ffmpeg -y -i '" + copyFrom + "' -c:v libx264 -preset veryfast -movflags faststart -profile:v high -level 4.2 -max_muxing_queue_size 9999 -vf mpdecimate -vsync vfr '" + copyTo + "'"; console.log("converting using: " + cmd); diff --git a/ffmpegServer.js b/ffmpegServer.js index ef8c3a6..c4acb1b 100644 --- a/ffmpegServer.js +++ b/ffmpegServer.js @@ -36,22 +36,22 @@ wss.on('connection', function connection(ws, req) { // FFmpeg will read input video from STDIN '-i', '-', - // Chromium doesn't support H.264, set the video codec to 'libx264' - // or similar to transcode it to H.264 here on the server. - '-vcodec', 'libx264', + // If we're encoding H.264 in-browser, we can set the video codec to 'copy' + // so that we don't waste any CPU and quality with unnecessary transcoding. + '-vcodec', 'copy', //No browser currently supports encoding AAC, so we must transcode the audio to AAC here on the server. '-acodec', 'aac', - // FLV is the container format used in conjunction with RTMP - '-f', 'flv', - '-max_muxing_queue_size', '99999', '-preset', 'veryfast', //'-vf', 'mpdecimate', '-vsync', 'vfr', //'-vf', 'mpdecimate,setpts=N/FRAME_RATE/TB', - + + // FLV is the container format used in conjunction with RTMP + '-f', 'flv', + // The output RTMP URL. // For debugging, you could set this to a filename like 'test.flv', and play // the resulting file with VLC. @@ -61,6 +61,8 @@ wss.on('connection', function connection(ws, req) { // If FFmpeg stops for any reason, close the WebSocket connection. ffmpeg.on('close', (code, signal) => { console.log('FFmpeg child process closed, code ' + code + ', signal ' + signal); + //console.log("reconnecting...") + ws.send("ffmpegClosed") ws.terminate(); }); diff --git a/liveJoin.js b/liveJoin.js index eaf241e..ed08a15 100644 --- a/liveJoin.js +++ b/liveJoin.js @@ -2,7 +2,9 @@ const puppeteer = require('puppeteer'); const Xvfb = require('xvfb'); var exec = require('child_process').exec; const fs = require('fs'); -const homedir = require('os').homedir(); +const os = require('os'); +const homedir = os.homedir(); +const platform = os.platform(); var xvfb = new Xvfb({ silent: true, @@ -26,16 +28,24 @@ var options = { ], } +if(platform == "linux"){ + options.executablePath = "/usr/bin/google-chrome" +}else if(platform == "darwin"){ + options.executablePath = "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" +} + async function main() { try{ - xvfb.startSync() + if(platform == "linux"){ + xvfb.startSync() + } var url = process.argv[2], exportname = process.argv[3], duration = process.argv[4], convert = process.argv[5] - if(!url){ url = 'http://tobiasahlin.com/spinkit/' } - if(!exportname){ exportname = 'spinner.webm' } + if(!url){ url = 'https://www.mynaparrot.com/' } + if(!exportname){ exportname = 'live.webm' } //if(!duration){ duration = 10 } if(!convert){ convert = false } @@ -87,7 +97,10 @@ async function main() { await page.waitForSelector('html.downloadComplete', {timeout: 0}) await page.close() await browser.close() - xvfb.stopSync() + + if(platform == "linux"){ + xvfb.stopSync() + } if(convert){ convertAndCopy(exportname) @@ -118,7 +131,7 @@ function convertAndCopy(filename){ console.log(copyTo); console.log(copyFrom); - var cmd = "ffmpeg -y -i '" + copyFrom + "' -preset veryfast -movflags faststart -profile:v high -level 4.2 -max_muxing_queue_size 9999 -vf mpdecimate -vsync vfr '" + copyTo + "'"; + var cmd = "ffmpeg -y -i '" + copyFrom + "' -c:v libx264 -preset veryfast -movflags faststart -profile:v high -level 4.2 -max_muxing_queue_size 9999 -vf mpdecimate -vsync vfr '" + copyTo + "'"; console.log("converting using: " + cmd); diff --git a/liveRTMP.js b/liveRTMP.js index 4e35857..7c83b23 100644 --- a/liveRTMP.js +++ b/liveRTMP.js @@ -3,7 +3,9 @@ const Xvfb = require('xvfb'); var exec = require('child_process').exec; const fs = require('fs'); var config = JSON.parse(fs.readFileSync("config.json", 'utf8')); -const homedir = require('os').homedir(); +const os = require('os'); +const homedir = os.homedir(); +const platform = os.platform(); const ffmpegServer = config.ffmpegServer + ":" + config.ffmpegServerPort + "/auth/" + config.auth; @@ -28,15 +30,22 @@ var options = { `--window-size=${width},${height}`, ], } +if(platform == "linux"){ + options.executablePath = "/usr/bin/google-chrome" +}else if(platform == "darwin"){ + options.executablePath = "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" +} async function main() { try{ - xvfb.startSync() + if(platform == "linux"){ + xvfb.startSync() + } var url = process.argv[2], duration = process.argv[3], exportname = 'liveMeeting.webm' - if(!url){ url = 'http://tobiasahlin.com/spinkit/' } + if(!url){ url = 'https://www.mynaparrot.com/' } //if(!duration){ duration = 10 } const browser = await puppeteer.launch(options) @@ -92,8 +101,11 @@ async function main() { await page.waitForSelector('html.downloadComplete', {timeout: 0}) await page.close() await browser.close() - xvfb.stopSync() + if(platform == "linux"){ + xvfb.stopSync() + } + fs.unlinkSync(homedir + "/Downloads/liveMeeting.webm"); }catch(err) { |
