refactor: remove ensureInitialized

This commit is contained in:
Radium Zheng 2018-07-10 21:20:36 +10:00
parent 337cea6488
commit ce308eaa8b
7 changed files with 210 additions and 145 deletions

View File

@ -95,11 +95,11 @@ class LocalRecordingInfoDialog extends Component<Props, State> {
}
/**
* Implements React's {@link Component#componentWillMount()}.
* Implements React's {@link Component#componentDidMount()}.
*
* @returns {void}
*/
componentWillMount() {
componentDidMount() {
this._timer = setInterval(
() => {
this.setState((_prevState, props) => {

View File

@ -398,8 +398,7 @@ class RecordingController {
this._state = ControllerState.RECORDING;
const delegate = this._adapters[this._currentSessionToken];
delegate.ensureInitialized()
.then(() => delegate.start())
delegate.start()
.then(() => {
logger.log('Local recording engaged.');
const message = i18next.t('localRecording.messages.engaged');

View File

@ -4,8 +4,8 @@ import { downloadBlob, timestampString } from './Utils';
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* RecordingAdapter implementation that uses MediaRecorder
* (default browser encoding with Opus codec)
* Recording adapter that uses {@code MediaRecorder} (default browser encoding
* with Opus codec).
*/
export class OggAdapter extends RecordingAdapter {
@ -15,35 +15,9 @@ export class OggAdapter extends RecordingAdapter {
_mediaRecorder = null;
/**
* Implements {@link RecordingAdapter#ensureInitialized()}.
*
* @inheritdoc
* Initialization promise.
*/
ensureInitialized() {
let p = null;
if (this._mediaRecorder === null) {
p = new Promise((resolve, error) => {
this._getAudioStream(0)
.then(stream => {
this._mediaRecorder = new MediaRecorder(stream);
this._mediaRecorder.ondataavailable
= e => this._saveMediaData(e.data);
resolve();
})
.catch(err => {
logger.error(`Error calling getUserMedia(): ${err}`);
error();
});
});
} else {
p = new Promise(resolve => {
resolve();
});
}
return p;
}
_initPromise = null;
/**
* Implements {@link RecordingAdapter#start()}.
@ -51,10 +25,16 @@ export class OggAdapter extends RecordingAdapter {
* @inheritdoc
*/
start() {
return new Promise(resolve => {
this._mediaRecorder.start();
resolve();
});
if (!this._initPromise) {
this._initPromise = this._initialize();
}
return this._initPromise.then(() =>
new Promise(resolve => {
this._mediaRecorder.start();
resolve();
})
);
}
/**
@ -82,14 +62,39 @@ export class OggAdapter extends RecordingAdapter {
downloadBlob(audioURL, `recording${timestampString()}.ogg`);
}
}
/**
* Callback for encoded data.
* Initialize the adapter.
*
* @private
* @param {*} data - Encoded data.
* @returns {Promise}
*/
_initialize() {
if (this._mediaRecorder) {
return Promise.resolve();
}
return new Promise((resolve, error) => {
this._getAudioStream(0)
.then(stream => {
this._mediaRecorder = new MediaRecorder(stream);
this._mediaRecorder.ondataavailable
= e => this._saveMediaData(e.data);
resolve();
})
.catch(err => {
logger.error(`Error calling getUserMedia(): ${err}`);
error();
});
});
}
/**
* Callback for storing the encoded data.
*
* @private
* @param {Blob} data - Encoded data.
* @returns {void}
*/
_saveMediaData(data) {

View File

@ -5,15 +5,6 @@ import JitsiMeetJS from '../../base/lib-jitsi-meet';
*/
export class RecordingAdapter {
/**
* Initialize the recording backend.
*
* @returns {Promise}
*/
ensureInitialized() {
throw new Error('Not implemented');
}
/**
* Starts recording.
*

View File

@ -11,17 +11,43 @@ const WAV_SAMPLE_RATE = 44100;
*/
export class WavAdapter extends RecordingAdapter {
/**
* {@code AudioContext} instance.
*/
_audioContext = null;
/**
* {@code ScriptProcessorNode} instance, which receives the raw PCM bits.
*/
_audioProcessingNode = null;
/**
* {@code MediaStreamAudioSourceNode} instance, which represents the mic.
*/
_audioSource = null;
/**
* Length of the WAVE file, in units of {@code sizeof(Float32)}.
*/
_wavLength = 0;
/**
* The {@code ArrayBuffer}s that stores the PCM bits.
*/
_wavBuffers = [];
/**
* Whether or not the {@code WavAdapter} is in a ready state.
*/
_isInitialized = false;
/**
* Initialization promise.
*/
_initPromise = null;
/**
* Constructor.
*
*/
constructor() {
super();
@ -29,50 +55,16 @@ export class WavAdapter extends RecordingAdapter {
this._saveWavPCM = this._saveWavPCM.bind(this);
}
/**
* Implements {@link RecordingAdapter#ensureInitialized()}.
*
* @inheritdoc
*/
ensureInitialized() {
if (this._isInitialized) {
return Promise.resolve();
}
const p = new Promise((resolve, reject) => {
this._getAudioStream(0)
.then(stream => {
this._audioContext = new AudioContext();
this._audioSource
= this._audioContext.createMediaStreamSource(stream);
this._audioProcessingNode
= this._audioContext.createScriptProcessor(4096, 1, 1);
this._audioProcessingNode.onaudioprocess = e => {
const channelLeft = e.inputBuffer.getChannelData(0);
// https://developer.mozilla.org/en-US/docs/
// Web/API/AudioBuffer/getChannelData
// the returned value is an Float32Array
this._saveWavPCM(channelLeft);
};
this._isInitialized = true;
resolve();
})
.catch(err => {
logger.error(`Error calling getUserMedia(): ${err}`);
reject();
});
});
return p;
}
/**
* Implements {@link RecordingAdapter#start()}.
*
* @inheritdoc
*/
start() {
if (!this._initPromise) {
this._initPromise = this._initialize();
}
return new Promise(
(resolve, /* eslint-disable */_reject/* eslint-enable */) => {
this._wavBuffers = [];
@ -95,6 +87,10 @@ export class WavAdapter extends RecordingAdapter {
this._audioProcessingNode.disconnect();
this._audioSource.disconnect();
this._data = this._exportMonoWAV(this._wavBuffers, this._wavLength);
this._audioContext = null;
this._audioProcessingNode = null;
this._audioSource = null;
this._isInitialized = false;
return Promise.resolve();
}
@ -110,7 +106,6 @@ export class WavAdapter extends RecordingAdapter {
downloadBlob(audioURL, `recording${timestampString()}.wav`);
}
}
/**
@ -167,6 +162,44 @@ export class WavAdapter extends RecordingAdapter {
return new Uint8Array(buffer);
}
/**
* Initialize the adapter.
*
* @private
* @returns {Promise}
*/
_initialize() {
if (this._isInitialized) {
return Promise.resolve();
}
const p = new Promise((resolve, reject) => {
this._getAudioStream(0)
.then(stream => {
this._audioContext = new AudioContext();
this._audioSource
= this._audioContext.createMediaStreamSource(stream);
this._audioProcessingNode
= this._audioContext.createScriptProcessor(4096, 1, 1);
this._audioProcessingNode.onaudioprocess = e => {
const channelLeft = e.inputBuffer.getChannelData(0);
// https://developer.mozilla.org/en-US/docs/
// Web/API/AudioBuffer/getChannelData
// the returned value is an Float32Array
this._saveWavPCM(channelLeft);
};
this._isInitialized = true;
resolve();
})
.catch(err => {
logger.error(`Error calling getUserMedia(): ${err}`);
reject();
});
});
return p;
}
/**
* Callback function that saves the PCM bits.

View File

@ -21,35 +21,86 @@ export class FlacAdapter extends RecordingAdapter {
_audioProcessingNode = null;
_audioSource = null;
/**
* Resolve function of the promise returned by {@code stop()}.
* This is called after the WebWorker sends back {@code WORKER_BLOB_READY}.
*/
_stopPromiseResolver = null;
/**
* Implements {@link RecordingAdapter#ensureInitialized}.
* Initialization promise.
*/
_initPromise = null;
/**
* Implements {@link RecordingAdapter#start()}.
*
* @inheritdoc
*/
ensureInitialized() {
start() {
if (!this._initPromise) {
this._initPromise = this._initialize();
}
return this._initPromise.then(() => {
this._audioSource.connect(this._audioProcessingNode);
this._audioProcessingNode.connect(this._audioContext.destination);
});
}
/**
* Implements {@link RecordingAdapter#stop()}.
*
* @inheritdoc
*/
stop() {
if (!this._encoder) {
logger.error('Attempting to stop but has nothing to stop.');
return Promise.reject();
}
return new Promise(resolve => {
this._initPromise = null;
this._audioProcessingNode.onaudioprocess = undefined;
this._audioProcessingNode.disconnect();
this._audioSource.disconnect();
this._stopPromiseResolver = resolve;
this._encoder.postMessage({
command: MAIN_THREAD_FINISH
});
});
}
/**
* Implements {@link RecordingAdapter#download()}.
*
* @inheritdoc
*/
download() {
if (this._data !== null) {
const audioURL = window.URL.createObjectURL(this._data);
downloadBlob(audioURL, `recording${timestampString()}.flac`);
}
}
/**
* Initialize the adapter.
*
* @private
* @returns {Promise}
*/
_initialize() {
if (this._encoder !== null) {
return Promise.resolve();
}
const promiseInitWorker = new Promise((resolve, reject) => {
// FIXME: workaround for different file names in development/
// production environments.
// We cannot import flacEncodeWorker as a webpack module,
// because it is in a different bundle and should be lazy-loaded
// only when flac recording is in use.
try {
// try load the minified version first
this._encoder = new Worker('/libs/flacEncodeWorker.min.js');
} catch (exception1) {
// if failed, try unminified version
try {
this._encoder = new Worker('/libs/flacEncodeWorker.js');
} catch (exception2) {
logger.error('Failed to load flacEncodeWorker.');
reject();
}
this._loadWebWorker();
} catch (e) {
reject();
}
// set up listen for messages from the WebWorker
@ -60,6 +111,8 @@ export class FlacAdapter extends RecordingAdapter {
if (this._stopPromiseResolver !== null) {
this._stopPromiseResolver();
this._stopPromiseResolver = null;
this._encoder.terminate();
this._encoder = null;
}
} else if (e.data.command === DEBUG) {
logger.log(e.data);
@ -116,43 +169,27 @@ export class FlacAdapter extends RecordingAdapter {
}
/**
* Implements {@link RecordingAdapter#start()}.
* Loads the WebWorker.
*
* @inheritdoc
* @private
* @returns {void}
*/
start() {
this._audioSource.connect(this._audioProcessingNode);
this._audioProcessingNode.connect(this._audioContext.destination);
}
/**
* Implements {@link RecordingAdapter#stop()}.
*
* @inheritdoc
*/
stop() {
return new Promise(resolve => {
this._audioProcessingNode.onaudioprocess = undefined;
this._audioProcessingNode.disconnect();
this._audioSource.disconnect();
this._stopPromiseResolver = resolve;
this._encoder.postMessage({
command: MAIN_THREAD_FINISH
});
});
}
/**
* Implements {@link RecordingAdapter#download()}.
*
* @inheritdoc
*/
download() {
if (this._data !== null) {
const audioURL = window.URL.createObjectURL(this._data);
downloadBlob(audioURL, `recording${timestampString()}.flac`);
_loadWebWorker() {
// FIXME: workaround for different file names in development/
// production environments.
// We cannot import flacEncodeWorker as a webpack module,
// because it is in a different bundle and should be lazy-loaded
// only when flac recording is in use.
try {
// try load the minified version first
this._encoder = new Worker('/libs/flacEncodeWorker.min.js');
} catch (exception1) {
// if failed, try unminified version
try {
this._encoder = new Worker('/libs/flacEncodeWorker.js');
} catch (exception2) {
throw new Error('Failed to load flacEncodeWorker.');
}
}
}
}

View File

@ -19,10 +19,10 @@ importScripts('/libs/libflac3-1.3.2.min.js');
// So we disable the ESLint rule `new-cap` in this file.
/* eslint-disable new-cap */
// Flow will complain about the number keys in `FLAC_ERRORS,
// Flow will complain about the number keys in `FLAC_ERRORS`,
// ESLint will complain about the `declare` statement.
// As the current workaround, add an exception for eslint.
/* eslint-disable flowtype/no-types-missing-file-annotation*/
/* eslint-disable flowtype/no-types-missing-file-annotation */
declare var Flac: Object;
const FLAC_ERRORS = {
@ -151,7 +151,7 @@ class Encoder {
_state = EncoderState.UNINTIALIZED;
/**
* The ready-for-grab downloadable blob.
* The ready-for-grab downloadable Blob.
*/
_data = null;