fix(conference) Fixes an issue where first unmute with disableInitialGUM=true was resulting in user staying muted.

When device list changes, create a new track with the preferred device only if the user is unmuted. If the user is audio/video muted, remove the existing track from conference. A new track will be created and replaced automatically when the user unmutes. Also since screensharing is a separate source, always check for updated camera devices.
This commit is contained in:
Jaya Allamsetty 2022-12-15 14:00:21 -05:00
parent 955367a157
commit 298c4bd1e3
2 changed files with 60 additions and 85 deletions

View File

@ -2342,7 +2342,7 @@ export default {
* @param {MediaDeviceInfo[]} devices * @param {MediaDeviceInfo[]} devices
* @returns {Promise} * @returns {Promise}
*/ */
_onDeviceListChanged(devices) { async _onDeviceListChanged(devices) {
const oldDevices = APP.store.getState()['features/base/devices'].availableDevices; const oldDevices = APP.store.getState()['features/base/devices'].availableDevices;
const localAudio = getLocalJitsiAudioTrack(APP.store.getState()); const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
const localVideo = getLocalJitsiVideoTrack(APP.store.getState()); const localVideo = getLocalJitsiVideoTrack(APP.store.getState());
@ -2356,13 +2356,10 @@ export default {
const newDevices const newDevices
= mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged( = mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
devices, devices,
this.isSharingScreen,
localVideo, localVideo,
localAudio, localAudio,
newLabelsOnly); newLabelsOnly);
const promises = []; const promises = [];
const audioWasMuted = this.isLocalAudioMuted();
const videoWasMuted = this.isLocalVideoMuted();
const requestedInput = { const requestedInput = {
audio: Boolean(newDevices.audioinput), audio: Boolean(newDevices.audioinput),
video: Boolean(newDevices.videoinput) video: Boolean(newDevices.videoinput)
@ -2374,7 +2371,6 @@ export default {
= setAudioOutputDeviceId(newDevices.audiooutput, dispatch) = setAudioOutputDeviceId(newDevices.audiooutput, dispatch)
.catch(); // Just ignore any errors in catch block. .catch(); // Just ignore any errors in catch block.
promises.push(setAudioOutputPromise); promises.push(setAudioOutputPromise);
} }
@ -2391,8 +2387,7 @@ export default {
} }
// Let's handle unknown/non-preferred devices // Let's handle unknown/non-preferred devices
const newAvailDevices const newAvailDevices = APP.store.getState()['features/base/devices'].availableDevices;
= APP.store.getState()['features/base/devices'].availableDevices;
let newAudioDevices = []; let newAudioDevices = [];
let oldAudioDevices = []; let oldAudioDevices = [];
@ -2408,103 +2403,85 @@ export default {
// check for audio // check for audio
if (newAudioDevices.length > 0) { if (newAudioDevices.length > 0) {
APP.store.dispatch( APP.store.dispatch(checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
} }
// check for video // check for video
if (!requestedInput.video) { if (!requestedInput.video) {
APP.store.dispatch( APP.store.dispatch(checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
} }
// When the 'default' mic needs to be selected, we need to // When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of 'default'
// pass the real device id to gUM instead of 'default' in order // in order to get the correct MediaStreamTrack from chrome because of the following bug.
// to get the correct MediaStreamTrack from chrome because of the
// following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689 // https://bugs.chromium.org/p/chromium/issues/detail?id=997689
const hasDefaultMicChanged = newDevices.audioinput === 'default'; 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()) { if (requestedInput.video && this.isLocalVideoMuted()) {
// We want to avoid creating a new video track in order to prevent turning on the camera. APP.store.dispatch(updateSettings({
requestedInput.video = false;
APP.store.dispatch(updateSettings({ // Update the current selected camera for the device selection dialog.
cameraDeviceId: newDevices.videoinput cameraDeviceId: newDevices.videoinput
})); }));
requestedInput.video = false;
delete newDevices.videoinput; delete newDevices.videoinput;
// Removing the current video track in order to force the unmute to select the preferred device. // Remove the track from the conference.
logger.debug('_onDeviceListChanged: Removing the current video track.'); if (localVideo) {
this.useVideoStream(null); await this.useVideoStream(null);
logger.debug('_onDeviceListChanged: Removed the current video track.');
}
} }
promises.push( // When the local audio is muted and a preferred device is connected, update the settings and remove the track
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged( // 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, createLocalTracksF,
newDevices.videoinput, newDevices.videoinput,
hasDefaultMicChanged hasDefaultMicChanged
? getDefaultDeviceId(APP.store.getState(), 'audioInput') ? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: newDevices.audioinput) : newDevices.audioinput);
.then(tracks => { } catch (error) {
// If audio or video muted before, or we unplugged current logger.error(`Track creation failed on device change, ${error}`);
// device and selected new one, then mute new track.
const muteSyncPromises = tracks.map(track => {
if ((track.isVideoTrack() && videoWasMuted)
|| (track.isAudioTrack() && audioWasMuted)) {
return track.mute();
}
return Promise.resolve(); return Promise.reject(error);
}); }
return Promise.all(muteSyncPromises) for (const track of tracks) {
.then(() => if (track.isAudioTrack()) {
Promise.all(Object.keys(requestedInput).map(mediaType => { promises.push(
if (requestedInput[mediaType]) { this.useAudioStream(track)
const useStream .then(() => {
= mediaType === 'audio' hasDefaultMicChanged && (track._realDeviceId = track.deviceId = 'default');
? this.useAudioStream.bind(this) this._updateAudioDeviceId();
: this.useVideoStream.bind(this); }));
const track = tracks.find(t => t.getType() === mediaType) || null; } else {
promises.push(
// Use the new stream or null if we failed to obtain it. this.useVideoStream(track)
return useStream(track) .then(() => {
.then(() => { this._updateVideoDeviceId();
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);
}
}));
return Promise.all(promises) return Promise.all(promises)
.then(() => { .then(() => {

View File

@ -158,7 +158,6 @@ export default {
* Determines if currently selected media devices should be changed after * Determines if currently selected media devices should be changed after
* list of available devices has been changed. * list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices * @param {MediaDeviceInfo[]} newDevices
* @param {boolean} isSharingScreen
* @param {JitsiLocalTrack} localVideo * @param {JitsiLocalTrack} localVideo
* @param {JitsiLocalTrack} localAudio * @param {JitsiLocalTrack} localAudio
* @returns {{ * @returns {{
@ -169,13 +168,12 @@ export default {
*/ */
getNewMediaDevicesAfterDeviceListChanged( // eslint-disable-line max-params getNewMediaDevicesAfterDeviceListChanged( // eslint-disable-line max-params
newDevices, newDevices,
isSharingScreen,
localVideo, localVideo,
localAudio, localAudio,
newLabels) { newLabels) {
return { return {
audioinput: getNewAudioInputDevice(newDevices, localAudio, newLabels), audioinput: getNewAudioInputDevice(newDevices, localAudio, newLabels),
videoinput: isSharingScreen ? undefined : getNewVideoInputDevice(newDevices, localVideo, newLabels), videoinput: getNewVideoInputDevice(newDevices, localVideo, newLabels),
audiooutput: getNewAudioOutputDevice(newDevices) audiooutput: getNewAudioOutputDevice(newDevices)
}; };
}, },