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} * @returns {void}
*/ */
componentWillMount() { componentDidMount() {
this._timer = setInterval( this._timer = setInterval(
() => { () => {
this.setState((_prevState, props) => { this.setState((_prevState, props) => {

View File

@ -398,8 +398,7 @@ class RecordingController {
this._state = ControllerState.RECORDING; this._state = ControllerState.RECORDING;
const delegate = this._adapters[this._currentSessionToken]; const delegate = this._adapters[this._currentSessionToken];
delegate.ensureInitialized() delegate.start()
.then(() => delegate.start())
.then(() => { .then(() => {
logger.log('Local recording engaged.'); logger.log('Local recording engaged.');
const message = i18next.t('localRecording.messages.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); const logger = require('jitsi-meet-logger').getLogger(__filename);
/** /**
* RecordingAdapter implementation that uses MediaRecorder * Recording adapter that uses {@code MediaRecorder} (default browser encoding
* (default browser encoding with Opus codec) * with Opus codec).
*/ */
export class OggAdapter extends RecordingAdapter { export class OggAdapter extends RecordingAdapter {
@ -15,35 +15,9 @@ export class OggAdapter extends RecordingAdapter {
_mediaRecorder = null; _mediaRecorder = null;
/** /**
* Implements {@link RecordingAdapter#ensureInitialized()}. * Initialization promise.
*
* @inheritdoc
*/ */
ensureInitialized() { _initPromise = null;
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;
}
/** /**
* Implements {@link RecordingAdapter#start()}. * Implements {@link RecordingAdapter#start()}.
@ -51,10 +25,16 @@ export class OggAdapter extends RecordingAdapter {
* @inheritdoc * @inheritdoc
*/ */
start() { start() {
return new Promise(resolve => { if (!this._initPromise) {
this._mediaRecorder.start(); this._initPromise = this._initialize();
resolve(); }
});
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`); downloadBlob(audioURL, `recording${timestampString()}.ogg`);
} }
} }
/** /**
* Callback for encoded data. * Initialize the adapter.
* *
* @private * @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} * @returns {void}
*/ */
_saveMediaData(data) { _saveMediaData(data) {

View File

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

View File

@ -11,17 +11,43 @@ const WAV_SAMPLE_RATE = 44100;
*/ */
export class WavAdapter extends RecordingAdapter { export class WavAdapter extends RecordingAdapter {
/**
* {@code AudioContext} instance.
*/
_audioContext = null; _audioContext = null;
/**
* {@code ScriptProcessorNode} instance, which receives the raw PCM bits.
*/
_audioProcessingNode = null; _audioProcessingNode = null;
/**
* {@code MediaStreamAudioSourceNode} instance, which represents the mic.
*/
_audioSource = null; _audioSource = null;
/**
* Length of the WAVE file, in units of {@code sizeof(Float32)}.
*/
_wavLength = 0; _wavLength = 0;
/**
* The {@code ArrayBuffer}s that stores the PCM bits.
*/
_wavBuffers = []; _wavBuffers = [];
/**
* Whether or not the {@code WavAdapter} is in a ready state.
*/
_isInitialized = false; _isInitialized = false;
/**
* Initialization promise.
*/
_initPromise = null;
/** /**
* Constructor. * Constructor.
*
*/ */
constructor() { constructor() {
super(); super();
@ -29,50 +55,16 @@ export class WavAdapter extends RecordingAdapter {
this._saveWavPCM = this._saveWavPCM.bind(this); 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()}. * Implements {@link RecordingAdapter#start()}.
* *
* @inheritdoc * @inheritdoc
*/ */
start() { start() {
if (!this._initPromise) {
this._initPromise = this._initialize();
}
return new Promise( return new Promise(
(resolve, /* eslint-disable */_reject/* eslint-enable */) => { (resolve, /* eslint-disable */_reject/* eslint-enable */) => {
this._wavBuffers = []; this._wavBuffers = [];
@ -95,6 +87,10 @@ export class WavAdapter extends RecordingAdapter {
this._audioProcessingNode.disconnect(); this._audioProcessingNode.disconnect();
this._audioSource.disconnect(); this._audioSource.disconnect();
this._data = this._exportMonoWAV(this._wavBuffers, this._wavLength); this._data = this._exportMonoWAV(this._wavBuffers, this._wavLength);
this._audioContext = null;
this._audioProcessingNode = null;
this._audioSource = null;
this._isInitialized = false;
return Promise.resolve(); return Promise.resolve();
} }
@ -110,7 +106,6 @@ export class WavAdapter extends RecordingAdapter {
downloadBlob(audioURL, `recording${timestampString()}.wav`); downloadBlob(audioURL, `recording${timestampString()}.wav`);
} }
} }
/** /**
@ -167,6 +162,44 @@ export class WavAdapter extends RecordingAdapter {
return new Uint8Array(buffer); 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. * Callback function that saves the PCM bits.

View File

@ -21,35 +21,86 @@ export class FlacAdapter extends RecordingAdapter {
_audioProcessingNode = null; _audioProcessingNode = null;
_audioSource = 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; _stopPromiseResolver = null;
/** /**
* Implements {@link RecordingAdapter#ensureInitialized}. * Initialization promise.
*/
_initPromise = null;
/**
* Implements {@link RecordingAdapter#start()}.
* *
* @inheritdoc * @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) { if (this._encoder !== null) {
return Promise.resolve(); return Promise.resolve();
} }
const promiseInitWorker = new Promise((resolve, reject) => { 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 {
// try load the minified version first this._loadWebWorker();
this._encoder = new Worker('/libs/flacEncodeWorker.min.js'); } catch (e) {
} catch (exception1) { reject();
// if failed, try unminified version
try {
this._encoder = new Worker('/libs/flacEncodeWorker.js');
} catch (exception2) {
logger.error('Failed to load flacEncodeWorker.');
reject();
}
} }
// set up listen for messages from the WebWorker // set up listen for messages from the WebWorker
@ -60,6 +111,8 @@ export class FlacAdapter extends RecordingAdapter {
if (this._stopPromiseResolver !== null) { if (this._stopPromiseResolver !== null) {
this._stopPromiseResolver(); this._stopPromiseResolver();
this._stopPromiseResolver = null; this._stopPromiseResolver = null;
this._encoder.terminate();
this._encoder = null;
} }
} else if (e.data.command === DEBUG) { } else if (e.data.command === DEBUG) {
logger.log(e.data); 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() { _loadWebWorker() {
this._audioSource.connect(this._audioProcessingNode); // FIXME: workaround for different file names in development/
this._audioProcessingNode.connect(this._audioContext.destination); // 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.
* Implements {@link RecordingAdapter#stop()}. try {
* // try load the minified version first
* @inheritdoc this._encoder = new Worker('/libs/flacEncodeWorker.min.js');
*/ } catch (exception1) {
stop() { // if failed, try unminified version
return new Promise(resolve => { try {
this._audioProcessingNode.onaudioprocess = undefined; this._encoder = new Worker('/libs/flacEncodeWorker.js');
this._audioProcessingNode.disconnect(); } catch (exception2) {
this._audioSource.disconnect(); throw new Error('Failed to load flacEncodeWorker.');
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`);
} }
} }
} }

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. // So we disable the ESLint rule `new-cap` in this file.
/* eslint-disable new-cap */ /* 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. // ESLint will complain about the `declare` statement.
// As the current workaround, add an exception for eslint. // 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; declare var Flac: Object;
const FLAC_ERRORS = { const FLAC_ERRORS = {
@ -151,7 +151,7 @@ class Encoder {
_state = EncoderState.UNINTIALIZED; _state = EncoderState.UNINTIALIZED;
/** /**
* The ready-for-grab downloadable blob. * The ready-for-grab downloadable Blob.
*/ */
_data = null; _data = null;