feat(lotus): report speakingWhileMuted for the local participant in io.lotus.call_state (#37)

While the mic is muted LiveKit keeps the capture alive and merely disables the
published MediaStreamTrack, so VAD says nothing. lotusMutedSpeech taps a CLONE
of the published track (post-denoise when the in-source processor is active),
samples RMS at 10 Hz through an AnalyserNode and runs a hysteresis gate (300 ms
of voice on, 800 ms of quiet off, threshold ≈ −36 dBFS). The tap only exists
while a muted mic publication exists; the flag is set on the local entry of
io.lotus.call_state only and is never sent to other participants. Gate is
unit-tested; verified end-to-end: muted with a tone mic → true within 2.5 s,
silence → false, unmute → false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-19 22:45:23 -04:00
co-authored by Claude Opus 5
parent 5421d545c4
commit c2267800be
4 changed files with 227 additions and 29 deletions
+5 -1
View File
@@ -53,7 +53,11 @@ function mockMember(id: string, userId: string): Member {
}
function mockVm(members: Member[]): CallViewModel {
return { userMedia$: of(members) } as unknown as CallViewModel;
return {
userMedia$: of(members),
// no livekit connections → the muted-speech tap never starts
allConnections$: of({ getConnections: () => [] }),
} as unknown as CallViewModel;
}
function participantsOf(call: number): unknown[] {