From f55bd7b114491f1d335d875a1c45e119f1ff04c0 Mon Sep 17 00:00:00 2001 From: Paulo Lanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:19:21 -0300 Subject: [PATCH] chore(audio): add I/O device data to audio logs (#21502) I/O device IDs are logged in some specific logCodes, but they aren't too useful on their own without the rest of the MediaDeviceInfo object. We need that extra data (label, group) to be able to better investigate incorrect device issues and NotFoundError occurrences. Register full I/O device info whenever the client fetches them and add those, unfiltered, to the following logCodes: - audiomanager_error_getting_device - audiomanager_error_device_not_found - audiomanager_error_unknown - audio_joined - audio_ended - audio_failure - audiomanager_input_live_device_change_failure - audiomanager_output_device_change_failure --- .../input-stream-live-selector/component.tsx | 15 +++ .../audio/audio-modal/component.jsx | 6 ++ .../audio/audio-modal/container.jsx | 2 + .../audio/audio-settings/component.jsx | 7 ++ .../imports/ui/components/audio/service.js | 2 + .../ui/services/audio-manager/index.js | 99 ++++++++++++++----- 6 files changed, 105 insertions(+), 26 deletions(-) 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) {