fix(device-selection): Add a workaround for a chrome bug with default mic

Pass the real deviceId to gUM instead of 'default' for Chrome to return the correct media stream
This commit is contained in:
Jaya Allamsetty 2020-06-03 17:49:08 -04:00 committed by Jaya Allamsetty
parent 26f7951894
commit f4bcad02d8
2 changed files with 62 additions and 14 deletions

View File

@ -48,6 +48,7 @@ import {
import { import {
checkAndNotifyForNewDevice, checkAndNotifyForNewDevice,
getAvailableDevices, getAvailableDevices,
getDefaultDeviceId,
notifyCameraError, notifyCameraError,
notifyMicError, notifyMicError,
setAudioOutputDeviceId, setAudioOutputDeviceId,
@ -2434,11 +2435,20 @@ export default {
micDeviceId => { micDeviceId => {
const audioWasMuted = this.isLocalAudioMuted(); 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')); sendAnalytics(createDeviceChangedEvent('audio', 'input'));
createLocalTracksF({ createLocalTracksF({
devices: [ 'audio' ], devices: [ 'audio' ],
cameraDeviceId: null, cameraDeviceId: null,
micDeviceId micDeviceId: hasDefaultMicChanged
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: micDeviceId
}) })
.then(([ stream ]) => { .then(([ stream ]) => {
// if audio was muted before changing the device, mute // if audio was muted before changing the device, mute
@ -2462,6 +2472,12 @@ export default {
return this.useAudioStream(stream); return this.useAudioStream(stream);
}) })
.then(() => { .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()}`); logger.log(`switched local audio device: ${this.localAudio?.getDeviceId()}`);
this._updateAudioDeviceId(); this._updateAudioDeviceId();
@ -2763,11 +2779,20 @@ export default {
checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput)); 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( promises.push(
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged( mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
createLocalTracksF, createLocalTracksF,
newDevices.videoinput, newDevices.videoinput,
newDevices.audioinput) hasDefaultMicChanged
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: newDevices.audioinput)
.then(tracks => { .then(tracks => {
// If audio or video muted before, or we unplugged current // If audio or video muted before, or we unplugged current
// device and selected new one, then mute new track. // 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. // Use the new stream or null if we failed to obtain it.
return useStream(tracks.find(track => track.getType() === mediaType) || null) return useStream(tracks.find(track => track.getType() === mediaType) || null)
.then(() => { .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' mediaType === 'audio'
? this._updateAudioDeviceId() ? this._updateAudioDeviceId()
: this._updateVideoDeviceId(); : this._updateVideoDeviceId();

View File

@ -8,6 +8,12 @@ import logger from './logger';
declare var APP: Object; 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 * Detects the use case when the labels are not available if the A/V permissions
* are not yet granted. * are not yet granted.
@ -41,6 +47,29 @@ export function getAudioOutputDeviceId() {
return JitsiMeetJS.mediaDevices.getAudioOutputDevice(); 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. * 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} * @returns {string|undefined}
*/ */
export function getDeviceIdByLabel(state: Object, label: string, kind: string) { export function getDeviceIdByLabel(state: Object, label: string, kind: string) {
const webrtcKindToJitsiKindTranslator = {
audioinput: 'audioInput',
audiooutput: 'audioOutput',
videoinput: 'videoInput'
};
const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind; const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind;
const device const device
@ -80,12 +103,6 @@ export function getDeviceIdByLabel(state: Object, label: string, kind: string) {
* @returns {string|undefined} * @returns {string|undefined}
*/ */
export function getDeviceLabelById(state: Object, id: string, kind: string) { export function getDeviceLabelById(state: Object, id: string, kind: string) {
const webrtcKindToJitsiKindTranslator = {
audioinput: 'audioInput',
audiooutput: 'audioOutput',
videoinput: 'videoInput'
};
const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind; const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind;
const device const device