diff options
| author | Jibon Costa <jiboncosta57@gmail.com> | 2020-03-26 13:39:02 +0600 |
|---|---|---|
| committer | Jibon Costa <jiboncosta57@gmail.com> | 2020-03-26 13:39:02 +0600 |
| commit | e000c457c8f0b6b58d6b1575ad69e0e30bc7f1c8 (patch) | |
| tree | d11f5c1beb94c7339b947421848f018ecca275eb | |
| download | bbb-recorder-e000c457c8f0b6b58d6b1575ad69e0e30bc7f1c8.tar.gz bbb-recorder-e000c457c8f0b6b58d6b1575ad69e0e30bc7f1c8.zip | |
first commit
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | LICENSE | 21 | ||||
| -rw-r--r-- | README.md | 48 | ||||
| -rw-r--r-- | background.js | 94 | ||||
| -rw-r--r-- | content_script.js | 23 | ||||
| -rw-r--r-- | export.js | 143 | ||||
| -rw-r--r-- | manifest.json | 18 | ||||
| -rw-r--r-- | package.json | 18 |
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 @@ -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" + } +} |
