Commit Graph
7732 Commits
Author SHA1 Message Date
Robin 24b6a632dd Let the publishing Connection authenticate itself
getLocalTransport no longer authenticates with the SFU up front and hands
the resulting config to the connection. Instead the ConnectionManager tags
the connection whose transport equals the local one as the publisher, and
every Connection fetches its own SFU config for its role.

The local member now only joins the MatrixRTC session once its connection
is authenticated, so that the LiveKit room exists before remote members
try to get a subscriber token for it. Subscribers authenticating with the
OpenID flow lack permission to create the room and would otherwise fail
without retrying, causing a cascade of leaves and rejoins at the start of
a call.
2026-10-12 10:22:33 +02:00
Robin 17c3223883 Move SFU authentication into livekit/auth and choose the endpoint by role
The OpenID flow moves to src/livekit/auth/openID.ts with the SFU config
types in a sibling file, takes an options object, and replaces the
MatrixRTC mode with the role we take on the transport plus the session's
slot ID instead of a hard-coded m.call#ROOM.

This changes behaviour: publishers on a livekit_service_url transport
always use the legacy /sfu/get endpoint, so that subscribers can predict
their user:device participant identity, and subscribers always use the
default /get_token endpoint. The MatrixRTC mode no longer influences which
endpoint is used.
2026-10-12 10:17:39 +02:00
Timo K. c79675e6a9 bump to compound-web main branch 2026-10-12 10:14:49 +02:00
Robin f260755f46 Group livekitRoomItems$ by connection
The old reducer keyed items by service URL and mutated the accumulator's
entries in place, so a transport without a service URL could not be
represented and items were not safe to compare. Group members by their
Connection object instead and carry the whole transport on the item, which
the audio renderer now receives in place of a URL.
2026-10-12 10:11:54 +02:00
Robin 3927c2b7dc Show the whole transport in debug UI and logs
A transport may soon carry a LiveKit SFU `url` instead of, or as well as,
a `livekit_service_url`, so a service URL alone no longer identifies it.
Pass the whole transport to the tiles' debug stats and print it as JSON in
log lines instead of picking out the service URL.
2026-10-12 10:09:50 +02:00
Robin 4e4ab23cda Model ConnectionState as a discriminated union
The enum mixed our own lifecycle states with a copy of livekit-client's
connection states, and errors were smuggled through the same observable as
a separate union member. A discriminated union keeps the LiveKit state
nested under "authenticated", carries the error as data, and lets the
local member represent the time before a connection exists as an
"initializing" state instead of null.
2026-10-12 10:08:22 +02:00
Robin 3f030e64dd Match LiveKit participants against all backend identities
A member authenticated through the legacy JWT endpoint publishes under a
`user:device` identity, while one authenticated through the default
endpoint publishes under a hashed identity. Both are listed in the
membership's backend identities, so match against the whole list rather
than only the primary one.
2026-10-12 10:04:47 +02:00
Johannes Marbach d8becaf868 Advertise the loopback address from the dev LiveKit SFUs
The container IP is not routable from the host when Docker runs in a VM
(colima, Docker Desktop), so calls against the dev stack drop once ICE
fails. Advertising 127.0.0.1 instead lets the browser reach the SFU through
the published ports. Firefox ignores loopback candidates, so this only
works in Chromium-based browsers.
2026-10-12 10:03:03 +02:00
Robin 994cd36c44 Fix typo in the name of the createJWTToken Playwright fixture 2026-10-12 10:03:03 +02:00
Johannes Marbach 7ac5373af9 Remove stray pause and improve logging for hung e2e tests
The list reporter is the only progress a hung CI run leaves behind, and a
global timeout under the job's 60 minute limit means a hung run still ends
with a report instead of being cancelled by GitHub.
2026-10-12 10:03:03 +02:00
renovate[bot] 32fcede65e Update ghcr.io/element-hq/element-web:develop Docker digest to d43e8d2 (#4345)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-12 02:40:40 +00:00
renovate[bot] 6438c18b73 Update ghcr.io/element-hq/element-web:develop Docker digest to 88eeb34 (#4344)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-11 00:49:17 +00:00
renovate[bot] f07e9f5c70 Update ghcr.io/element-hq/element-web:develop Docker digest to 1488790 (#4343)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-10 02:09:04 +00:00
Johannes Marbach c97e3bf7a6 Merge pull request #4337 from element-hq/johannes/remove-per-participant-e2ee-param
Remove perParticipantE2EE
2026-10-09 12:01:45 +02:00
Timo K. 674692c926 Fix breaking Ci tests due to compound-web 10.1.0 -> 10.3.1 bump 2026-10-09 11:09:41 +02:00
Timo K. c3d07b89bd review: unnecassary matchMedia overwrite on test start. 2026-10-09 11:08:03 +02:00
renovate[bot] f89cbc140e Update ghcr.io/element-hq/element-web:develop Docker digest to 181a526 (#4339)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-09 01:18:17 +00:00
Timo K. 3bd892ebd3 bump compound-web 2026-10-08 21:26:34 +02:00
Timo K. f322af2742 [ElementCallComponent] Use compound-web's PortalRoot in EC. 2026-10-08 19:14:01 +02:00
Johannes Marbach a9fa48f68c Remove perParticipantE2EE 2026-10-08 17:15:19 +02:00
Robin 471c76c0e7 Merge pull request #4336 from element-hq/robin/vite-warning
Fix Vite warning
2026-10-08 16:40:45 +02:00
Timo K. 799619557b [SDK] make stats$ a polled method and add audioLevel$ to audio tracks 2026-10-08 16:36:25 +02:00
Timo K. 5f914b8a01 [SDK] refactor track processor api to be lk independent 2026-10-08 16:36:17 +02:00
Timo K. aa897ae9bc [SDK] simplify public api (no lk processor, no encryption$ as obs) 2026-10-08 16:36:11 +02:00
Johannes Marbach 4881fd5fc6 Merge pull request #4280 from element-hq/johannes/claude
Add workflow for automated Claude review of pull requests
2026-10-08 15:27:13 +02:00
Johannes Marbach 49c29f160a Add workflow for automated Claude review of pull requests 2026-10-08 14:57:19 +02:00
Robin aa8f73e632 Fix Vite warning 2026-10-08 14:23:59 +02:00
Timo K. 2bb870f6e7 [SDK] fix slots missing initial status value 2026-10-08 13:53:57 +02:00
Timo K. e44dfe1898 [SDK] add codecov 2026-10-08 13:53:57 +02:00
Timo K. b5fac4a2ff Adapt to matrix-js-sdk develop and cover RTCSlot's js-sdk imports 2026-10-08 13:53:57 +02:00
Timo K. 66f326fcf0 [SDK] Update SdkArchitecture.md 2026-10-08 13:53:57 +02:00
Timo K. 3cdb452d5d [SDK] update state structure (less duplication, one state enum)
- remove media from LocalMembership file
2026-10-08 13:53:57 +02:00
Timo K. f49c1cd3d4 [SDK] rename file LocalMember -> LocalMembership 2026-10-08 13:53:57 +02:00
Timo K. dc983e5493 [SDK] split the clint object into -> slot and participation 2026-10-08 13:53:57 +02:00
Timo K. 9cc9eb0889 Create Publisher.test.ts 2026-10-08 13:53:57 +02:00
Timo K. ca4f0cdeae fixup docs 2026-10-08 13:53:57 +02:00
Timo K. 4790af08a4 flatten media api (no MemberMedia wrapper object 2026-10-08 13:53:57 +02:00
Timo K. 6ffab92f0f split api.ts into api.ts and media-api.ts 2026-10-08 13:53:57 +02:00
Timo K. f1ea4cc5a2 Merge LocalMediaInputs into MatrixRTCClientOptions
The SDK now takes `publish` requests and audio output control directly
in the main options object, removing the separate `LocalMediaInputs`
type. Audio output device selection moves from a reactive behavior to an
imperative method on `MatrixRTCClient`, giving hosts direct control over
when devices switch.
2026-10-08 13:53:57 +02:00
Timo K. 5b13e1da81 renames and restructure
- session -> matrixrtc (folder)
 - move MatrixRtcClient to top level of sdk
 - RtcTransport -> Transport
 - backend -> media-backend
2026-10-08 13:53:57 +02:00
Timo K. d0a2ff12f1 Put the media behind a backend seam and move LiveKit into it
The session speaks MatrixRTC and sees media through MediaBackend in
sdk/src/backend/api.ts. Everything LiveKit lives in
sdk/src/backend/livekit/,
the only place in the SDK that imports livekit-client; the import
boundary rule
now enforces that outside sdk/src/backend/ the LiveKit packages appear
in type
imports only.

The seam sits above the connection manager: one connection per transport
url
is the LiveKit multi-SFU strategy, not a MatrixRTC concept, so a
cascading SFU
or full mesh replaces the folder, not the session. A backend prepares
the local
transport the session discovered (the token exchange and the delegation
probe,
reported as canDelegateDelayedLeave), hands the delayed leave over per
delay
id, gives the local member one publish switch plus publish/unpublish,
builds a
remote member's media from its membership and registers that
membership's
transport for as long as the member lives, and reports its connections
for the
transport registry, which keys on the raw transport serialised with
sorted
keys. The backend's context carries no Matrix client (a backend picks
what it
calls in its own options), no membership list, and no js-sdk session:
media
keys arrive as a stream replayed on subscribe, and MatrixKeyProvider
subscribes
to it. The MatrixRTC mode no longer reaches LiveKit; it becomes a
tokenEndpoint
choice made where the default backend is built.

Two behaviour changes ride along. Publishing is one boolean, joined and
homeserver reachable, where before a sync hiccup after leave() resumed
the
upstream of tracks that were meant to stay silent. And
ResolvedConnection keeps
one ResolvedTransport object per fetch, so a state change no longer
reads as a
change of what was fetched.

MatrixRTCClientOptions gains backend?: MediaBackendFactory, defaulting
to
createLivekitBackend built from the client options.
authenticateWithTransport
takes tokenEndpoint instead of matrixRTCMode.
2026-10-08 13:53:57 +02:00
Timo K.andClaude Fable 5.1 64d7eb99cc Add the media words to the architecture vocabulary
media, track, source, publish request, active and attach/detach, between
participant and transport, and the participant row names the adapter as
the only reader.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 13:53:57 +02:00
Timo K.andClaude Fable 5.1 cad00aef08 Mark the speaker detection check as fixme
Chromium's fake microphone never registers as a speaker at the SFU, and Firefox's steady tone does so in about two runs out of three, with or without the browser's audio processing. The test stays, out of the gate, until the dev SFU gets a lower speaker threshold or the harness a louder fake device.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 13:53:57 +02:00
Timo K.andClaude Fable 5.1 6bd6e3f953 Take the word call out of the member API
membership$ is typed as RTCMembership, the subset of the js-sdk CallMembership that hosts read. UnknownCallError is UnknownRTCError throughout, not only in the export. applicationData reaches the membership whole, since the js-sdk takes it next to the intent. The plan records the three slices as implemented.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 13:53:57 +02:00
Timo K.andClaude Fable 5.1 b9f79be106 Publish and unpublish on the local member
One way to put media on the session: publish(request) resolves with the local track once LiveKit has it, and unpublish(id) removes it. A request names a source with its device, processor and capture settings, so CaptureSettings on the client options goes, as do setMicrophoneEnabled and setCameraEnabled on the client and the four screen share fields on the local member; sharing is tracks$ having a screenShare track, and a capture failure rejects the promise. LocalMediaInputs becomes the list of initial requests, which also become the room defaults so that one permission prompt covers microphone and camera. The publisher keeps the requests across a reconnection and republishes what is not muted. The harness gains a Share screen button.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 13:53:57 +02:00
Timo K.andClaude Fable 5.1 5a43eaf5a6 Give the local tracks their controls
Our own tracks are LocalAudioMediaTrack and LocalVideoMediaTrack, with setEnabled and setDevice, and on video facingMode$, switchFacingMode and setProcessor. switchCamera on the local media goes, as do the device and processor behaviors in LocalMediaInputs: a host calls the track when its setting changes instead of the SDK watching behaviors it does not own. The input device ids and the processor stay as initial values for the tracks created at the join. setMicrophoneEnabled and setCameraEnabled stay for the case of no track yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 13:53:57 +02:00
Timo K.andClaude Fable 5.1 63ccfa50be Test and document the MemberMedia track list
Unit tests for tracks$ and trackBySource$, an end-to-end check of isActive$ through a mute cycle (skipped on Chromium, whose fake microphone the SFU never hears), the architecture document brought in line with the track list, and the changed behaviour recorded in the plan.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 13:53:57 +02:00
Timo K. 18e0065086 specific call tracks -> generic tracks 2026-10-08 13:53:57 +02:00
Timo K. d7ab2b27b6 Create MemberMediaPlan.md 2026-10-08 13:53:57 +02:00
Timo K. 3afe52bd4a Create MediaBackendPlan.md 2026-10-08 13:53:57 +02:00