feat(call): PTT, deafen label, camera default off, screenshare confirm, noise suppression setting
- Push to Talk: keydown/keyup binds mic to configurable key (default Space) with visual PTT indicator and key-binding UI in Settings > General > Calls - Camera always defaults OFF on join; cameraOnJoin setting for explicit opt-in - Deafen button tooltip corrected to Deafen/Undeafen instead of Turn Off/On Sound - Screenshare confirmation dialog before broadcasting to call participants - Noise suppression toggle wired from settings through CallEmbed URL params - CallControl.setMicrophone() public method for programmatic mic control - Calls settings section added to General settings page Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { MouseEventHandler, useCallback, useRef, useState } from 'react';
|
import React, { MouseEventHandler, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@@ -26,6 +26,8 @@ import {
|
|||||||
VideoButton,
|
VideoButton,
|
||||||
} from './Controls';
|
} from './Controls';
|
||||||
import { CallEmbed, useCallControlState } from '../../plugins/call';
|
import { CallEmbed, useCallControlState } from '../../plugins/call';
|
||||||
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
|
import { settingsAtom } from '../../state/settings';
|
||||||
import { useResizeObserver } from '../../hooks/useResizeObserver';
|
import { useResizeObserver } from '../../hooks/useResizeObserver';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||||
@@ -51,6 +53,10 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [cords, setCords] = useState<RectCords>();
|
const [cords, setCords] = useState<RectCords>();
|
||||||
|
const [shareConfirm, setShareConfirm] = useState(false);
|
||||||
|
const [pttMode] = useSetting(settingsAtom, 'pttMode');
|
||||||
|
const [pttKey] = useSetting(settingsAtom, 'pttKey');
|
||||||
|
const [pttActive, setPttActive] = useState(false);
|
||||||
|
|
||||||
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||||
setCords(evt.currentTarget.getBoundingClientRect());
|
setCords(evt.currentTarget.getBoundingClientRect());
|
||||||
@@ -71,12 +77,37 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
|||||||
setCords(undefined);
|
setCords(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pttMode) return;
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.code === pttKey && !e.repeat && !microphone) {
|
||||||
|
e.preventDefault();
|
||||||
|
callEmbed.control.setMicrophone(true);
|
||||||
|
setPttActive(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onKeyUp = (e: KeyboardEvent) => {
|
||||||
|
if (e.code === pttKey) {
|
||||||
|
callEmbed.control.setMicrophone(false);
|
||||||
|
setPttActive(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('keydown', onKeyDown);
|
||||||
|
window.addEventListener('keyup', onKeyUp);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', onKeyDown);
|
||||||
|
window.removeEventListener('keyup', onKeyUp);
|
||||||
|
};
|
||||||
|
}, [pttMode, pttKey, callEmbed, microphone]);
|
||||||
|
|
||||||
const [hangupState, hangup] = useAsyncCallback(
|
const [hangupState, hangup] = useAsyncCallback(
|
||||||
useCallback(() => callEmbed.hangup(), [callEmbed])
|
useCallback(() => callEmbed.hangup(), [callEmbed])
|
||||||
);
|
);
|
||||||
const exiting =
|
const exiting =
|
||||||
hangupState.status === AsyncStatus.Loading || hangupState.status === AsyncStatus.Success;
|
hangupState.status === AsyncStatus.Loading || hangupState.status === AsyncStatus.Success;
|
||||||
|
|
||||||
|
const pttKeyLabel = pttKey === 'Space' ? 'SPACE' : pttKey.replace('Key', '').replace('Digit', '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
ref={controlRef}
|
ref={controlRef}
|
||||||
@@ -84,6 +115,63 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
|||||||
justifyContent="Center"
|
justifyContent="Center"
|
||||||
alignItems="Center"
|
alignItems="Center"
|
||||||
>
|
>
|
||||||
|
{pttMode && (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-2.5rem',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
background: pttActive ? 'rgba(0,255,136,0.18)' : 'rgba(255,107,0,0.12)',
|
||||||
|
border: `1px solid ${pttActive ? 'rgba(0,255,136,0.55)' : 'rgba(255,107,0,0.35)'}`,
|
||||||
|
borderRadius: '99px',
|
||||||
|
padding: '0.2rem 0.9rem',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text size="T200" style={{ color: pttActive ? '#00FF88' : '#FF6B00', fontWeight: 700, letterSpacing: '0.08em' }}>
|
||||||
|
{pttActive ? '● LIVE' : `PTT — Hold ${pttKeyLabel}`}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{shareConfirm && (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '110%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
background: 'var(--bg-surface)',
|
||||||
|
border: '1px solid var(--border-color)',
|
||||||
|
borderRadius: '0.75rem',
|
||||||
|
padding: '1rem 1.25rem',
|
||||||
|
zIndex: 100,
|
||||||
|
minWidth: '260px',
|
||||||
|
boxShadow: '0 8px 32px rgba(0,0,0,0.35)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '0.75rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text size="T300" style={{ fontWeight: 600 }}>Share your screen?</Text>
|
||||||
|
<Text size="T200" style={{ opacity: 0.75 }}>Your screen will be visible to all participants in this call.</Text>
|
||||||
|
<Box gap="200">
|
||||||
|
<Button
|
||||||
|
size="300" variant="Success" fill="Solid" radii="300"
|
||||||
|
onClick={() => { callEmbed.control.toggleScreenshare(); setShareConfirm(false); }}
|
||||||
|
>
|
||||||
|
<Text size="B300">Share</Text>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="300" variant="Surface" fill="Soft" radii="300" outlined
|
||||||
|
onClick={() => setShareConfirm(false)}
|
||||||
|
>
|
||||||
|
<Text size="B300">Cancel</Text>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<SequenceCard
|
<SequenceCard
|
||||||
className={css.ControlCard}
|
className={css.ControlCard}
|
||||||
variant="SurfaceVariant"
|
variant="SurfaceVariant"
|
||||||
@@ -105,7 +193,10 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
|||||||
<VideoButton enabled={video} onToggle={() => callEmbed.control.toggleVideo()} />
|
<VideoButton enabled={video} onToggle={() => callEmbed.control.toggleVideo()} />
|
||||||
<ScreenShareButton
|
<ScreenShareButton
|
||||||
enabled={screenshare}
|
enabled={screenshare}
|
||||||
onToggle={() => callEmbed.control.toggleScreenshare()}
|
onToggle={() => screenshare
|
||||||
|
? callEmbed.control.toggleScreenshare()
|
||||||
|
: setShareConfirm(true)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function SoundButton({ enabled, onToggle }: SoundButtonProps) {
|
|||||||
delay={500}
|
delay={500}
|
||||||
tooltip={
|
tooltip={
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<Text size="T200">{enabled ? 'Turn Off Sound' : 'Turn On Sound'}</Text>
|
<Text size="T200">{enabled ? 'Deafen' : 'Undeafen'}</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -805,6 +805,80 @@ function Editor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Calls() {
|
||||||
|
const [cameraOnJoin, setCameraOnJoin] = useSetting(settingsAtom, 'cameraOnJoin');
|
||||||
|
const [callNoiseSuppression, setCallNoiseSuppression] = useSetting(settingsAtom, 'callNoiseSuppression');
|
||||||
|
const [pttMode, setPttMode] = useSetting(settingsAtom, 'pttMode');
|
||||||
|
const [pttKey, setPttKey] = useSetting(settingsAtom, 'pttKey');
|
||||||
|
const [listeningForKey, setListeningForKey] = useState(false);
|
||||||
|
|
||||||
|
const handleKeyBind = () => {
|
||||||
|
setListeningForKey(true);
|
||||||
|
const onKey = (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.code === 'Escape') {
|
||||||
|
setListeningForKey(false);
|
||||||
|
} else {
|
||||||
|
setPttKey(e.code);
|
||||||
|
setListeningForKey(false);
|
||||||
|
}
|
||||||
|
window.removeEventListener('keydown', onKey, true);
|
||||||
|
};
|
||||||
|
window.addEventListener('keydown', onKey, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyLabel = (code: string) => code === 'Space' ? 'Space' : code.replace('Key', '').replace('Digit', '');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box direction="Column" gap="100">
|
||||||
|
<Text size="L400">Calls</Text>
|
||||||
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||||
|
<SettingTile
|
||||||
|
title="Join with Camera On"
|
||||||
|
description="Enable camera automatically when joining a voice or video call. Camera is off by default."
|
||||||
|
after={<Switch variant="Primary" value={cameraOnJoin} onChange={setCameraOnJoin} />}
|
||||||
|
/>
|
||||||
|
</SequenceCard>
|
||||||
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||||
|
<SettingTile
|
||||||
|
title="Noise Suppression"
|
||||||
|
description="Apply AI noise suppression to filter background noise during calls (powered by Element Call)."
|
||||||
|
after={<Switch variant="Primary" value={callNoiseSuppression} onChange={setCallNoiseSuppression} />}
|
||||||
|
/>
|
||||||
|
</SequenceCard>
|
||||||
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column" gap="400">
|
||||||
|
<SettingTile
|
||||||
|
title="Push to Talk"
|
||||||
|
description="Mute your microphone by default. Hold the PTT key to speak."
|
||||||
|
after={<Switch variant="Primary" value={pttMode} onChange={setPttMode} />}
|
||||||
|
/>
|
||||||
|
{pttMode && (
|
||||||
|
<SettingTile
|
||||||
|
title="PTT Key Binding"
|
||||||
|
description="Press a key to bind it as your push-to-talk key."
|
||||||
|
after={
|
||||||
|
<Button
|
||||||
|
size="300"
|
||||||
|
variant={listeningForKey ? 'Warning' : 'Secondary'}
|
||||||
|
fill={listeningForKey ? 'Solid' : 'Soft'}
|
||||||
|
radii="300"
|
||||||
|
outlined
|
||||||
|
onClick={handleKeyBind}
|
||||||
|
style={{ minWidth: '90px' }}
|
||||||
|
>
|
||||||
|
<Text size="B300">
|
||||||
|
{listeningForKey ? 'Press a key…' : keyLabel(pttKey)}
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SequenceCard>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function ChatBgGrid() {
|
function ChatBgGrid() {
|
||||||
const [chatBackground, setChatBackground] = useSetting(settingsAtom, 'chatBackground');
|
const [chatBackground, setChatBackground] = useSetting(settingsAtom, 'chatBackground');
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -1110,6 +1184,7 @@ export function General({ requestClose }: GeneralProps) {
|
|||||||
<DateAndTime />
|
<DateAndTime />
|
||||||
<Editor />
|
<Editor />
|
||||||
<Messages />
|
<Messages />
|
||||||
|
<Calls />
|
||||||
</Box>
|
</Box>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Scroll>
|
</Scroll>
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { useResizeObserver } from './useResizeObserver';
|
|||||||
import { CallControlState } from '../plugins/call/CallControlState';
|
import { CallControlState } from '../plugins/call/CallControlState';
|
||||||
import { useCallMembersChange, useCallSession } from './useCall';
|
import { useCallMembersChange, useCallSession } from './useCall';
|
||||||
import { CallPreferences } from '../state/callPreferences';
|
import { CallPreferences } from '../state/callPreferences';
|
||||||
|
import { useSetting } from '../state/hooks/settings';
|
||||||
|
import { settingsAtom } from '../state/settings';
|
||||||
|
|
||||||
const CallEmbedContext = createContext<CallEmbed | undefined>(undefined);
|
const CallEmbedContext = createContext<CallEmbed | undefined>(undefined);
|
||||||
|
|
||||||
@@ -42,14 +44,15 @@ export const createCallEmbed = (
|
|||||||
dm: boolean,
|
dm: boolean,
|
||||||
themeKind: ElementCallThemeKind,
|
themeKind: ElementCallThemeKind,
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
pref?: CallPreferences
|
pref?: CallPreferences,
|
||||||
|
noiseSuppression = true
|
||||||
): CallEmbed => {
|
): CallEmbed => {
|
||||||
const rtcSession = mx.matrixRTC.getRoomSession(room);
|
const rtcSession = mx.matrixRTC.getRoomSession(room);
|
||||||
const ongoing =
|
const ongoing =
|
||||||
MatrixRTCSession.sessionMembershipsForRoom(room, rtcSession.sessionDescription).length > 0;
|
MatrixRTCSession.sessionMembershipsForRoom(room, rtcSession.sessionDescription).length > 0;
|
||||||
|
|
||||||
const intent = CallEmbed.getIntent(dm, ongoing);
|
const intent = CallEmbed.getIntent(dm, ongoing);
|
||||||
const widget = CallEmbed.getWidget(mx, room, intent, themeKind);
|
const widget = CallEmbed.getWidget(mx, room, intent, themeKind, noiseSuppression);
|
||||||
const controlState = pref && new CallControlState(pref.microphone, pref.video, pref.sound);
|
const controlState = pref && new CallControlState(pref.microphone, pref.video, pref.sound);
|
||||||
|
|
||||||
const embed = new CallEmbed(mx, room, widget, container, controlState);
|
const embed = new CallEmbed(mx, room, widget, container, controlState);
|
||||||
@@ -62,6 +65,7 @@ export const useCallStart = (dm = false) => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setCallEmbed = useSetAtom(callEmbedAtom);
|
const setCallEmbed = useSetAtom(callEmbedAtom);
|
||||||
const callEmbedRef = useCallEmbedRef();
|
const callEmbedRef = useCallEmbedRef();
|
||||||
|
const [callNoiseSuppression] = useSetting(settingsAtom, 'callNoiseSuppression');
|
||||||
|
|
||||||
const startCall = useCallback(
|
const startCall = useCallback(
|
||||||
(room: Room, pref?: CallPreferences) => {
|
(room: Room, pref?: CallPreferences) => {
|
||||||
@@ -69,11 +73,11 @@ export const useCallStart = (dm = false) => {
|
|||||||
if (!container) {
|
if (!container) {
|
||||||
throw new Error('Failed to start call, No embed container element found!');
|
throw new Error('Failed to start call, No embed container element found!');
|
||||||
}
|
}
|
||||||
const callEmbed = createCallEmbed(mx, room, dm, theme.kind, container, pref);
|
const callEmbed = createCallEmbed(mx, room, dm, theme.kind, container, pref, callNoiseSuppression ?? true);
|
||||||
|
|
||||||
setCallEmbed(callEmbed);
|
setCallEmbed(callEmbed);
|
||||||
},
|
},
|
||||||
[mx, dm, theme, setCallEmbed, callEmbedRef]
|
[mx, dm, theme, setCallEmbed, callEmbedRef, callNoiseSuppression]
|
||||||
);
|
);
|
||||||
|
|
||||||
return startCall;
|
return startCall;
|
||||||
|
|||||||
@@ -173,6 +173,14 @@ export class CallControl extends EventEmitter implements CallControlState {
|
|||||||
this.emitStateUpdate();
|
this.emitStateUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setMicrophone(enabled: boolean) {
|
||||||
|
const payload: ElementMediaStatePayload = {
|
||||||
|
audio_enabled: enabled,
|
||||||
|
video_enabled: this.video,
|
||||||
|
};
|
||||||
|
return this.setMediaState(payload);
|
||||||
|
}
|
||||||
|
|
||||||
public toggleMicrophone() {
|
public toggleMicrophone() {
|
||||||
const payload: ElementMediaStatePayload = {
|
const payload: ElementMediaStatePayload = {
|
||||||
audio_enabled: !this.microphone,
|
audio_enabled: !this.microphone,
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ export class CallEmbed {
|
|||||||
mx: MatrixClient,
|
mx: MatrixClient,
|
||||||
room: Room,
|
room: Room,
|
||||||
intent: ElementCallIntent,
|
intent: ElementCallIntent,
|
||||||
themeKind: ElementCallThemeKind
|
themeKind: ElementCallThemeKind,
|
||||||
|
noiseSuppression = true
|
||||||
): Widget {
|
): Widget {
|
||||||
const userId = mx.getSafeUserId();
|
const userId = mx.getSafeUserId();
|
||||||
const deviceId = mx.getDeviceId() ?? '';
|
const deviceId = mx.getDeviceId() ?? '';
|
||||||
@@ -81,6 +82,7 @@ export class CallEmbed {
|
|||||||
perParticipantE2EE: room.hasEncryptionStateEvent().toString(),
|
perParticipantE2EE: room.hasEncryptionStateEvent().toString(),
|
||||||
lang: 'en-EN',
|
lang: 'en-EN',
|
||||||
theme: themeKind,
|
theme: themeKind,
|
||||||
|
noiseSuppression: noiseSuppression.toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const widgetUrl = new URL(
|
const widgetUrl = new URL(
|
||||||
|
|||||||
@@ -28,10 +28,13 @@ export const makeCallPreferencesAtom = (userId: string): CallPreferencesAtom =>
|
|||||||
storeKey,
|
storeKey,
|
||||||
(key) => {
|
(key) => {
|
||||||
const v = getLocalStorageItem<CallPreferences>(key, DEFAULT_PREFERENCES);
|
const v = getLocalStorageItem<CallPreferences>(key, DEFAULT_PREFERENCES);
|
||||||
return v;
|
// Never restore camera state — always start with camera off for privacy.
|
||||||
|
// Users can toggle it in the prescreen before joining.
|
||||||
|
return { ...v, video: false };
|
||||||
},
|
},
|
||||||
(key, value) => {
|
(key, value) => {
|
||||||
setLocalStorageItem(key, value);
|
// Don't persist video state — always resets to off on next load.
|
||||||
|
setLocalStorageItem(key, { ...value, video: false });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { createContext, useCallback, useContext } from 'react';
|
import { createContext, useCallback, useContext } from 'react';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { CallPreferences, CallPreferencesAtom } from '../callPreferences';
|
import { CallPreferences, CallPreferencesAtom } from '../callPreferences';
|
||||||
|
import { useSetting } from './settings';
|
||||||
|
import { settingsAtom } from '../settings';
|
||||||
|
|
||||||
const CallPreferencesAtomContext = createContext<CallPreferencesAtom | null>(null);
|
const CallPreferencesAtomContext = createContext<CallPreferencesAtom | null>(null);
|
||||||
export const CallPreferencesProvider = CallPreferencesAtomContext.Provider;
|
export const CallPreferencesProvider = CallPreferencesAtomContext.Provider;
|
||||||
@@ -21,6 +23,7 @@ export const useCallPreferences = (): CallPreferences & {
|
|||||||
} => {
|
} => {
|
||||||
const callPrefAtom = useCallPreferencesAtom();
|
const callPrefAtom = useCallPreferencesAtom();
|
||||||
const [pref, setPref] = useAtom(callPrefAtom);
|
const [pref, setPref] = useAtom(callPrefAtom);
|
||||||
|
const [cameraOnJoin] = useSetting(settingsAtom, 'cameraOnJoin');
|
||||||
|
|
||||||
const toggleMicrophone = useCallback(() => {
|
const toggleMicrophone = useCallback(() => {
|
||||||
const microphone = !pref.microphone;
|
const microphone = !pref.microphone;
|
||||||
@@ -54,6 +57,7 @@ export const useCallPreferences = (): CallPreferences & {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...pref,
|
...pref,
|
||||||
|
video: cameraOnJoin ? pref.video : false,
|
||||||
toggleMicrophone,
|
toggleMicrophone,
|
||||||
toggleVideo,
|
toggleVideo,
|
||||||
toggleSound,
|
toggleSound,
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ export interface Settings {
|
|||||||
|
|
||||||
chatBackground: ChatBackground;
|
chatBackground: ChatBackground;
|
||||||
perMessageProfiles: boolean;
|
perMessageProfiles: boolean;
|
||||||
|
|
||||||
|
cameraOnJoin: boolean;
|
||||||
|
callNoiseSuppression: boolean;
|
||||||
|
pttMode: boolean;
|
||||||
|
pttKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSettings: Settings = {
|
const defaultSettings: Settings = {
|
||||||
@@ -84,6 +89,11 @@ const defaultSettings: Settings = {
|
|||||||
|
|
||||||
chatBackground: 'none',
|
chatBackground: 'none',
|
||||||
perMessageProfiles: false,
|
perMessageProfiles: false,
|
||||||
|
|
||||||
|
cameraOnJoin: false,
|
||||||
|
callNoiseSuppression: true,
|
||||||
|
pttMode: false,
|
||||||
|
pttKey: 'Space',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSettings = () => {
|
export const getSettings = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user