diff --git a/react/features/local-recording/controller/RecordingController.js b/react/features/local-recording/controller/RecordingController.js index 40aff950ff..6fc4f9303d 100644 --- a/react/features/local-recording/controller/RecordingController.js +++ b/react/features/local-recording/controller/RecordingController.js @@ -258,7 +258,7 @@ class RecordingController { this._format = newFormat; logger.log(`Recording format switched to ${newFormat}`); - // will be used next time + // the new format will be used in the next recording session } /** diff --git a/react/features/local-recording/middleware.js b/react/features/local-recording/middleware.js index 933cde3562..3fde2fe88e 100644 --- a/react/features/local-recording/middleware.js +++ b/react/features/local-recording/middleware.js @@ -14,7 +14,6 @@ MiddlewareRegistry.register(({ getState, dispatch }) => next => action => { switch (action.type) { case CONFERENCE_JOINED: { - // the Conference object is ready const { conference } = getState()['features/base/conference']; recordingController.registerEvents(conference); @@ -48,5 +47,8 @@ MiddlewareRegistry.register(({ getState, dispatch }) => next => action => { break; } + // @todo: detect change in features/base/settings micDeviceID + // @todo: SET_AUDIO_MUTED, when audio is muted + return result; }); diff --git a/react/features/local-recording/recording/OggAdapter.js b/react/features/local-recording/recording/OggAdapter.js index 3f9ebc636c..e699947bed 100644 --- a/react/features/local-recording/recording/OggAdapter.js +++ b/react/features/local-recording/recording/OggAdapter.js @@ -9,6 +9,9 @@ const logger = require('jitsi-meet-logger').getLogger(__filename); */ export class OggAdapter extends RecordingAdapter { + /** + * Instance of MediaRecorder. + */ _mediaRecorder = null; /** @@ -21,29 +24,17 @@ export class OggAdapter extends RecordingAdapter { if (this._mediaRecorder === null) { p = new Promise((resolve, error) => { - navigator.getUserMedia( - - // constraints, only audio needed - { - audioBitsPerSecond: 44100, // 44 kbps - audio: true, - mimeType: 'application/ogg' - }, - - // success callback - stream => { - this._mediaRecorder = new MediaRecorder(stream); - this._mediaRecorder.ondataavailable - = e => this._saveMediaData(e.data); - resolve(); - }, - - // Error callback - err => { - logger.error(`Error calling getUserMedia(): ${err}`); - 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 => { diff --git a/react/features/local-recording/recording/RecordingAdapter.js b/react/features/local-recording/recording/RecordingAdapter.js index 15690805b5..761d54da40 100644 --- a/react/features/local-recording/recording/RecordingAdapter.js +++ b/react/features/local-recording/recording/RecordingAdapter.js @@ -1,5 +1,7 @@ +import JitsiMeetJS from '../../base/lib-jitsi-meet'; + /** - * Common interface for recording mechanisms + * Base class for recording backends. */ export class RecordingAdapter { @@ -38,4 +40,31 @@ export class RecordingAdapter { download() { throw new Error('Not implemented'); } + + /** + * Helper method for getting an audio MediaStream. Use this instead of + * calling browser APIs directly. + * + * @protected + * @param {number} micDeviceId - The ID of the current audio device. + * @returns {Promise} + */ + _getAudioStream(micDeviceId) { + return JitsiMeetJS.createLocalTracks({ + devices: [ 'audio' ], + micDeviceId + }).then(result => { + if (result.length !== 1) { + throw new Error('Unexpected number of streams ' + + 'from createLocalTracks.'); + } + const mediaStream = result[0].stream; + + if (mediaStream === undefined) { + throw new Error('Failed to get MediaStream.'); + } + + return mediaStream; + }); + } } diff --git a/react/features/local-recording/recording/WavAdapter.js b/react/features/local-recording/recording/WavAdapter.js index 25b4b8fcd0..88a0307c2e 100644 --- a/react/features/local-recording/recording/WavAdapter.js +++ b/react/features/local-recording/recording/WavAdapter.js @@ -40,40 +40,28 @@ export class WavAdapter extends RecordingAdapter { } const p = new Promise((resolve, reject) => { - navigator.getUserMedia( + 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); - // constraints - only audio needed for this app - { - audioBitsPerSecond: WAV_SAMPLE_RATE * WAV_BITS_PER_SAMPLE, - audio: true, - mimeType: 'application/ogg' // useless? - }, - - // Success callback - 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(); - }, - - // Error callback - err => { - logger.error(`Error calling getUserMedia(): ${err}`); - reject(); - } - ); + // 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; diff --git a/react/features/local-recording/recording/flac/FlacAdapter.js b/react/features/local-recording/recording/flac/FlacAdapter.js index 8fb2e6f741..26b8b94503 100644 --- a/react/features/local-recording/recording/flac/FlacAdapter.js +++ b/react/features/local-recording/recording/flac/FlacAdapter.js @@ -12,7 +12,7 @@ import { const logger = require('jitsi-meet-logger').getLogger(__filename); /** - * Recording adapter that uses libflac in the background + * Recording adapter that uses libflac.js in the background. */ export class FlacAdapter extends RecordingAdapter { @@ -43,7 +43,7 @@ export class FlacAdapter extends RecordingAdapter { // try load the minified version first this._encoder = new Worker('/libs/flacEncodeWorker.min.js'); } catch (exception1) { - // if failed, try un minified version + // if failed, try unminified version try { this._encoder = new Worker('/libs/flacEncodeWorker.js'); } catch (exception2) { @@ -83,41 +83,29 @@ export class FlacAdapter extends RecordingAdapter { }); const callbackInitAudioContext = (resolve, reject) => { - navigator.getUserMedia( + 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 => { + // delegate to the WebWorker to do the encoding + const channelLeft = e.inputBuffer.getChannelData(0); - // constraints - only audio needed for this app - { - audioBitsPerSecond: 44100, // 44 kbps - audio: true, - mimeType: 'application/ogg' // useless? - }, - - // Success callback - stream => { - this._audioContext = new AudioContext(); - this._audioSource - = this._audioContext.createMediaStreamSource(stream); - this._audioProcessingNode - = this._audioContext.createScriptProcessor(4096, 1, 1); - this._audioProcessingNode.onaudioprocess = e => { - // delegate to the WebWorker to do the encoding - const channelLeft = e.inputBuffer.getChannelData(0); - - this._encoder.postMessage({ - command: MAIN_THREAD_NEW_DATA_ARRIVED, - buf: channelLeft - }); - }; - logger.debug('AudioContext is set up.'); - resolve(); - }, - - // Error callback - err => { - logger.error(`Error calling getUserMedia(): ${err}`); - reject(); - } - ); + this._encoder.postMessage({ + command: MAIN_THREAD_NEW_DATA_ARRIVED, + buf: channelLeft + }); + }; + logger.debug('AudioContext is set up.'); + resolve(); + }) + .catch(err => { + logger.error(`Error calling getUserMedia(): ${err}`); + reject(); + }); }; // FIXME: because Promise constructor immediately executes the executor diff --git a/react/features/local-recording/recording/flac/flacEncodeWorker.js b/react/features/local-recording/recording/flac/flacEncodeWorker.js index 300b2bbc75..c6cbcbc771 100644 --- a/react/features/local-recording/recording/flac/flacEncodeWorker.js +++ b/react/features/local-recording/recording/flac/flacEncodeWorker.js @@ -26,39 +26,33 @@ importScripts('/libs/libflac3-1.3.2.min.js'); declare var Flac: Object; const FLAC_ERRORS = { - // The encoder is in the normal OK state and - // samples can be processed. + // The encoder is in the normal OK state and samples can be processed. 0: 'FLAC__STREAM_ENCODER_OK', - // The encoder is in the - // uninitialized state one of the FLAC__stream_encoder_init_*() functions - // must be called before samples can be processed. + // The encoder is in the uninitialized state one of the + // FLAC__stream_encoder_init_*() functions must be called before samples can + // be processed. 1: 'FLAC__STREAM_ENCODER_UNINITIALIZED', // An error occurred in the underlying Ogg layer. 2: 'FLAC__STREAM_ENCODER_OGG_ERROR', - // An error occurred in the - // underlying verify stream decoder; check + // An error occurred in the underlying verify stream decoder; check // FLAC__stream_encoder_get_verify_decoder_state(). 3: 'FLAC__STREAM_ENCODER_VERIFY_DECODER_ERROR', - // The verify decoder detected a mismatch between the - // original audio signal and the decoded audio signal. - + // The verify decoder detected a mismatch between the original audio signal + // and the decoded audio signal. 4: 'FLAC__STREAM_ENCODER_VERIFY_MISMATCH_IN_AUDIO_DATA', - // One of the callbacks returned - // a fatal error. + // One of the callbacks returned a fatal error. 5: 'FLAC__STREAM_ENCODER_CLIENT_ERROR', - // An I/O error occurred while - // opening/reading/writing a file. Check errno. - + // An I/O error occurred while opening/reading/writing a file. Check errno. 6: 'FLAC__STREAM_ENCODER_IO_ERROR', - // An error occurred while writing - // the stream; usually, the write_callback returned an error. + // An error occurred while writing the stream; usually, the write_callback + // returned an error. 7: 'FLAC__STREAM_ENCODER_FRAMING_ERROR', // Memory allocation failed. diff --git a/react/features/local-recording/reducer.js b/react/features/local-recording/reducer.js index 0e556bd9f9..7e370e64c8 100644 --- a/react/features/local-recording/reducer.js +++ b/react/features/local-recording/reducer.js @@ -9,11 +9,7 @@ import { } from './actionTypes'; import { recordingController } from './controller'; -const logger = require('jitsi-meet-logger').getLogger(__filename); - ReducerRegistry.register('features/local-recording', (state = {}, action) => { - logger.debug(`Redux state (features/local-recording):\n ${ - JSON.stringify(state)}`); switch (action.type) { case LOCAL_RECORDING_ENGAGED: { return {