# MatrixRTC SDK architecture `@element-hq/matrixrtc-sdk` is the model under Element Call, on its own: one `MatrixRTCClient` per room that joins and leaves the MatrixRTC session, discovers and connects to the media transports, publishes the local media, distributes the encryption keys, and hands out every member with its media as observables. It has no UI, reads no configuration and no settings, and never names LiveKit in its interface. Element Call becomes one consumer of it: `CallViewModel` keeps what makes a session a _call_ (ringing, auto-leave, layout, sounds, reactions, settings, the host bridge) and takes everything else from the client. The dependency direction is therefore `Element Call → sdk`, never the reverse. The migration from today's `CallViewModel` to that shape is in [`SdkMigration.md`](./SdkMigration.md). ## The architecture ``` host application (Element Call, a whiteboard, watch-together, …) │ │ createMatrixRTCClient(scope, matrixClient, room, localMedia, options) ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ MatrixRTCClient │ │ │ │ public API, all Behaviors: │ │ join() leave() status$ connected$ reconnecting$ fatalError$ │ │ localMember$ remoteMembers$ memberCount$ │ │ keyRotationSuppressed$ connectedTransports$ │ │ │ │ per member RTCMember { id userId deviceId membership$ displayName$ │ │ avatarUrl$ transport$ media$ } │ │ per media MemberMedia { tracks$ encryptionError$ } │ │ per track MediaTrack { source kind muted$ encrypted$ stats$ attach() }│ │ │ │ ┌──────────────────────────┐ memberships ┌───────────────────────────┐ │ │ │ MatrixRTC core │ ───────────────▶ │ transports │ │ │ │ (js-sdk MatrixRTCSession,│ transports │ │ │ │ │ later the rust-rtc │ │ ConnectionManager │ │ │ │ crate behind a bridge) │ │ one Connection per │ │ │ │ │ │ transport url │ │ │ │ memberships from room │ │ Connection │ │ │ │ state and sticky events│ │ OpenID → JWT → LiveKit │ │ │ │ own membership, delayed │ │ room, participants, │ │ │ │ leave, retries │ media keys │ tracks │ │ │ │ media keys over │ ───────────────▶ │ Publisher │ │ │ │ to-device messages │ (key provider) │ local tracks, upstream │ │ │ └──────────────────────────┘ └───────────────────────────┘ │ │ │ │ │ │ └──── RTCMember = membership + participant ─┘ │ │ matched on rtcBackendIdentity = LiveKit identity │ └──────────────────────────────────────────────────────────────────────────────┘ │ │ ▼ ▼ MatrixClient (sync, room state, to-device, OpenID) LiveKit SFU ``` The left box is MatrixRTC: who is in the session, with which transport, and the keys. The right box is media: a connection to every transport any member advertises, and our own publication on the one we advertise. The client is the join of the two: a member exists once its membership does, and gets its media once a participant with the matching identity shows up on the member's transport. ### How Element Call uses it ``` Element Call, as app, widget or component ┌────────────────────────────────────────────────────────────────┐ │ views: InCallView, tiles, header, footer, settings │ │ ▲ Behaviors │ │ CallViewModel │ │ layout, ringing and notifications, auto-leave, sounds, │ │ reactions, hand raise, settings, audio routing, host bridge │ │ ▲ status$, localMember$, remoteMembers$, MemberMedia │ │ MatrixRTCClient @element-hq/matrixrtc-sdk │ └────────────────────────────────────────────────────────────────┘ │ MatrixClient + Room │ livekit-client ▼ ▼ homeserver SFU ``` All three modes already hold a `MatrixClient` and a `Room` by the time a call starts: standalone after `useLoadGroupCall`, the component from the host's `client` prop, the widget from `createRoomWidgetClient`. The view model passes them on and builds its media view models from `RTCMember` and `MemberMedia` instead of from LiveKit participants. ### Joining, in order ``` host MatrixRTCClient homeserver / JWT service LiveKit SFU │ create ───▶│ │ │ │ │ GET rtc/transports ──────────────▶│ │ │ │◀── preferred transport ───────────│ │ │ │ OpenID token, then /sfu/get ─────▶│ │ │ │◀── sfu url, jwt, alias, identity ─│ = resolved$ of the │ │ │ │ local transport │ │ │ connect(url, jwt) ───────────────────────────────────────▶│ │ join() ───▶│ │ │ │ │ membership state event + delayed leave ──▶│ │ │ │ create tracks, resume upstream ──────────────────────────▶│ │ │ media key to every other member (to-device) ──▶│ │ │ │◀── other memberships (sync) ──────│◀── participants ────────│ │◀─ status$ "connected", localMember$, remoteMembers$ with media$ ──────────│ ``` The transport is resolved and connected before `join()`, so that the membership can name it and the first frames go out as soon as the state event is sent. Until `join()` the tracks exist but their upstream is paused: a host can show a preview without anyone hearing it. `leave()` pauses the upstream again and sends the leave. ## Vocabulary | Word | Meaning here | Elsewhere | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | client | a `MatrixRTCClient`: one room, one slot, one local member | `MatrixClient` is always written out | | membership | one device's MatrixRTC state event, seen by hosts as `RTCMembership`, the js-sdk `CallMembership` behind it | | | member | an `RTCMember`: a membership plus what the SDK derives from it (display name, transport, media) | Element Call's "matrixLivekitMember" | | media | a `MemberMedia`: what a member sends, once it has arrived on its transport. `null` while waiting for media | Element Call's `MediaViewModel` is a view of it | | track | a `MediaTrack`: one publication of a member, audio or video, with `source`, `kind`, `id` and the behaviors on it. `LocalAudioMediaTrack` and `LocalVideoMediaTrack` add the controls over our own | LiveKit `TrackPublication` and `Track`, which never leave the SDK | | source | a `MediaSource`: what a track is captured from, `microphone`, `camera`, `screenShare`, `screenShareAudio` or `unknown`. Picked out of `tracks$` with `trackBySource$` | LiveKit `Track.Source` | | publish request | a `PublishRequest`: a source, where to capture it from and how to encode it. Given at the join in `LocalMediaInputs.publish`, or later to `publish` on the local member | | | active | `isActive$` on an audio track: the track carries sound, as the backend measures it. "Speaking" is a call's reading of it on the microphone track | LiveKit "speaking", `isSpeaking` | | attach, detach | handing a `