diff --git a/conference.js b/conference.js index ea4841a39..813fbb864 100644 --- a/conference.js +++ b/conference.js @@ -2342,7 +2342,7 @@ export default { * @param {MediaDeviceInfo[]} devices * @returns {Promise} */ - _onDeviceListChanged(devices) { + async _onDeviceListChanged(devices) { const oldDevices = APP.store.getState()['features/base/devices'].availableDevices; const localAudio = getLocalJitsiAudioTrack(APP.store.getState()); const localVideo = getLocalJitsiVideoTrack(APP.store.getState()); @@ -2356,13 +2356,10 @@ export default { const newDevices = mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged( devices, - this.isSharingScreen, localVideo, localAudio, newLabelsOnly); const promises = []; - const audioWasMuted = this.isLocalAudioMuted(); - const videoWasMuted = this.isLocalVideoMuted(); const requestedInput = { audio: Boolean(newDevices.audioinput), video: Boolean(newDevices.videoinput) @@ -2374,7 +2371,6 @@ export default { = setAudioOutputDeviceId(newDevices.audiooutput, dispatch) .catch(); // Just ignore any errors in catch block. - promises.push(setAudioOutputPromise); } @@ -2391,8 +2387,7 @@ export default { } // Let's handle unknown/non-preferred devices - const newAvailDevices - = APP.store.getState()['features/base/devices'].availableDevices; + const newAvailDevices = APP.store.getState()['features/base/devices'].availableDevices; let newAudioDevices = []; let oldAudioDevices = []; @@ -2408,103 +2403,85 @@ export default { // check for audio if (newAudioDevices.length > 0) { - APP.store.dispatch( - checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices)); + APP.store.dispatch(checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices)); } // check for video if (!requestedInput.video) { - APP.store.dispatch( - checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput)); + APP.store.dispatch(checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput)); } - // When the 'default' mic needs to be selected, we need to - // pass the real device id to gUM instead of 'default' in order - // to get the correct MediaStreamTrack from chrome because of the - // following bug. + // When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of 'default' + // in order to get the correct MediaStreamTrack from chrome because of the following bug. // https://bugs.chromium.org/p/chromium/issues/detail?id=997689 const hasDefaultMicChanged = newDevices.audioinput === 'default'; - // This is the case when the local video is muted and a preferred device is connected. + // When the local video is muted and a preferred device is connected, update the settings and remove the track + // from the conference. A new track will be created and replaced when the user unmutes their camera. if (requestedInput.video && this.isLocalVideoMuted()) { - // We want to avoid creating a new video track in order to prevent turning on the camera. - requestedInput.video = false; - APP.store.dispatch(updateSettings({ // Update the current selected camera for the device selection dialog. + APP.store.dispatch(updateSettings({ cameraDeviceId: newDevices.videoinput })); + requestedInput.video = false; delete newDevices.videoinput; - // Removing the current video track in order to force the unmute to select the preferred device. - logger.debug('_onDeviceListChanged: Removing the current video track.'); - this.useVideoStream(null); - + // Remove the track from the conference. + if (localVideo) { + await this.useVideoStream(null); + logger.debug('_onDeviceListChanged: Removed the current video track.'); + } } - promises.push( - mediaDeviceHelper.createLocalTracksAfterDeviceListChanged( + // When the local audio is muted and a preferred device is connected, update the settings and remove the track + // from the conference. A new track will be created and replaced when the user unmutes their mic. + if (requestedInput.audio && this.isLocalAudioMuted()) { + APP.store.dispatch(updateSettings({ + micDeviceId: newDevices.audioinput + })); + requestedInput.audio = false; + delete newDevices.audioinput; + + // Remove the track from the conference. + if (localAudio) { + await this.useAudioStream(null); + logger.debug('_onDeviceListChanged: Removed the current audio track.'); + } + } + + // Create the tracks and replace them only if the user is unmuted. + if (requestedInput.audio || requestedInput.video) { + let tracks = []; + + try { + tracks = await mediaDeviceHelper.createLocalTracksAfterDeviceListChanged( createLocalTracksF, newDevices.videoinput, hasDefaultMicChanged ? getDefaultDeviceId(APP.store.getState(), 'audioInput') - : newDevices.audioinput) - .then(tracks => { - // If audio or video muted before, or we unplugged current - // device and selected new one, then mute new track. - const muteSyncPromises = tracks.map(track => { - if ((track.isVideoTrack() && videoWasMuted) - || (track.isAudioTrack() && audioWasMuted)) { - return track.mute(); - } + : newDevices.audioinput); + } catch (error) { + logger.error(`Track creation failed on device change, ${error}`); - return Promise.resolve(); - }); + return Promise.reject(error); + } - return Promise.all(muteSyncPromises) - .then(() => - Promise.all(Object.keys(requestedInput).map(mediaType => { - if (requestedInput[mediaType]) { - const useStream - = mediaType === 'audio' - ? this.useAudioStream.bind(this) - : this.useVideoStream.bind(this); - const track = tracks.find(t => t.getType() === mediaType) || null; - - // Use the new stream or null if we failed to obtain it. - return useStream(track) - .then(() => { - if (track?.isAudioTrack() && hasDefaultMicChanged) { - // workaround for the default device to be shown as selected in the - // settings even when the real device id was passed to gUM because of - // the above mentioned chrome bug. - track._realDeviceId = track.deviceId = 'default'; - } - mediaType === 'audio' - ? this._updateAudioDeviceId() - : this._updateVideoDeviceId(); - }); - } - - return Promise.resolve(); - }))); - }) - .then(() => { - // Log and sync known mute state. - if (audioWasMuted) { - sendAnalytics(createTrackMutedEvent( - 'audio', - 'device list changed')); - logger.log('Audio mute: device list changed'); - muteLocalAudio(true); - } - - if (!this.isSharingScreen && videoWasMuted) { - sendAnalytics(createTrackMutedEvent( - 'video', - 'device list changed')); - logger.log('Video mute: device list changed'); - muteLocalVideo(true); - } - })); + for (const track of tracks) { + if (track.isAudioTrack()) { + promises.push( + this.useAudioStream(track) + .then(() => { + hasDefaultMicChanged && (track._realDeviceId = track.deviceId = 'default'); + this._updateAudioDeviceId(); + })); + } else { + promises.push( + this.useVideoStream(track) + .then(() => { + this._updateVideoDeviceId(); + })); + } + } + } return Promise.all(promises) .then(() => { diff --git a/modules/devices/mediaDeviceHelper.js b/modules/devices/mediaDeviceHelper.js index ef6e18aae..74a9c54be 100644 --- a/modules/devices/mediaDeviceHelper.js +++ b/modules/devices/mediaDeviceHelper.js @@ -158,7 +158,6 @@ export default { * Determines if currently selected media devices should be changed after * list of available devices has been changed. * @param {MediaDeviceInfo[]} newDevices - * @param {boolean} isSharingScreen * @param {JitsiLocalTrack} localVideo * @param {JitsiLocalTrack} localAudio * @returns {{ @@ -169,13 +168,12 @@ export default { */ getNewMediaDevicesAfterDeviceListChanged( // eslint-disable-line max-params newDevices, - isSharingScreen, localVideo, localAudio, newLabels) { return { audioinput: getNewAudioInputDevice(newDevices, localAudio, newLabels), - videoinput: isSharingScreen ? undefined : getNewVideoInputDevice(newDevices, localVideo, newLabels), + videoinput: getNewVideoInputDevice(newDevices, localVideo, newLabels), audiooutput: getNewAudioOutputDevice(newDevices) }; },