diff --git a/conference.js b/conference.js index 25252db96..ef1099acc 100644 --- a/conference.js +++ b/conference.js @@ -26,6 +26,7 @@ import { conferenceFailed, conferenceJoined, conferenceLeft, + toggleAudioOnly, EMAIL_COMMAND, lockStateChanged } from './react/features/base/conference'; @@ -459,11 +460,14 @@ export default { * Creates local media tracks and connects to a room. Will show error * dialogs in case accessing the local microphone and/or camera failed. Will * show guidance overlay for users on how to give access to camera and/or - * microphone, + * microphone. * @param {string} roomName * @param {object} options - * @param {boolean} options.startScreenSharing - if true should - * start with screensharing instead of camera video. + * @param {boolean} options.startAudioOnly=false - if true then + * only audio track will be created and the audio only mode will be turned + * on. + * @param {boolean} options.startScreenSharing=false - if true + * should start with screensharing instead of camera video. * @returns {Promise.} */ createInitialLocalTracksAndConnect(roomName, options = {}) { @@ -482,8 +486,18 @@ export default { // First try to retrieve both audio and video. let tryCreateLocalTracks; + // FIXME there is no video muted indication visible on the remote side, + // after starting in audio only (there's no video track) // FIXME the logic about trying to go audio only on error is duplicated - if (options.startScreenSharing) { + if (options.startAudioOnly) { + tryCreateLocalTracks + = createLocalTracks({ devices: ['audio'] }, true) + .catch(err => { + audioOnlyError = err; + + return []; + }); + } else if (options.startScreenSharing) { tryCreateLocalTracks = this._createDesktopTrack() .then(desktopStream => { return createLocalTracks({ devices: ['audio'] }, true) @@ -591,6 +605,7 @@ export default { analytics.init(); return this.createInitialLocalTracksAndConnect( options.roomName, { + startAudioOnly: config.startAudioOnly, startScreenSharing: config.startScreenSharing }); }).then(([tracks, con]) => { @@ -649,6 +664,15 @@ export default { this.updateVideoIconEnabled(); } + // Enable audio only mode + if (config.startAudioOnly) { + // It is important to have that toggled after video muted + // state is adjusted by the code about lack of video tracks + // above. That's because audio only will store muted state + // on toggle action. + APP.store.dispatch(toggleAudioOnly()); + } + this._initDeviceList(); if (config.iAmRecorder) @@ -2035,7 +2059,7 @@ export default { JitsiMeetJS.mediaDevices.enumerateDevices(devices => { // Ugly way to synchronize real device IDs with local // storage and settings menu. This is a workaround until - // getConstraints() method will be implemented + // getConstraints() method will be implemented // in browsers. if (localAudio) { APP.settings.setMicDeviceId( diff --git a/config.js b/config.js index 38a7942d5..011e2a2b2 100644 --- a/config.js +++ b/config.js @@ -72,6 +72,7 @@ var config = { // eslint-disable-line no-unused-vars // page redirection when call is hangup disableSimulcast: false, // requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room. + startAudioOnly: false, // Will start the conference in the audio only mode (no video is being received nor sent) startScreenSharing: false, // Will try to start with screensharing instead of camera // startAudioMuted: 10, // every participant after the Nth will start audio muted // startVideoMuted: 10, // every participant after the Nth will start video muted diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.js index bd61aaae4..fbec4c46a 100644 --- a/react/features/base/conference/middleware.js +++ b/react/features/base/conference/middleware.js @@ -15,7 +15,7 @@ import { _setAudioOnlyVideoMuted, setLastN } from './actions'; -import { SET_AUDIO_ONLY, SET_LASTN } from './actionTypes'; +import { CONFERENCE_JOINED, SET_AUDIO_ONLY, SET_LASTN } from './actionTypes'; import { _addLocalTracksToConference, _handleParticipantError, @@ -33,6 +33,9 @@ MiddlewareRegistry.register(store => next => action => { case CONNECTION_ESTABLISHED: return _connectionEstablished(store, next, action); + case CONFERENCE_JOINED: + return _conferenceJoined(store, next, action); + case PIN_PARTICIPANT: return _pinParticipant(store, next, action); @@ -76,6 +79,35 @@ function _connectionEstablished(store, next, action) { return result; } +/** + * Does extra sync up on properties that may need to be updated, after + * the conference was joined. + * + * @param {Store} store - The Redux store in which the specified action is being + * dispatched. + * @param {Dispatch} next - The Redux dispatch function to dispatch the + * specified action to the specified store. + * @param {Action} action - The Redux action CONFERENCE_JOINED which is being + * dispatched in the specified store. + * @private + * @returns {Object} The new state that is the result of the reduction of the + * specified action. + */ +function _conferenceJoined(store, next, action) { + const result = next(action); + const { audioOnly, conference } + = store.getState()['features/base/conference']; + + // FIXME On Web the audio only mode for "start audio only" is toggled before + // conference is added to the redux store ("on conference joined" action) + // and the LastN value needs to be synchronized here. + if (audioOnly && conference.getLastN() !== 0) { + store.dispatch(setLastN(0)); + } + + return result; +} + /** * Notifies the feature base/conference that the action PIN_PARTICIPANT is being * dispatched within a specific Redux store. Pins the specified remote