Skip to content

Commit

Permalink
add proper deinit, handle max recording duration and bitrate config s…
Browse files Browse the repository at this point in the history
…ettings
  • Loading branch information
streamer45 committed Aug 3, 2019
1 parent 4224988 commit 30e071d
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 28 deletions.
2 changes: 2 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
<script src="main.js"></script>
</head>
<body>
<button onclick="init()">INIT RECORDER</button>
<button onclick="start()">START RECORDING</button>
<button onclick="stop()">STOP RECORDING</button>
<button onclick="deinit()">DEINIT RECORDER</button>
<div id="recording"></div>
</body>
</html>
Expand Down
30 changes: 25 additions & 5 deletions demo/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,31 @@ const recorder = new Recorder({
workerURL: '/dist/recorder.worker.js',
});

recorder.init().then(() => {
console.log('recorder initialized');
}).catch((err) => {
console.log(err);
});
function init() {
recorder.init({
maxDuration: 10,
bitRate: 64,
}).then(() => {
console.log('recorder initialized');
}).catch((err) => {
console.log(err);
});
}

function deinit() {
recorder.deinit().then(() => {
console.log('recorder deinitialized');
}).catch((err) => {
console.log(err);
});
}

function start() {
console.log('start');
recorder.on('maxduration', () => {
console.log('reached max duration');
stop();
});
recorder.start().then(() => {
console.log('recorder started');
}).catch((err) => {
Expand All @@ -20,10 +37,13 @@ function start() {
function stop() {
console.log('stop');
recorder.stop().then(({blob, duration}) => {
console.log(blob);
console.log(duration);
const url = URL.createObjectURL(blob);
document.getElementById('recording').innerHTML = `<audio controls="controls">
<source src="${url}" type="audio/mpeg"></source>
</audio>`;
}).catch((err) => {
console.log(err);
});
}
90 changes: 74 additions & 16 deletions src/recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,38 @@ class Recorder {
this.startTime = 0;
this.stopTime = 0;
this.numSamples = config.numSamples || 4096;
this.bitRate = 64;
this.maxDuration = 300;
this._onData = null;
this._onMaxDuration = null;
}

_loadConfig(config) {
if (!config) return;

if (config.maxDuration) {
if (typeof config.maxDuration !== 'number') {
throw new Error('config.maxDuration should be a number');
}

if (config.maxDuration < 0 || config.maxDuration > 3600) {
throw new Error('config.maxDuration should be in [0, 3600]');
}

this.maxDuration = Math.round(config.maxDuration);
}

if (config.bitRate) {
if (typeof config.bitRate !== 'number') {
throw new Error('config.bitRate should be a number');
}

if (config.bitRate < 32 || config.bitRate > 320) {
throw new Error('config.bitRate should be in [32, 320]');
}

this.bitRate = Math.round(config.bitRate);
}
}

_startCapture() {
Expand Down Expand Up @@ -39,17 +71,26 @@ class Recorder {
}

_audioProcess(ev) {
if (!this.startTime) {
this.startTime = new Date().getTime();
}
if (!this.startTime) this.startTime = new Date().getTime();
if (this.worker) {
const samples = ev.inputBuffer.getChannelData(0);
const duration = new Date().getTime() - this.startTime;
if (duration >= (this.maxDuration * 1000)) {
if (this._onMaxDuration) return this._onMaxDuration();
this.stop();
throw new Error('maxDuration reached and callback not defined');
}
this.worker.postMessage(samples, [samples.buffer]);
}
}

init() {
if (this.worker) return Promise.reject(new Error('recorder already initialized'));
init(config) {
if (this.worker) return Promise.reject(new Error('Recorder already initialized'));
try {
this._loadConfig(config);
} catch (err) {
return Promise.reject(err);
}
return new Promise((res, rej) => {
const worker = new Worker(this.workerURL);
worker.onmessage = (ev) => {
Expand All @@ -62,8 +103,9 @@ class Recorder {
} else if (ev.data === 'init') {
this.worker = worker;
res();
} else if (ev.data === 'destroy') {
} else if (ev.data === 'deinit') {
this.worker = null;
if (this._onDeinit) this._onDeinit();
}
};
worker.onerror = (err) => {
Expand All @@ -75,7 +117,7 @@ class Recorder {
start() {
if (!this.audioCtx) {
if (!this.worker) {
return Promise.reject(new Error('Worker is not initialized'));
return Promise.reject(new Error('Recorder is not initialized'));
}
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (!AudioContext) {
Expand All @@ -84,10 +126,10 @@ class Recorder {
this.audioCtx = new AudioContext();
this.worker.postMessage({
sampleRate: this.audioCtx.sampleRate,
bitRate: 64,
bitRate: this.bitRate,
channels: 1,
numSamples: this.numSamples,
maxDuration: 300, // 5 minutes
maxDuration: this.maxDuration,
});
}
return this._startCapture().then((stream) => {
Expand All @@ -106,6 +148,7 @@ class Recorder {

stop() {
return new Promise((res, rej) => {
if (!this.audioCtx || !this.worker) return rej(new Error('Recorder not initialized'));
this._onData = (blob, duration) => res({blob, duration});
this.srcNode.disconnect(this.procNode);
this.procNode.disconnect(this.muteNode);
Expand All @@ -117,13 +160,28 @@ class Recorder {
});
}

destroy() {
if (this.audioCtx) {
this.audioCtx.close();
this.audioCtx = null;
}
if (this.worker) {
this.worker.postMessage('destroy');
deinit() {
return new Promise((res, rej) => {
if (this.audioCtx) {
this.audioCtx.close();
this.audioCtx = null;
}
if (!this.worker) return rej(new Error('Recorder not initialized'));
if (this.worker) {
this._onDeinit = () => {
this.startTime = 0;
this.stopTime = 0;
this._onMaxDuration = null;
res();
};
this.worker.postMessage('deinit');
}
});
}

on(type, cb) {
if (type === 'maxduration') {
this._onMaxDuration = cb;
}
}
}
Expand Down
10 changes: 3 additions & 7 deletions src/recorder.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const state = {
coded: null,
data: null,
data_len: 0,
start: null,
initialized: false,
};

Expand All @@ -36,18 +35,16 @@ function init(config) {
}

function deinit() {
Module._enc_destroy(state.encoder);
Module._encoder_destroy(state.encoder);
Module._free(state.samples_ptr);
Module._free(state.coded_ptr);
state.data = null;
state.data_len = 0;
state.start = null;
state.initialized = false;
}

onmessage = (ev) => {
if (ev.data instanceof Float32Array) {
if (!state.start) state.start = new Date().getTime();
const nsamples = ev.data.length;
state.samples.set(ev.data);
const ret = Module._encoder_encode(state.encoder, state.samples_ptr,
Expand All @@ -74,10 +71,9 @@ onmessage = (ev) => {

state.data = new Uint8Array(data_sz);
state.data_len = 0;
state.start = null;
} else if (ev.data === 'destroy') {
} else if (ev.data === 'deinit') {
deinit();
postMessage('destroy');
postMessage('deinit');
self.close();
} else if (ev.data instanceof Object) {
if (!state.initialized) init(ev.data);
Expand Down

0 comments on commit 30e071d

Please sign in to comment.