Voice-activity indicators on spaces and subspaces (opt-in experiment until delayed events are common) #94

Open
opened 2026-10-07 20:25:52 +00:00 by robocub · 0 comments
Member

Why

Discord puts a speaker (or screen) icon on a server in the sidebar when someone in a channel you can see is in voice or streaming. The same thing works on Matrix for spaces and subspaces, built from call membership state the client already receives.

Proposal

1. Expiry-aware call-participant helper (needed by this issue and by Friends → Active Now)

  • MatrixVoipRoomComponent.getCurrentParticipants() counts every non-empty org.matrix.msc3401.call.member and ignores expires. On a homeserver without delayed events (MSC4140), a crashed client's membership never gets removed, so it stays listed in the voice room's member list indefinitely.
  • MatrixActivitiesComponent already skips expired memberships; share one helper between the two.
  • The helper should treat a membership as live only if it isn't empty and hasn't passed its expiry time (created_ts, else origin_server_ts, + expires).

2. Space / subspace indicator (opt-in experiment)

  • A space icon gets a voice badge if any joined room in it, or in any of its subspaces, has a live call member. A subspace gets its own badge; the top-level space shows the combined result.
  • Rooms you haven't joined don't send us their state, so they can't contribute. That roughly matches Discord's "channels you can access".
  • Experiment toggle, off by default. Without delayed events, a crashed client's membership lasts until expires: up to 4 h for Commet/Vommet's own memberships, and Element Call's default is similar. That's long enough for false "someone's in voice" badges. Make it default-on once delayed events are common (see support table).

3. Video / screen-share badge (later)

  • Membership events don't say whether someone has video or screen share on; only LiveKit knows that. Joining calls silently to find out is rejected: heavy, a privacy problem, and it would show us as present.
  • Option: Vommet adds a field to its own membership, e.g. im.nether.vommet.media: ["video","screen"], and re-posts it when sharing starts or stops. Other clients ignore unknown fields. It only lights up for Vommet users; the voice bridge could set it for Discord Go Live.

Sliding sync (#5)

Indicators must never slow down the first sync.

  • First list: visible rooms, minimal state, no call or space-tree state.
  • After the first response, add a background list with required_state = org.matrix.msc3401.call.member + m.space.child, timeline_limit: 0, and a range that grows (100 → 500 → all). It has to cover all rooms, not just the visible window; after that, changes are pushed live.
  • Cache last-known indicators locally and draw them at startup; the expiry check removes stale cached badges.
  • Verify Tuwunel handles multiple lists, growing ranges and per-list required_state before relying on it.

Delayed events (MSC4140) support, probed 2026-10-07

Method: unstable_features["org.matrix.msc4140"] in /_matrix/client/versions, and server name/version from federation /version.

Server Software MSC4140
matrix.org Synapse 1.163.0rc1 off
kde.org, gitter.im, fosdem.org Synapse 1.162.0+pro.1 on
tchncs.de, 4d2.org, unredacted.org Synapse 1.162.0 on
beeper.com Synapse 1.160.0 on
matrix.im, nitro.chat, fairydust.space Synapse 1.162.0 off
converser.eu Synapse 1.160.0 off
continuwuity.org, federated.nexus, explodie.org continuwuity 26.9.1 not implemented (endpoint 404)
nether.im Tuwunel 1.9.3 not implemented (endpoint 404); matrix-construct/tuwunel PR #555 open, issue #178

Takeaways:

  • Synapse implements it but it's per-server opt-in; the endpoint exists either way, so check the feature flag, not the endpoint.
  • The Rust Conduit family doesn't implement it yet.
  • The largest server, matrix.org, advertises it off.
  • Vommet already uses delayed events (heartbeat/leave in matrix_livekit_voip_session.dart) when the flag is advertised, so nothing is needed client-side; this is purely about other people's crashed clients.
## Why Discord puts a speaker (or screen) icon on a server in the sidebar when someone in a channel you can see is in voice or streaming. The same thing works on Matrix for spaces and subspaces, built from call membership state the client already receives. ## Proposal **1. Expiry-aware call-participant helper** (needed by this issue and by Friends → Active Now) - `MatrixVoipRoomComponent.getCurrentParticipants()` counts every non-empty `org.matrix.msc3401.call.member` and **ignores `expires`**. On a homeserver without delayed events (MSC4140), a crashed client's membership never gets removed, so it stays listed in the voice room's member list indefinitely. - `MatrixActivitiesComponent` already skips expired memberships; share one helper between the two. - The helper should treat a membership as live only if it isn't empty and hasn't passed its expiry time (`created_ts`, else `origin_server_ts`, + `expires`). **2. Space / subspace indicator (opt-in experiment)** - A space icon gets a voice badge if any **joined** room in it, or in any of its subspaces, has a live call member. A subspace gets its own badge; the top-level space shows the combined result. - Rooms you haven't joined don't send us their state, so they can't contribute. That roughly matches Discord's "channels you can access". - **Experiment toggle, off by default.** Without delayed events, a crashed client's membership lasts until `expires`: up to 4 h for Commet/Vommet's own memberships, and Element Call's default is similar. That's long enough for false "someone's in voice" badges. Make it default-on once delayed events are common (see support table). **3. Video / screen-share badge (later)** - Membership events don't say whether someone has video or screen share on; only LiveKit knows that. Joining calls silently to find out is rejected: heavy, a privacy problem, and it would show us as present. - Option: Vommet adds a field to its own membership, e.g. `im.nether.vommet.media: ["video","screen"]`, and re-posts it when sharing starts or stops. Other clients ignore unknown fields. It only lights up for Vommet users; the voice bridge could set it for Discord Go Live. ## Sliding sync (#5) Indicators must never slow down the first sync. - First list: visible rooms, minimal state, no call or space-tree state. - After the first response, add a background list with `required_state` = `org.matrix.msc3401.call.member` + `m.space.child`, `timeline_limit: 0`, and a range that grows (100 → 500 → all). It has to cover all rooms, not just the visible window; after that, changes are pushed live. - Cache last-known indicators locally and draw them at startup; the expiry check removes stale cached badges. - Verify Tuwunel handles multiple lists, growing ranges and per-list `required_state` before relying on it. ## Delayed events (MSC4140) support, probed 2026-10-07 Method: `unstable_features["org.matrix.msc4140"]` in `/_matrix/client/versions`, and server name/version from federation `/version`. | Server | Software | MSC4140 | |---|---|---| | matrix.org | Synapse 1.163.0rc1 | **off** | | kde.org, gitter.im, fosdem.org | Synapse 1.162.0+pro.1 | on | | tchncs.de, 4d2.org, unredacted.org | Synapse 1.162.0 | on | | beeper.com | Synapse 1.160.0 | on | | matrix.im, nitro.chat, fairydust.space | Synapse 1.162.0 | off | | converser.eu | Synapse 1.160.0 | off | | continuwuity.org, federated.nexus, explodie.org | continuwuity 26.9.1 | **not implemented** (endpoint 404) | | nether.im | Tuwunel 1.9.3 | **not implemented** (endpoint 404); matrix-construct/tuwunel PR #555 open, issue #178 | Takeaways: - Synapse implements it but it's per-server opt-in; the endpoint exists either way, so check the feature flag, not the endpoint. - The Rust Conduit family doesn't implement it yet. - The largest server, matrix.org, advertises it off. - Vommet already uses delayed events (heartbeat/leave in `matrix_livekit_voip_session.dart`) when the flag is advertised, so nothing is needed client-side; this is purely about other people's crashed clients.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
nether/vommet#94
No description provided.