Skip to content

Commit 6e2fd1e

Browse files
authored
Merge pull request #214 from chris-rudmin/reuseWorkers
- 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.
2 parents 59d2f49 + 28e655b commit 6e2fd1e

13 files changed

+177
-150
lines changed

README.md

+11-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Creates a recorder instance.
3939
- **monitorGain** - (*optional*) Sets the gain of the monitoring output. Gain is an a-weighted value between `0` and `1`. Defaults to `0`
4040
- **numberOfChannels** - (*optional*) The number of channels to record. `1` = mono, `2` = stereo. Defaults to `1`. Maximum `2` channels are supported.
4141
- **recordingGain** - (*optional*) Sets the gain of the recording input. Gain is an a-weighted value between `0` and `1`. Defaults to `1`
42+
- **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.
4243

4344

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

6566

67+
```js
68+
rec.close()
69+
```
70+
71+
**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.
72+
6673
```js
6774
rec.pause([flush])
6875
```
@@ -88,10 +95,10 @@ rec.setMonitorGain( gain )
8895
**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`.
8996

9097
```js
91-
rec.start( [sourceNode] )
98+
rec.start()
9299
```
93100

94-
**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.
101+
**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.
95102

96103
```js
97104
rec.stop()
@@ -189,8 +196,8 @@ const rec = new Recorder({ encoderPath });
189196

190197
---------
191198
### Gotchas
192-
- To be able to read the mic stream, the page must be served over https
193-
- 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
199+
- To be able to read the mic stream, the page must be served over https. Use ngrok for local development with https.
200+
- 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.
194201
- macOS and iOS Safari native opus playback is not yet supported
195202

196203

dist-unminified/encoderWorker.js

+5-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist-unminified/recorder.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist-unminified/waveWorker.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/encoderWorker.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/recorder.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/waveWorker.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/encoder.html

+50-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<head>
55
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
66
<title>Ogg Opus Encoder Example</title>
7-
<script src="../dist/recorder.min.js"></script>
7+
<script src="../dist-unminified/recorder.js"></script>
88
<style type='text/css'>
99
ul { list-style: none; }
1010
li { margin: 1em; }
@@ -63,6 +63,7 @@ <h2>Recorder Commands</h2>
6363
<button id="pause" disabled>Pause</button>
6464
<button id="resume" disabled>Resume</button>
6565
<button id="stopButton" disabled>Stop</button>
66+
<button id="closeButton" disabled>Close</button>
6667

6768
<h2>Recordings</h2>
6869
<ul id="recordingslist"></ul>
@@ -81,20 +82,31 @@ <h2>Log</h2>
8182

8283
else {
8384
init.addEventListener( "click", function(){
85+
// (async function test() {
8486
init.disabled = true;
8587
start.disabled = false;
8688
monitorGain.disabled = true;
8789
recordingGain.disabled = true;
8890
numberOfChannels.disabled = true;
8991
encoderBitRate.disabled = true;
9092
encoderSampleRate.disabled = true;
93+
closeButton.disabled = false;
94+
encoderApplication.disabled = true;
95+
encoderComplexity.disabled = true;
96+
97+
// const AudioContext = window.AudioContext || window.webkitAudioContext;
98+
// const sourceNode = await navigator.mediaDevices.getUserMedia({ audio : true }).then( ( stream ) => {
99+
// const context = new AudioContext();
100+
// return context.createMediaStreamSource( stream );
101+
// });
91102

92103
var options = {
93104
monitorGain: parseInt(monitorGain.value, 10),
94105
recordingGain: parseInt(recordingGain.value, 10),
95106
numberOfChannels: parseInt(numberOfChannels.value, 10),
96107
encoderSampleRate: parseInt(encoderSampleRate.value, 10),
97-
encoderPath: "../dist/encoderWorker.min.js"
108+
encoderPath: "../dist-unminified/encoderWorker.js"
109+
// sourceNode: sourceNode
98110
};
99111

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

112124
var recorder = new Recorder(options);
113125

114-
pause.addEventListener( "click", function(){ recorder.pause(); });
115-
resume.addEventListener( "click", function(){ recorder.resume(); });
116-
stopButton.addEventListener( "click", function(){ recorder.stop(); });
117-
start.addEventListener( "click", function(){
126+
var recorderPause = function(){ recorder.pause(); };
127+
var recorderStop = function(){ recorder.stop(); };
128+
var recorderResume = function(){ recorder.resume(); };
129+
var recorderStart = function(){
118130
recorder.start().catch(function(e){
119131
screenLogger('Error encountered:', e.message );
120132
});
121-
});
133+
};
134+
var recorderClose = function(){
135+
screenLogger('Recorder is closed');
136+
recorder.close();
137+
init.disabled = false;
138+
139+
start.disabled = true;
140+
pause.disabled = true;
141+
stopButton.disabled = true;
142+
closeButton.disabled = true;
143+
144+
monitorGain.disabled = false;
145+
recordingGain.disabled = false;
146+
numberOfChannels.disabled = false;
147+
encoderBitRate.disabled = false;
148+
encoderSampleRate.disabled = false
149+
encoderApplication.disabled = false;
150+
encoderComplexity.disabled = false;
151+
152+
resume.removeEventListener("click", recorderResume);
153+
stopButton.removeEventListener("click", recorderStop);
154+
pause.removeEventListener("click", recorderPause);
155+
start.removeEventListener("click", recorderStart);
156+
closeButton.removeEventListener("click", recorderClose);
157+
}
158+
159+
pause.addEventListener( "click", recorderPause);
160+
resume.addEventListener( "click", recorderResume);
161+
stopButton.addEventListener( "click", recorderStop);
162+
start.addEventListener( "click", recorderStart);
163+
closeButton.addEventListener( "click", recorderClose);
122164

123165
recorder.onstart = function(e){
124166
screenLogger('Recorder is started');
@@ -166,6 +208,7 @@ <h2>Log</h2>
166208
recordingslist.appendChild(li);
167209
};
168210
});
211+
// })();
169212
}
170213

171214
</script>

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opus-recorder",
3-
"version": "7.0.0",
3+
"version": "8.0.0",
44
"description": "A library for recording opus encoded audio",
55
"homepage": "https://github.com/chris-rudmin/opus-recorder",
66
"author": "Chris Rudmin",

src/encoderWorker.js

+5-8
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,6 @@ OggOpusEncoder.prototype.segmentPacket = function( packetLength ) {
309309
return exportPages;
310310
};
311311

312-
313312
// Run in AudioWorkletGlobal scope
314313
if (typeof registerProcessor === 'function') {
315314

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

330329
case 'done':
331330
this.encoder.encodeFinalFrame().forEach(pageData => this.postPage(pageData));
331+
this.encoder.destroy();
332+
delete this.encoder;
332333
this.port.postMessage( {message: 'done'} );
333334
break;
334335

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

351352
case 'init':
352-
if ( this.encoder ) {
353-
this.encoder.destroy();
354-
}
355353
this.encoder = new OggOpusEncoder( data, Module );
356354
this.port.postMessage( {message: 'ready'} );
357355
break;
@@ -363,7 +361,7 @@ if (typeof registerProcessor === 'function') {
363361
}
364362

365363
process(inputs) {
366-
if (this.encoder && inputs[0] && inputs[0].length){
364+
if (this.encoder && inputs[0] && inputs[0].length && inputs[0][0] && inputs[0][0].length){
367365
this.encoder.encode( inputs[0] ).forEach(pageData => this.postPage(pageData));
368366
}
369367
return this.continueProcess;
@@ -403,6 +401,8 @@ else {
403401

404402
case 'done':
405403
encoder.encodeFinalFrame().forEach(pageData => postPageGlobal(pageData));
404+
encoder.destroy();
405+
encoder = null;
406406
postMessage( {message: 'done'} );
407407
break;
408408

@@ -423,9 +423,6 @@ else {
423423
break;
424424

425425
case 'init':
426-
if ( encoder ) {
427-
encoder.destroy();
428-
}
429426
encoder = new OggOpusEncoder( data, Module );
430427
postMessage( {message: 'ready'} );
431428
break;

0 commit comments

Comments
 (0)