refactor: remove ensureInitialized
This commit is contained in:
parent
337cea6488
commit
ce308eaa8b
|
@ -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) => {
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue