正在显示
8 个修改的文件
包含
437 行增加
和
0 行删除
.DS_Store
0 → 100644
不能预览此文件类型
.gitignore
0 → 100644
| 1 | +node_modules/* |
ajax-loader.gif
0 → 100644
7.8 KB
index.html
0 → 100644
| 1 | +<!-- | ||
| 2 | +// Muaz Khan - www.MuazKhan.com | ||
| 3 | +// MIT License - www.WebRTC-Experiment.com/licence | ||
| 4 | +// Experiments - github.com/muaz-khan/RecordRTC | ||
| 5 | +--> | ||
| 6 | + | ||
| 7 | +<!DOCTYPE html> | ||
| 8 | +<html> | ||
| 9 | + | ||
| 10 | +<head> | ||
| 11 | + <meta charset="utf-8" /> | ||
| 12 | + <title>RecordRTC to Node.js</title> | ||
| 13 | + <script> | ||
| 14 | + if (location.href.indexOf('file:') == 0) { | ||
| 15 | + document.write('<h1 style="color:red;">Please load this HTML file on HTTP or HTTPS.</h1>'); | ||
| 16 | + } | ||
| 17 | + </script> | ||
| 18 | + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> | ||
| 19 | + <link rel="author" type="text/html" href="https://plus.google.com/+MuazKhan"> | ||
| 20 | + <meta name="author" content="Muaz Khan"> | ||
| 21 | + | ||
| 22 | + <style> | ||
| 23 | + html { | ||
| 24 | + background-color: #f7f7f7; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + body { | ||
| 28 | + background-color: white; | ||
| 29 | + border: 1px solid rgb(15, 158, 238); | ||
| 30 | + margin: 1% 35%; | ||
| 31 | + text-align: center; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + hr { | ||
| 35 | + border: 0; | ||
| 36 | + border-top: 1px solid rgb(15, 158, 238); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + a { | ||
| 40 | + color: #2844FA; | ||
| 41 | + text-decoration: none; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + a:hover, | ||
| 45 | + a:focus { | ||
| 46 | + color: #1B29A4; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + a:active { | ||
| 50 | + color: #000; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + audio, | ||
| 54 | + video { | ||
| 55 | + border: 1px solid rgb(15, 158, 238); | ||
| 56 | + width: 94%; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + button[disabled], | ||
| 60 | + input[disabled] { | ||
| 61 | + background: rgba(216, 205, 205, 0.2); | ||
| 62 | + border: 1px solid rgb(233, 224, 224); | ||
| 63 | + } | ||
| 64 | + </style> | ||
| 65 | +</head> | ||
| 66 | + | ||
| 67 | +<body> | ||
| 68 | + <h1>RecordRTC to Node.js</h1> | ||
| 69 | + <p> | ||
| 70 | + <video></video> | ||
| 71 | + </p> | ||
| 72 | + <hr /> | ||
| 73 | + | ||
| 74 | + <div> | ||
| 75 | + <label id="percentage">0%</label> | ||
| 76 | + <progress id="progress-bar" value=0></progress><br /> | ||
| 77 | + </div> | ||
| 78 | + | ||
| 79 | + <hr /> | ||
| 80 | + | ||
| 81 | + <div> | ||
| 82 | + <button id="btn-start-recording">Start Recording</button> | ||
| 83 | + <button id="btn-stop-recording" disabled="">Stop Recording</button> | ||
| 84 | + </div> | ||
| 85 | + | ||
| 86 | + <script src="/node_modules/recordrtc/RecordRTC.js"> </script> | ||
| 87 | + | ||
| 88 | + <script> | ||
| 89 | + // fetching DOM references | ||
| 90 | + var btnStartRecording = document.querySelector('#btn-start-recording'); | ||
| 91 | + var btnStopRecording = document.querySelector('#btn-stop-recording'); | ||
| 92 | + | ||
| 93 | + var videoElement = document.querySelector('video'); | ||
| 94 | + | ||
| 95 | + var progressBar = document.querySelector('#progress-bar'); | ||
| 96 | + var percentage = document.querySelector('#percentage'); | ||
| 97 | + | ||
| 98 | + var recorder; | ||
| 99 | + | ||
| 100 | + // reusable helpers | ||
| 101 | + | ||
| 102 | + // this function submits recorded blob to nodejs server | ||
| 103 | + function postFiles() { | ||
| 104 | + var blob = recorder.getBlob(); | ||
| 105 | + | ||
| 106 | + // getting unique identifier for the file name | ||
| 107 | + var fileName = generateRandomString() + '.webm'; | ||
| 108 | + | ||
| 109 | + var file = new File([blob], fileName, { | ||
| 110 | + type: 'video/webm' | ||
| 111 | + }); | ||
| 112 | + | ||
| 113 | + videoElement.src = videoElement.srcObject = null; | ||
| 114 | + videoElement.src = URL.createObjectURL(recorder.getBlob()); | ||
| 115 | + if (mediaStream) mediaStream.stop(); | ||
| 116 | + return; | ||
| 117 | + | ||
| 118 | + // videoElement.src = ''; | ||
| 119 | + // videoElement.poster = '/ajax-loader.gif'; | ||
| 120 | + | ||
| 121 | + xhr('/uploadFile', file, function (responseText) { | ||
| 122 | + var fileURL = JSON.parse(responseText).fileURL; | ||
| 123 | + | ||
| 124 | + console.info('fileURL', fileURL); | ||
| 125 | + videoElement.src = fileURL; | ||
| 126 | + videoElement.play(); | ||
| 127 | + videoElement.muted = false; | ||
| 128 | + videoElement.controls = true; | ||
| 129 | + | ||
| 130 | + document.querySelector('#footer-h2').innerHTML = '<a href="' + videoElement.src + '">' + videoElement.src + '</a>'; | ||
| 131 | + }); | ||
| 132 | + | ||
| 133 | + if (mediaStream) mediaStream.stop(); | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + // XHR2/FormData | ||
| 137 | + function xhr(url, data, callback) { | ||
| 138 | + var request = new XMLHttpRequest(); | ||
| 139 | + request.onreadystatechange = function () { | ||
| 140 | + if (request.readyState == 4 && request.status == 200) { | ||
| 141 | + callback(request.responseText); | ||
| 142 | + } | ||
| 143 | + }; | ||
| 144 | + | ||
| 145 | + request.upload.onprogress = function (event) { | ||
| 146 | + progressBar.max = event.total; | ||
| 147 | + progressBar.value = event.loaded; | ||
| 148 | + progressBar.innerHTML = 'Upload Progress ' + Math.round(event.loaded / event.total * 100) + "%"; | ||
| 149 | + }; | ||
| 150 | + | ||
| 151 | + request.upload.onload = function () { | ||
| 152 | + percentage.style.display = 'none'; | ||
| 153 | + progressBar.style.display = 'none'; | ||
| 154 | + }; | ||
| 155 | + request.open('POST', url); | ||
| 156 | + | ||
| 157 | + var formData = new FormData(); | ||
| 158 | + formData.append('file', data); | ||
| 159 | + request.send(formData); | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + // generating random string | ||
| 163 | + function generateRandomString() { | ||
| 164 | + if (window.crypto) { | ||
| 165 | + var a = window.crypto.getRandomValues(new Uint32Array(3)), | ||
| 166 | + token = ''; | ||
| 167 | + for (var i = 0, l = a.length; i < l; i++) token += a[i].toString(36); | ||
| 168 | + return token; | ||
| 169 | + } else { | ||
| 170 | + return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, ''); | ||
| 171 | + } | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + var mediaStream = null; | ||
| 175 | + // reusable getUserMedia | ||
| 176 | + function captureUserMedia(success_callback) { | ||
| 177 | + var session = { | ||
| 178 | + audio: true, | ||
| 179 | + video: true | ||
| 180 | + }; | ||
| 181 | + | ||
| 182 | + navigator.getUserMedia(session, success_callback, function (error) { | ||
| 183 | + alert('Unable to capture your camera. Please check console logs.'); | ||
| 184 | + console.error(error); | ||
| 185 | + }); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + // UI events handling | ||
| 189 | + btnStartRecording.onclick = function () { | ||
| 190 | + btnStartRecording.disabled = true; | ||
| 191 | + | ||
| 192 | + captureUserMedia(function (stream) { | ||
| 193 | + mediaStream = stream; | ||
| 194 | + // videoElement.src = window.URL.createObjectURL(stream); | ||
| 195 | + try { | ||
| 196 | + videoElement.srcObject = stream; | ||
| 197 | + } catch (error) { | ||
| 198 | + videoElement.src = window.URL.createObjectURL(stream); | ||
| 199 | + } | ||
| 200 | + videoElement.play(); | ||
| 201 | + videoElement.muted = true; | ||
| 202 | + videoElement.controls = true; | ||
| 203 | + recorder = RecordRTC(stream, { | ||
| 204 | + type: 'video' | ||
| 205 | + }); | ||
| 206 | + | ||
| 207 | + recorder.startRecording(); | ||
| 208 | + | ||
| 209 | + // enable stop-recording button | ||
| 210 | + btnStopRecording.disabled = false; | ||
| 211 | + }); | ||
| 212 | + }; | ||
| 213 | + | ||
| 214 | + | ||
| 215 | + btnStopRecording.onclick = function () { | ||
| 216 | + btnStartRecording.disabled = false; | ||
| 217 | + btnStopRecording.disabled = true; | ||
| 218 | + | ||
| 219 | + recorder.stopRecording(postFiles); | ||
| 220 | + }; | ||
| 221 | + | ||
| 222 | + window.onbeforeunload = function () { | ||
| 223 | + startRecording.disabled = false; | ||
| 224 | + }; | ||
| 225 | + </script> | ||
| 226 | + <footer style="width:100%;position: fixed; right: 0; text-align: center;color:red;"> | ||
| 227 | + <h2 id="footer-h2"></h2> | ||
| 228 | + Questions?? <a href="mailto:muazkh@gmail.com">muazkh@gmail.com</a> | ||
| 229 | + | ||
| 230 | + <br><br> | ||
| 231 | + Open-Sourced here:<br> | ||
| 232 | + <a | ||
| 233 | + href="https://github.com/muaz-khan/RecordRTC/tree/master/RecordRTC-to-Nodejs">https://github.com/muaz-khan/RecordRTC/tree/master/RecordRTC-to-Nodejs</a> | ||
| 234 | + </footer> | ||
| 235 | +</body> | ||
| 236 | + | ||
| 237 | +</html> |
package.json
0 → 100644
| 1 | +{ | ||
| 2 | + "name": "recordrtc-nodejs", | ||
| 3 | + "preferGlobal": true, | ||
| 4 | + "version": "1.0.6", | ||
| 5 | + "author": { | ||
| 6 | + "name": "Muaz Khan", | ||
| 7 | + "email": "muazkh@gmail.com" | ||
| 8 | + }, | ||
| 9 | + "description": "Records audio/video separately as wav/webm. POST both files in single HttpPost-Request to Node.js (FormData). Node.js code saves both files into disk. Node.js code invokes ffmpeg to merge wav/webm in single webm file. The merged webm file's URL is returned using same HTTP-callback for playback!", | ||
| 10 | + "contributors": [ | ||
| 11 | + { | ||
| 12 | + "name": "Muaz Khan", | ||
| 13 | + "email": "muazkh@gmail.com" | ||
| 14 | + } | ||
| 15 | + ], | ||
| 16 | + "scripts": { | ||
| 17 | + "start": "node server.js" | ||
| 18 | + }, | ||
| 19 | + "main": "./server.js", | ||
| 20 | + "repository": { | ||
| 21 | + "type": "git", | ||
| 22 | + "url": "https://github.com/muaz-khan/RecordRTC.git" | ||
| 23 | + }, | ||
| 24 | + "keywords": [ | ||
| 25 | + "webrtc", | ||
| 26 | + "javascript", | ||
| 27 | + "RecordRTC", | ||
| 28 | + "Node.js", | ||
| 29 | + "ffmpeg", | ||
| 30 | + "audio-recording", | ||
| 31 | + "video-recording", | ||
| 32 | + "gif-recording", | ||
| 33 | + "audio/video recording", | ||
| 34 | + "webp", | ||
| 35 | + "webm", | ||
| 36 | + "wav" | ||
| 37 | + ], | ||
| 38 | + "analyze": false, | ||
| 39 | + "license": "MIT", | ||
| 40 | + "engines": { | ||
| 41 | + "node": ">=0.6" | ||
| 42 | + }, | ||
| 43 | + "readmeFilename": "README.md", | ||
| 44 | + "bugs": { | ||
| 45 | + "url": "https://github.com/muaz-khan/WebRTC-Experiment/issues" | ||
| 46 | + }, | ||
| 47 | + "homepage": "https://github.com/muaz-khan/RecordRTC/tree/master/RecordRTC-to-Nodejs", | ||
| 48 | + "_id": "recordrtc-nodejs@", | ||
| 49 | + "_from": "recordrtc-nodejs@", | ||
| 50 | + "dependencies": { | ||
| 51 | + "formidable": "latest", | ||
| 52 | + "mime": "latest", | ||
| 53 | + "recordrtc": "latest" | ||
| 54 | + } | ||
| 55 | +} |
server.js
0 → 100644
| 1 | +// http://127.0.0.1:9001 | ||
| 2 | +// http://localhost:9001 | ||
| 3 | + | ||
| 4 | +var server = require('http'), | ||
| 5 | + url = require('url'), | ||
| 6 | + path = require('path'), | ||
| 7 | + fs = require('fs'); | ||
| 8 | + | ||
| 9 | +var port = 9001; | ||
| 10 | + | ||
| 11 | +function serverHandler(request, response) { | ||
| 12 | + var uri = url.parse(request.url).pathname, | ||
| 13 | + filename = path.join(process.cwd(), uri); | ||
| 14 | + | ||
| 15 | + var isWin = !!process.platform.match(/^win/); | ||
| 16 | + | ||
| 17 | + if (filename && filename.toString().indexOf(isWin ? '\\uploadFile' : '/uploadFile') != -1 && request.method.toLowerCase() == 'post') { | ||
| 18 | + uploadFile(request, response); | ||
| 19 | + return; | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + fs.exists(filename, function(exists) { | ||
| 23 | + if (!exists) { | ||
| 24 | + response.writeHead(404, { | ||
| 25 | + 'Content-Type': 'text/plain' | ||
| 26 | + }); | ||
| 27 | + response.write('404 Not Found: ' + filename + '\n'); | ||
| 28 | + response.end(); | ||
| 29 | + return; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + if (filename.indexOf('favicon.ico') !== -1) { | ||
| 33 | + return; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + if (fs.statSync(filename).isDirectory() && !isWin) { | ||
| 37 | + filename += '/index.html'; | ||
| 38 | + } else if (fs.statSync(filename).isDirectory() && !!isWin) { | ||
| 39 | + filename += '\\index.html'; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + fs.readFile(filename, 'binary', function(err, file) { | ||
| 43 | + if (err) { | ||
| 44 | + response.writeHead(500, { | ||
| 45 | + 'Content-Type': 'text/plain' | ||
| 46 | + }); | ||
| 47 | + response.write(err + '\n'); | ||
| 48 | + response.end(); | ||
| 49 | + return; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + var contentType; | ||
| 53 | + | ||
| 54 | + if (filename.indexOf('.html') !== -1) { | ||
| 55 | + contentType = 'text/html'; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + if (filename.indexOf('.js') !== -1) { | ||
| 59 | + contentType = 'application/javascript'; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + if (contentType) { | ||
| 63 | + response.writeHead(200, { | ||
| 64 | + 'Content-Type': contentType | ||
| 65 | + }); | ||
| 66 | + } else response.writeHead(200); | ||
| 67 | + | ||
| 68 | + response.write(file, 'binary'); | ||
| 69 | + response.end(); | ||
| 70 | + }); | ||
| 71 | + }); | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +var app; | ||
| 75 | + | ||
| 76 | +app = server.createServer(serverHandler); | ||
| 77 | + | ||
| 78 | +app = app.listen(port, process.env.IP || "0.0.0.0", function() { | ||
| 79 | + var addr = app.address(); | ||
| 80 | + | ||
| 81 | + if (addr.address == '0.0.0.0') { | ||
| 82 | + addr.address = 'localhost'; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + app.address = addr.address; | ||
| 86 | + | ||
| 87 | + console.log("Server listening at", 'http://' + addr.address + ":" + addr.port); | ||
| 88 | +}); | ||
| 89 | + | ||
| 90 | +function uploadFile(request, response) { | ||
| 91 | + // parse a file upload | ||
| 92 | + var mime = require('mime'); | ||
| 93 | + var formidable = require('formidable'); | ||
| 94 | + var util = require('util'); | ||
| 95 | + | ||
| 96 | + var form = new formidable.IncomingForm(); | ||
| 97 | + | ||
| 98 | + var dir = !!process.platform.match(/^win/) ? '\\uploads\\' : '/uploads/'; | ||
| 99 | + | ||
| 100 | + form.uploadDir = __dirname + dir; | ||
| 101 | + form.keepExtensions = true; | ||
| 102 | + form.maxFieldsSize = 10 * 1024 * 1024; | ||
| 103 | + form.maxFields = 1000; | ||
| 104 | + form.multiples = false; | ||
| 105 | + | ||
| 106 | + form.parse(request, function(err, fields, files) { | ||
| 107 | + var file = util.inspect(files); | ||
| 108 | + | ||
| 109 | + response.writeHead(200, getHeaders('Content-Type', 'application/json')); | ||
| 110 | + | ||
| 111 | + var fileName = file.split('path:')[1].split('\',')[0].split(dir)[1].toString().replace(/\\/g, '').replace(/\//g, ''); | ||
| 112 | + var fileURL = 'http://' + app.address + ':' + port + '/uploads/' + fileName; | ||
| 113 | + | ||
| 114 | + console.log('fileURL: ', fileURL); | ||
| 115 | + response.write(JSON.stringify({ | ||
| 116 | + fileURL: fileURL | ||
| 117 | + })); | ||
| 118 | + response.end(); | ||
| 119 | + }); | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +function getHeaders(opt, val) { | ||
| 123 | + try { | ||
| 124 | + var headers = {}; | ||
| 125 | + headers["Access-Control-Allow-Origin"] = "https://secure.seedocnow.com"; | ||
| 126 | + headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS"; | ||
| 127 | + headers["Access-Control-Allow-Credentials"] = true; | ||
| 128 | + headers["Access-Control-Max-Age"] = '86400'; // 24 hours | ||
| 129 | + headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept"; | ||
| 130 | + | ||
| 131 | + if (opt) { | ||
| 132 | + headers[opt] = val; | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + return headers; | ||
| 136 | + } catch (e) { | ||
| 137 | + return {}; | ||
| 138 | + } | ||
| 139 | +} |
uploads/README.md
0 → 100644
-
请 注册 或 登录 后发表评论