// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { AudioVideoFacade, VoiceFocusTransformDevice, } from 'amazon-chime-sdk-js'; import isEqual from 'lodash.isequal'; import React, { ReactNode, useEffect, useState } from 'react'; import { useToggleLocalMute } from '../../../hooks/sdk/useToggleLocalMute'; import { useAudioVideo } from '../../../providers/AudioVideoProvider'; import { useAudioInputs } from '../../../providers/DevicesProvider'; import { useLogger } from '../../../providers/LoggerProvider'; import { useMeetingManager } from '../../../providers/MeetingProvider'; import { useVoiceFocus } from '../../../providers/VoiceFocusProvider'; import { DeviceType } from '../../../types'; import { isOptionActive } from '../../../utils/device-utils'; import useMemoCompare from '../../../utils/use-memo-compare'; import { ControlBarButton } from '../../ui/ControlBar/ControlBarButton'; import { Microphone } from '../../ui/icons'; import { Spinner } from '../../ui/icons'; import { PopOverItem } from '../../ui/PopOver/PopOverItem'; import PopOverSeparator from '../../ui/PopOver/PopOverSeparator'; import { BaseSdkProps } from '../Base'; interface Props extends BaseSdkProps { /** The label that will be shown when microphone is muted, it defaults to `Mute`. */ muteLabel?: string; /** The label that will be shown when microphone is unmuted, it defaults to `Unmute`. */ unmuteLabel?: string; /** Title attribute for the icon when muted, it defaults to `Muted microphone` in . */ mutedIconTitle?: string; /** Title attribute for the icon when unmuted, it defaults to `Microphone` in . */ unmutedIconTitle?: string; /** The label that will be shown when the current input audio is an Amazon Voice Focus device, * it defaults to `Amazon Voice Focus enabled`. */ voiceFocusOnLabel?: string; /** The label that will be shown when the current input audio is not an Amazon Voice Focus device, * it defaults to `Enable Amazon Voice Focus`. */ voiceFocusOffLabel?: string; } export const AudioInputVFControl: React.FC> = ({ muteLabel = 'Mute', unmuteLabel = 'Unmute', mutedIconTitle, unmutedIconTitle, voiceFocusOnLabel = 'Amazon Voice Focus enabled', voiceFocusOffLabel = 'Enable Amazon Voice Focus', ...rest }) => { const logger = useLogger(); const audioVideo = useAudioVideo(); const meetingManager = useMeetingManager(); const [isLoading, setIsLoading] = useState(false); // When the user click on Amazon Voice Focus option, the state will change. const [isVoiceFocusChecked, setIsVoiceFocusChecked] = useState(false); // Only when the current input audio device is an Amazon Voice Focus device, // the state will be true. Otherwise, it will be false. const [isVoiceFocusEnabled, setIsVoiceFocusEnabled] = useState(false); const [dropdownWithVFOptions, setDropdownWithVFOptions] = useState< ReactNode[] | null >(null); const { muted, toggleMute } = useToggleLocalMute(); const { isVoiceFocusSupported, addVoiceFocus } = useVoiceFocus(); const { devices, selectedDevice } = useAudioInputs(); const audioInputDevices: DeviceType[] = useMemoCompare( devices, (prev: DeviceType[], next: DeviceType[]): boolean => { return isEqual(prev, next); } ); useEffect(() => { logger.info( `Amazon Voice Focus is ${isVoiceFocusEnabled ? 'enabled' : 'disabled'}.` ); }, [isVoiceFocusEnabled]); useEffect(() => { // Only when the current input audio device is an Amazon Voice Focus transform device, // Amazon Voice Focus button will be checked. if (selectedDevice instanceof VoiceFocusTransformDevice) { setIsVoiceFocusEnabled(true); } else { setIsVoiceFocusEnabled(false); } return () => { if (selectedDevice instanceof VoiceFocusTransformDevice) { selectedDevice.stop(); } }; }, [selectedDevice]); useEffect(() => { if (!audioVideo) { return; } if ( selectedDevice instanceof VoiceFocusTransformDevice && isVoiceFocusEnabled ) { selectedDevice.observeMeetingAudio(audioVideo as AudioVideoFacade); } }, [audioVideo, isVoiceFocusEnabled, selectedDevice]); useEffect(() => { const handleClick = async (deviceId: string): Promise => { try { if (isVoiceFocusChecked && !isLoading) { setIsLoading(true); const receivedDevice = deviceId; const currentDevice = await addVoiceFocus(receivedDevice); await meetingManager.startAudioInputDevice(currentDevice); } else { await meetingManager.startAudioInputDevice(deviceId); } } catch (error) { logger.error('AudioInputVFControl failed to select audio input device'); } finally { setIsLoading(false); } }; const getDropdownWithVFOptions = async (): Promise => { const dropdownOptions: ReactNode[] = await Promise.all( audioInputDevices.map(async (device) => ( await handleClick(device.deviceId)} > {device.label} )) ); if (isVoiceFocusSupported) { const vfOption: ReactNode = ( { setIsLoading(true); setIsVoiceFocusChecked((current) => !current); }} > <> {isLoading && } {isVoiceFocusEnabled ? voiceFocusOnLabel : voiceFocusOffLabel} ); dropdownOptions?.push(); dropdownOptions?.push(vfOption); } setDropdownWithVFOptions(dropdownOptions); }; getDropdownWithVFOptions(); }, [ // The contents of this dropdown depends, of course, on the current selected device, // but also on the Voice Focus state, including `addVoiceFocus` which is used inside // the click handler. addVoiceFocus, meetingManager, meetingManager.startAudioInputDevice, audioInputDevices, isLoading, isVoiceFocusEnabled, isVoiceFocusChecked, isVoiceFocusSupported, selectedDevice, ]); useEffect(() => { const onVFCheckboxChange = async (): Promise => { if (!selectedDevice) { return; } try { let current = selectedDevice; if (isVoiceFocusChecked) { logger.info('User turned on Amazon Voice Focus.'); if (typeof selectedDevice === 'string') { current = await addVoiceFocus(selectedDevice); } } else { logger.info( 'Amazon Voice Focus is off by default or user turned off Amazon Voice Focus.' ); if (selectedDevice instanceof VoiceFocusTransformDevice) { current = selectedDevice.getInnerDevice(); } } await meetingManager.startAudioInputDevice(current); } catch (error) { logger.error( 'AudioInputVFControl failed to select audio input device onVFCheckboxChange change' ); } setIsLoading(false); }; onVFCheckboxChange(); }, [isVoiceFocusChecked]); return ( } onClick={toggleMute} label={muted ? unmuteLabel : muteLabel} {...rest} > {dropdownWithVFOptions} ); }; export default AudioInputVFControl;