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
This commit is contained in:
parent
63bf4f35bc
commit
f55bd7b114
@ -79,6 +79,8 @@ interface InputStreamLiveSelectorProps extends InputStreamLiveSelectorContainerP
|
|||||||
away: boolean;
|
away: boolean;
|
||||||
permissionStatus: string;
|
permissionStatus: string;
|
||||||
supportsTransparentListenOnly: boolean;
|
supportsTransparentListenOnly: boolean;
|
||||||
|
updateInputDevices: (devices: InputDeviceInfo[]) => void;
|
||||||
|
updateOutputDevices: (devices: MediaDeviceInfo[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputStreamLiveSelector: React.FC<InputStreamLiveSelectorProps> = ({
|
const InputStreamLiveSelector: React.FC<InputStreamLiveSelectorProps> = ({
|
||||||
@ -100,6 +102,8 @@ const InputStreamLiveSelector: React.FC<InputStreamLiveSelectorProps> = ({
|
|||||||
permissionStatus,
|
permissionStatus,
|
||||||
supportsTransparentListenOnly,
|
supportsTransparentListenOnly,
|
||||||
openAudioSettings,
|
openAudioSettings,
|
||||||
|
updateInputDevices,
|
||||||
|
updateOutputDevices,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const toggleVoice = useToggleVoice();
|
const toggleVoice = useToggleVoice();
|
||||||
@ -164,6 +168,9 @@ const InputStreamLiveSelector: React.FC<InputStreamLiveSelectorProps> = ({
|
|||||||
const audioOutputDevices = devices.filter((i) => i.kind === AUDIO_OUTPUT);
|
const audioOutputDevices = devices.filter((i) => i.kind === AUDIO_OUTPUT);
|
||||||
setInputDevices(audioInputDevices as InputDeviceInfo[]);
|
setInputDevices(audioInputDevices as InputDeviceInfo[]);
|
||||||
setOutputDevices(audioOutputDevices);
|
setOutputDevices(audioOutputDevices);
|
||||||
|
// Update audio devices in AudioManager
|
||||||
|
updateInputDevices(audioInputDevices as InputDeviceInfo[]);
|
||||||
|
updateOutputDevices(audioOutputDevices);
|
||||||
|
|
||||||
if (inAudio) updateRemovedDevices(audioInputDevices, audioOutputDevices);
|
if (inAudio) updateRemovedDevices(audioInputDevices, audioOutputDevices);
|
||||||
})
|
})
|
||||||
@ -308,6 +315,12 @@ const InputStreamLiveSelectorContainer: React.FC<InputStreamLiveSelectorContaine
|
|||||||
const permissionStatus = useReactiveVar(AudioManager._permissionStatus.value) as string;
|
const permissionStatus = useReactiveVar(AudioManager._permissionStatus.value) as string;
|
||||||
// @ts-ignore - temporary while hybrid (meteor+GraphQl)
|
// @ts-ignore - temporary while hybrid (meteor+GraphQl)
|
||||||
const supportsTransparentListenOnly = useReactiveVar(AudioManager._transparentListenOnlySupported.value) as boolean;
|
const supportsTransparentListenOnly = useReactiveVar(AudioManager._transparentListenOnlySupported.value) as boolean;
|
||||||
|
const updateInputDevices = (devices: InputDeviceInfo[] = []) => {
|
||||||
|
AudioManager.inputDevices = devices;
|
||||||
|
};
|
||||||
|
const updateOutputDevices = (devices: MediaDeviceInfo[] = []) => {
|
||||||
|
AudioManager.outputDevices = devices;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputStreamLiveSelector
|
<InputStreamLiveSelector
|
||||||
@ -330,6 +343,8 @@ const InputStreamLiveSelectorContainer: React.FC<InputStreamLiveSelectorContaine
|
|||||||
openAudioSettings={openAudioSettings}
|
openAudioSettings={openAudioSettings}
|
||||||
permissionStatus={permissionStatus}
|
permissionStatus={permissionStatus}
|
||||||
supportsTransparentListenOnly={supportsTransparentListenOnly}
|
supportsTransparentListenOnly={supportsTransparentListenOnly}
|
||||||
|
updateInputDevices={updateInputDevices}
|
||||||
|
updateOutputDevices={updateOutputDevices}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -38,6 +38,8 @@ const propTypes = {
|
|||||||
leaveEchoTest: PropTypes.func.isRequired,
|
leaveEchoTest: PropTypes.func.isRequired,
|
||||||
changeInputDevice: PropTypes.func.isRequired,
|
changeInputDevice: PropTypes.func.isRequired,
|
||||||
changeOutputDevice: PropTypes.func.isRequired,
|
changeOutputDevice: PropTypes.func.isRequired,
|
||||||
|
updateInputDevices: PropTypes.func.isRequired,
|
||||||
|
updateOutputDevices: PropTypes.func.isRequired,
|
||||||
isEchoTest: PropTypes.bool.isRequired,
|
isEchoTest: PropTypes.bool.isRequired,
|
||||||
isConnecting: PropTypes.bool.isRequired,
|
isConnecting: PropTypes.bool.isRequired,
|
||||||
isConnected: PropTypes.bool.isRequired,
|
isConnected: PropTypes.bool.isRequired,
|
||||||
@ -207,6 +209,8 @@ const AudioModal = ({
|
|||||||
unmuteOnExit = false,
|
unmuteOnExit = false,
|
||||||
permissionStatus = null,
|
permissionStatus = null,
|
||||||
isTranscriptionEnabled,
|
isTranscriptionEnabled,
|
||||||
|
updateInputDevices,
|
||||||
|
updateOutputDevices,
|
||||||
}) => {
|
}) => {
|
||||||
const [content, setContent] = useState(initialContent);
|
const [content, setContent] = useState(initialContent);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
@ -528,6 +532,8 @@ const AudioModal = ({
|
|||||||
permissionStatus={permissionStatus}
|
permissionStatus={permissionStatus}
|
||||||
isTranscriptionEnabled={isTranscriptionEnabled}
|
isTranscriptionEnabled={isTranscriptionEnabled}
|
||||||
skipAudioOptions={skipAudioOptions}
|
skipAudioOptions={skipAudioOptions}
|
||||||
|
updateInputDevices={updateInputDevices}
|
||||||
|
updateOutputDevices={updateOutputDevices}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -129,6 +129,8 @@ const AudioModalContainer = (props) => {
|
|||||||
liveChangeInputDevice={Service.liveChangeInputDevice}
|
liveChangeInputDevice={Service.liveChangeInputDevice}
|
||||||
changeInputStream={Service.changeInputStream}
|
changeInputStream={Service.changeInputStream}
|
||||||
changeOutputDevice={Service.changeOutputDevice}
|
changeOutputDevice={Service.changeOutputDevice}
|
||||||
|
updateInputDevices={Service.updateInputDevices}
|
||||||
|
updateOutputDevices={Service.updateOutputDevices}
|
||||||
joinEchoTest={Service.joinEchoTest}
|
joinEchoTest={Service.joinEchoTest}
|
||||||
exitAudio={Service.exitAudio}
|
exitAudio={Service.exitAudio}
|
||||||
localEchoEnabled={LOCAL_ECHO_TEST_ENABLED}
|
localEchoEnabled={LOCAL_ECHO_TEST_ENABLED}
|
||||||
|
@ -21,6 +21,8 @@ const propTypes = {
|
|||||||
changeInputDevice: PropTypes.func.isRequired,
|
changeInputDevice: PropTypes.func.isRequired,
|
||||||
liveChangeInputDevice: PropTypes.func.isRequired,
|
liveChangeInputDevice: PropTypes.func.isRequired,
|
||||||
changeOutputDevice: PropTypes.func.isRequired,
|
changeOutputDevice: PropTypes.func.isRequired,
|
||||||
|
updateInputDevices: PropTypes.func.isRequired,
|
||||||
|
updateOutputDevices: PropTypes.func.isRequired,
|
||||||
handleBack: PropTypes.func.isRequired,
|
handleBack: PropTypes.func.isRequired,
|
||||||
handleConfirmation: PropTypes.func.isRequired,
|
handleConfirmation: PropTypes.func.isRequired,
|
||||||
handleGUMFailure: PropTypes.func.isRequired,
|
handleGUMFailure: PropTypes.func.isRequired,
|
||||||
@ -384,11 +386,16 @@ class AudioSettings extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateDeviceList() {
|
updateDeviceList() {
|
||||||
|
const { updateInputDevices, updateOutputDevices } = this.props;
|
||||||
|
|
||||||
return navigator.mediaDevices.enumerateDevices()
|
return navigator.mediaDevices.enumerateDevices()
|
||||||
.then((devices) => {
|
.then((devices) => {
|
||||||
const audioInputDevices = devices.filter((i) => i.kind === 'audioinput');
|
const audioInputDevices = devices.filter((i) => i.kind === 'audioinput');
|
||||||
const audioOutputDevices = devices.filter((i) => i.kind === 'audiooutput');
|
const audioOutputDevices = devices.filter((i) => i.kind === 'audiooutput');
|
||||||
|
|
||||||
|
// Update audio devices in AudioManager
|
||||||
|
updateInputDevices(audioInputDevices);
|
||||||
|
updateOutputDevices(audioOutputDevices);
|
||||||
this.setState({
|
this.setState({
|
||||||
audioInputDevices,
|
audioInputDevices,
|
||||||
audioOutputDevices,
|
audioOutputDevices,
|
||||||
|
@ -186,6 +186,8 @@ export default {
|
|||||||
outputDeviceId,
|
outputDeviceId,
|
||||||
isLive,
|
isLive,
|
||||||
) => AudioManager.changeOutputDevice(outputDeviceId, isLive),
|
) => AudioManager.changeOutputDevice(outputDeviceId, isLive),
|
||||||
|
updateInputDevices: (devices) => { AudioManager.inputDevices = devices },
|
||||||
|
updateOutputDevices: (devices) => { AudioManager.outputDevices = devices },
|
||||||
toggleMuteMicrophone,
|
toggleMuteMicrophone,
|
||||||
toggleMuteMicrophoneSystem,
|
toggleMuteMicrophoneSystem,
|
||||||
isConnectedToBreakout: () => {
|
isConnectedToBreakout: () => {
|
||||||
|
@ -88,6 +88,8 @@ class AudioManager {
|
|||||||
this._outputDeviceId = {
|
this._outputDeviceId = {
|
||||||
value: makeVar(null),
|
value: makeVar(null),
|
||||||
};
|
};
|
||||||
|
this._inputDevices = [];
|
||||||
|
this._outputDevices = [];
|
||||||
|
|
||||||
this.BREAKOUT_AUDIO_TRANSFER_STATES = BREAKOUT_AUDIO_TRANSFER_STATES;
|
this.BREAKOUT_AUDIO_TRANSFER_STATES = BREAKOUT_AUDIO_TRANSFER_STATES;
|
||||||
this._voiceActivityObserver = null;
|
this._voiceActivityObserver = null;
|
||||||
@ -185,6 +187,34 @@ class AudioManager {
|
|||||||
return this._outputDeviceId.value();
|
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() {
|
shouldBypassGUM() {
|
||||||
return this.supportsTransparentListenOnly() && this.inputDeviceId === 'listen-only';
|
return this.supportsTransparentListenOnly() && this.inputDeviceId === 'listen-only';
|
||||||
}
|
}
|
||||||
@ -432,15 +462,17 @@ class AudioManager {
|
|||||||
break;
|
break;
|
||||||
case 'NotFoundError':
|
case 'NotFoundError':
|
||||||
errorPayload.errCode = AudioErrors.MIC_ERROR.DEVICE_NOT_FOUND;
|
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({
|
logger.error({
|
||||||
logCode: 'audiomanager_error_device_not_found',
|
logCode: 'audiomanager_error_device_not_found',
|
||||||
extraInfo: {
|
extraInfo: {
|
||||||
errorName: error.name,
|
errorName: error.name,
|
||||||
errorMessage: error.message,
|
errorMessage: error.message,
|
||||||
|
inputDeviceId: this.inputDeviceId,
|
||||||
|
inputDevices: this.inputDevicesJSON,
|
||||||
},
|
},
|
||||||
}, `Error getting microphone - {${error.name}: ${error.message}}`);
|
}, `Error getting microphone - {${error.name}: ${error.message}}`);
|
||||||
|
// Reset the input device ID so the user can select a new one
|
||||||
|
this.changeInputDevice(null);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logger.error({
|
logger.error({
|
||||||
@ -448,6 +480,11 @@ class AudioManager {
|
|||||||
extraInfo: {
|
extraInfo: {
|
||||||
errorName: error.name,
|
errorName: error.name,
|
||||||
errorMessage: error.message,
|
errorMessage: error.message,
|
||||||
|
errorStack: error?.stack,
|
||||||
|
inputDeviceId: this.inputDeviceId,
|
||||||
|
inputDevices: this.inputDevicesJSON,
|
||||||
|
outputDeviceId: this.outputDeviceId,
|
||||||
|
outputDevices: this.outputDevicesJSON,
|
||||||
},
|
},
|
||||||
}, `Error enabling audio - {${name}: ${message}}`);
|
}, `Error enabling audio - {${name}: ${message}}`);
|
||||||
break;
|
break;
|
||||||
@ -607,10 +644,13 @@ class AudioManager {
|
|||||||
extraInfo: {
|
extraInfo: {
|
||||||
secondsToActivateAudio,
|
secondsToActivateAudio,
|
||||||
inputDeviceId: this.inputDeviceId,
|
inputDeviceId: this.inputDeviceId,
|
||||||
|
inputDevices: this.inputDevicesJSON,
|
||||||
outputDeviceId: this.outputDeviceId,
|
outputDeviceId: this.outputDeviceId,
|
||||||
|
outputDevices: this.outputDevicesJSON,
|
||||||
isListenOnly: this.isListenOnly,
|
isListenOnly: this.isListenOnly,
|
||||||
},
|
},
|
||||||
}, 'Audio Joined');
|
}, 'Audio Joined');
|
||||||
|
|
||||||
if (STATS.enabled) this.monitor();
|
if (STATS.enabled) this.monitor();
|
||||||
this.audioEventHandler({
|
this.audioEventHandler({
|
||||||
name: 'started',
|
name: 'started',
|
||||||
@ -674,8 +714,17 @@ class AudioManager {
|
|||||||
breakoutMeetingId: '',
|
breakoutMeetingId: '',
|
||||||
status: BREAKOUT_AUDIO_TRANSFER_STATES.DISCONNECTED,
|
status: BREAKOUT_AUDIO_TRANSFER_STATES.DISCONNECTED,
|
||||||
});
|
});
|
||||||
logger.info({ logCode: 'audio_ended' }, 'Audio ended without issue');
|
|
||||||
this.onAudioExit();
|
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) {
|
} else if (status === FAILED) {
|
||||||
this.isReconnecting = false;
|
this.isReconnecting = false;
|
||||||
this.setBreakoutAudioTransferStatus({
|
this.setBreakoutAudioTransferStatus({
|
||||||
@ -693,7 +742,9 @@ class AudioManager {
|
|||||||
cause: bridgeError,
|
cause: bridgeError,
|
||||||
bridge,
|
bridge,
|
||||||
inputDeviceId: this.inputDeviceId,
|
inputDeviceId: this.inputDeviceId,
|
||||||
|
inputDevices: this.inputDevicesJSON,
|
||||||
outputDeviceId: this.outputDeviceId,
|
outputDeviceId: this.outputDeviceId,
|
||||||
|
outputDevices: this.outputDevicesJSON,
|
||||||
isListenOnly: this.isListenOnly,
|
isListenOnly: this.isListenOnly,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -768,18 +819,16 @@ class AudioManager {
|
|||||||
this.setSenderTrackEnabled(!this.isMuted);
|
this.setSenderTrackEnabled(!this.isMuted);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error(
|
logger.error({
|
||||||
{
|
|
||||||
logCode: 'audiomanager_input_live_device_change_failure',
|
logCode: 'audiomanager_input_live_device_change_failure',
|
||||||
extraInfo: {
|
extraInfo: {
|
||||||
errorName: error.name,
|
errorName: error.name,
|
||||||
errorMessage: error.message,
|
errorMessage: error.message,
|
||||||
deviceId: currentDeviceId,
|
deviceId: currentDeviceId,
|
||||||
newDeviceId: deviceId,
|
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;
|
throw error;
|
||||||
});
|
});
|
||||||
@ -820,18 +869,16 @@ class AudioManager {
|
|||||||
|
|
||||||
return this.outputDeviceId;
|
return this.outputDeviceId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error({
|
||||||
{
|
|
||||||
logCode: 'audiomanager_output_device_change_failure',
|
logCode: 'audiomanager_output_device_change_failure',
|
||||||
extraInfo: {
|
extraInfo: {
|
||||||
errorName: error.name,
|
errorName: error.name,
|
||||||
errorMessage: error.message,
|
errorMessage: error.message,
|
||||||
deviceId: currentDeviceId,
|
deviceId: currentDeviceId,
|
||||||
newDeviceId: targetDeviceId,
|
newDeviceId: targetDeviceId,
|
||||||
},
|
outputDevices: this.outputDevicesJSON,
|
||||||
},
|
}
|
||||||
`Error changing output device - {${error.name}: ${error.message}}`
|
}, `Error changing output device - {${error.name}: ${error.message}}`);
|
||||||
);
|
|
||||||
|
|
||||||
// Rollback/enforce current sinkId (if possible)
|
// Rollback/enforce current sinkId (if possible)
|
||||||
if (sinkIdSupported) {
|
if (sinkIdSupported) {
|
||||||
|
Loading…
Reference in New Issue
Block a user