summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJibon Costa <jiboncosta57@gmail.com>2020-03-28 16:38:08 +0600
committerJibon Costa <jiboncosta57@gmail.com>2020-03-28 16:38:08 +0600
commitb15978e75643ce6c216e81f0e0efc11aab0be084 (patch)
tree41f64eda3549609ecba6a44fe4b4f9d7780607e6
parent81e67d3be90c46910123b30bc07cab3688c041fb (diff)
downloadbbb-recorder-b15978e75643ce6c216e81f0e0efc11aab0be084.tar.gz
bbb-recorder-b15978e75643ce6c216e81f0e0efc11aab0be084.zip
Live RTMP broadcasting
-rw-r--r--README.md30
-rw-r--r--background.js22
-rw-r--r--ffmpegServer.js83
-rw-r--r--liveRTMP.js99
-rw-r--r--package.json3
5 files changed, 231 insertions, 6 deletions
diff --git a/README.md b/README.md
index a53c77b..cb11e46 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,26 @@ You can also use `liveJoin.js` to live join meeting as a recorder & perform reco
node liveJoin.js "https://BBB_HOST/bigbluebutton/api/join?meetingId=MEETING_ID...." liveRecord.webm 10 true
```
+**Live RTMP broadcasting.(Experimental)**
+
+Sometime you may want to broadcast meeting via RTMP. I did some experiment on it & got success but not 100%. To test you can use `ffmpegServer.js` to run websocket server & `liveRTMP.js` to join the meeting. You'll have to edit `rtmpUrl` from `ffmpegServer.js` & port (if need). Then add websocket value as `ffmpegServer` in `liveRTMP.js` file.
+
+1) Frist run websocket server by `node ffmpegServer.js`
+2) Then in another terminal tab
+
+```sh
+node liveJoin.js "https://BBB_HOST/bigbluebutton/api/join?meetingId=MEETING_ID...."
+```
+You can also set duration like this too otherwise it will close after meeting end or kickout:
+
+```sh
+node liveJoin.js "https://BBB_HOST/bigbluebutton/api/join?meetingId=MEETING_ID...." 20
+```
+
+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.
### 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.
@@ -53,9 +73,13 @@ When you will run the command that time `chromium` browser will be open in backg
**Note: It will use extra CPU to process chrome & ffmpeg.**
+### Looking for Bigbluebutton shared hosting?
+
+We are offering cheaper [Bigbluebutton shared hosting](https://www.mynaparrot.com/classroom) or Bigbluebutton insallation/configuration service. You can send me email jibon[@]mynaparrot.com
+
-Thanks to [@muralikg](https://github.com/muralikg/puppetcam). Everything was copied from there & I did some adjustment.
+### Thanks to
+[puppetcam](https://github.com/muralikg/puppetcam). Most of the parts were copied from there.
-### Looking for Bigbluebutton shared hosting?
-We are offering cheaper [Bigbluebutton shared hosting](https://www.mynaparrot.com/classroom) or Bigbluebutton insallation/configuration service. You can send me email jibon[@]mynaparrot.com
+[Canvas-Streaming-Example](https://github.com/fbsamples/Canvas-Streaming-Example) \ No newline at end of file
diff --git a/background.js b/background.js
index cb2fb8b..253e81e 100644
--- a/background.js
+++ b/background.js
@@ -2,6 +2,8 @@
let recorder = null;
let filename = null;
+let ws;
+let liveSteam = false;
chrome.runtime.onConnect.addListener(port => {
port.onMessage.addListener(msg => {
@@ -10,11 +12,20 @@ chrome.runtime.onConnect.addListener(port => {
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':
- recorder.start();
+ if(liveSteam){
+ recorder.start(1000);
+ }else{
+ recorder.start();
+ }
break
case 'REC_CLIENT_PLAY':
if(recorder){
@@ -42,7 +53,7 @@ chrome.runtime.onConnect.addListener(port => {
}
}
}, stream => {
- var chunks=[];
+ var chunks = [];
recorder = new MediaRecorder(stream, {
videoBitsPerSecond: 2500000,
ignoreMutedMedia: true,
@@ -51,10 +62,17 @@ chrome.runtime.onConnect.addListener(port => {
recorder.ondataavailable = function (event) {
if (event.data.size > 0) {
chunks.push(event.data);
+ if(liveSteam){
+ ws.send(event.data);
+ }
}
};
recorder.onstop = function () {
+ if(liveSteam){
+ ws.close();
+ }
+
var superBuffer = new Blob(chunks, {
type: 'video/webm'
});
diff --git a/ffmpegServer.js b/ffmpegServer.js
new file mode 100644
index 0000000..0ab0e6a
--- /dev/null
+++ b/ffmpegServer.js
@@ -0,0 +1,83 @@
+const child_process = require('child_process');
+const WebSocketServer = require('ws').Server;
+const http = require('http');
+
+const server = http.createServer().listen(4000, () => {
+ console.log('Listening...');
+});
+
+const wss = new WebSocketServer({
+ server: server
+});
+
+
+const rtmpUrl = "rtmp://a.rtmp.youtube.com/live2/MyKey";
+
+wss.on('connection', function connection(ws, req) {
+ console.log('connection');
+
+ let auth;
+
+ if ( !(auth = req.url.match(/^\/auth\/(.*)$/)) ) {
+ ws.terminate();
+ return;
+ }
+
+ if(auth[1] !== "mZFZN4yc"){
+ ws.terminate();
+ return;
+ }
+
+ const ffmpeg = child_process.spawn('ffmpeg', [
+
+ // 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',
+
+ //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',
+
+ //'-vf', 'mpdecimate', '-vsync', 'vfr',
+ //'-vf', 'mpdecimate,setpts=N/FRAME_RATE/TB',
+
+ // The output RTMP URL.
+ // For debugging, you could set this to a filename like 'test.flv', and play
+ // the resulting file with VLC.
+ rtmpUrl
+ ])
+
+ // 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);
+ ws.terminate();
+ });
+
+ ffmpeg.stdin.on('error', (e) => {
+ console.log('FFmpeg STDIN Error', e);
+ });
+
+ ffmpeg.stderr.on('data', (data) => {
+ console.log('FFmpeg STDERR:', data.toString());
+ });
+
+ // When data comes in from the WebSocket, write it to FFmpeg's STDIN.
+ ws.on('message', (msg) => {
+ console.log('DATA', msg);
+ ffmpeg.stdin.write(msg);
+ });
+
+ // If the client disconnects, stop FFmpeg.
+ ws.on('close', (e) => {
+ ffmpeg.kill('SIGINT');
+ ws.terminate();
+ });
+
+}); \ No newline at end of file
diff --git a/liveRTMP.js b/liveRTMP.js
new file mode 100644
index 0000000..0fcbfef
--- /dev/null
+++ b/liveRTMP.js
@@ -0,0 +1,99 @@
+const puppeteer = require('puppeteer');
+const Xvfb = require('xvfb');
+var exec = require('child_process').exec;
+const fs = require('fs');
+const homedir = require('os').homedir();
+const ffmpegServer = "ws://localhost:4000/auth/mZFZN4yc";
+
+var xvfb = new Xvfb({silent: true});
+var width = 1280;
+var height = 720;
+var options = {
+ headless: false,
+ args: [
+ '--enable-usermedia-screen-capturing',
+ '--allow-http-screen-capture',
+ '--auto-select-desktop-capture-source=bbbrecorder',
+ '--load-extension=' + __dirname,
+ '--disable-extensions-except=' + __dirname,
+ '--disable-infobars',
+ '--no-sandbox',
+ '--shm-size=1gb',
+ '--disable-dev-shm-usage',
+ `--window-size=${width},${height}`,
+ ],
+}
+
+async function main() {
+ try{
+ xvfb.startSync()
+ var url = process.argv[2],
+ duration = process.argv[3],
+ exportname = 'liveMeeting.webm'
+
+ if(!url){ url = 'http://tobiasahlin.com/spinkit/' }
+ //if(!duration){ duration = 10 }
+
+ const browser = await puppeteer.launch(options)
+ const pages = await browser.pages()
+
+ const page = pages[0]
+
+ page.on('console', msg => {
+ var m = msg.text();
+ console.log('PAGE LOG:', m)
+ });
+
+ await page._client.send('Emulation.clearDeviceMetricsOverride')
+ await page.goto(url, {waitUntil: 'networkidle2'})
+ await page.setBypassCSP(true)
+
+ await page.evaluate((serverAddress) => {
+ console.log("FFMPEG_SERVER");
+ window.postMessage({type: 'FFMPEG_SERVER', ffmpegServer: serverAddress}, '*')
+ }, ffmpegServer)
+
+ await page.waitForSelector('[aria-label="Listen only"]');
+ await page.click('[aria-label="Listen only"]', {waitUntil: 'domcontentloaded'});
+
+ await page.waitForSelector('[id="chat-toggle-button"]');
+ await page.click('[id="chat-toggle-button"]', {waitUntil: 'domcontentloaded'});
+ await page.click('button[aria-label="Users and messages toggle"]', {waitUntil: 'domcontentloaded'});
+ await page.$eval('[class^=navbar]', element => element.style.display = "none");
+
+ await page.$eval('.Toastify', element => element.style.display = "none");
+ await page.waitForSelector('button[aria-label="Leave audio"]');
+ await page.$eval('[class^=actionsbar] > [class^=center]', element => element.style.display = "none");
+
+ await page.evaluate((x) => {
+ console.log("REC_START");
+ window.postMessage({type: 'REC_START'}, '*')
+ })
+
+ if(duration > 0){
+ await page.waitFor((duration * 1000))
+ }else{
+ await page.waitForSelector('[class^=modal] > [class^=content] > button[description="Logs you out of the meeting"]', {
+ timeout: 0
+ });
+ }
+
+ await page.evaluate(filename=>{
+ window.postMessage({type: 'SET_EXPORT_PATH', filename: filename}, '*')
+ window.postMessage({type: 'REC_STOP'}, '*')
+ }, exportname)
+
+ // Wait for download of webm to complete
+ await page.waitForSelector('html.downloadComplete', {timeout: 0})
+ await page.close()
+ await browser.close()
+ xvfb.stopSync()
+
+ fs.unlinkSync(homedir + "/Downloads/liveMeeting.webm");
+
+ }catch(err) {
+ console.log(err)
+ }
+}
+
+main()
diff --git a/package.json b/package.json
index fecd5b0..32de7e1 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
],
"dependencies": {
"puppeteer": "2.0.0",
- "xvfb": "^0.2.3"
+ "xvfb": "^0.2.3",
+ "ws": "^7.2.3"
}
}