summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJibon Costa <jiboncosta57@gmail.com>2020-03-26 13:39:02 +0600
committerJibon Costa <jiboncosta57@gmail.com>2020-03-26 13:39:02 +0600
commite000c457c8f0b6b58d6b1575ad69e0e30bc7f1c8 (patch)
treed11f5c1beb94c7339b947421848f018ecca275eb
downloadbbb-recorder-e000c457c8f0b6b58d6b1575ad69e0e30bc7f1c8.tar.gz
bbb-recorder-e000c457c8f0b6b58d6b1575ad69e0e30bc7f1c8.zip
first commit
-rw-r--r--.gitignore3
-rw-r--r--LICENSE21
-rw-r--r--README.md48
-rw-r--r--background.js94
-rw-r--r--content_script.js23
-rw-r--r--export.js143
-rw-r--r--manifest.json18
-rw-r--r--package.json18
8 files changed, 368 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f2d09a8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+.DS_Store
+package-lock.json \ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b026bf6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Murali K G
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..50d6cb3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,48 @@
+# bbb-recorder
+
+Bigbluebutton recordings to `webm` or `mp4`. This is an example how I have implemented BBB recordings to distibutable file.
+
+
+1. Videos will be copied to `/var/www/bigbluebutton-default/record`
+3. Can converted to `mp4`. Default `webm`
+2. Specify bitrate to control quality of the exported video by adjusting `videoBitsPerSecond` property in `background.js`
+
+
+### Dependencies
+
+1. xvfb (`apt install xvfb`)
+2. npm modules listed in package.json (`npm install`)
+
+### Usage
+
+Clone the project first:
+
+```javascript
+git clone https://github.com/jibon57/bbb-recorder
+cd bbb-recorder
+npm install
+```
+
+Now run:
+
+```sh
+node export.js "https://BBB_HOST/playback/presentation/2.0/playback.html?meetingId=MEETING_ID" meeting.webm 10 true
+```
+
+### Options
+
+You can pass 4 args
+
+1) BBB recording link
+2) Export file name. Should be `.webm` at end
+3) Duration of recording in seconds. Default 10 seconds
+4) Convert to mp4 or not (true for convert to mp4). Default false
+
+### 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.
+
+**Note: It will use huge CPU to process chrome & ffmpeg.**
+
+
+
+Thanks to [@muralikg](https://github.com/muralikg/puppetcam). Everything was copied from there & I did some adjustment.
diff --git a/background.js b/background.js
new file mode 100644
index 0000000..bc33384
--- /dev/null
+++ b/background.js
@@ -0,0 +1,94 @@
+/* global chrome, MediaRecorder, FileReader */
+
+let recorder = null;
+let filename = null;
+chrome.runtime.onConnect.addListener(port => {
+
+ port.onMessage.addListener(msg => {
+ console.log(msg);
+ switch (msg.type) {
+ case 'SET_EXPORT_PATH':
+ filename = msg.filename
+ break
+ case 'REC_STOP':
+ recorder.stop()
+ 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);
+ }
+ };
+
+ recorder.onstop = function () {
+ var superBuffer = new Blob(chunks, {
+ type: 'video/webm'
+ });
+
+ 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.download({
+ url: url,
+ filename: filename
+ }, ()=>{
+ });
+ }
+ setTimeout(function(){
+ recorder.start();
+ }, 3000)
+ }, 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){}
+ });
+
+})
diff --git a/content_script.js b/content_script.js
new file mode 100644
index 0000000..680ac6c
--- /dev/null
+++ b/content_script.js
@@ -0,0 +1,23 @@
+window.onload = () => {
+ if (window.recorderInjected) return
+ Object.defineProperty(window, 'recorderInjected', { value: true, writable: false })
+
+ // Setup message passing
+ const port = chrome.runtime.connect(chrome.runtime.id)
+ port.onMessage.addListener(msg => window.postMessage(msg, '*'))
+ window.addEventListener('message', event => {
+ // Relay client messages
+ if (event.source === window && event.data.type) {
+ port.postMessage(event.data)
+ }
+ if(event.data.type === 'PLAYBACK_COMPLETE'){
+ port.postMessage({ type: 'REC_STOP' }, '*')
+ }
+ if(event.data.downloadComplete){
+ document.querySelector('html').classList.add('downloadComplete')
+ }
+ })
+
+ document.title = 'bbbrecorder'
+ window.postMessage({ type: 'REC_CLIENT_PLAY', data: { url: window.location.origin } }, '*')
+}
diff --git a/export.js b/export.js
new file mode 100644
index 0000000..a2c4ddc
--- /dev/null
+++ b/export.js
@@ -0,0 +1,143 @@
+const puppeteer = require('puppeteer');
+const Xvfb = require('xvfb');
+var exec = require('child_process').exec;
+const fs = require('fs');
+const homedir = require('os').homedir();
+
+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],
+ 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(!duration){ duration = 10 }
+ if(!convert){ convert = false }
+
+ const browser = await puppeteer.launch(options)
+ const pages = await browser.pages()
+
+ const page = pages[0]
+
+ page.on('console', msg => console.log('PAGE LOG:', msg.text()));
+
+ await page._client.send('Emulation.clearDeviceMetricsOverride')
+ await page.goto(url, {waitUntil: 'networkidle2'})
+ await page.setBypassCSP(true)
+
+ await page.click('button[class=acorn-play-button]', {waitUntil: 'domcontentloaded'});
+ await page.$eval('#navbar', element => element.parentNode.removeChild(element));
+ await page.$eval('.acorn-controls', element => element.parentNode.removeChild(element));
+ await page.$eval('#copyright', element => element.parentNode.removeChild(element));
+
+ //const form = await page.$('.acorn-play-button');
+ //await form.evaluate( form => form.click() );
+
+ // Perform any actions that have to be captured in the exported video
+ await page.waitFor((duration * 1000))
+
+ 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()
+
+ if(convert){
+ convertAndCopy(exportname)
+ }else{
+ copyOnly(exportname)
+ }
+
+ }catch(err) {
+ console.log(err)
+ }
+}
+
+main()
+
+function convertAndCopy(filename){
+
+ var copyFromPath = homedir + "/Downloads";
+ var copyToPath = "/var/www/bigbluebutton-default/record";
+ var onlyfileName = filename.split(".webm")
+ var mp4File = onlyfileName[0] + ".mp4"
+ var copyFrom = copyFromPath + "/" + filename + ""
+ var copyTo = copyToPath + "/" + mp4File;
+
+ if(!fs.existsSync(copyToPath)){
+ fs.mkdirSync(copyToPath);
+ }
+
+ console.log(copyTo);
+ console.log(copyFrom);
+
+ var cmd = "ffmpeg -y -i '" + copyFrom + "' -preset veryfast -movflags faststart -profile:v high -level 4.2 '" + copyTo + "'";
+
+ console.log("converting using: " + cmd);
+
+ exec(cmd, function(err, stdout, stderr) {
+
+ if (err) console.log('err:\n' + err);
+ //if (stderr) console.log('stderr:\n' + stderr);
+
+ if(!err){
+ console.log("Now deleting " + copyFrom)
+ try {
+ fs.unlinkSync(copyFrom);
+ console.log('successfully deleted ' + copyFrom);
+ } catch (err) {
+ console.log(err)
+ }
+ }
+ });
+}
+
+function copyOnly(filename){
+
+ var copyFrom = homedir + "/Downloads/" + filename;
+ var copyToPath = "/var/www/bigbluebutton-default/record";
+ var copyTo = copyToPath + "/" + filename;
+
+ if(!fs.existsSync(copyToPath)){
+ fs.mkdirSync(copyToPath);
+ }
+
+ try {
+
+ fs.copyFileSync(copyFrom, copyTo)
+ console.log('successfully copied ' + copyTo);
+
+ fs.unlinkSync(copyFrom);
+ console.log('successfully delete ' + copyFrom);
+ } catch (err) {
+ console.log(err)
+ }
+}
+
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..b23914f
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,18 @@
+{
+ "name": "BigBlueButton Recorder",
+ "version": "0.1.0",
+ "manifest_version": 2,
+ "background": {
+ "scripts": ["background.js"]
+ },
+ "content_scripts": [{
+ "matches": ["<all_urls>"],
+ "js": ["content_script.js"],
+ "run_at": "document_start"
+ }],
+ "permissions": [
+ "desktopCapture",
+ "<all_urls>",
+ "downloads"
+ ]
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fecd5b0
--- /dev/null
+++ b/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "bbbrecorder",
+ "version": "0.0.1",
+ "description": "Capture all automated actions in a browser and export as a video",
+ "scripts": {
+ "example": "node export.js http://tobiasahlin.com/spinkit/ spinner.webm"
+ },
+ "keywords": [
+ "record",
+ "chrome",
+ "puppeteer",
+ "screencast"
+ ],
+ "dependencies": {
+ "puppeteer": "2.0.0",
+ "xvfb": "^0.2.3"
+ }
+}