Voice and video calls: TURN endpoint and web client calls

Server: GET /voip/turnServer, configured under client_api.turn. A shared
secret (coturn use-auth-secret) gives each user expiring TURN REST API
credentials; static credentials are the fallback; with nothing configured
it answers {} so clients still try a direct connection.

Web client: 1:1 calls (Matrix VoIP via matrix-js-sdk) in rooms with one
other person. Voice and video buttons in the room header, a ringing card
with answer, answer without video and decline, and a call panel that
stays up across rooms: mute, camera, screen sharing, expand, hang up,
ring tones. Call events show in the timeline and room list. Works around
the SDK keeping a failed microphone request forever.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5.5 committed 2026-10-18 18:04:02 +02:00
1 parent 69ce927b6d
commit 325431a5be
25 files changed
+930 -2

No files matched your search

+8
View File
@@ -47,6 +47,7 @@ type ClientAPI struct {
remoteProfile RemoteProfiles
regTokens *userapi.RegistrationTokens
appservices *appservice.API
turn config.TURN
log *slog.Logger
now func() time.Time
@@ -141,6 +142,9 @@ type Options struct {
// RateLimit configures the per-endpoint buckets.
RateLimit config.RateLimit
// TURN is the relay handed to clients for calls.
TURN config.TURN
// TrustXForwardedFor takes the client address from the proxy header.
TrustXForwardedFor bool
@@ -200,6 +204,7 @@ func New(opts Options) (*ClientAPI, error) {
remoteProfile: opts.RemoteProfiles,
regTokens: opts.RegistrationTokens,
appservices: opts.AppServices,
turn: opts.TURN,
log: opts.Logger,
now: opts.Now,
uia: newUIAStore(uiaSessionTTL, opts.Now),
@@ -267,6 +272,9 @@ func (c *ClientAPI) RegisterRoutes(r *httputil.Router) {
c.authed(r, "POST "+v1+"/appservice/{appserviceID}/ping", c.limitDefault, c.handleAppServicePing)
c.authed(r, "GET "+v3+"/capabilities", c.limitDefault, c.handleCapabilities)
// Calls: the relay a client sets its peer connection up with.
c.authed(r, "GET "+v3+"/voip/turnServer", c.limitDefault, c.handleTurnServer)
// Devices.
c.authed(r, "GET "+v3+"/devices", c.limitDefault, c.handleGetDevices)
c.authed(r, "GET "+v3+"/devices/{deviceID}", c.limitDefault, c.handleGetDevice)