diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-graphql/audio-controls/input-stream-live-selector/component.tsx b/bigbluebutton-html5/imports/ui/components/audio/audio-graphql/audio-controls/input-stream-live-selector/component.tsx index c2d4d5c458..cc8d483458 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-graphql/audio-controls/input-stream-live-selector/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-graphql/audio-controls/input-stream-live-selector/component.tsx @@ -79,6 +79,8 @@ interface InputStreamLiveSelectorProps extends InputStreamLiveSelectorContainerP away: boolean; permissionStatus: string; supportsTransparentListenOnly: boolean; + updateInputDevices: (devices: InputDeviceInfo[]) => void; + updateOutputDevices: (devices: MediaDeviceInfo[]) => void; } const InputStreamLiveSelector: React.FC = ({ @@ -100,6 +102,8 @@ const InputStreamLiveSelector: React.FC = ({ permissionStatus, supportsTransparentListenOnly, openAudioSettings, + updateInputDevices, + updateOutputDevices, }) => { const intl = useIntl(); const toggleVoice = useToggleVoice(); @@ -164,6 +168,9 @@ const InputStreamLiveSelector: React.FC = ({ const audioOutputDevices = devices.filter((i) => i.kind === AUDIO_OUTPUT); setInputDevices(audioInputDevices as InputDeviceInfo[]); setOutputDevices(audioOutputDevices); + // Update audio devices in AudioManager + updateInputDevices(audioInputDevices as InputDeviceInfo[]); + updateOutputDevices(audioOutputDevices); if (inAudio) updateRemovedDevices(audioInputDevices, audioOutputDevices); }) @@ -308,6 +315,12 @@ const InputStreamLiveSelectorContainer: React.FC { + AudioManager.inputDevices = devices; + }; + const updateOutputDevices = (devices: MediaDeviceInfo[] = []) => { + AudioManager.outputDevices = devices; + }; return ( ); }; diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx index 1cd29fd4f7..030ea1fa19 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx @@ -38,6 +38,8 @@ const propTypes = { leaveEchoTest: PropTypes.func.isRequired, changeInputDevice: PropTypes.func.isRequired, changeOutputDevice: PropTypes.func.isRequired, + updateInputDevices: PropTypes.func.isRequired, + updateOutputDevices: PropTypes.func.isRequired, isEchoTest: PropTypes.bool.isRequired, isConnecting: PropTypes.bool.isRequired, isConnected: PropTypes.bool.isRequired, @@ -207,6 +209,8 @@ const AudioModal = ({ unmuteOnExit = false, permissionStatus = null, isTranscriptionEnabled, + updateInputDevices, + updateOutputDevices, }) => { const [content, setContent] = useState(initialContent); const [hasError, setHasError] = useState(false); @@ -528,6 +532,8 @@ const AudioModal = ({ permissionStatus={permissionStatus} isTranscriptionEnabled={isTranscriptionEnabled} skipAudioOptions={skipAudioOptions} + updateInputDevices={updateInputDevices} + updateOutputDevices={updateOutputDevices} /> ); }; diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx index 9c7b34ae1f..b70ef05848 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx @@ -129,6 +129,8 @@ const AudioModalContainer = (props) => { liveChangeInputDevice={Service.liveChangeInputDevice} changeInputStream={Service.changeInputStream} changeOutputDevice={Service.changeOutputDevice} + updateInputDevices={Service.updateInputDevices} + updateOutputDevices={Service.updateOutputDevices} joinEchoTest={Service.joinEchoTest} exitAudio={Service.exitAudio} localEchoEnabled={LOCAL_ECHO_TEST_ENABLED} diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx index 9a75fc239e..ce2bee9bdd 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx @@ -21,6 +21,8 @@ const propTypes = { changeInputDevice: PropTypes.func.isRequired, liveChangeInputDevice: PropTypes.func.isRequired, changeOutputDevice: PropTypes.func.isRequired, + updateInputDevices: PropTypes.func.isRequired, + updateOutputDevices: PropTypes.func.isRequired, handleBack: PropTypes.func.isRequired, handleConfirmation: PropTypes.func.isRequired, handleGUMFailure: PropTypes.func.isRequired, @@ -384,11 +386,16 @@ class AudioSettings extends React.Component { } updateDeviceList() { + const { updateInputDevices, updateOutputDevices } = this.props; + return navigator.mediaDevices.enumerateDevices() .then((devices) => { const audioInputDevices = devices.filter((i) => i.kind === 'audioinput'); const audioOutputDevices = devices.filter((i) => i.kind === 'audiooutput'); + // Update audio devices in AudioManager + updateInputDevices(audioInputDevices); + updateOutputDevices(audioOutputDevices); this.setState({ audioInputDevices, audioOutputDevices, diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js index 6c78a516e2..b59919b686 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/service.js +++ b/bigbluebutton-html5/imports/ui/components/audio/service.js @@ -186,6 +186,8 @@ export default { outputDeviceId, isLive, ) => AudioManager.changeOutputDevice(outputDeviceId, isLive), + updateInputDevices: (devices) => { AudioManager.inputDevices = devices }, + updateOutputDevices: (devices) => { AudioManager.outputDevices = devices }, toggleMuteMicrophone, toggleMuteMicrophoneSystem, isConnectedToBreakout: () => { diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js index 73c86583e0..e1435db7d4 100755 --- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js +++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js @@ -88,6 +88,8 @@ class AudioManager { this._outputDeviceId = { value: makeVar(null), }; + this._inputDevices = []; + this._outputDevices = []; this.BREAKOUT_AUDIO_TRANSFER_STATES = BREAKOUT_AUDIO_TRANSFER_STATES; this._voiceActivityObserver = null; @@ -185,6 +187,34 @@ class AudioManager { return this._outputDeviceId.value(); } + set inputDevices(value) { + if (value?.length) { + this._inputDevices = value; + } + } + + get inputDevices() { + return this._inputDevices; + } + + get inputDevicesJSON() { + return this.inputDevices.map((device) => device.toJSON()); + } + + set outputDevices(value) { + if (value?.length) { + this._outputDevices = value; + } + } + + get outputDevices() { + return this._outputDevices; + } + + get outputDevicesJSON() { + return this.outputDevices.map((device) => device.toJSON()); + } + shouldBypassGUM() { return this.supportsTransparentListenOnly() && this.inputDeviceId === 'listen-only'; } @@ -432,15 +462,17 @@ class AudioManager { break; case 'NotFoundError': errorPayload.errCode = AudioErrors.MIC_ERROR.DEVICE_NOT_FOUND; - // Reset the input device ID so the user can select a new one - this.changeInputDevice(null); logger.error({ logCode: 'audiomanager_error_device_not_found', extraInfo: { errorName: error.name, errorMessage: error.message, + inputDeviceId: this.inputDeviceId, + inputDevices: this.inputDevicesJSON, }, }, `Error getting microphone - {${error.name}: ${error.message}}`); + // Reset the input device ID so the user can select a new one + this.changeInputDevice(null); break; default: logger.error({ @@ -448,6 +480,11 @@ class AudioManager { extraInfo: { errorName: error.name, errorMessage: error.message, + errorStack: error?.stack, + inputDeviceId: this.inputDeviceId, + inputDevices: this.inputDevicesJSON, + outputDeviceId: this.outputDeviceId, + outputDevices: this.outputDevicesJSON, }, }, `Error enabling audio - {${name}: ${message}}`); break; @@ -607,10 +644,13 @@ class AudioManager { extraInfo: { secondsToActivateAudio, inputDeviceId: this.inputDeviceId, + inputDevices: this.inputDevicesJSON, outputDeviceId: this.outputDeviceId, + outputDevices: this.outputDevicesJSON, isListenOnly: this.isListenOnly, }, }, 'Audio Joined'); + if (STATS.enabled) this.monitor(); this.audioEventHandler({ name: 'started', @@ -674,8 +714,17 @@ class AudioManager { breakoutMeetingId: '', status: BREAKOUT_AUDIO_TRANSFER_STATES.DISCONNECTED, }); - logger.info({ logCode: 'audio_ended' }, 'Audio ended without issue'); this.onAudioExit(); + logger.info({ + logCode: 'audio_ended', + extraInfo: { + inputDeviceId: this.inputDeviceId, + inputDevices: this.inputDevicesJSON, + outputDeviceId: this.outputDeviceId, + outputDevices: this.outputDevicesJSON, + isListenOnly: this.isListenOnly, + }, + }, 'Audio ended without issue'); } else if (status === FAILED) { this.isReconnecting = false; this.setBreakoutAudioTransferStatus({ @@ -693,7 +742,9 @@ class AudioManager { cause: bridgeError, bridge, inputDeviceId: this.inputDeviceId, + inputDevices: this.inputDevicesJSON, outputDeviceId: this.outputDeviceId, + outputDevices: this.outputDevicesJSON, isListenOnly: this.isListenOnly, }, }, @@ -768,18 +819,16 @@ class AudioManager { this.setSenderTrackEnabled(!this.isMuted); }) .catch((error) => { - logger.error( - { - logCode: 'audiomanager_input_live_device_change_failure', - extraInfo: { - errorName: error.name, - errorMessage: error.message, - deviceId: currentDeviceId, - newDeviceId: deviceId, - }, + logger.error({ + logCode: 'audiomanager_input_live_device_change_failure', + extraInfo: { + errorName: error.name, + errorMessage: error.message, + deviceId: currentDeviceId, + newDeviceId: deviceId, + inputDevices: this.inputDevicesJSON, }, - `Input device live change failed - {${error.name}: ${error.message}}` - ); + }, `Input device live change failed - {${error.name}: ${error.message}}`); throw error; }); @@ -820,18 +869,16 @@ class AudioManager { return this.outputDeviceId; } catch (error) { - logger.error( - { - logCode: 'audiomanager_output_device_change_failure', - extraInfo: { - errorName: error.name, - errorMessage: error.message, - deviceId: currentDeviceId, - newDeviceId: targetDeviceId, - }, - }, - `Error changing output device - {${error.name}: ${error.message}}` - ); + logger.error({ + logCode: 'audiomanager_output_device_change_failure', + extraInfo: { + errorName: error.name, + errorMessage: error.message, + deviceId: currentDeviceId, + newDeviceId: targetDeviceId, + outputDevices: this.outputDevicesJSON, + } + }, `Error changing output device - {${error.name}: ${error.message}}`); // Rollback/enforce current sinkId (if possible) if (sinkIdSupported) {