Deafen via per-participant setVolume is undone by EC volume controls and clobbers tile volumes on undeafen #1
Closed
opened 2026-09-12 02:12:56 -04:00 by jared
·
1 comment
No Branch/Tag Specified
lotus
chore/dev-tool-security
feat/expected-devices
lotus-upstream-v0.28.0
feat/call-summary-details
feat/soundboard-mic-mix
feat/mobile-self-tile
feat/screenshare-audio-state
feat/decorations-origin
docs/lotus-call-error
lotus-stall-retry
docs/lotus-protocol
lotus-soundboard-not-permitted
lotus-mic-sampler-churn
lotus-call-error
fix/audio-output-list
lotus-notify-none
lotus-screenshare-prompt
lotus-call-origin
v0.28.0-lotus.4
v0.28.0-lotus.3
v0.28.0-lotus.2
v0.28.0-lotus.1
v0.25.0-lotus.29
v0.25.0-lotus.28
v0.25.0-lotus.27
v0.25.0-lotus.26
v0.25.0-lotus.25
v0.25.0-lotus.24
v0.25.0-lotus.23
v0.25.0-lotus.22
v0.25.0-lotus.21
v0.25.0-lotus.20
v0.25.0-lotus.19
v0.25.0-lotus.18
v0.25.0-lotus.17
v0.25.0-lotus.16
v0.25.0-lotus.15
v0.25.0-lotus.14
v0.25.0-lotus.13
v0.25.0-lotus.12
v0.25.0-lotus.11
v0.25.0-lotus.10
v0.25.0-lotus.9
v0.25.0-lotus.8
v0.25.0-lotus.7
v0.25.0-lotus.6
v0.25.0-lotus.5
v0.25.0-lotus.4
v0.25.0-lotus.3
v0.25.0-lotus.2
v0.25.0-lotus.1
v0.20.1
Labels
Clear labels
a11y
area: audio-pipeline
area: build-publish
area: deafen
area: decorations
area: denoise
area: livekit
area: mobile-ui
area: soundboard-inject
area: upstream-sync
area: widget-actions
bug
dependencies
docs
enhancement
performance
priority: critical
priority: high
priority: low
priority: medium
research
security
tech-debt
ux
Accessibility
Fork area: audio-pipeline
Fork area: build-publish
Fork area: deafen
Fork area: decorations
Fork area: denoise
Fork area: livekit
Fork area: mobile-ui
Fork area: soundboard-inject
Fork area: upstream-sync
Fork area: widget-actions
Something is not working
Package versions and advisories
Docs wrong or missing
New feature
Audio pipeline cost, re-render storms, leaks
Data loss, security hole, or crash on a main path
Broken feature or serious usability problem
Minor issue or polish
Wrong behaviour in an edge case or notable degradation
Origin checks, capability leaks, media/key handling
Code health, fragile patterns
Usability or visual inconsistency
Milestone
No items
No Milestone
EC fork audit 2026-09 · High
Projects
Clear projects
No projects
Notifications
Due Date
Dependencies
No dependencies set.
Reference: LotusGuild/element-call#1
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: high · Type: bug · Confidence: high
Location:
src/lotus/lotusDeafen.ts:51-56,src/state/media/RemoteUserMediaViewModel.ts:53,src/state/VolumeControls.ts:80-90src/lotus/lotusDeafen.ts:51-56src/lotus/lotusDeafen.ts:75-95,src/state/CallViewModel/CallViewModel.ts:643-684Problem
lotusDeafenimplements deafen by callingRemoteParticipant.setVolume(0, Track.Source.Microphone). Upstream EC uses the same API for its per-participant volume slider:createVolumeControlssubscribescombineLatest([sink$, playbackVolume$])and callsp.setVolume(volume)(default source =Microphone) every time either input emits.sink$is derived fromparticipant$, so it re-emits whenever a member'sRemoteParticipantobject appears or is replaced — i.e. on every join, and on participant re-resolution after a reconnect. The defaultplaybackVolume$is1, so EC writessetVolume(1)after lotusDeafen wrote0, and the LiveKitvolumeMapkeeps the last writer. Deafen therefore does not hold for anyone who joins (or reconnects) while you are deafened — exactly the case the module's docstring claims to cover viaParticipantConnected.Second symptom (undeafen):
applyToParticipantunconditionally writes1when not deafened, for bothMicrophoneandScreenShareAudio. EC exposes a per-participant volume slider and a per-tile "mute this participant" toggle backed byVolumeControls(playbackVolume$,togglePlaybackMuted). A deafen/undeafen cycle silently resets all of those to full volume, whileplaybackVolume$— and therefore the slider position and the muted icon inGridTile/SpotlightTile— still shows the old value. The UI and the actual audio diverge with no way to resync short of nudging the slider.Third symptom (first joiner audible while deafened): The module keys its
RoomEvent.ParticipantConnectedlisteners offvm.livekitRoomItems$, which only surfaces a room once at least one matrix-validated, non-local member has a resolved LiveKit participant (CallViewModel.ts:648-654returnsnullfor local/unresolved). While you are alone in the call,roomsis empty and no listener is attached, so the very first joiner is covered only by theapplyToRoom(room)call that runs on the subsequent stream emit — which lands after matrix membership resolution, i.e. potentially after their audio track has already subscribed and started playing throughMatrixAudioRenderer. The sibling lotus modules (lotusDenoise.ts:116,lotusQuality.ts:87,lotusAudioInject.ts:46) all explicitly avoidlivekitRoomItems$for this reason; deafen is the one that still uses it.How to trigger
Deafen in cinny, then have a second user join the call (or force a LiveKit reconnect for an existing remote). Their voice becomes audible while the cinny UI still shows deafened.
Also: Turn a participant down to 20% (or mute them) on their tile, then deafen and undeafen. They come back at 100% with the tile still showing 20% / muted.
Also: Deafen while alone in a call, then have someone join and speak immediately. A short burst of their audio is audible.
Suggested fix
Stop fighting
setVolume; drive EC's existing global mute instead —setAudioEnabled$.next(false)feedsmuteAllAudio$(src/state/MuteAllAudioModel.ts:17), whichInCallViewalready passes asmutedto everyLivekitRoomAudioRendererplus the event/reaction renderers. That also coversTrack.Source.Unknownsoundboard audio (the known P6-2 divergence) without anysetVolumeconflict.Also: Same as above — implement deafen as a global output mute rather than per-participant volume writes, so EC's per-participant state is left untouched.
Also: Switch to
vm.allConnections$like the sibling modules so the room (and itsParticipantConnectedlistener) exists from connect time; the global-mute fix above removes the problem entirely.Filed from the September 2026 audit (branch
lotus).Fixed in
f1cfcc73, shipped in0.25.0-lotus.1(published by CI from tag v0.25.0-lotus.1) and consumed by cinny at 9a85a487.