Matrix: voice and video calls #81

Closed
opened 2026-09-02 00:04:22 -04:00 by Salastil · 3 comments
Owner

No VoIP of any kind: neither 1:1 m.call.invite/answer/candidates nor group calls via MatrixRTC (MSC3401 / Element Call). Zero hits across the backend.

Discord voice is fully built in this project (discord_voice.rs, Songbird), so the audio plumbing and the call UI — CallView, IncomingCallPanel, device selection, level meters — already exist and are protocol-agnostic in the frontend.

Done looks like: 1:1 m.call.* first, reusing the existing call UI. Group calls are a much larger piece.

No VoIP of any kind: neither 1:1 `m.call.invite`/`answer`/`candidates` nor group calls via MatrixRTC (MSC3401 / Element Call). Zero hits across the backend. Discord voice is fully built in this project (`discord_voice.rs`, Songbird), so the audio plumbing and the call UI — `CallView`, `IncomingCallPanel`, device selection, level meters — already exist and are protocol-agnostic in the frontend. **Done looks like:** 1:1 `m.call.*` first, reusing the existing call UI. Group calls are a much larger piece.
Author
Owner

Assessed while working through the Matrix issues, and not started — the blocker is structural rather than a matter of effort, so it is worth writing down before anybody picks this up.

The existing voice stack cannot be reused. moho's voice is Songbird, which speaks Discord's own voice protocol: its own UDP handshake, its own encryption, Opus over a Discord voice gateway. Matrix calls are WebRTC — ICE, DTLS-SRTP, SDP negotiation, TURN credentials fetched from the homeserver. The two share the fact that audio is Opus and nothing else. So this is not "wire Matrix into the call code"; it is adding a second, larger media stack (webrtc-rs or equivalent) to the daemon.

What is already reusable is the half above the media: CallView, IncomingCallPanel, the ringtone, device selection and the level meters are protocol-agnostic in the frontend, and getIncomingCalls/acceptCall/declineCall are generic RPCs that Discord happens to be the only implementor of. So the client work is small and the daemon work is large.

Rough shape if it is wanted: 1:1 m.call.invite/m.call.answer/m.call.candidates/m.call.hangup over to-device or room events, a WebRTC peer connection per call, and TURN credentials from /_matrix/client/v3/voip/turnServer. Group calls via MatrixRTC/Element Call (MSC3401) are a considerably larger thing again and should not be attempted in the same pass.

Worth weighing against #14 (Discord screen share), which is blocked on the same absence for a different reason — Songbird carries audio only. A WebRTC stack in the daemon would not fix #14 by itself, but the two are the only reasons anybody would add one, so they are worth deciding together.

Assessed while working through the Matrix issues, and not started — the blocker is structural rather than a matter of effort, so it is worth writing down before anybody picks this up. **The existing voice stack cannot be reused.** moho's voice is Songbird, which speaks Discord's own voice protocol: its own UDP handshake, its own encryption, Opus over a Discord voice gateway. Matrix calls are **WebRTC** — ICE, DTLS-SRTP, SDP negotiation, TURN credentials fetched from the homeserver. The two share the fact that audio is Opus and nothing else. So this is not "wire Matrix into the call code"; it is adding a second, larger media stack (`webrtc-rs` or equivalent) to the daemon. **What is already reusable** is the half above the media: `CallView`, `IncomingCallPanel`, the ringtone, device selection and the level meters are protocol-agnostic in the frontend, and `getIncomingCalls`/`acceptCall`/`declineCall` are generic RPCs that Discord happens to be the only implementor of. So the client work is small and the daemon work is large. **Rough shape if it is wanted:** 1:1 `m.call.invite`/`m.call.answer`/`m.call.candidates`/`m.call.hangup` over to-device or room events, a WebRTC peer connection per call, and TURN credentials from `/_matrix/client/v3/voip/turnServer`. Group calls via MatrixRTC/Element Call (MSC3401) are a considerably larger thing again and should not be attempted in the same pass. Worth weighing against #14 (Discord screen share), which is blocked on the same absence for a different reason — Songbird carries audio only. A WebRTC stack in the daemon would not fix #14 by itself, but the two are the only reasons anybody would add one, so they are worth deciding together.
Salastil added the feature label 2026-09-03 13:11:28 -04:00
Author
Owner

Finished — https://git.salastil.com/Salastil/nobilis/commit/c9d06bc and https://git.salastil.com/Salastil/moho/commit/5d99b42

1:1 m.call.* calls, voice and video, with the media in the window (Chromium has a complete WebRTC stack; the daemon would have to grow a browser) and the signalling in the daemon (an offer, an answer, the network paths and a hangup are room events, encrypted where the room is). TURN credentials come from the homeserver's own /voip/turnServer. Screen sharing is included, as its own track added to a call in progress.

Verified live between two homeservers: a call placed from matrix.salastil.com rang on poa.st, was answered, and both ends reported connected with a live audio track each way — then a shared screen arrived at the far end as a second track and was drawn.

Three things this turned up on the way: Chromium will not hand over a microphone unasked and an unanswered permission request hangs rather than failing (which is what a call that never started looked like); the daemon's existing send_room_event always sends m.room.message, so a call event routed through it went out with its type in the room-id slot; and a machine with no camera now drops the camera and says so rather than failing the whole request, microphone included.

Group calls (MatrixRTC / MSC3401) are not included — as the issue says, a much larger piece.

Finished — https://git.salastil.com/Salastil/nobilis/commit/c9d06bc and https://git.salastil.com/Salastil/moho/commit/5d99b42 1:1 `m.call.*` calls, voice and video, with the media in the window (Chromium has a complete WebRTC stack; the daemon would have to grow a browser) and the signalling in the daemon (an offer, an answer, the network paths and a hangup are room events, encrypted where the room is). TURN credentials come from the homeserver's own `/voip/turnServer`. Screen sharing is included, as its own track added to a call in progress. **Verified live between two homeservers:** a call placed from matrix.salastil.com rang on poa.st, was answered, and both ends reported connected with a live audio track each way — then a shared screen arrived at the far end as a second track and was drawn. Three things this turned up on the way: Chromium will not hand over a microphone unasked and an unanswered permission request *hangs* rather than failing (which is what a call that never started looked like); the daemon's existing `send_room_event` always sends `m.room.message`, so a call event routed through it went out with its type in the room-id slot; and a machine with no camera now drops the camera and says so rather than failing the whole request, microphone included. Group calls (MatrixRTC / MSC3401) are not included — as the issue says, a much larger piece.
Author
Owner

Finished — https://git.salastil.com/Salastil/moho/commit/7eec5bf

The multi-party view on top of the 1:1 call above: a fitted tile grid, a green ring on whoever is talking, click-to-enlarge with the others in a strip underneath, and a corner view that names who is in the call.

Finished — https://git.salastil.com/Salastil/moho/commit/7eec5bf The multi-party view on top of the 1:1 call above: a fitted tile grid, a green ring on whoever is talking, click-to-enlarge with the others in a strip underneath, and a corner view that names who is in the call.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Salastil/moho#81