Group voice and video calls with MatrixRTC and LiveKit
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s

Every call in the web client now goes through MatrixRTC with a LiveKit
SFU, the model Element Call and Element X use; the legacy 1:1 m.call.*
calls are gone. In a room of two the other person is rung and can answer
or decline; in a bigger room a banner offers to join the call. Calls show
a grid of participant tiles with mute, camera, screen sharing and leave,
and media is end-to-end encrypted in encrypted rooms.

Server:
- MSC4140 delayed events (send/state with a delay, plus the
  delayed_events list/restart/cancel/send API), stored and scheduled, so
  a member whose client drops off is taken out of the call.
- POST /user/{userId}/openid/request_token, which lk-jwt-service uses to
  hand out LiveKit tokens.
- org.matrix.msc4143.rtc_foci in /.well-known/matrix/client, configured
  under client_api.rtc.
- Room presets let members send call membership state.
- Migration 0014 for both SQL dialects.

Deploy: LiveKit and lk-jwt-service in compose (calls.yml) and the helm
chart. Docs: docs/calls.md, configuration and deployment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5.5 committed 2026-10-18 19:33:29 +02:00
1 parent 325431a5be
commit 76dfc98a86
49 files changed
+3849 -432

No files matched your search

+48
View File
@@ -182,6 +182,39 @@ type ClientAPI struct {
// TURN is what GET /voip/turnServer hands clients for voice and video
// calls.
TURN TURN `yaml:"turn"`
// RTC is the media backend for group calls (MatrixRTC), advertised in
// /.well-known/matrix/client.
RTC RTC `yaml:"rtc"`
// DelayedEvents bounds the delayed events (MSC4140) a client may
// schedule. Group calls depend on them: a member's "I left" is
// scheduled in advance, so a client that disappears still leaves.
DelayedEvents DelayedEvents `yaml:"delayed_events"`
}
// RTC configures group calls. Warp does not carry call media itself:
// MatrixRTC clients publish to a LiveKit SFU, and get the token for it from
// a LiveKit JWT service (lk-jwt-service) in exchange for an OpenID token
// from this server. With no service URL no focus is advertised, and clients
// that need one cannot start a group call.
type RTC struct {
// LiveKitServiceURL is the base URL of the LiveKit JWT service, e.g.
// "https://livekit-jwt.example.org". It is advertised as an
// org.matrix.msc4143.rtc_foci entry of type livekit.
LiveKitServiceURL string `yaml:"livekit_service_url" env:"WARP_RTC_LIVEKIT_SERVICE_URL"`
}
// DelayedEvents configures MSC4140 delayed events.
type DelayedEvents struct {
// MaxDelay is the longest delay a client may ask for. Zero turns
// delayed events off: the delay parameter is refused and the feature
// is not advertised.
MaxDelay time.Duration `yaml:"max_delay" env:"WARP_DELAYED_EVENTS_MAX_DELAY"`
// MaxPerUser caps how many delayed events one user may have scheduled
// at once.
MaxPerUser int `yaml:"max_per_user" env:"WARP_DELAYED_EVENTS_MAX_PER_USER"`
}
// TURN configures the relay servers clients use to set up calls. With no
@@ -531,6 +564,10 @@ func Default() *Config {
Media: RateLimitBucket{Burst: 100, Interval: 20 * time.Millisecond},
},
TURN: TURN{UserLifetime: time.Hour},
// A MatrixRTC client restarts its delayed leave every few
// seconds; a day leaves room for other uses without letting a
// forgotten event linger for weeks.
DelayedEvents: DelayedEvents{MaxDelay: 24 * time.Hour, MaxPerUser: 100},
},
Sync: Sync{
Presence: false,
@@ -714,6 +751,17 @@ func (c *Config) Validate() error {
if c.ClientAPI.TURN.SharedSecret != "" && c.ClientAPI.TURN.UserLifetime <= 0 {
bad("client_api.turn.user_lifetime: must be positive")
}
if raw := c.ClientAPI.RTC.LiveKitServiceURL; raw != "" {
if u, err := url.Parse(raw); err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
bad("client_api.rtc.livekit_service_url: must be an http(s) URL, got %q", raw)
}
}
if c.ClientAPI.DelayedEvents.MaxDelay < 0 {
bad("client_api.delayed_events.max_delay: must not be negative")
}
if c.ClientAPI.DelayedEvents.MaxDelay > 0 && c.ClientAPI.DelayedEvents.MaxPerUser < 1 {
bad("client_api.delayed_events.max_per_user: must be at least 1")
}
if c.Sync.TypingTimeout <= 0 {
bad("sync.typing_timeout: must be positive")
}