1 <!DOCTYPE html> 2 3 <html> 4 <head> 5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 6 <title>Live input record and playback</title> 7 <style type=‘text/css‘> 8 ul { list-style: none; } 9 #recordingslist audio { display: block; margin-bottom: 10px; } 10 </style> 11 </head> 12 <body> 13 14 <h1>Recorder.js simple WAV export example</h1> 15 16 <p>Make sure you are using a recent version of Google Chrome.</p> 17 <p>Also before you enable microphone input either plug in headphones or turn the volume down if you want to avoid ear splitting feedback!</p> 18 19 <button onclick="startRecording(this);">开始录音</button> 20 <button onclick="stopRecording(this);" disabled>停止</button> 21 22 <h2>Recordings</h2> 23 <ul id="recordingslist"></ul> 24 25 <h2>Log</h2> 26 <pre id="log"></pre> 27 28 <script> 29 function __log(e, data) { 30 log.innerHTML += "\n" + e + " " + (data || ‘‘); 31 } 32 33 var audio_context; 34 var recorder; 35 36 function startUserMedia(stream) { 37 var input = audio_context.createMediaStreamSource(stream); 38 __log(‘Media stream created.‘); 39 40 // Uncomment if you want the audio to feedback directly 41 //input.connect(audio_context.destination); 42 //__log(‘Input connected to audio context destination.‘); 43 44 recorder = new Recorder(input); 45 __log(‘Recorder initialised.‘); 46 } 47 48 function startRecording(button) { 49 recorder && recorder.record(); 50 button.disabled = true; 51 button.nextElementSibling.disabled = false; 52 __log(‘Recording...‘); 53 } 54 55 function stopRecording(button) { 56 recorder && recorder.stop(); 57 button.disabled = true; 58 button.previousElementSibling.disabled = false; 59 __log(‘Stopped recording.‘); 60 61 // create WAV download link using audio data blob 62 createDownloadLink(); 63 64 recorder.clear(); 65 } 66 67 function createDownloadLink() { 68 recorder && recorder.exportWAV(function(blob) { 69 var url = URL.createObjectURL(blob); 70 var li = document.createElement(‘li‘); 71 var au = document.createElement(‘audio‘); 72 var hf = document.createElement(‘a‘); 73 74 au.controls = true; 75 au.src = url; 76 hf.href = url; 77 hf.download = new Date().toISOString() + ‘.wav‘; 78 hf.innerHTML = hf.download; 79 li.appendChild(au); 80 li.appendChild(hf); 81 recordingslist.appendChild(li); 82 }); 83 } 84 85 window.onload = function init() { 86 try { 87 // webkit shim 88 window.AudioContext = window.AudioContext || window.webkitAudioContext; 89 navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia; 90 window.URL = window.URL || window.webkitURL; 91 92 audio_context = new AudioContext; 93 __log(‘Audio context set up.‘); 94 __log(‘navigator.getUserMedia ‘ + (navigator.getUserMedia ? ‘available.‘ : ‘not present!‘)); 95 } catch (e) { 96 alert(‘No web audio support in this browser!‘); 97 } 98 99 navigator.getUserMedia({audio: true}, startUserMedia, function(e) { 100 __log(‘No live audio input: ‘ + e); 101 }); 102 }; 103 </script> 104 105 <script src="./dist/recorder.js"></script> 106 </body> 107 </html>
这是HTML代码部份,
1 (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Recorder = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module ‘"+o+"‘");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 2 "use strict"; 3 4 module.exports = require("./recorder").Recorder; 5 6 },{"./recorder":2}],2:[function(require,module,exports){ 7 ‘use strict‘; 8 9 var _createClass = (function () { 10 function defineProperties(target, props) { 11 for (var i = 0; i < props.length; i++) { 12 var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor); 13 } 14 }return function (Constructor, protoProps, staticProps) { 15 if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor; 16 }; 17 })(); 18 19 Object.defineProperty(exports, "__esModule", { 20 value: true 21 }); 22 exports.Recorder = undefined; 23 24 var _inlineWorker = require(‘inline-worker‘); 25 26 var _inlineWorker2 = _interopRequireDefault(_inlineWorker); 27 28 function _interopRequireDefault(obj) { 29 return obj && obj.__esModule ? obj : { default: obj }; 30 } 31 32 function _classCallCheck(instance, Constructor) { 33 if (!(instance instanceof Constructor)) { 34 throw new TypeError("Cannot call a class as a function"); 35 } 36 } 37 38 var Recorder = exports.Recorder = (function () { 39 function Recorder(source, cfg) { 40 var _this = this; 41 42 _classCallCheck(this, Recorder); 43 44 this.config = { 45 bufferLen: 4096, 46 numChannels: 2, 47 mimeType: ‘audio/wav‘ 48 }; 49 this.recording = false; 50 this.callbacks = { 51 getBuffer: [], 52 exportWAV: [] 53 }; 54 55 Object.assign(this.config, cfg); 56 this.context = source.context; 57 this.node = (this.context.createScriptProcessor || this.context.createJavaScriptNode).call(this.context, this.config.bufferLen, this.config.numChannels, this.config.numChannels); 58 59 this.node.onaudioprocess = function (e) { 60 if (!_this.recording) return; 61 62 var buffer = []; 63 for (var channel = 0; channel < _this.config.numChannels; channel++) { 64 buffer.push(e.inputBuffer.getChannelData(channel)); 65 } 66 _this.worker.postMessage({ 67 command: ‘record‘, 68 buffer: buffer 69 }); 70 }; 71 72 source.connect(this.node); 73 this.node.connect(this.context.destination); //this should not be necessary 74 75 var self = {}; 76 this.worker = new _inlineWorker2.default(function () { 77 var recLength = 0, 78 recBuffers = [], 79 sampleRate = undefined, 80 numChannels = undefined; 81 82 self.onmessage = function (e) { 83 switch (e.data.command) { 84 case ‘init‘: 85 init(e.data.config); 86 break; 87 case ‘record‘: 88 record(e.data.buffer); 89 break; 90 case ‘exportWAV‘: 91 exportWAV(e.data.type); 92 break; 93 case ‘getBuffer‘: 94 getBuffer(); 95 break; 96 case ‘clear‘: 97 clear(); 98 break; 99 } 100 }; 101 102 function init(config) { 103 sampleRate = config.sampleRate; 104 numChannels = config.numChannels; 105 initBuffers(); 106 } 107 108 function record(inputBuffer) { 109 for (var channel = 0; channel < numChannels; channel++) { 110 recBuffers[channel].push(inputBuffer[channel]); 111 } 112 recLength += inputBuffer[0].length; 113 } 114 115 function exportWAV(type) { 116 var buffers = []; 117 for (var channel = 0; channel < numChannels; channel++) { 118 buffers.push(mergeBuffers(recBuffers[channel], recLength)); 119 } 120 var interleaved = undefined; 121 if (numChannels === 2) { 122 interleaved = interleave(buffers[0], buffers[1]); 123 } else { 124 interleaved = buffers[0]; 125 } 126 var dataview = encodeWAV(interleaved); 127 var audioBlob = new Blob([dataview], { type: type }); 128 129 self.postMessage({ command: ‘exportWAV‘, data: audioBlob }); 130 } 131 132 function getBuffer() { 133 var buffers = []; 134 for (var channel = 0; channel < numChannels; channel++) { 135 buffers.push(mergeBuffers(recBuffers[channel], recLength)); 136 } 137 self.postMessage({ command: ‘getBuffer‘, data: buffers }); 138 } 139 140 function clear() { 141 recLength = 0; 142 recBuffers = []; 143 initBuffers(); 144 } 145 146 function initBuffers() { 147 for (var channel = 0; channel < numChannels; channel++) { 148 recBuffers[channel] = []; 149 } 150 } 151 152 function mergeBuffers(recBuffers, recLength) { 153 var result = new Float32Array(recLength); 154 var offset = 0; 155 for (var i = 0; i < recBuffers.length; i++) { 156 result.set(recBuffers[i], offset); 157 offset += recBuffers[i].length; 158 } 159 return result; 160 } 161 162 function interleave(inputL, inputR) { 163 var length = inputL.length + inputR.length; 164 var result = new Float32Array(length); 165 166 var index = 0, 167 inputIndex = 0; 168 169 while (index < length) { 170 result[index++] = inputL[inputIndex]; 171 result[index++] = inputR[inputIndex]; 172 inputIndex++; 173 } 174 return result; 175 } 176 177 function floatTo16BitPCM(output, offset, input) { 178 for (var i = 0; i < input.length; i++, offset += 2) { 179 var s = Math.max(-1, Math.min(1, input[i])); 180 output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); 181 } 182 } 183 184 function writeString(view, offset, string) { 185 for (var i = 0; i < string.length; i++) { 186 view.setUint8(offset + i, string.charCodeAt(i)); 187 } 188 } 189 190 function encodeWAV(samples) { 191 var buffer = new ArrayBuffer(44 + samples.length * 2); 192 var view = new DataView(buffer); 193 194 /* RIFF identifier */ 195 writeString(view, 0, ‘RIFF‘); 196 /* RIFF chunk length */ 197 view.setUint32(4, 36 + samples.length * 2, true); 198 /* RIFF type */ 199 writeString(view, 8, ‘WAVE‘); 200 /* format chunk identifier */ 201 writeString(view, 12, ‘fmt ‘); 202 /* format chunk length */ 203 view.setUint32(16, 16, true); 204 /* sample format (raw) */ 205 view.setUint16(20, 1, true); 206 /* channel count */ 207 view.setUint16(22, numChannels, true); 208 /* sample rate */ 209 view.setUint32(24, sampleRate, true); 210 /* byte rate (sample rate * block align) */ 211 view.setUint32(28, sampleRate * 4, true); 212 /* block align (channel count * bytes per sample) */ 213 view.setUint16(32, numChannels * 2, true); 214 /* bits per sample */ 215 view.setUint16(34, 16, true); 216 /* data chunk identifier */ 217 writeString(view, 36, ‘data‘); 218 /* data chunk length */ 219 view.setUint32(40, samples.length * 2, true); 220 221 floatTo16BitPCM(view, 44, samples); 222 223 return view; 224 } 225 }, self); 226 227 this.worker.postMessage({ 228 command: ‘init‘, 229 config: { 230 sampleRate: this.context.sampleRate, 231 numChannels: this.config.numChannels 232 } 233 }); 234 235 this.worker.onmessage = function (e) { 236 var cb = _this.callbacks[e.data.command].pop(); 237 if (typeof cb == ‘function‘) { 238 cb(e.data.data); 239 } 240 }; 241 } 242 243 _createClass(Recorder, [{ 244 key: ‘record‘, 245 value: function record() { 246 this.recording = true; 247 } 248 }, { 249 key: ‘stop‘, 250 value: function stop() { 251 this.recording = false; 252 } 253 }, { 254 key: ‘clear‘, 255 value: function clear() { 256 this.worker.postMessage({ command: ‘clear‘ }); 257 } 258 }, { 259 key: ‘getBuffer‘, 260 value: function getBuffer(cb) { 261 cb = cb || this.config.callback; 262 if (!cb) throw new Error(‘Callback not set‘); 263 264 this.callbacks.getBuffer.push(cb); 265 266 this.worker.postMessage({ command: ‘getBuffer‘ }); 267 } 268 }, { 269 key: ‘exportWAV‘, 270 value: function exportWAV(cb, mimeType) { 271 mimeType = mimeType || this.config.mimeType; 272 cb = cb || this.config.callback; 273 if (!cb) throw new Error(‘Callback not set‘); 274 275 this.callbacks.exportWAV.push(cb); 276 277 this.worker.postMessage({ 278 command: ‘exportWAV‘, 279 type: mimeType 280 }); 281 } 282 }], [{ 283 key: ‘forceDownload‘, 284 value: function forceDownload(blob, filename) { 285 var url = (window.URL || window.webkitURL).createObjectURL(blob); 286 var link = window.document.createElement(‘a‘); 287 link.href = url; 288 link.download = filename || ‘output.wav‘; 289 var click = document.createEvent("Event"); 290 click.initEvent("click", true, true); 291 link.dispatchEvent(click); 292 } 293 }]); 294 295 return Recorder; 296 })(); 297 298 exports.default = Recorder; 299 300 },{"inline-worker":3}],3:[function(require,module,exports){ 301 "use strict"; 302 303 module.exports = require("./inline-worker"); 304 },{"./inline-worker":4}],4:[function(require,module,exports){ 305 (function (global){ 306 "use strict"; 307 308 var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 309 310 var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 311 312 var WORKER_ENABLED = !!(global === global.window && global.URL && global.Blob && global.Worker); 313 314 var InlineWorker = (function () { 315 function InlineWorker(func, self) { 316 var _this = this; 317 318 _classCallCheck(this, InlineWorker); 319 320 if (WORKER_ENABLED) { 321 var functionBody = func.toString().trim().match(/^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/)[1]; 322 var url = global.URL.createObjectURL(new global.Blob([functionBody], { type: "text/javascript" })); 323 324 return new global.Worker(url); 325 } 326 327 this.self = self; 328 this.self.postMessage = function (data) { 329 setTimeout(function () { 330 _this.onmessage({ data: data }); 331 }, 0); 332 }; 333 334 setTimeout(function () { 335 func.call(self); 336 }, 0); 337 } 338 339 _createClass(InlineWorker, { 340 postMessage: { 341 value: function postMessage(data) { 342 var _this = this; 343 344 setTimeout(function () { 345 _this.self.onmessage({ data: data }); 346 }, 0); 347 } 348 } 349 }); 350 351 return InlineWorker; 352 })(); 353 354 module.exports = InlineWorker; 355 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 356 },{}]},{},[1])(1) 357 });