|
|
|
"use strict";
|
|
|
|
|
|
|
|
var root = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || this;
|
|
|
|
|
|
|
|
(function( global ) {
|
|
|
|
|
|
|
|
var Recorder = function( config ){
|
|
|
|
|
|
|
|
var that = this;
|
|
|
|
|
|
|
|
if ( !Recorder.isRecordingSupported() ) {
|
|
|
|
throw new Error("Recording is not supported in this browser");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.state = "inactive";
|
|
|
|
this.eventTarget = global.document.createDocumentFragment();
|
|
|
|
this.audioContext = new global.AudioContext();
|
|
|
|
this.monitorNode = this.audioContext.createGain();
|
|
|
|
|
|
|
|
this.config = config = config || {};
|
|
|
|
this.config.command = "init";
|
|
|
|
this.config.bufferLength = config.bufferLength || 4096;
|
|
|
|
this.config.monitorGain = config.monitorGain || 0;
|
|
|
|
this.config.numberOfChannels = config.numberOfChannels || 1;
|
|
|
|
this.config.originalSampleRate = this.audioContext.sampleRate;
|
|
|
|
this.config.encoderSampleRate = config.encoderSampleRate || 48000;
|
|
|
|
this.config.encoderPath = config.encoderPath || 'encoderWorker.min.js';
|
|
|
|
this.config.streamPages = config.streamPages || false;
|
|
|
|
this.config.leaveStreamOpen = config.leaveStreamOpen || false;
|
|
|
|
this.config.maxBuffersPerPage = config.maxBuffersPerPage || 40;
|
|
|
|
this.config.encoderApplication = config.encoderApplication || 2049;
|
|
|
|
this.config.encoderFrameSize = config.encoderFrameSize || 20;
|
|
|
|
this.config.resampleQuality = config.resampleQuality || 3;
|
|
|
|
this.config.streamOptions = config.streamOptions || {
|
|
|
|
optional: [],
|
|
|
|
mandatory: {
|
|
|
|
googEchoCancellation: false,
|
|
|
|
googAutoGainControl: false,
|
|
|
|
googNoiseSuppression: false,
|
|
|
|
googHighpassFilter: false
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.setMonitorGain( this.config.monitorGain );
|
|
|
|
this.scriptProcessorNode = this.audioContext.createScriptProcessor( this.config.bufferLength, this.config.numberOfChannels, this.config.numberOfChannels );
|
|
|
|
this.scriptProcessorNode.onaudioprocess = function( e ){
|
|
|
|
that.encodeBuffers( e.inputBuffer );
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.isRecordingSupported = function(){
|
|
|
|
return global.AudioContext && global.navigator && ( global.navigator.getUserMedia || ( global.navigator.mediaDevices && global.navigator.mediaDevices.getUserMedia ) );
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.addEventListener = function( type, listener, useCapture ){
|
|
|
|
this.eventTarget.addEventListener( type, listener, useCapture );
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.clearStream = function() {
|
|
|
|
if ( this.stream ) {
|
|
|
|
|
|
|
|
if ( this.stream.getTracks ) {
|
|
|
|
this.stream.getTracks().forEach(function ( track ) {
|
|
|
|
track.stop();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
this.stream.stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
delete this.stream;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.encodeBuffers = function( inputBuffer ){
|
|
|
|
if ( this.state === "recording" ) {
|
|
|
|
var buffers = [];
|
|
|
|
for ( var i = 0; i < inputBuffer.numberOfChannels; i++ ) {
|
|
|
|
buffers[i] = inputBuffer.getChannelData(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.encoder.postMessage({
|
|
|
|
command: "encode",
|
|
|
|
buffers: buffers
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.initStream = function(){
|
|
|
|
var that = this;
|
|
|
|
|
|
|
|
var onStreamInit = function( stream ){
|
|
|
|
that.stream = stream;
|
|
|
|
that.sourceNode = that.audioContext.createMediaStreamSource( stream );
|
|
|
|
that.sourceNode.connect( that.scriptProcessorNode );
|
|
|
|
that.sourceNode.connect( that.monitorNode );
|
|
|
|
that.eventTarget.dispatchEvent( new global.Event( "streamReady" ) );
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
var onStreamError = function( e ){
|
|
|
|
that.eventTarget.dispatchEvent( new global.ErrorEvent( "streamError", { error: e } ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
var constraints = { audio : this.config.streamOptions };
|
|
|
|
|
|
|
|
if ( this.stream ) {
|
|
|
|
this.eventTarget.dispatchEvent( new global.Event( "streamReady" ) );
|
|
|
|
return global.Promise.resolve( this.stream );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( global.navigator.mediaDevices && global.navigator.mediaDevices.getUserMedia ) {
|
|
|
|
return global.navigator.mediaDevices.getUserMedia( constraints ).then( onStreamInit, onStreamError );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( global.navigator.getUserMedia ) {
|
|
|
|
return new global.Promise( function( resolve, reject ) {
|
|
|
|
global.navigator.getUserMedia( constraints, resolve, reject );
|
|
|
|
}).then( onStreamInit, onStreamError );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.pause = function(){
|
|
|
|
if ( this.state === "recording" ){
|
|
|
|
this.state = "paused";
|
|
|
|
this.eventTarget.dispatchEvent( new global.Event( 'pause' ) );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.removeEventListener = function( type, listener, useCapture ){
|
|
|
|
this.eventTarget.removeEventListener( type, listener, useCapture );
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.resume = function() {
|
|
|
|
if ( this.state === "paused" ) {
|
|
|
|
this.state = "recording";
|
|
|
|
this.eventTarget.dispatchEvent( new global.Event( 'resume' ) );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.setMonitorGain = function( gain ){
|
|
|
|
this.monitorNode.gain.value = gain;
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.start = function(){
|
|
|
|
if ( this.state === "inactive" && this.stream ) {
|
|
|
|
var that = this;
|
|
|
|
this.encoder = new global.Worker( this.config.encoderPath );
|
|
|
|
|
|
|
|
if (this.config.streamPages){
|
|
|
|
this.encoder.addEventListener( "message", function( e ) {
|
|
|
|
that.streamPage( e.data );
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
this.recordedPages = [];
|
|
|
|
this.totalLength = 0;
|
|
|
|
this.encoder.addEventListener( "message", function( e ) {
|
|
|
|
that.storePage( e.data );
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// First buffer can contain old data. Don't encode it.
|
|
|
|
this.encodeBuffers = function(){
|
|
|
|
delete this.encodeBuffers;
|
|
|
|
};
|
|
|
|
|
|
|
|
this.state = "recording";
|
|
|
|
this.monitorNode.connect( this.audioContext.destination );
|
|
|
|
this.scriptProcessorNode.connect( this.audioContext.destination );
|
|
|
|
this.eventTarget.dispatchEvent( new global.Event( 'start' ) );
|
|
|
|
this.encoder.postMessage( this.config );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.stop = function(){
|
|
|
|
if ( this.state !== "inactive" ) {
|
|
|
|
this.state = "inactive";
|
|
|
|
this.monitorNode.disconnect();
|
|
|
|
this.scriptProcessorNode.disconnect();
|
|
|
|
|
|
|
|
if ( !this.config.leaveStreamOpen ) {
|
|
|
|
this.clearStream();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.audioContext.close();
|
|
|
|
this.audioContext = null;
|
|
|
|
|
|
|
|
this.encoder.postMessage({ command: "done" });
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.storePage = function( page ) {
|
|
|
|
if ( page === null ) {
|
|
|
|
var outputData = new Uint8Array( this.totalLength );
|
|
|
|
var outputIndex = 0;
|
|
|
|
|
|
|
|
for ( var i = 0; i < this.recordedPages.length; i++ ) {
|
|
|
|
outputData.set( this.recordedPages[i], outputIndex );
|
|
|
|
outputIndex += this.recordedPages[i].length;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.eventTarget.dispatchEvent( new global.CustomEvent( 'dataAvailable', {
|
|
|
|
detail: outputData
|
|
|
|
}));
|
|
|
|
|
|
|
|
this.recordedPages = [];
|
|
|
|
this.eventTarget.dispatchEvent( new global.Event( 'stop' ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
this.recordedPages.push( page );
|
|
|
|
this.totalLength += page.length;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Recorder.prototype.streamPage = function( page ) {
|
|
|
|
if ( page === null ) {
|
|
|
|
this.eventTarget.dispatchEvent( new global.Event( 'stop' ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
this.eventTarget.dispatchEvent( new global.CustomEvent( 'dataAvailable', {
|
|
|
|
detail: page
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Exports
|
|
|
|
global.Recorder = Recorder;
|
|
|
|
|
|
|
|
if ( typeof define === 'function' && define.amd ) {
|
|
|
|
define( [], function() {
|
|
|
|
return Recorder;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
else if ( typeof module == 'object' && module.exports ) {
|
|
|
|
module.exports = Recorder;
|
|
|
|
}
|
|
|
|
|
|
|
|
})(root);
|