From f0e5984d81838177911404e8f5e2500f620d1b88 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 27 Apr 2015 08:24:28 -0400 Subject: [PATCH 1/4] Fixed bug with granule position --- oggopus.js | 18 +++++++++++------- recorder.js | 35 +++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/oggopus.js b/oggopus.js index 9e3df299..0c3dff01 100644 --- a/oggopus.js +++ b/oggopus.js @@ -28,11 +28,10 @@ var OggOpus = function( config ){ OggOpus.prototype.encode = function( samples ) { var sampleIndex = 0; - var lengthToCopy; while ( sampleIndex < samples.length ) { - lengthToCopy = Math.min( this.encoderBufferLength - this.encoderBufferIndex, samples.length - sampleIndex ); + var lengthToCopy = Math.min( this.encoderBufferLength - this.encoderBufferIndex, samples.length - sampleIndex ); this.encoderBuffer.set( samples.subarray( sampleIndex, sampleIndex+lengthToCopy ), this.encoderBufferIndex ); sampleIndex += lengthToCopy; this.encoderBufferIndex += lengthToCopy; @@ -172,18 +171,23 @@ OggOpus.prototype.segmentPacket = function( packetLength ) { var packetIndex = 0; while ( packetLength >= 0 ) { + + if ( this.segmentTableIndex === 255 ) { + this.generatePage(); + this.headerType = 1; + } + var segmentLength = Math.min( packetLength, 255 ); this.segmentTable[ this.segmentTableIndex++ ] = segmentLength; this.segmentData.set( this.encoderOutputBuffer.subarray( packetIndex, packetIndex + segmentLength ), this.segmentDataIndex ); this.segmentDataIndex += segmentLength; packetIndex += segmentLength; packetLength -= 255; - - if ( this.segmentTableIndex === 255 ) { - this.generatePage(); - this.headerType = ( packetLength >= 0 ) ? 1 : 0; - } } this.granulePosition += ( 48 * this.encoderFrameSize ); + if ( this.segmentTableIndex === 255 ) { + this.generatePage(); + this.headerType = 0; + } }; diff --git a/recorder.js b/recorder.js index f065b094..411c690f 100755 --- a/recorder.js +++ b/recorder.js @@ -49,25 +49,28 @@ Recorder.prototype.createAudioNodes = function(){ this.monitorNode = this.audioContext.createGain(); this.setMonitorGain( this.config.monitorGain ); - // 6th order butterworth if ( this.config.sampleRate < this.audioContext.sampleRate ) { - this.filterNode = this.audioContext.createBiquadFilter(); - this.filterNode2 = this.audioContext.createBiquadFilter(); - this.filterNode3 = this.audioContext.createBiquadFilter(); - this.filterNode.type = this.filterNode2.type = this.filterNode3.type = "lowpass"; - - var nyquistFreq = this.config.sampleRate / 2; - this.filterNode.frequency.value = this.filterNode2.frequency.value = this.filterNode3.frequency.value = nyquistFreq - ( nyquistFreq / 3.5355 ); - this.filterNode.Q.value = 0.51764; - this.filterNode2.Q.value = 0.70711; - this.filterNode3.Q.value = 1.93184; - - this.filterNode.connect( this.filterNode2 ); - this.filterNode2.connect( this.filterNode3 ); - this.filterNode3.connect( this.scriptProcessorNode ); + this.createButterworthFilter(); } }; +Recorder.prototype.createButterworthFilter = function(){ + this.filterNode = this.audioContext.createBiquadFilter(); + this.filterNode2 = this.audioContext.createBiquadFilter(); + this.filterNode3 = this.audioContext.createBiquadFilter(); + this.filterNode.type = this.filterNode2.type = this.filterNode3.type = "lowpass"; + + var nyquistFreq = this.config.sampleRate / 2; + this.filterNode.frequency.value = this.filterNode2.frequency.value = this.filterNode3.frequency.value = nyquistFreq - ( nyquistFreq / 3.5355 ); + this.filterNode.Q.value = 0.51764; + this.filterNode2.Q.value = 0.70711; + this.filterNode3.Q.value = 1.93184; + + this.filterNode.connect( this.filterNode2 ); + this.filterNode2.connect( this.filterNode3 ); + this.filterNode3.connect( this.scriptProcessorNode ); +}; + Recorder.prototype.initStream = function(){ var that = this; navigator.getUserMedia( @@ -152,7 +155,7 @@ Recorder.prototype.start = function(){ this.state = "recording"; this.recordingTime = 0; - this.recordBuffers = function(){ delete this.recordBuffers }; + this.recordBuffers = function(){ delete this.recordBuffers }; // First buffer can contain old data this.eventTarget.dispatchEvent( new Event( 'start' ) ); this.eventTarget.dispatchEvent( new CustomEvent( 'recordingProgress', { "detail": this.recordingTime } ) ); } From 74e78dec5c46d3cceb20bbac8c0ab6d0c0fc0f18 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 27 Apr 2015 18:19:16 -0400 Subject: [PATCH 2/4] Adjusted stream events Added streamReady and renamed recordingError to streamError --- README.md | 2 +- example.html | 11 +++++++---- recorder.js | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 23f9e37a..4040f79d 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The Opus encoder will not work if the value is not 8000, 12000, 16000, 24000 or rec.addEventListener( type, listener[, useCapture] ) -**addEventListener** will add an event listener to the event target. Custom events are "recordingProgress", "recordingError", "dataAvailable", "start", "pause", "resume" and "stop". +**addEventListener** will add an event listener to the event target. Available events are "recordingProgress", "streamError", "streamReady", dataAvailable", "start", "pause", "resume" and "stop". rec.setMonitorGain( gain ) diff --git a/example.html b/example.html index 796fbee5..3c51b5cd 100644 --- a/example.html +++ b/example.html @@ -72,9 +72,6 @@

Log

init.addEventListener( "click", function(){ - init.disabled = pause.disabled = resume.disabled = stopButton.disabled = true; - start.disabled = false; - recorder = new Recorder({ monitorGain: monitorGain.value, numberOfChannels: numberOfChannels.value, @@ -111,10 +108,16 @@

Log

screenLogger('Recorded ' + e.detail + ' seconds'); }); - recorder.addEventListener( "recordingError", function(e){ + recorder.addEventListener( "streamError", function(e){ screenLogger('Error encountered: ' + e.error.name ); }); + recorder.addEventListener( "streamReady", function(e){ + screenLogger('Audio stream is ready. Recording can begin.'); + init.disabled = pause.disabled = resume.disabled = stopButton.disabled = true; + start.disabled = false; + }); + recorder.addEventListener( "dataAvailable", function(e){ var fileName = new Date().toISOString() + "." + e.detail.type.split("/")[1]; var url = URL.createObjectURL( e.detail ); diff --git a/recorder.js b/recorder.js index 411c690f..8a1ee977 100755 --- a/recorder.js +++ b/recorder.js @@ -80,9 +80,10 @@ Recorder.prototype.initStream = function(){ that.sourceNode = that.audioContext.createMediaStreamSource( stream ); that.sourceNode.connect( that.filterNode || that.scriptProcessorNode ); that.sourceNode.connect( that.monitorNode ); + that.eventTarget.dispatchEvent( new Event( "streamReady" ) ); }, function ( e ) { - that.eventTarget.dispatchEvent( new ErrorEvent( "recordingError", { error: e } ) ); + that.eventTarget.dispatchEvent( new ErrorEvent( "streamError", { error: e } ) ); } ); }; From 8041fca5e255b33816e7d77ead9b4cb3c0e955ca Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 27 Apr 2015 18:43:29 -0400 Subject: [PATCH 3/4] copy eventTarget methods instead of wrapping --- recorder.js | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/recorder.js b/recorder.js index 8a1ee977..e62d57dc 100755 --- a/recorder.js +++ b/recorder.js @@ -25,9 +25,12 @@ var Recorder = function( config ){ } }; + var eventTarget = document.createDocumentFragment(); + this.addEventListener = eventTarget.addEventListener; + this.dispatchEvent = eventTarget.dispatchEvent; + this.removeEventListener = eventTarget.removeEventListener; this.config = config; this.state = "inactive"; - this.eventTarget = document.createDocumentFragment(); this.createAudioNodes(); this.initStream(); }; @@ -36,10 +39,6 @@ Recorder.isRecordingSupported = function(){ return AudioContext && navigator.getUserMedia; }; -Recorder.prototype.addEventListener = function( type, listener, useCapture ){ - this.eventTarget.addEventListener( type, listener, useCapture ); -}; - Recorder.prototype.audioContext = new AudioContext(); Recorder.prototype.createAudioNodes = function(){ @@ -80,10 +79,10 @@ Recorder.prototype.initStream = function(){ that.sourceNode = that.audioContext.createMediaStreamSource( stream ); that.sourceNode.connect( that.filterNode || that.scriptProcessorNode ); that.sourceNode.connect( that.monitorNode ); - that.eventTarget.dispatchEvent( new Event( "streamReady" ) ); + that.dispatchEvent( new Event( "streamReady" ) ); }, function ( e ) { - that.eventTarget.dispatchEvent( new ErrorEvent( "streamError", { error: e } ) ); + that.dispatchEvent( new ErrorEvent( "streamError", { error: e } ) ); } ); }; @@ -91,7 +90,7 @@ Recorder.prototype.initStream = function(){ Recorder.prototype.pause = function(){ if ( this.state === "recording" ){ this.state = "paused"; - this.eventTarget.dispatchEvent( new Event( 'pause' ) ); + this.dispatchEvent( new Event( 'pause' ) ); } }; @@ -105,14 +104,10 @@ Recorder.prototype.recordBuffers = function( inputBuffer ){ this.worker.postMessage({ command: "recordBuffers", buffers: buffers }); this.recordingTime += inputBuffer.duration; - this.eventTarget.dispatchEvent( new CustomEvent( 'recordingProgress', { "detail": this.recordingTime } ) ); + this.dispatchEvent( new CustomEvent( 'recordingProgress', { "detail": this.recordingTime } ) ); } }; -Recorder.prototype.removeEventListener = function( type, listener, useCapture ){ - this.eventTarget.removeEventListener( type, listener, useCapture ); -}; - Recorder.prototype.requestData = function( callback ) { if ( this.state !== "recording" ) { this.worker.postMessage({ command: "requestData" }); @@ -122,7 +117,7 @@ Recorder.prototype.requestData = function( callback ) { Recorder.prototype.resume = function( callback ) { if ( this.state === "paused" ) { this.state = "recording"; - this.eventTarget.dispatchEvent( new Event( 'resume' ) ); + this.dispatchEvent( new Event( 'resume' ) ); } }; @@ -133,13 +128,10 @@ Recorder.prototype.setMonitorGain = function( gain ){ Recorder.prototype.start = function(){ if ( this.state === "inactive" && this.sourceNode ) { - this.monitorNode.connect( this.audioContext.destination ); - this.scriptProcessorNode.connect( this.audioContext.destination ); - var that = this; this.worker = new Worker( this.config.workerPath ); this.worker.addEventListener( "message", function( e ) { - that.eventTarget.dispatchEvent( new CustomEvent( 'dataAvailable', { + that.dispatchEvent( new CustomEvent( 'dataAvailable', { "detail": new Blob( [e.data], { type: that.config.recordOpus ? "audio/ogg" : "audio/wav" } ) })); }); @@ -156,9 +148,11 @@ Recorder.prototype.start = function(){ this.state = "recording"; this.recordingTime = 0; + this.monitorNode.connect( this.audioContext.destination ); + this.scriptProcessorNode.connect( this.audioContext.destination ); this.recordBuffers = function(){ delete this.recordBuffers }; // First buffer can contain old data - this.eventTarget.dispatchEvent( new Event( 'start' ) ); - this.eventTarget.dispatchEvent( new CustomEvent( 'recordingProgress', { "detail": this.recordingTime } ) ); + this.dispatchEvent( new Event( 'start' ) ); + this.dispatchEvent( new CustomEvent( 'recordingProgress', { "detail": this.recordingTime } ) ); } }; @@ -167,7 +161,7 @@ Recorder.prototype.stop = function(){ this.monitorNode.disconnect(); this.scriptProcessorNode.disconnect(); this.state = "inactive"; - this.eventTarget.dispatchEvent( new Event( 'stop' ) ); + this.dispatchEvent( new Event( 'stop' ) ); this.worker.postMessage({ command: "requestData" }); this.worker.postMessage({ command: "stop" }); } From 832c48ef13c160edb73ed84c3d5bc6642e5c7f0c Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 27 Apr 2015 18:47:33 -0400 Subject: [PATCH 4/4] Undo last commit Seems we can't clone event target actually --- recorder.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/recorder.js b/recorder.js index e62d57dc..23bba6e9 100755 --- a/recorder.js +++ b/recorder.js @@ -25,12 +25,9 @@ var Recorder = function( config ){ } }; - var eventTarget = document.createDocumentFragment(); - this.addEventListener = eventTarget.addEventListener; - this.dispatchEvent = eventTarget.dispatchEvent; - this.removeEventListener = eventTarget.removeEventListener; this.config = config; this.state = "inactive"; + this.eventTarget = document.createDocumentFragment(); this.createAudioNodes(); this.initStream(); }; @@ -39,6 +36,10 @@ Recorder.isRecordingSupported = function(){ return AudioContext && navigator.getUserMedia; }; +Recorder.prototype.addEventListener = function( type, listener, useCapture ){ + this.eventTarget.addEventListener( type, listener, useCapture ); +}; + Recorder.prototype.audioContext = new AudioContext(); Recorder.prototype.createAudioNodes = function(){ @@ -79,10 +80,10 @@ Recorder.prototype.initStream = function(){ that.sourceNode = that.audioContext.createMediaStreamSource( stream ); that.sourceNode.connect( that.filterNode || that.scriptProcessorNode ); that.sourceNode.connect( that.monitorNode ); - that.dispatchEvent( new Event( "streamReady" ) ); + that.eventTarget.dispatchEvent( new Event( "streamReady" ) ); }, function ( e ) { - that.dispatchEvent( new ErrorEvent( "streamError", { error: e } ) ); + that.eventTarget.dispatchEvent( new ErrorEvent( "streamError", { error: e } ) ); } ); }; @@ -90,7 +91,7 @@ Recorder.prototype.initStream = function(){ Recorder.prototype.pause = function(){ if ( this.state === "recording" ){ this.state = "paused"; - this.dispatchEvent( new Event( 'pause' ) ); + this.eventTarget.dispatchEvent( new Event( 'pause' ) ); } }; @@ -104,10 +105,14 @@ Recorder.prototype.recordBuffers = function( inputBuffer ){ this.worker.postMessage({ command: "recordBuffers", buffers: buffers }); this.recordingTime += inputBuffer.duration; - this.dispatchEvent( new CustomEvent( 'recordingProgress', { "detail": this.recordingTime } ) ); + this.eventTarget.dispatchEvent( new CustomEvent( 'recordingProgress', { "detail": this.recordingTime } ) ); } }; +Recorder.prototype.removeEventListener = function( type, listener, useCapture ){ + this.eventTarget.removeEventListener( type, listener, useCapture ); +}; + Recorder.prototype.requestData = function( callback ) { if ( this.state !== "recording" ) { this.worker.postMessage({ command: "requestData" }); @@ -117,7 +122,7 @@ Recorder.prototype.requestData = function( callback ) { Recorder.prototype.resume = function( callback ) { if ( this.state === "paused" ) { this.state = "recording"; - this.dispatchEvent( new Event( 'resume' ) ); + this.eventTarget.dispatchEvent( new Event( 'resume' ) ); } }; @@ -131,7 +136,7 @@ Recorder.prototype.start = function(){ var that = this; this.worker = new Worker( this.config.workerPath ); this.worker.addEventListener( "message", function( e ) { - that.dispatchEvent( new CustomEvent( 'dataAvailable', { + that.eventTarget.dispatchEvent( new CustomEvent( 'dataAvailable', { "detail": new Blob( [e.data], { type: that.config.recordOpus ? "audio/ogg" : "audio/wav" } ) })); }); @@ -151,8 +156,8 @@ Recorder.prototype.start = function(){ this.monitorNode.connect( this.audioContext.destination ); this.scriptProcessorNode.connect( this.audioContext.destination ); this.recordBuffers = function(){ delete this.recordBuffers }; // First buffer can contain old data - this.dispatchEvent( new Event( 'start' ) ); - this.dispatchEvent( new CustomEvent( 'recordingProgress', { "detail": this.recordingTime } ) ); + this.eventTarget.dispatchEvent( new Event( 'start' ) ); + this.eventTarget.dispatchEvent( new CustomEvent( 'recordingProgress', { "detail": this.recordingTime } ) ); } }; @@ -161,7 +166,7 @@ Recorder.prototype.stop = function(){ this.monitorNode.disconnect(); this.scriptProcessorNode.disconnect(); this.state = "inactive"; - this.dispatchEvent( new Event( 'stop' ) ); + this.eventTarget.dispatchEvent( new Event( 'stop' ) ); this.worker.postMessage({ command: "requestData" }); this.worker.postMessage({ command: "stop" }); }