From ce308eaa8b9dda4f57c0e9020117f393dda386d2 Mon Sep 17 00:00:00 2001 From: Radium Zheng Date: Tue, 10 Jul 2018 21:20:36 +1000 Subject: [PATCH] refactor: remove ensureInitialized --- .../components/LocalRecordingInfoDialog.js | 4 +- .../controller/RecordingController.js | 3 +- .../local-recording/recording/OggAdapter.js | 79 +++++----- .../recording/RecordingAdapter.js | 9 -- .../local-recording/recording/WavAdapter.js | 113 +++++++++----- .../recording/flac/FlacAdapter.js | 141 +++++++++++------- .../recording/flac/flacEncodeWorker.js | 6 +- 7 files changed, 210 insertions(+), 145 deletions(-) diff --git a/react/features/local-recording/components/LocalRecordingInfoDialog.js b/react/features/local-recording/components/LocalRecordingInfoDialog.js index 387cf56ce..4651d8b29 100644 --- a/react/features/local-recording/components/LocalRecordingInfoDialog.js +++ b/react/features/local-recording/components/LocalRecordingInfoDialog.js @@ -95,11 +95,11 @@ class LocalRecordingInfoDialog extends Component { } /** - * Implements React's {@link Component#componentWillMount()}. + * Implements React's {@link Component#componentDidMount()}. * * @returns {void} */ - componentWillMount() { + componentDidMount() { this._timer = setInterval( () => { this.setState((_prevState, props) => { diff --git a/react/features/local-recording/controller/RecordingController.js b/react/features/local-recording/controller/RecordingController.js index 6fc4f9303..5bfa4fd6f 100644 --- a/react/features/local-recording/controller/RecordingController.js +++ b/react/features/local-recording/controller/RecordingController.js @@ -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'); diff --git a/react/features/local-recording/recording/OggAdapter.js b/react/features/local-recording/recording/OggAdapter.js index e699947be..0270ae36f 100644 --- a/react/features/local-recording/recording/OggAdapter.js +++ b/react/features/local-recording/recording/OggAdapter.js @@ -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) { diff --git a/react/features/local-recording/recording/RecordingAdapter.js b/react/features/local-recording/recording/RecordingAdapter.js index 761d54da4..6dea382e1 100644 --- a/react/features/local-recording/recording/RecordingAdapter.js +++ b/react/features/local-recording/recording/RecordingAdapter.js @@ -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. * diff --git a/react/features/local-recording/recording/WavAdapter.js b/react/features/local-recording/recording/WavAdapter.js index 88a0307c2..cce1695c0 100644 --- a/react/features/local-recording/recording/WavAdapter.js +++ b/react/features/local-recording/recording/WavAdapter.js @@ -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. diff --git a/react/features/local-recording/recording/flac/FlacAdapter.js b/react/features/local-recording/recording/flac/FlacAdapter.js index 26b8b9450..06e730ee0 100644 --- a/react/features/local-recording/recording/flac/FlacAdapter.js +++ b/react/features/local-recording/recording/flac/FlacAdapter.js @@ -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.'); + } } - } } diff --git a/react/features/local-recording/recording/flac/flacEncodeWorker.js b/react/features/local-recording/recording/flac/flacEncodeWorker.js index c6cbcbc77..8013c4abb 100644 --- a/react/features/local-recording/recording/flac/flacEncodeWorker.js +++ b/react/features/local-recording/recording/flac/flacEncodeWorker.js @@ -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;