Add voice notes

// FREEBIE
This commit is contained in:
lilia 2016-08-15 15:36:29 -07:00
parent cc15af549b
commit 4f46a164ba
22 changed files with 47762 additions and 14 deletions

View file

@ -0,0 +1,199 @@
(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);

View file

@ -0,0 +1,91 @@
importScripts("Mp3LameEncoder.min.js");
var NUM_CH = 2, // constant
sampleRate = 44100,
options = undefined,
maxBuffers = undefined,
encoder = undefined,
recBuffers = undefined,
bufferCount = 0;
function error(message) {
self.postMessage({ command: "error", message: "mp3: " + message });
}
function init(data) {
if (data.config.numChannels === NUM_CH) {
sampleRate = data.config.sampleRate;
options = data.options;
} else
error("numChannels must be " + NUM_CH);
};
function setOptions(opt) {
if (encoder || recBuffers)
error("cannot set options during recording");
else
options = opt;
}
function start(bufferSize) {
maxBuffers = Math.ceil(options.timeLimit * sampleRate / bufferSize);
if (options.encodeAfterRecord)
recBuffers = [];
else
encoder = new Mp3LameEncoder(sampleRate, options.mp3.bitRate);
}
function record(buffer) {
if (bufferCount++ < maxBuffers)
if (encoder)
encoder.encode(buffer);
else
recBuffers.push(buffer);
else
self.postMessage({ command: "timeout" });
};
function postProgress(progress) {
self.postMessage({ command: "progress", progress: progress });
};
function finish() {
if (recBuffers) {
postProgress(0);
encoder = new Mp3LameEncoder(sampleRate, options.mp3.bitRate);
var timeout = Date.now() + options.progressInterval;
while (recBuffers.length > 0) {
encoder.encode(recBuffers.shift());
var now = Date.now();
if (now > timeout) {
postProgress((bufferCount - recBuffers.length) / bufferCount);
timeout = now + options.progressInterval;
}
}
postProgress(1);
}
self.postMessage({
command: "complete",
blob: encoder.finish(options.mp3.mimeType)
});
cleanup();
};
function cleanup() {
encoder = recBuffers = undefined;
bufferCount = 0;
}
self.onmessage = function(event) {
var data = event.data;
switch (data.command) {
case "init": init(data); break;
case "options": setOptions(data.options); break;
case "start": start(data.bufferSize); break;
case "record": record(data.buffer); break;
case "finish": finish(); break;
case "cancel": cleanup();
}
};
self.postMessage({ command: "loaded" });