fix(audio): retry gUM without pre-set deviceIds on OverconstrainedError(s)
There are some situations where previously set deviceIds ( local/session storage) may become stale. This causes an unexpected behavior where audio is temporarily borked until the user clears their local storage. This issue has been seen more recently on Safari endpoints when switching back-and-forth breakout rooms in environments running under iframes. Also seen randomly on endpoints with virtual input devices. This centralizes audio gUM calling into a single method that retries the gUM procedure without pre-set deviceIds only if the initial call fails due with an OverconstrainedError - hopefully circumventing the issue.
This commit is contained in:
parent
10c81bf689
commit
b3eebbb926
@ -5,6 +5,7 @@ import logger from '/imports/startup/client/logger';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import {
|
||||
getAudioConstraints,
|
||||
doGUM,
|
||||
} from '/imports/api/audio/client/bridge/service';
|
||||
|
||||
const MEDIA = Meteor.settings.public.media;
|
||||
@ -94,9 +95,8 @@ export default class BaseAudioBridge {
|
||||
this.inputStream.getAudioTracks().forEach((track) => track.stop());
|
||||
}
|
||||
|
||||
newStream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
newStream = await doGUM(constraints);
|
||||
await this.setInputStream(newStream);
|
||||
this.inputDeviceId = deviceId;
|
||||
if (backupStream && backupStream.active) {
|
||||
backupStream.getAudioTracks().forEach((track) => track.stop());
|
||||
backupStream = null;
|
||||
|
@ -75,7 +75,8 @@ const filterSupportedConstraints = (audioDeviceConstraints) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getAudioConstraints = ({ deviceId = '' }) => {
|
||||
const getAudioConstraints = (constraintFields = {}) => {
|
||||
const { deviceId = '' } = constraintFields;
|
||||
const userSettingsConstraints = Settings.application.microphoneConstraints;
|
||||
const audioDeviceConstraints = userSettingsConstraints
|
||||
|| AUDIO_MICROPHONE_CONSTRAINTS || {};
|
||||
@ -91,6 +92,29 @@ const getAudioConstraints = ({ deviceId = '' }) => {
|
||||
return matchConstraints;
|
||||
};
|
||||
|
||||
const doGUM = async (constraints, retryOnFailure = false) => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
return stream;
|
||||
} catch (error) {
|
||||
// This is probably a deviceId mistmatch. Retry with base constraints
|
||||
// without an exact deviceId.
|
||||
if (error.name === 'OverconstrainedError' && retryOnFailure) {
|
||||
logger.warn({
|
||||
logCode: 'audio_overconstrainederror_rollback',
|
||||
extraInfo: {
|
||||
constraints,
|
||||
},
|
||||
}, 'Audio getUserMedia returned OverconstrainedError, rollback');
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: getAudioConstraints() });
|
||||
}
|
||||
|
||||
// Not OverconstrainedError - bubble up the error.
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
DEFAULT_INPUT_DEVICE_ID,
|
||||
DEFAULT_OUTPUT_DEVICE_ID,
|
||||
@ -106,4 +130,5 @@ export {
|
||||
storeAudioInputDeviceId,
|
||||
getStoredAudioOutputDeviceId,
|
||||
storeAudioOutputDeviceId,
|
||||
doGUM,
|
||||
};
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
getAudioSessionNumber,
|
||||
getAudioConstraints,
|
||||
filterSupportedConstraints,
|
||||
doGUM,
|
||||
} from '/imports/api/audio/client/bridge/service';
|
||||
import { shouldForceRelay } from '/imports/ui/services/bbb-webrtc-sfu/utils';
|
||||
|
||||
@ -316,9 +317,7 @@ export default class SFUAudioBridge extends BaseAudioBridge {
|
||||
|
||||
if (IS_CHROME) {
|
||||
matchConstraints.deviceId = this.inputDeviceId;
|
||||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
{ audio: matchConstraints },
|
||||
);
|
||||
const stream = await doGUM({ audio: matchConstraints });
|
||||
await this.setInputStream(stream);
|
||||
} else {
|
||||
this.inputStream.getAudioTracks()
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
getAudioSessionNumber,
|
||||
getAudioConstraints,
|
||||
filterSupportedConstraints,
|
||||
doGUM,
|
||||
} from '/imports/api/audio/client/bridge/service';
|
||||
|
||||
const MEDIA = Meteor.settings.public.media;
|
||||
@ -384,7 +385,8 @@ class SIPSession {
|
||||
if (!constraints.audio && !constraints.video) {
|
||||
return Promise.resolve(new MediaStream());
|
||||
}
|
||||
return navigator.mediaDevices.getUserMedia(constraints);
|
||||
|
||||
return doGUM(constraints, true);
|
||||
}
|
||||
|
||||
createUserAgent(iceServers) {
|
||||
@ -1117,9 +1119,7 @@ class SIPSession {
|
||||
if (isChrome) {
|
||||
matchConstraints.deviceId = this.inputDeviceId;
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
{ audio: matchConstraints },
|
||||
);
|
||||
const stream = await doGUM({ audio: matchConstraints });
|
||||
|
||||
this.currentSession.sessionDescriptionHandler
|
||||
.setLocalMediaStream(stream);
|
||||
|
@ -11,6 +11,7 @@ import LocalEchoContainer from '/imports/ui/components/audio/local-echo/containe
|
||||
import DeviceSelector from '/imports/ui/components/audio/device-selector/component';
|
||||
import {
|
||||
getAudioConstraints,
|
||||
doGUM,
|
||||
} from '/imports/api/audio/client/bridge/service';
|
||||
import MediaStreamUtils from '/imports/utils/media-stream-utils';
|
||||
|
||||
@ -245,7 +246,7 @@ class AudioSettings extends React.Component {
|
||||
audio: getAudioConstraints({ deviceId: inputDeviceId }),
|
||||
};
|
||||
|
||||
return navigator.mediaDevices.getUserMedia(constraints);
|
||||
return doGUM(constraints, true);
|
||||
}
|
||||
|
||||
renderOutputTest() {
|
||||
|
@ -717,19 +717,15 @@ class AudioManager {
|
||||
// a new one will be created for the new stream
|
||||
this.inputStream = null;
|
||||
return this.bridge.liveChangeInputDevice(deviceId).then((stream) => {
|
||||
logger.debug({
|
||||
logCode: 'audiomanager_input_live_device_change',
|
||||
extraInfo: {
|
||||
deviceId: currentDeviceId,
|
||||
newDeviceId: deviceId,
|
||||
},
|
||||
}, `Microphone input device (live) changed: from ${currentDeviceId} to ${deviceId}`);
|
||||
this.setSenderTrackEnabled(!this.isMuted);
|
||||
this.inputDeviceId = deviceId;
|
||||
this.inputStream = stream;
|
||||
const extractedDeviceId = MediaStreamUtils.extractDeviceIdFromStream(this.inputStream, 'audio');
|
||||
if (extractedDeviceId && extractedDeviceId !== this.inputDeviceId) {
|
||||
this.changeInputDevice(extractedDeviceId);
|
||||
}
|
||||
// Live input device change - add device ID to session storage so it
|
||||
// can be re-used on refreshes/other sessions
|
||||
storeAudioInputDeviceId(deviceId);
|
||||
this.inputStream = stream;
|
||||
storeAudioInputDeviceId(extractedDeviceId);
|
||||
this.setSenderTrackEnabled(!this.isMuted);
|
||||
}).catch((error) => {
|
||||
logger.error({
|
||||
logCode: 'audiomanager_input_live_device_change_failure',
|
||||
|
Loading…
Reference in New Issue
Block a user