diff --git a/conference.js b/conference.js index 92046c0dd6..c6d1795d6f 100644 --- a/conference.js +++ b/conference.js @@ -48,6 +48,7 @@ import { import { checkAndNotifyForNewDevice, getAvailableDevices, + getDefaultDeviceId, notifyCameraError, notifyMicError, setAudioOutputDeviceId, @@ -2434,11 +2435,20 @@ export default { micDeviceId => { const audioWasMuted = this.isLocalAudioMuted(); + // 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 = micDeviceId === 'default'; + sendAnalytics(createDeviceChangedEvent('audio', 'input')); createLocalTracksF({ devices: [ 'audio' ], cameraDeviceId: null, - micDeviceId + micDeviceId: hasDefaultMicChanged + ? getDefaultDeviceId(APP.store.getState(), 'audioInput') + : micDeviceId }) .then(([ stream ]) => { // if audio was muted before changing the device, mute @@ -2462,6 +2472,12 @@ export default { return this.useAudioStream(stream); }) .then(() => { + if (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. + this.localAudio._realDeviceId = this.localAudio.deviceId = 'default'; + } logger.log(`switched local audio device: ${this.localAudio?.getDeviceId()}`); this._updateAudioDeviceId(); @@ -2763,11 +2779,20 @@ export default { 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. + // https://bugs.chromium.org/p/chromium/issues/detail?id=997689 + const hasDefaultMicChanged = newDevices.audioinput === 'default'; + promises.push( mediaDeviceHelper.createLocalTracksAfterDeviceListChanged( createLocalTracksF, newDevices.videoinput, - newDevices.audioinput) + 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. @@ -2792,6 +2817,12 @@ export default { // Use the new stream or null if we failed to obtain it. return useStream(tracks.find(track => track.getType() === mediaType) || null) .then(() => { + if (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. + this.localAudio._realDeviceId = this.localAudio.deviceId = 'default'; + } mediaType === 'audio' ? this._updateAudioDeviceId() : this._updateVideoDeviceId(); diff --git a/react/features/base/devices/functions.js b/react/features/base/devices/functions.js index 62552cfed1..5c84e487aa 100644 --- a/react/features/base/devices/functions.js +++ b/react/features/base/devices/functions.js @@ -8,6 +8,12 @@ import logger from './logger'; declare var APP: Object; +const webrtcKindToJitsiKindTranslator = { + audioinput: 'audioInput', + audiooutput: 'audioOutput', + videoinput: 'videoInput' +}; + /** * Detects the use case when the labels are not available if the A/V permissions * are not yet granted. @@ -41,6 +47,29 @@ export function getAudioOutputDeviceId() { return JitsiMeetJS.mediaDevices.getAudioOutputDevice(); } +/** + * Finds the real device id of the default device of the given type. + * + * @param {Object} state - The redux state. + * @param {*} kind - The type of the device. One of "audioInput", + * "audioOutput", and "videoInput". Also supported is all lowercase versions + * of the preceding types. + * @returns {string|undefined} + */ +export function getDefaultDeviceId(state: Object, kind: string) { + const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind; + const defaultDevice = (state['features/base/devices'].availableDevices[kindToSearch] || []) + .find(d => d.deviceId === 'default'); + + // Find the device with a matching group id. + const matchingDevice = (state['features/base/devices'].availableDevices[kindToSearch] || []) + .find(d => d.deviceId !== 'default' && d.groupId === defaultDevice.groupId); + + if (matchingDevice) { + return matchingDevice.deviceId; + } +} + /** * Finds a device with a label that matches the passed label and returns its id. * @@ -52,12 +81,6 @@ export function getAudioOutputDeviceId() { * @returns {string|undefined} */ export function getDeviceIdByLabel(state: Object, label: string, kind: string) { - const webrtcKindToJitsiKindTranslator = { - audioinput: 'audioInput', - audiooutput: 'audioOutput', - videoinput: 'videoInput' - }; - const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind; const device @@ -80,12 +103,6 @@ export function getDeviceIdByLabel(state: Object, label: string, kind: string) { * @returns {string|undefined} */ export function getDeviceLabelById(state: Object, id: string, kind: string) { - const webrtcKindToJitsiKindTranslator = { - audioinput: 'audioInput', - audiooutput: 'audioOutput', - videoinput: 'videoInput' - }; - const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind; const device