import React, { Fragment, useCallback, useEffect, useState } from 'react'; import { HMSKrispPlugin } from '@100mslive/hms-noise-cancellation'; import { DeviceType, HMSRoomState, selectIsLocalAudioPluginPresent, selectLocalAudioTrackID, selectLocalPeer, selectLocalVideoTrackID, selectRoom, selectRoomState, selectVideoTrackByID, useAVToggle, useDevices, useHMSActions, useHMSStore, useHMSVanillaStore, } from '@100mslive/react-sdk'; import { AudioLevelIcon, CameraFlipIcon, CheckIcon, MicOffIcon, MicOnIcon, SettingsIcon, SpeakerIcon, VideoOffIcon, VideoOnIcon, } from '@100mslive/react-icons'; import { IconButtonWithOptions } from './IconButtonWithOptions/IconButtonWithOptions'; // @ts-ignore: No implicit Any import { ActionTile } from './MoreSettings/ActionTile'; // @ts-ignore: No implicit Any import SettingsModal from './Settings/SettingsModal'; // @ts-ignore: No implicit Any import { ToastManager } from './Toast/ToastManager'; import { AudioLevel } from '../../AudioLevel'; import { Dropdown } from '../../Dropdown'; import { Box, Flex } from '../../Layout'; import { Switch } from '../../Switch'; import { Text } from '../../Text'; import { Tooltip } from '../../Tooltip'; import IconButton from '../IconButton'; import { useRoomLayoutConferencingScreen } from '../provider/roomLayoutProvider/hooks/useRoomLayoutScreen'; // @ts-ignore: No implicit Any import { useIsNoiseCancellationEnabled, useSetNoiseCancellation } from './AppData/useUISettings'; import { useAudioOutputTest } from './hooks/useAudioOutputTest'; import { isAndroid, isIOS, isMacOS, TEST_AUDIO_URL } from '../common/constants'; const krispPlugin = new HMSKrispPlugin(); // const optionsCSS = { fontWeight: '$semiBold', color: '$on_surface_high', w: '100%' }; export const Options = ({ options, selectedDeviceId, onClick, }: { options?: Array; selectedDeviceId?: string; onClick: (deviceId: string) => Promise; }) => { return ( <> {options?.map(option => ( { onClick(option.deviceId); }} > {option.label} {selectedDeviceId === option.deviceId ? : null} ))} ); }; const OptionLabel = ({ children, icon }: { children: React.ReactNode; icon: React.ReactNode }) => { return ( {icon} {children} ); }; const useNoiseCancellationWithPlugin = () => { const actions = useHMSActions(); const [inProgress, setInProgress] = useState(false); const [, setNoiseCancellationEnabled] = useSetNoiseCancellation(); const isEnabledForRoom = useHMSStore(selectRoom)?.isNoiseCancellationEnabled; const setNoiseCancellationWithPlugin = useCallback( async (enabled: boolean) => { if (!isEnabledForRoom || inProgress) { return; } if (!krispPlugin.checkSupport().isSupported) { throw Error('Krisp plugin is not supported'); } setInProgress(true); if (enabled) { await actions.addPluginToAudioTrack(krispPlugin); } else { await actions.removePluginFromAudioTrack(krispPlugin); } setNoiseCancellationEnabled(enabled); setInProgress(false); }, [actions, inProgress, isEnabledForRoom, setNoiseCancellationEnabled], ); return { setNoiseCancellationWithPlugin, inProgress, }; }; export const NoiseCancellation = ({ actionTile, iconOnly, setOpenOptionsSheet, }: { setOpenOptionsSheet?: (value: boolean) => void; iconOnly?: boolean; actionTile?: boolean; }) => { const localPeerAudioTrackID = useHMSStore(selectLocalAudioTrackID); const isNoiseCancellationEnabled = useIsNoiseCancellationEnabled(); const { setNoiseCancellationWithPlugin, inProgress } = useNoiseCancellationWithPlugin(); const room = useHMSStore(selectRoom); const isKrispPluginAdded = useHMSStore(selectIsLocalAudioPluginPresent(krispPlugin.getName())); if (!krispPlugin.isSupported() || !room.isNoiseCancellationEnabled || !localPeerAudioTrackID) { return null; } if (actionTile) { return ( { await setNoiseCancellationWithPlugin(!isNoiseCancellationEnabled); setOpenOptionsSheet?.(false); }} > {isNoiseCancellationEnabled ? 'Noise Reduced' : 'Reduce Noise'} ); } if (iconOnly) { return ( { await setNoiseCancellationWithPlugin(!isNoiseCancellationEnabled); }} disabled={inProgress} css={{ bg: isNoiseCancellationEnabled && isKrispPluginAdded ? '$surface_brighter' : '$background_dim', borderColor: isNoiseCancellationEnabled && isKrispPluginAdded ? '$border_brighter' : '$border_bright', }} > ); } return ( <> { e.preventDefault(); await setNoiseCancellationWithPlugin(!isNoiseCancellationEnabled); }} > Reduce Noise e.stopPropagation()} onCheckedChange={async value => { await setNoiseCancellationWithPlugin(value); }} /> ); }; const AudioOutputLabel = ({ deviceId }: { deviceId: string }) => { const { playing, setPlaying, audioRef } = useAudioOutputTest({ deviceId }); return ( }> Speakers { if (playing) { return; } await audioRef.current?.play(); }} > ); }; const AudioSettings = ({ onClick }: { onClick: () => void }) => { return ( <> Audio Settings ); }; export const AudioVideoToggle = ({ hideOptions = false }: { hideOptions?: boolean }) => { const { allDevices, selectedDeviceIDs, updateDevice } = useDevices(error => { ToastManager.addToast({ title: error.message, variant: 'error', duration: 2000, }); }); const { videoInput, audioInput, audioOutput } = allDevices; const localPeer = useHMSStore(selectLocalPeer); const { isLocalVideoEnabled, isLocalAudioEnabled, toggleAudio, toggleVideo } = useAVToggle(); const actions = useHMSActions(); const vanillaStore = useHMSVanillaStore(); const videoTrackId = useHMSStore(selectLocalVideoTrackID); const localVideoTrack = useHMSStore(selectVideoTrackByID(videoTrackId)); const roomState = useHMSStore(selectRoomState); const hasAudioDevices = Number(audioInput?.length) > 0; const hasVideoDevices = Number(videoInput?.length) > 0; const shouldShowAudioOutput = 'setSinkId' in HTMLMediaElement.prototype && Number(audioOutput?.length) > 0; const { screenType } = useRoomLayoutConferencingScreen(); const [showSettings, setShowSettings] = useState(false); const isKrispPluginAdded = useHMSStore(selectIsLocalAudioPluginPresent(krispPlugin.getName())); const isNoiseCancellationEnabled = useIsNoiseCancellationEnabled(); const { setNoiseCancellationWithPlugin, inProgress } = useNoiseCancellationWithPlugin(); const showMuteIcon = !isLocalAudioEnabled || !toggleAudio; useEffect(() => { (async () => { const isEnabledForRoom = vanillaStore.getState(selectRoom)?.isNoiseCancellationEnabled; if ( isEnabledForRoom && isNoiseCancellationEnabled && !isKrispPluginAdded && !inProgress && localPeer?.audioTrack ) { try { await setNoiseCancellationWithPlugin(true); ToastManager.addToast({ title: `Noise Reduction Enabled`, variant: 'standard', duration: 2000, icon: , }); } catch (error) { console.error(error); } } })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isNoiseCancellationEnabled, localPeer?.audioTrack, inProgress]); if (!toggleAudio && !toggleVideo) { return null; } return ( {toggleAudio ? ( : } active={isLocalAudioEnabled} onClick={toggleAudio} key="toggleAudio" > }> {!shouldShowAudioOutput ? 'Audio' : 'Microphone'} {!showMuteIcon && } updateDevice({ deviceId, deviceType: DeviceType.audioInput })} /> {shouldShowAudioOutput && ( <> updateDevice({ deviceId, deviceType: DeviceType.audioOutput })} /> )} setShowSettings(true)} /> ) : null} {toggleVideo ? ( : } key="toggleVideo" active={isLocalVideoEnabled} onClick={toggleVideo} > updateDevice({ deviceId, deviceType: DeviceType.videoInput })} /> ) : null} {localVideoTrack?.facingMode && roomState === HMSRoomState.Preview && (isIOS || isAndroid) ? ( { try { await actions.switchCamera(); } catch (e) { ToastManager.addToast({ title: `Error while flipping camera ${(e as Error).message || ''}`, variant: 'error', }); } }} > ) : null} {showSettings && ( setShowSettings(false)} screenType={screenType} /> )} ); };