From c795f691bf89103403ed8d1db08efc28f4c97b51 Mon Sep 17 00:00:00 2001 From: Alois Date: Fri, 15 May 2026 16:19:03 +0200 Subject: [PATCH] (feat): improved microphone gate (fix): weird behaviour related to users that are screensharing --- packages/call/src/components/modals/base.tsx | 6 +++--- packages/call/src/speakingIndicator.ts | 5 ++++- packages/call/src/store.tsx | 19 +++++++++++++++++-- packages/call/src/views/main/focused.tsx | 9 ++++++++- packages/shared/src/data.ts | 4 ++-- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/packages/call/src/components/modals/base.tsx b/packages/call/src/components/modals/base.tsx index 35e4ddd..768f6f5 100644 --- a/packages/call/src/components/modals/base.tsx +++ b/packages/call/src/components/modals/base.tsx @@ -152,12 +152,12 @@ export default function Base({ const onClick = () => { if (view === "grid") { - focusParticipant(user.user_id); + focusParticipant(user.user_id, type); } else { if (user.user_id === focusedParticipantId) { setCallView("grid"); } else { - focusParticipant(user.user_id); + focusParticipant(user.user_id, type); } } }; @@ -215,7 +215,7 @@ export default function Base({ ref={currentCard} className={`transition-all duration-150 z-10 bg-card absolute top-0 left-0 w-full h-full flex gap-2 items-center justify-center ${ flush ? "rounded-none" : "rounded-sm" - } ${isSpeaking ? "border-4 border-(--primary-foreground-alt)/75" : "border-0"}`} + } ${type === "user" && isSpeaking ? "border-4 border-(--primary-foreground-alt)/75" : "border-0"}`} style={{ containerType: "size" }} > {/* Detect video / user and place here */} diff --git a/packages/call/src/speakingIndicator.ts b/packages/call/src/speakingIndicator.ts index 38fe478..0c4f5a5 100644 --- a/packages/call/src/speakingIndicator.ts +++ b/packages/call/src/speakingIndicator.ts @@ -1,7 +1,8 @@ +import { log } from "@tensamin/shared/log"; import { useCall } from "./store"; const SPEAKING_THRESHOLD = 0.01; -const SPEAKING_HANGTIME_MS = 250; +const SPEAKING_HANGTIME_MS = 500; const ANALYSIS_INTERVAL_MS = 30; const FFT_SIZE = 256; @@ -137,8 +138,10 @@ class SpeakingDetector { const db = 20 * Math.log10(Math.max(rms, 0.0001)); if (!this.localMicGateClosed && db < this.gateThresholdStart) { + log(3, "noise gate", "purple", "closed"); this.muteLocalTrack(true); } else if (this.localMicGateClosed && db > this.gateThresholdEnd) { + log(3, "noise gate", "purple", "opened"); this.muteLocalTrack(false); } } diff --git a/packages/call/src/store.tsx b/packages/call/src/store.tsx index 38498da..72efd39 100644 --- a/packages/call/src/store.tsx +++ b/packages/call/src/store.tsx @@ -91,6 +91,7 @@ type CallStore = { screenShareEnabled: boolean; screenShareSession: ScreenShareSession | null; focusedParticipantId: number | null; + focusedParticipantType: "user" | "stream" | null; usersInFocusedViewHidden: boolean; watchedStreamParticipantIds: number[]; pendingWatchedParticipantIds: number[]; @@ -464,6 +465,8 @@ function syncScreenShareParticipants() { watchedStreamParticipantIds, pendingWatchedParticipantIds, focusedParticipantId, + focusedParticipantType: + focusedParticipantId == null ? null : state.focusedParticipantType, view: state.view === "focused" && focusedParticipantId == null ? "grid" @@ -634,6 +637,7 @@ export function startWatchingStream(participantId: number) { ? state.pendingWatchedParticipantIds : [...state.pendingWatchedParticipantIds, participantId], focusedParticipantId: participantId, + focusedParticipantType: "stream", })); } @@ -653,9 +657,13 @@ export function setParticipantTrackSubscribed( } // Focus a participant in the main call view even when they are not sharing a screen. -export function focusParticipant(participantId: number) { +export function focusParticipant( + participantId: number, + type: "user" | "stream" = "user", +) { useCall.setState({ focusedParticipantId: participantId, + focusedParticipantType: type, view: "focused", }); } @@ -675,6 +683,10 @@ export function stopWatchingStream(participantId: number) { state.focusedParticipantId === participantId ? null : state.focusedParticipantId, + focusedParticipantType: + state.focusedParticipantId === participantId + ? null + : state.focusedParticipantType, view: state.view === "focused" && state.focusedParticipantId === participantId ? "grid" @@ -790,6 +802,7 @@ export async function disconnect() { view: "preview", screenShareSession: null, focusedParticipantId: null, + focusedParticipantType: null, usersInFocusedViewHidden: false, watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], @@ -950,6 +963,7 @@ export function resetCallState() { deaf: false, screenShareSession: null, focusedParticipantId: null, + focusedParticipantType: null, usersInFocusedViewHidden: false, watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], @@ -1002,6 +1016,7 @@ export const useCall = create(() => ({ screenShareEnabled: room.localParticipant.isScreenShareEnabled, screenShareSession: null, focusedParticipantId: null, + focusedParticipantType: null, usersInFocusedViewHidden: false, watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], @@ -1038,7 +1053,7 @@ export function useInitializeCall() { new DeepFilterNoiseFilterProcessor({ enabled: true, enableNoiseReduction: true, - noiseReductionLevel: 80, + noiseReductionLevel: 60, sampleRate: 48000, assetConfig: { cdnUrl: "/assets", diff --git a/packages/call/src/views/main/focused.tsx b/packages/call/src/views/main/focused.tsx index d52362b..ec654d8 100644 --- a/packages/call/src/views/main/focused.tsx +++ b/packages/call/src/views/main/focused.tsx @@ -16,6 +16,9 @@ export default function View() { const callIsFullscreen = useCall((state) => state.callIsFullscreen); const focusedParticipantId = useCall((state) => state.focusedParticipantId); + const focusedParticipantType = useCall( + (state) => state.focusedParticipantType, + ); const activeScreenShareParticipantIds = useCall( (state) => state.activeScreenShareParticipantIds, ); @@ -157,6 +160,10 @@ export default function View() { const focusedParticipant = getParticipantById(focusedParticipantId); const focusedParticipantHasActiveScreenShare = activeScreenShareParticipantIdSet.has(focusedParticipantId); + const focusedTileType: "user" | "stream" = + focusedParticipantType === "stream" && focusedParticipantHasActiveScreenShare + ? "stream" + : "user"; const isImmersiveFocusedView = callIsFullscreen && usersInFocusedViewHidden; return ( @@ -179,7 +186,7 @@ export default function View() { diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts index 82d3452..857e9af 100644 --- a/packages/shared/src/data.ts +++ b/packages/shared/src/data.ts @@ -280,6 +280,6 @@ export const storageDefaults: Storage = { cached_contacts: [], cached_communities: [], ttp_url: "https://tensamin.net:959", - call_mute_range_start: -50, - call_mute_range_end: -40, + call_mute_range_start: -55, + call_mute_range_end: -45, };