Skip to content

Releases: element-hq/matrix-rust-rtc

v0.5.0-rc.5

v0.5.0-rc.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 15 Oct 13:05

Breaking

  • CallJoinOptions::kind (FFI FfiJoinSessionParams::kind, required) replaces notify: Group { notify } for a group call's notification, Direct { ring } for a DM call the library rings, follows and ends; web joinCall refuses notification_type: "ring".
  • An MSC4075 ring now lasts 90 s by default, as Element Call's does.
  • notify_session_started takes the DialectBackend, so a notification always goes out in the room's membership format.
  • CallEvent::Ended, CallEvent::MediaConnectionState and EndedReason (FFI FfiEndedReason, web ended and media_connection_state) are removed: the call's status says both.

Added

  • RtcCall::status/subscribe_status (FFI status/subscribe_status, web status and onStatus, LiveKitCall::status) report the call as connecting, calling (a DM's ring, with the callee's user ID and our media's progress), connected since the pickup or our media, or ended with a reason, including a DM's decline, no answer and everyone else leaving.
  • ApplicationIntake::sticky_event_types/on_sticky_events hand an application the sticky events of its types, and BaseRtcRoom::room_members the room's joined users.

v0.5.0-rc.4

v0.5.0-rc.4 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 13 Oct 10:05

Breaking

  • HandRaised and HandLowered now announce only hands raised or lowered while we are in the call, never the hands we arrive to or a member arrives with, and Reaction is sent only while we are in the call and for reactions newer than ReactionsConfig::max_age_ms (10 s by default).

Added

  • Participants and tiles carry hand_raised_position, their 1-based place in the raised-hand queue.

v0.5.0-rc.3

v0.5.0-rc.3 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 12 Oct 12:48

Breaking

  • ParticipantJoined and ParticipantLeft now report only arrivals and departures while we are in the call, never the roster we join, lapse or return to, and carry participant_count, the memberships after the change.

Added

  • A local video publication reports send statistics per simulcast layer (size, frame rate, bytes, whether dynacast paused it), as LocalTrackHandle::send_stats and FfiLocalTrack::send_stats.

v0.5.0-rc.2

v0.5.0-rc.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 08 Oct 08:32

Added

  • sdk_version() (web sdkVersion()) returns the SDK release version, for hosts to display or attach to log reports, and logging setup logs it.
  • set_ranking_threshold(tiles) keeps tiles in join order, screen shares first, while a call has no more than that many remote tiles, so small calls stop shuffling on speaking, video or a raised hand; sticky and MSC4143 memberships take their join time from the first member event's origin_server_ts.

v0.5.0-rc.1

v0.5.0-rc.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 06 Oct 13:19

Breaking

  • matrix-rtc-media is renamed matrix-rtc-call-sdk, and LiveKitCall, open_slot, attach_livekit and the examples and e2e test move there from matrix-rtc-livekit (features livekit and matrix-sdk), leaving matrix-rtc-livekit the transport alone, without a matrix-sdk feature.
  • The transport contract, frames, constraints and media key handler move from matrix-rtc-media to the new matrix-rtc-transport, and matrix-rtc-livekit-proto becomes its livekit module; matrix-rtc-media keeps the call's roster, tiles, events and attach_media.
  • LiveKitMediaTransport::connect_livekit becomes OwnFocusTransport::connect_own, and LiveKitTransportConnection::set_local_key_index moves onto TransportConnection.
  • connect_media_session/connectMedia take the own focus from the call and the account from the backend: MediaSessionConfig loses livekit_service_url, user_id and device_id, and a receive-only call connects only to its peers' foci.
  • MembershipSnapshotSubscription::next_snapshot(), which polled, becomes an async next() that waits for the next roster and returns None once the room is gone.
  • FFI and web joinCall take an optional application_slot_id (m.call#{application_slot_id}, room when omitted) instead of slot_id and application.
  • The room-wide slot is {application}#room, as MSC4143 spells it, instead of {application}#ROOM (the 2025 Element Call format still respells it #ROOM on the wire); ROOM_SLOT_NAME becomes ROOM_APPLICATION_SLOT_ID.
  • CallEvent::KeyImported (web key_imported) carries the identity the key was installed under.
  • A join refused because its slot is not open fails with MatrixRtcFfiError::SlotClosed instead of InvalidInput.
  • A join's transport is one choice, Advertised, Publish on a LiveKit focus, or ReceiveOnly, replacing transport, receive_only and can_subscribe in the FFI and web join params.
  • The library keeps a joined session alive and performs its key rotations at their deadline itself; heartbeat(), flushDueKeyRotation()/flush_due_key_rotation, keyRotationDueAtMs()/key_rotation_due_at_ms and HEARTBEAT_INTERVAL_MS are gone, and LiveKitCallOptions::heartbeat_interval becomes keep_alive_interval_ms (default 10 s).
  • RtcClient::room spawns the room's feeds on matrix_rtc_core::executor and returns the room alone (RoomRuns is gone); natively it must be called within a tokio runtime, and LiveKitCall::join no longer needs a LocalSet.
  • The session managers are replaced by client → room → session (RtcClient → RtcRoom → RtcSession/RtcCall, likewise on the FFI and wasm), and the core's manager by the per-room BaseRtcRoom.
  • matrix_rtc_livekit::Call, CallOptions and CallError are renamed LiveKitCall, LiveKitCallOptions and LiveKitCallError, so they no longer read as the call crate's RtcCall.
  • compat, feeder and transports move from matrix-rtc-bridge to matrix-rtc-call, and what remains is renamed matrix-rtc-matrix-sdk, with no matrix-sdk feature; its SdkBackend becomes SdkMatrixBackend.
  • One MatrixBackend trait (matrix-rtc-core) replaces RtcCommandSender and every inbound method on the managers and bindings (set_current_sticky_state/set_current_membership, on_room_slots_received, on_room_members_received, on_room_encryption_received, receive_encryption_key/receive_legacy_encryption_key, on_room_timeline_events, on_event_redacted, on_relations_received). A host implements the trait — the sends, a per-room subscription delivering the room's complete current sets, a to-device subscription, /relations, the OpenID token and GET /rtc/transports — and opens rooms through RtcClient::room; the feeder (matrix_rtc_call::feeder) does the feeding, including the pre-2026 compatibility funnels, for every host. Gone with it: CommandSenderCallback, OpenIdTokenProvider, OpenIdTokenSource, SdkCommandSender, run_membership_bridge, discover_livekit_transport, and the typed inbound records (StickyEvent, SlotEvent, FfiTimelineEvent, FfiReceivedEncryptionKey, RawMemberEvent, …).
  • send_delayed_event takes an optional state key; send_delayed_state_event is folded into it.
  • The compatibility mode is given when a room is opened, not on join; the FFI and wasm join params no longer take user_id/device_id (the backend knows them) and their transport is optional — the library picks from rtc_transports() when it is absent.
  • LiveKitCall::join takes LiveKitCallOptions::livekit_transport as an override instead of livekit_service_url_fallback; connect_media_session no longer takes a token provider.
  • Joining a slot the room's state says is closed (or that is absent) fails with JoinError::SlotClosed; nothing opens a slot implicitly — open_slot is the room admin's, or the room's initial state.
  • Call features (reactions, raised hand, MSC4075 notify) moved from matrix-rtc-core to the new matrix-rtc-call crate, joined with RtcRoom::join_call and CallJoinOptions.
  • CallMembershipEvent is renamed RtcMembershipEvent, and try_into_call_membership_event is renamed try_into_membership_event.
  • JoinSessionParams.application and JoinedMembership.application are the whole MSC4143 ApplicationInfo, and the wasm roster serializes it as an object.
  • BaseRtcRoom::join returns the event id of the membership it sent, and RtcSession::membership_event_id reads the current one.
  • The feeder feeds any ApplicationIntake, subscribing to the event types it names.
  • The feeder moves from matrix_rtc_call::feeder to matrix_rtc_core::feeder, reading a room through an IngestDialect.
  • JoinSessionParams no longer takes the user and device, which are the backend's: JoinSessionParams::application("org.example.board").slot("planning") composes the slot id ({application}#room without .slot), and every other field is a named setter (.transport(..), required, .member_id(..), .keep_alive_interval_ms(..), …); JoinSessionParams::new and with_transport_intent are gone. The call layer's options read the same way: CallJoinOptions::new() joins m.call#room and .slot("standup") names another, JoinOptions::application(..).slot(..) for any application; JoinOptions::new and CallJoinOptions::new(slot_id) are gone.
  • The pre-2026 membership formats move from matrix_rtc_call::compat to matrix_rtc_core::compat, and ElementCallCompat { Off, StickyEvents, StateEvents } becomes MembershipFormat { Current, Sticky2025, RoomState } on every host (FfiMembershipFormat, the wasm format: "current" | "sticky_2025" | "room_state" room option, LiveKitCallOptions::format, RoomOptions::format, the load test's --format).
  • WasmRtcSession, the bare single-session wasm API, is removed; WasmRtcClient over a MatrixBackendHost is the one entry point, and joinCall rings like every other host.

Added

  • A call leaves on its own when its slot closes (leave_reason slot_closed), and every ending — leave, slot close, room close — ends its media and closes its SFU connections on one path — at once on a leave, even one that fails to send — reported by RtcSession::subscribe_ended / FFI RtcCall.waitForEnd().
  • matrix_rtc_transport::pool::MediaPool, the MSC4195 multi-SFU connection pool extracted from the call engine, reports a session's media per member_id for any application on the core.
  • matrix_rtc_call_sdk::attach_media and attach_livekit attach media to a joined call, now the one wiring behind the FFI and web media sessions and LiveKitCall.
  • A room still waiting on its first deliveries logs which RoomSink calls are missing every 5 s, so a host that never delivers one sees why room() has not returned.
  • BaseRtcClient opens a core room that subscribes through the backend and feeds itself, so a host of the core alone feeds and ticks nothing.
  • A core room opens in a MembershipFormat (BaseRtcClient::room(room_id, RoomOptions)) and renders its joins in it, so a host of the core alone can join in the 2025 sticky or the pre-sticky room-state format.
  • BaseRtcRoom::join starts the slot's upkeep (keep-alive, sticky refresh, key rotations at their deadline) until the leave, so a host on the core alone ticks nothing either; upkeep_abort_handle stops it for an owner that drops the join without leaving.
  • BaseRtcRoom::add_membership_listener: synchronous notice of every change to a slot's joined memberships, for applications built on the core.
  • ApplicationIntake and RelationsRequest, how a host feeds an application beyond membership.
  • BaseRtcRoom::backend(), and a core testing feature exposing MockBackend, whose sinks a test delivers sets into.
  • Reactions, the raised hand and the /relations backfill on every host (FFI, wasm, LiveKitCall), since the feeder does the work once.
  • matrix_rtc_call::transports::choose: the library's transport choice from the homeserver's GET /rtc/transports.
  • SdkMatrixBackend discovers transports through matrix-sdk's cached discover_rtc_transports, falling back to the well-known rtc_foci; a failed request is an error rather than "none advertised".
  • A member event carrying both msc4354_sticky_key and sticky_key (as seen on the wire) now parses; it was dropped as a duplicate field.

Fixed

  • The web onEvent callback now receives key_imported for keys that arrived before connectMedia, and LiveKitCall::join leaves the slot on any media attach failure, not only a refused SFU.
  • In the 2025 Element Call format the room-wide slot is sent and read as m.call#ROOM, including in the token request, so we share a session and an SFU room with Element Call again.
  • The media engine's first event subscriber now receives events emitted before it subscribed, so KeyImported for keys replayed during connect is no longer lost.

v0.4.0-rc.1

v0.4.0-rc.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 25 Sep 16:13

Added

  • The call tile roster: MediaSession::next_roster() / next_local_state()
    publish one ranked, damped CallTile per renderable stream, with thresholds
    configurable via stability.
  • A tile's kind is a TileKind — Person or ScreenShare — rather than a stream
    kind: TileId.kind, CallTile.kind and their FfiTileKind mirrors. The stream a
    tile draws is TileKind::video_stream(); a microphone is never a tile.
  • MediaSession::receive_stats_for(streams) (and CallEngine::receive_stats_for)
    reads receive counters for a set of (member_id, kind) in one call, awaiting
    the transports concurrently: one round trip per sample for a host that polls
    the tiles it draws, instead of one per stream.

Breaking

  • MSC4354 sticky events now come from upstream matrix-rust-sdk and are always on with the matrix-sdk feature; the experimental-sticky feature, STICKY_EVENTS_SUPPORTED and CallError::StickyEventsUnsupported are gone.

Removed

  • FfiCallEvent::ActiveSpeakers. Who is speaking is FfiCallTile::speaking, damped
    into the roster's order; the event was the noisiest thing on the stream and no
    host drew from it. The event stream now carries one-shots and diagnostics only —
    see MediaSession::next_event's docs. wasm keeps its own active_speakers.
    Drive audio playback from the tile roster's order, not from StreamStarted;
    participants() is a diagnostics pull, not something to re-read per event.

v0.3.0-rc.2

v0.3.0-rc.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 22 Sep 06:44

Added

  • PublishOptions::muted publishes a track already muted, for a lobby that
    joins muted; also on FfiPublishOptions.

Fixed

  • Our own publications now land on the roster when they are made before our
    membership does, mute state included (#50).

v0.3.0-rc.1

v0.3.0-rc.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Sep 09:59

Project

  • The project is now dual licensed by Element Creations Ltd: AGPL-3.0-only
    OR the Element Commercial License (AGPL-3.0-only OR LicenseRef-Element-Commercial).

Breaking

Hosts implementing the FFI/WASM command sender must update. All of these are
compile errors, not silent behaviour changes.

  • The generated bindings have their release names. Kotlin moves from
    package uniffi.matrix_rtc_ffi to org.matrix.rtc (the package of the
    hand-written MatrixRtc / RtcLogging helpers, so one import prefix covers
    the whole API); the Swift module is MatrixRtc and the C module inside the
    xcframework MatrixRtcFFI (was matrix_rtc_ffiFFI). The Android module now
    declares JNA as an api dependency (net.java.dev.jna:jna:5.19.1@aar), so a
    consumer gets it from the POM, and drops the appcompat and jna-platform
    dependencies nothing used. The Swift package moves to the repository root
    (Package.swift + Sources/MatrixRtc) and is consumed as a remote package
    at a v* tag; mobile/ios/Package.swift is gone.

  • The Rust-driven path now depends on upstream matrix-org/matrix-rust-sdk
    by default; MSC4354 sticky events are behind a new experimental-sticky
    feature.
    matrix-rtc-bridge and matrix-rtc-livekit (feature matrix-sdk)
    no longer pin the BillCarsonFr/matrix-rust-sdk fork. Against the stock SDK
    they carry membership as org.matrix.msc3401.call.member room state only, so
    Call::join accepts ElementCallCompat::StateEvents and returns the new
    CallError::StickyEventsUnsupported for Off and StickyEvents; a sticky
    send reaching SdkCommandSender directly fails with
    CommandError::ClientRejected. experimental-sticky compiles the sticky
    paths and needs the fork, selected with the .cargo/experimental-sticky.toml
    overlay (scripts/cargo-sticky.sh, make check-sticky / clippy-sticky /
    test-e2e-sticky), plus matrix-sdk/unstable-msc4354 and
    matrix-sdk-ui/unstable-msc4354 on the command line — a cargo feature cannot
    forward to an SDK feature upstream lacks without breaking resolution for
    everyone. matrix_rtc_bridge::STICKY_EVENTS_SUPPORTED says which build a
    caller has. run_sticky_bridge is renamed run_membership_bridge. Consumers
    must replicate the SDK's [patch] blocks as before; the set is now
    upstream's (the tracing pins are gone, a ruma patch is added). The FFI and
    WASM bindings are unaffected: they never had matrix-sdk in their graph.

  • In StateEvents mode the MSC4075 notification goes out through
    send_room_event / sendRoomEvent, not the sticky send.
    The pre-sticky
    wire has no sticky map, so that generation's ring is an ordinary room event.
    MemberEventRoute gains a Room variant for it (exhaustive matches break).
    A host whose SDK has no MSC4354 support can now run a StateEvents call end
    to end, notification included, without ever implementing sendStickyEvent.

  • The command sender gains sendRoomEvent(roomId, eventType, content) and
    redactEvent(roomId, eventId, reason?).
    Kotlin/Swift: two new methods on
    CommandSenderCallback; JS: two new methods on the client object handed to
    setupCommandSender, resolving like sendStickyEvent does ({event_id})
    and to anything at all, respectively. They carry Element Call's reactions
    (io.element.call.reaction) and the raised hand (an m.reaction
    annotation, lowered by redacting it) — plain message-like sends, encrypted
    in an encrypted room by the client SDK's ordinary send. The matrix-js-sdk
    host module implements both.

  • Sticky events fed in should now carry their event_id. StickyEvent
    (FFI), StickyEventIn / RawMemberEventIn / LegacyStateMemberEventIn
    (JS) and the FFI RawMemberEvent / LegacyStateMemberEvent records gain an
    optional event_id. Optional so existing hosts compile, but a member fed
    without one cannot be reacted for and none of their reactions validate:
    Element Call relates every reaction to the reacting member's membership
    event. JoinedMembership / MembershipSnapshot grow the matching
    membership_event_id, which moves on every sticky refresh.

v0.3.0 sweep — deliberately batched into one release while there is still a
single integrator, rather than dripped out over several:

  • CallOptions::legacy_element_call: bool is now
    CallOptions::element_call_compat: ElementCallCompat
    (Off / StickyEvents
    / StateEvents). There are two pre-2026 Element Call generations to render for
    now, and they disagree about the carrier of a membership rather than just its
    fields, so they are mutually exclusive by construction — an enum makes the
    impossible fourth state unrepresentable. LiveKitTransportConfig likewise
    gains a token_endpoint: TokenEndpoint field. The load generator's
    --legacy-element-call flag becomes --element-call-compat <off|sticky|state>,
    and join_and_record's LEGACY_ELEMENT_CALL now takes sticky or state
    (any other value still means sticky).

  • sendStickyEvent and sendStateEvent now return the event id.
    Kotlin/Swift: String; JS: resolve to matrix-js-sdk's {event_id} (a bare
    string and {eventId} are accepted too), and resolving to anything else is
    now a failed send rather than a silent success.

    The id is what MSC4075 needs: a call notification must carry an m.reference
    relation to the membership event that justifies it. It is not optional because
    every Matrix send responds with one — PUT /rooms/{id}/send/{type}/{txn} and
    PUT /rooms/{id}/state/{type}/{key} both do — so an implementation that
    cannot produce it is broken, and failing loudly beats a call that joins fine
    and quietly never rings. sendStateEvent is included because in StateEvents
    compat mode the membership goes out through it; nothing reads the id of a
    slot event.

    The alternative, recovering the id from our own membership echoing back
    through sync (what matrix-js-sdk does), puts a full sync round trip in front
    of the ring.

  • sendToDeviceMessage now takes a recipient list and returns a result per
    recipient
    , replacing the per-device call whose only failure channel was an
    exception. That exception landed inside the core's loop over members, so one
    undeliverable recipient silenced every member after it; and because the core
    had no way to learn who was actually served, it recorded everyone as holding
    the key and never retried. Now: return FfiToDeviceDelivery { userId, deviceId, error } per recipient — a recipient you report as delivered is
    never re-sent to, one you mark failed or omit is retried on the next rollout.
    Reserve Err for "the batch could not be attempted at all". This also removes
    a loop and several FFI crossings, and maps onto matrix-rust-sdk's own
    recipient-list sendToDeviceMessage.

  • restartDelayedEvent / cancelDelayedEvent take delayId, not
    eventId.
    The value was always the MSC4140 delay id; the parameter name
    said otherwise, and transposing the two fails silently, surfacing minutes
    later as the dead man's switch retiring a live membership. Renamed everywhere,
    including sendDelayedEvent's documented return.

  • CommandSenderCallback is async, and so is every manager-handle method
    that touches the core.
    In Kotlin they are suspend funs; in Swift, async.
    A synchronous callback forced hosts to bridge to their async Matrix client
    themselves — runBlocking on a dedicated dispatcher on Android — while every
    corresponding matrix-rust-sdk call is already async.

    setCommandSender now takes the interface directly rather than a boxed one
    (uniffi with_foreign), and CommandSenderError gained a Display impl,
    which uniffi requires of a foreign-trait error type.

    This was blocked until now by a subtlety worth recording: uniffi's async
    exports must be Send, while matrix-rtc-core's command traits were
    ?Send on every target to accommodate WASM's JS-backed futures. They are now
    Send everywhere except wasm32, via a cfg_attr pair that every
    implementation of those traits must carry
    . A consequence is that
    matrix-rtc-wasm no longer compiles for a host target — its crate body is
    gated to wasm32 and a Clippy (wasm32) CI job type-checks it instead.

    Internally this removed the blocking bridge entirely: no block_on, and the
    keep-alive is a spawned task rather than a thread per session (it needed a
    thread only because its future held a std::sync::MutexGuard and so was not
    Send).

  • One way in for membership: setCurrentStickyState(roomId, events). It
    carries the room's complete current sticky state and replaces what the
    core held — a member absent from it is gone, and an empty list clears the
    room. initialStickyForRoom, onStickyEventsSnapshotReceived,
    stickyUpdateForRoom and onStickyEventsUpdateReceived are all gone, along
    with the StickyEventUpdate record.

    A delta bought nothing: the core discarded the previous half of an update,
    flattened every removal to a plain leave, and an explicit leave arrives inside
    the current state anyway as a leave-shaped sticky replacing the join under the
    same key. Meanwhile matrix-sdk-ffi hands hosts a full snapshot on every
    change (it collapses the SDK's delta before it crosses the boundary), so the
    current state is the shape hosts already have — and the core now does the
    diffing once instead of each host doing it. Feeding a shrunken set is how an
    expired membership departs.

  • RtcSessionHandle is gone. Its leave() could only ever return an error,
    and a session created through it was invisible to RtcSessionManagerHandle,
    so nothing else could act on it. Its one unique capability — observing the
    roster — is now on the manager. Drive sessions through the manager handle.

  • FfiAudioFrame.data is a ByteArray of little-endian int16, not a
    List<Short>. uni...

Read more