refactor(call): move noise-suppression toggle out of the call bar
Default flipped to off so voice doesn't get coloured by browser NS on first run. Users turn it on explicitly under Settings → Sprache, where the toggle already lived before the in-call button shipped. The in-call SparklesIcon button and all the associated wiring (onToggleNoiseSuppression, noiseSuppression prop, local subscription in InCallPanel) is removed. The hot-swap UX is preserved: a new subscribeAudioSettings watcher in CallContext detects noiseSuppression flips during an active call and re-runs setupMicPipeline so the change takes effect without rejoining. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
||||
MonitorStopIcon,
|
||||
MusicIcon,
|
||||
PhoneOffIcon,
|
||||
SparklesIcon,
|
||||
UsersIcon,
|
||||
VideoIcon,
|
||||
} from './icons';
|
||||
@@ -19,7 +18,6 @@ interface Props {
|
||||
sharing: boolean;
|
||||
video: boolean;
|
||||
deafened: boolean;
|
||||
noiseSuppression?: boolean;
|
||||
onToggleMute: () => void;
|
||||
onToggleShare: () => void;
|
||||
/** Right-click on the share button opens the quality picker dialog while
|
||||
@@ -28,7 +26,6 @@ interface Props {
|
||||
onShareContextMenu?: (e: React.MouseEvent) => void;
|
||||
onToggleVideo?: () => void;
|
||||
onToggleDeafen: () => void;
|
||||
onToggleNoiseSuppression?: () => void;
|
||||
onHangup: () => void;
|
||||
onOpenParticipants?: () => void;
|
||||
/** Toggle the in-call soundboard popover. Active = panel currently open. */
|
||||
@@ -48,13 +45,11 @@ export function CallControls({
|
||||
sharing,
|
||||
video,
|
||||
deafened,
|
||||
noiseSuppression,
|
||||
onToggleMute,
|
||||
onToggleShare,
|
||||
onShareContextMenu,
|
||||
onToggleVideo,
|
||||
onToggleDeafen,
|
||||
onToggleNoiseSuppression,
|
||||
onHangup,
|
||||
onOpenParticipants,
|
||||
onToggleSoundboard,
|
||||
@@ -134,26 +129,6 @@ export function CallControls({
|
||||
<MonitorShareIcon className="h-5 w-5" />
|
||||
)}
|
||||
</CallButton>
|
||||
{onToggleNoiseSuppression && (
|
||||
<CallButton
|
||||
label={
|
||||
noiseSuppression
|
||||
? t('app:call.ns_off', {
|
||||
defaultValue: 'Rauschunterdrückung aus',
|
||||
})
|
||||
: t('app:call.ns_on', {
|
||||
defaultValue: 'Rauschunterdrückung an',
|
||||
})
|
||||
}
|
||||
active={noiseSuppression ?? false}
|
||||
activeTone="accent"
|
||||
onClick={onToggleNoiseSuppression}
|
||||
glass={glass}
|
||||
className={btnSize}
|
||||
>
|
||||
<SparklesIcon className="h-5 w-5" />
|
||||
</CallButton>
|
||||
)}
|
||||
{onToggleSoundboard && (
|
||||
<CallButton
|
||||
label={t('app:soundboard.toggle', { defaultValue: 'Soundboard' })}
|
||||
|
||||
@@ -6,11 +6,6 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { type CallMode, useCall } from '../context/CallContext';
|
||||
import {
|
||||
getAudioSettings,
|
||||
subscribeAudioSettings,
|
||||
updateAudioSettings,
|
||||
} from '../lib/audioSettings';
|
||||
import {
|
||||
getPttSettings,
|
||||
type PttSettings,
|
||||
@@ -80,7 +75,6 @@ export function InCallPanel({ conversation }: Props) {
|
||||
hangup,
|
||||
setCallMode,
|
||||
setFocusedId,
|
||||
setAudioInputDevice,
|
||||
micError,
|
||||
clearMicError,
|
||||
retryMic,
|
||||
@@ -94,10 +88,6 @@ export function InCallPanel({ conversation }: Props) {
|
||||
const [volumeMenu, setVolumeMenu] = useState<
|
||||
{ userId: string; displayName: string; x: number; y: number } | null
|
||||
>(null);
|
||||
const [noiseSuppression, setNoiseSuppression] = useState<boolean>(
|
||||
() => getAudioSettings().noiseSuppression,
|
||||
);
|
||||
useEffect(() => subscribeAudioSettings((s) => setNoiseSuppression(s.noiseSuppression)), []);
|
||||
// Active-speaker auto-focus uses "who most recently started speaking"
|
||||
// rather than "exactly one speaker" — matches Discord more closely and
|
||||
// handles the case where two people talk briefly without the focus
|
||||
@@ -180,7 +170,6 @@ export function InCallPanel({ conversation }: Props) {
|
||||
sharing={isScreenSharing}
|
||||
video={isCameraEnabled}
|
||||
deafened={isDeafened}
|
||||
noiseSuppression={noiseSuppression}
|
||||
onToggleMute={toggleMute}
|
||||
// 1-click share uses last-saved preset + displaySurface. Right-click
|
||||
// opens the quality picker for users who want to change settings
|
||||
@@ -198,14 +187,6 @@ export function InCallPanel({ conversation }: Props) {
|
||||
}}
|
||||
onToggleVideo={() => void toggleCamera()}
|
||||
onToggleDeafen={toggleDeafen}
|
||||
// Hot-swap the mic track with new constraints by replaying the
|
||||
// input-device switch with the same id. setAudioInputDevice re-reads
|
||||
// audioSettings, so flipping noiseSuppression first is enough.
|
||||
onToggleNoiseSuppression={() => {
|
||||
const prev = getAudioSettings().noiseSuppression;
|
||||
updateAudioSettings({ noiseSuppression: !prev });
|
||||
void setAudioInputDevice(getAudioSettings().inputDeviceId);
|
||||
}}
|
||||
onOpenParticipants={() => setParticipantsOpen((v) => !v)}
|
||||
participantsOpen={participantsOpen}
|
||||
onToggleSoundboard={() => setSoundboardOpen((v) => !v)}
|
||||
|
||||
@@ -1235,6 +1235,23 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Hot-swap the mic track when the user flips noiseSuppression in Settings
|
||||
// so the change takes effect without needing to rejoin the call. Tracks
|
||||
// the previous value in a ref so we only re-acquire getUserMedia on
|
||||
// actual transitions (avoid a rebuild on every unrelated settings save).
|
||||
useEffect(() => {
|
||||
let lastNs = getAudioSettings().noiseSuppression;
|
||||
return subscribeAudioSettings((s) => {
|
||||
if (s.noiseSuppression === lastNs) return;
|
||||
lastNs = s.noiseSuppression;
|
||||
const r = roomRef.current;
|
||||
if (!r) return;
|
||||
// setupMicPipeline re-reads audioSettings so the new NS constraint
|
||||
// gets picked up. Same helper as the initial join + retry paths.
|
||||
void setupMicPipeline(r);
|
||||
});
|
||||
}, [setupMicPipeline]);
|
||||
|
||||
// --- Push-to-talk ------------------------------------------------------
|
||||
// While PTT is active + we're in a connected call, the mic is held off
|
||||
// except while the configured key is pressed. Under Tauri we also register
|
||||
|
||||
@@ -41,7 +41,10 @@ const DEFAULTS: AudioSettings = {
|
||||
inputDeviceId: null,
|
||||
outputDeviceId: null,
|
||||
voiceThreshold: 0.03,
|
||||
noiseSuppression: true,
|
||||
// Off by default — browser-native NS colours voice audibly on some
|
||||
// mics and is a frequent "why does my voice sound weird" report.
|
||||
// Users who want it enable it explicitly in Settings → Sprache.
|
||||
noiseSuppression: false,
|
||||
videoBackgroundBlur: false,
|
||||
ringtoneVolume: 0.9,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user