正在显示
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
-
请 注册 或 登录 后发表评论