Skip to content

Commit

Permalink
Merge pull request #214 from chris-rudmin/reuseWorkers
Browse files Browse the repository at this point in the history
- Initialize workers on Recorder instantiation.
- Add recorder.close() to tear down all the audio setup and workers, so it can be cleaned up by the browser
- Accept optional config parameter sourceNode which is an instance of MediaStreamAudioSourceNode
- Remove sourceNode parameter from recorder.start()
- Reuse the workers to avoid expensive teardown and setup when doing multiple recordings.
  • Loading branch information
chris-rudmin authored Jul 21, 2020
2 parents 59d2f49 + 28e655b commit 6e2fd1e
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 150 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Creates a recorder instance.
- **monitorGain** - (*optional*) Sets the gain of the monitoring output. Gain is an a-weighted value between `0` and `1`. Defaults to `0`
- **numberOfChannels** - (*optional*) The number of channels to record. `1` = mono, `2` = stereo. Defaults to `1`. Maximum `2` channels are supported.
- **recordingGain** - (*optional*) Sets the gain of the recording input. Gain is an a-weighted value between `0` and `1`. Defaults to `1`
- **sourceNode** - (*optional*) An Instance of MediaStreamAudioSourceNode to use. If a sourceNode is provided, then closing the stream and audioContext will need to be managed by the implementation.


#### Config options for OGG OPUS encoder
Expand All @@ -63,6 +64,12 @@ Creates a recorder instance.
#### Instance Methods


```js
rec.close()
```

**close** will close the audioContext, destroy the workers, disconnect the audio nodes and close the mic stream. A new Recorder instance will be required for additional recordings. if a `sourceNode` was provided in the initial config, then the implementation will need to close the audioContext and close the mic stream.

```js
rec.pause([flush])
```
Expand All @@ -88,10 +95,10 @@ rec.setMonitorGain( gain )
**setMonitorGain** will set the volume on what will be passed to the monitor. Monitor level does not affect the recording volume. Gain is an a-weighted value between `0` and `1`.

```js
rec.start( [sourceNode] )
rec.start()
```

**start** Initalizes the worker, audio context, and an audio stream and begin capturing audio. Returns a promise which resolves when recording is started. Will callback `onstart` when started. Optionally accepts a source node which can be used in place of initializing the microphone stream. For iOS support, `start` needs to be initiated from a user action. If a sourceNode is provided, then the stream and audioContext will need to be managed by the implementation.
**start** Begins a new recording. Returns a promise which resolves when recording is started. Will callback `onstart` when started. `start` ***needs to be initiated from a user action*** (click or touch) so that the audioContext can be resumed and the stream can have audio data.

```js
rec.stop()
Expand Down Expand Up @@ -189,8 +196,8 @@ const rec = new Recorder({ encoderPath });

---------
### Gotchas
- To be able to read the mic stream, the page must be served over https
- macOS and iOS Safari requires `rec.start()` to be called from a user initiated event. Otherwise the mic stream will be empty with no logged errors
- To be able to read the mic stream, the page must be served over https. Use ngrok for local development with https.
- All browsers require that `rec.start()` to be called from a user initiated event. In iOS and macOS Safari, the mic stream will be empty with no logged errors. In Chrome and Firefox the audioContext could be suspended.
- macOS and iOS Safari native opus playback is not yet supported


Expand Down
13 changes: 5 additions & 8 deletions dist-unminified/encoderWorker.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dist-unminified/recorder.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist-unminified/waveWorker.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/encoderWorker.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/recorder.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/waveWorker.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 50 additions & 7 deletions example/encoder.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Ogg Opus Encoder Example</title>
<script src="../dist/recorder.min.js"></script>
<script src="../dist-unminified/recorder.js"></script>
<style type='text/css'>
ul { list-style: none; }
li { margin: 1em; }
Expand Down Expand Up @@ -63,6 +63,7 @@ <h2>Recorder Commands</h2>
<button id="pause" disabled>Pause</button>
<button id="resume" disabled>Resume</button>
<button id="stopButton" disabled>Stop</button>
<button id="closeButton" disabled>Close</button>

<h2>Recordings</h2>
<ul id="recordingslist"></ul>
Expand All @@ -81,20 +82,31 @@ <h2>Log</h2>

else {
init.addEventListener( "click", function(){
// (async function test() {
init.disabled = true;
start.disabled = false;
monitorGain.disabled = true;
recordingGain.disabled = true;
numberOfChannels.disabled = true;
encoderBitRate.disabled = true;
encoderSampleRate.disabled = true;
closeButton.disabled = false;
encoderApplication.disabled = true;
encoderComplexity.disabled = true;

// const AudioContext = window.AudioContext || window.webkitAudioContext;
// const sourceNode = await navigator.mediaDevices.getUserMedia({ audio : true }).then( ( stream ) => {
// const context = new AudioContext();
// return context.createMediaStreamSource( stream );
// });

var options = {
monitorGain: parseInt(monitorGain.value, 10),
recordingGain: parseInt(recordingGain.value, 10),
numberOfChannels: parseInt(numberOfChannels.value, 10),
encoderSampleRate: parseInt(encoderSampleRate.value, 10),
encoderPath: "../dist/encoderWorker.min.js"
encoderPath: "../dist-unminified/encoderWorker.js"
// sourceNode: sourceNode
};

if (encoderBitRate.value) {
Expand All @@ -111,14 +123,44 @@ <h2>Log</h2>

var recorder = new Recorder(options);

pause.addEventListener( "click", function(){ recorder.pause(); });
resume.addEventListener( "click", function(){ recorder.resume(); });
stopButton.addEventListener( "click", function(){ recorder.stop(); });
start.addEventListener( "click", function(){
var recorderPause = function(){ recorder.pause(); };
var recorderStop = function(){ recorder.stop(); };
var recorderResume = function(){ recorder.resume(); };
var recorderStart = function(){
recorder.start().catch(function(e){
screenLogger('Error encountered:', e.message );
});
});
};
var recorderClose = function(){
screenLogger('Recorder is closed');
recorder.close();
init.disabled = false;

start.disabled = true;
pause.disabled = true;
stopButton.disabled = true;
closeButton.disabled = true;

monitorGain.disabled = false;
recordingGain.disabled = false;
numberOfChannels.disabled = false;
encoderBitRate.disabled = false;
encoderSampleRate.disabled = false
encoderApplication.disabled = false;
encoderComplexity.disabled = false;

resume.removeEventListener("click", recorderResume);
stopButton.removeEventListener("click", recorderStop);
pause.removeEventListener("click", recorderPause);
start.removeEventListener("click", recorderStart);
closeButton.removeEventListener("click", recorderClose);
}

pause.addEventListener( "click", recorderPause);
resume.addEventListener( "click", recorderResume);
stopButton.addEventListener( "click", recorderStop);
start.addEventListener( "click", recorderStart);
closeButton.addEventListener( "click", recorderClose);

recorder.onstart = function(e){
screenLogger('Recorder is started');
Expand Down Expand Up @@ -166,6 +208,7 @@ <h2>Log</h2>
recordingslist.appendChild(li);
};
});
// })();
}

</script>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opus-recorder",
"version": "7.0.0",
"version": "8.0.0",
"description": "A library for recording opus encoded audio",
"homepage": "https://github.com/chris-rudmin/opus-recorder",
"author": "Chris Rudmin",
Expand Down
13 changes: 5 additions & 8 deletions src/encoderWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,6 @@ OggOpusEncoder.prototype.segmentPacket = function( packetLength ) {
return exportPages;
};


// Run in AudioWorkletGlobal scope
if (typeof registerProcessor === 'function') {

Expand All @@ -329,6 +328,8 @@ if (typeof registerProcessor === 'function') {

case 'done':
this.encoder.encodeFinalFrame().forEach(pageData => this.postPage(pageData));
this.encoder.destroy();
delete this.encoder;
this.port.postMessage( {message: 'done'} );
break;

Expand All @@ -349,9 +350,6 @@ if (typeof registerProcessor === 'function') {
break;

case 'init':
if ( this.encoder ) {
this.encoder.destroy();
}
this.encoder = new OggOpusEncoder( data, Module );
this.port.postMessage( {message: 'ready'} );
break;
Expand All @@ -363,7 +361,7 @@ if (typeof registerProcessor === 'function') {
}

process(inputs) {
if (this.encoder && inputs[0] && inputs[0].length){
if (this.encoder && inputs[0] && inputs[0].length && inputs[0][0] && inputs[0][0].length){
this.encoder.encode( inputs[0] ).forEach(pageData => this.postPage(pageData));
}
return this.continueProcess;
Expand Down Expand Up @@ -403,6 +401,8 @@ else {

case 'done':
encoder.encodeFinalFrame().forEach(pageData => postPageGlobal(pageData));
encoder.destroy();
encoder = null;
postMessage( {message: 'done'} );
break;

Expand All @@ -423,9 +423,6 @@ else {
break;

case 'init':
if ( encoder ) {
encoder.destroy();
}
encoder = new OggOpusEncoder( data, Module );
postMessage( {message: 'ready'} );
break;
Expand Down
Loading

0 comments on commit 6e2fd1e

Please sign in to comment.