4f46a164ba
// FREEBIE
199 lines
6.2 KiB
JavaScript
199 lines
6.2 KiB
JavaScript
(function(window) {
|
|
// internal: same as jQuery.extend(true, args...)
|
|
var extend = function() {
|
|
var target = arguments[0],
|
|
sources = [].slice.call(arguments, 1);
|
|
for (var i = 0; i < sources.length; ++i) {
|
|
var src = sources[i];
|
|
for (key in src) {
|
|
var val = src[key];
|
|
target[key] = typeof val === "object"
|
|
? extend(typeof target[key] === "object" ? target[key] : {}, val)
|
|
: val;
|
|
}
|
|
}
|
|
return target;
|
|
};
|
|
|
|
var WORKER_FILE = {
|
|
wav: "WebAudioRecorderWav.js",
|
|
ogg: "WebAudioRecorderOgg.js",
|
|
mp3: "WebAudioRecorderMp3.js"
|
|
};
|
|
|
|
// default configs
|
|
var CONFIGS = {
|
|
workerDir: "/", // worker scripts dir (end with /)
|
|
numChannels: 2, // number of channels
|
|
encoding: "wav", // encoding (can be changed at runtime)
|
|
|
|
// runtime options
|
|
options: {
|
|
timeLimit: 300, // recording time limit (sec)
|
|
encodeAfterRecord: false, // process encoding after recording
|
|
progressInterval: 1000, // encoding progress report interval (millisec)
|
|
bufferSize: undefined, // buffer size (use browser default)
|
|
|
|
// encoding-specific options
|
|
wav: {
|
|
mimeType: "audio/wav"
|
|
},
|
|
ogg: {
|
|
mimeType: "audio/ogg",
|
|
quality: 0.5 // (VBR only): quality = [-0.1 .. 1]
|
|
},
|
|
mp3: {
|
|
mimeType: "audio/mpeg",
|
|
bitRate: 160 // (CBR only): bit rate = [64 .. 320]
|
|
}
|
|
}
|
|
};
|
|
|
|
// constructor
|
|
var WebAudioRecorder = function(sourceNode, configs) {
|
|
extend(this, CONFIGS, configs || {});
|
|
this.context = sourceNode.context;
|
|
if (this.context.createScriptProcessor == null)
|
|
this.context.createScriptProcessor = this.context.createJavaScriptNode;
|
|
this.input = this.context.createGain();
|
|
sourceNode.connect(this.input);
|
|
this.buffer = [];
|
|
this.initWorker();
|
|
};
|
|
|
|
// instance methods
|
|
extend(WebAudioRecorder.prototype, {
|
|
isRecording: function() { return this.processor != null; },
|
|
|
|
setEncoding: function(encoding) {
|
|
if (this.isRecording())
|
|
this.error("setEncoding: cannot set encoding during recording");
|
|
else if (this.encoding !== encoding) {
|
|
this.encoding = encoding;
|
|
this.initWorker();
|
|
}
|
|
},
|
|
|
|
setOptions: function(options) {
|
|
if (this.isRecording())
|
|
this.error("setOptions: cannot set options during recording");
|
|
else {
|
|
extend(this.options, options);
|
|
this.worker.postMessage({ command: "options", options: this.options });
|
|
}
|
|
},
|
|
|
|
startRecording: function() {
|
|
if (this.isRecording())
|
|
this.error("startRecording: previous recording is running");
|
|
else {
|
|
var numChannels = this.numChannels,
|
|
buffer = this.buffer,
|
|
worker = this.worker;
|
|
this.processor = this.context.createScriptProcessor(
|
|
this.options.bufferSize,
|
|
this.numChannels, this.numChannels);
|
|
this.input.connect(this.processor);
|
|
this.processor.connect(this.context.destination);
|
|
this.processor.onaudioprocess = function(event) {
|
|
for (var ch = 0; ch < numChannels; ++ch)
|
|
buffer[ch] = event.inputBuffer.getChannelData(ch);
|
|
worker.postMessage({ command: "record", buffer: buffer });
|
|
};
|
|
this.worker.postMessage({
|
|
command: "start",
|
|
bufferSize: this.processor.bufferSize
|
|
});
|
|
this.startTime = Date.now();
|
|
}
|
|
},
|
|
|
|
recordingTime: function() {
|
|
return this.isRecording() ? (Date.now() - this.startTime) * 0.001 : null;
|
|
},
|
|
|
|
cancelRecording: function() {
|
|
if (this.isRecording()) {
|
|
this.input.disconnect();
|
|
this.processor.disconnect();
|
|
delete this.processor;
|
|
this.worker.postMessage({ command: "cancel" });
|
|
} else
|
|
this.error("cancelRecording: no recording is running");
|
|
},
|
|
|
|
finishRecording: function() {
|
|
if (this.isRecording()) {
|
|
this.input.disconnect();
|
|
this.processor.disconnect();
|
|
delete this.processor;
|
|
this.worker.postMessage({ command: "finish" });
|
|
} else
|
|
this.error("finishRecording: no recording is running");
|
|
},
|
|
|
|
cancelEncoding: function() {
|
|
if (this.options.encodeAfterRecord)
|
|
if (this.isRecording())
|
|
this.error("cancelEncoding: recording is not finished");
|
|
else {
|
|
this.onEncodingCanceled(this);
|
|
this.initWorker();
|
|
}
|
|
else
|
|
this.error("cancelEncoding: invalid method call");
|
|
},
|
|
|
|
initWorker: function() {
|
|
if (this.worker != null)
|
|
this.worker.terminate();
|
|
this.onEncoderLoading(this, this.encoding);
|
|
this.worker = new Worker(this.workerDir + WORKER_FILE[this.encoding]);
|
|
var _this = this;
|
|
this.worker.onmessage = function(event) {
|
|
var data = event.data;
|
|
switch (data.command) {
|
|
case "loaded":
|
|
_this.onEncoderLoaded(_this, _this.encoding);
|
|
break;
|
|
case "timeout":
|
|
_this.onTimeout(_this);
|
|
break;
|
|
case "progress":
|
|
_this.onEncodingProgress(_this, data.progress);
|
|
break;
|
|
case "complete":
|
|
_this.onComplete(_this, data.blob);
|
|
break;
|
|
case "error":
|
|
_this.error(data.message);
|
|
}
|
|
};
|
|
this.worker.postMessage({
|
|
command: "init",
|
|
config: {
|
|
sampleRate: this.context.sampleRate,
|
|
numChannels: this.numChannels
|
|
},
|
|
options: this.options
|
|
});
|
|
},
|
|
|
|
error: function(message) {
|
|
this.onError(this, "WebAudioRecorder.js:" + message);
|
|
},
|
|
|
|
// event handlers
|
|
onEncoderLoading: function(recorder, encoding) {},
|
|
onEncoderLoaded: function(recorder, encoding) {},
|
|
onTimeout: function(recorder) { recorder.finishRecording(); },
|
|
onEncodingProgress: function (recorder, progress) {},
|
|
onEncodingCanceled: function(recorder) {},
|
|
onComplete: function(recorder, blob) {
|
|
recorder.onError(recorder, "WebAudioRecorder.js: You must override .onComplete event");
|
|
},
|
|
onError: function(recorder, message) { console.log(message); }
|
|
});
|
|
|
|
window.WebAudioRecorder = WebAudioRecorder;
|
|
})(window);
|