Skip to content

Latest commit

ย 

History

7,510 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Element Call

Chat Localazy License Codecov

๐ŸŽฌ Live Demo ๐ŸŽฌ

The world's first ๐ŸŒ decentralized and ๐Ÿค federated video conferencing solution powered by the Matrix protocol.

๐Ÿ“Œ Overview

Element Call is a native Matrix video conferencing application developed by Element, designed for secure, scalable, privacy-respecting, and decentralized video and voice calls over the Matrix protocol. Built on MatrixRTC (MSC4143), it utilizes MSC4195 with LiveKit as its backend.

A demo of Element Call with six people

You can find the latest development version continuously deployed to call.element.dev.

Note

For prior version of the Element Call that relied solely on full-mesh logic, check full-mesh branch.

โœจ Key Features

โœ… Decentralized & Federated โ€“ No central authority; works across Matrix homeservers.
โœ… End-to-End Encrypted โ€“ Secure and private calls.
โœ… Standalone, Widget & Component Mode โ€“ Use as an independent app, embed in Matrix clients as a widget, or (experimentally) mount it as a React component inside your own application.
โœ… WebRTC-based โ€“ No additional software required.
โœ… Scalable with LiveKit โ€“ Supports large meetings via SFU (MSC4195: MatrixRTC using LiveKit backend).
โœ… Raise Hand โ€“ Participants can signal when they want to speak, helping to organize the flow of the meeting.
โœ… Emoji Reactions โ€“ Users can react with emojis ๐Ÿ‘๏ธ ๐ŸŽ‰ ๐Ÿ‘ ๐Ÿค˜, adding engagement and interactivity to the conversation.

๐Ÿš€ Deployment & Packaging Options

Element Call is developed using the Matrix js-sdk with Matroska mode. This allows the app to run either as a Standalone App directly connected to a homeserver with login interfaces or it can be used as a widget within a Matrix client.

๐Ÿ–ฅ๏ธ Standalone Mode

Element Call in Standalone Mode

In Standalone mode, Element Call operates as an independent, full-featured video conferencing web application, enabling users to join or host calls without requiring a separate Matrix client.

๐Ÿ“ฒ In-App Calling (Widget Mode in Messenger Apps)

When used as a widget ๐Ÿงฉ, Element Call is solely responsible for the core calling functionality (MatrixRTC). Authentication, event handling, and room state updates (via the Client-Server API) are handled by the hosting client. Communication between Element Call and the client is managed through the widget API.

Element Call in Widget Mode

Element Call can be embedded as a widget inside apps like Element Web or Element X (iOS, Android), bringing MatrixRTC capabilities to messenger apps for seamless decentralized video and voice calls within Matrix rooms.

Important

Embedded packaging is recommended for Element Call in widget mode!

๐Ÿ“ฆ Element Call Packaging

Element Call offers two packaging options: one for standalone or widget deployment, and another for seamless widget-based integration into messenger apps. A third, experimental option builds it as a React component library for applications that want to render a call inside their own page rather than in an iframe. Below is an overview of each option.

Full Package โ€“ Supports both Standalone and Widget mode. It is hosted as a static web page and can be accessed via a URL when used as a widget.

Element Call Full Package

Embedded Package โ€“ Designed specifically for Widget mode only. It is bundled with a messenger app for seamless integration and this is the recommended method for embedding Element Call.

Element Call Embedded Package

Component Package (experimental) โ€“ A library build of Element Call as a React component, consumed as a dependency by a host application that already has a Matrix client. See Element Call as a component below.

For more details on the packages, see the Embedded vs. Standalone Guide.

๐Ÿ› ๏ธ Self-Hosting

For operating and deploying Element Call on your own server, refer to the Self-Hosting Guide.

MatrixRTC Transports

For proper operation of Element Call, each deployment needs to set up a MatrixRTC transport in the form of a LiveKit server as outlined in the Self-Hosting Guide. A typical federated site deployment for three different sites A, B and C is depicted below.

Element Call federated setup

Transport Discovery

Element Call discovers the available MatrixRTC transports (as defined by MSC4519) by hitting the GET /_matrix/client/unstable/org.matrix.msc4143/rtc/transports endpoint of the Client-Server API. An example response:

{
  "rtc_transports": [
    {
      "type": "livekit",
      "livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt"
    }
  ]
}

where the format for MatrixRTC using LiveKit backend is defined in MSC4195. In the example above Matrix clients do discover a focus of type livekit which points them to a MatrixRTC Authorization Service via livekit_service_url.

Backend Selection

  • Each call participant proposes their discovered MatrixRTC transport from org.matrix.msc4143.rtc_foci in their org.matrix.msc3401.call.member state event.
  • For the LiveKit MatrixRTC backend (MSC4195), the first participant who joined the call defines which backend will be used for this call via the foci_preferred key in their org.matrix.msc3401.call.member state event.
  • During the actual call join flow, the MatrixRTC Authorization Service provides the client with the LiveKit SFU WebSocket URL and an access JWT token in order to exchange media via WebRTC.

The example below illustrates how backend selection works across Matrix federation, using the setup from sites A, B, and C. It demonstrates backend selection for Matrix rooms 123 and 456, which include users from different homeservers.

Element Call SFU selection over Matrix federation

๐ŸŒ Translation

If you'd like to help translate Element Call, head over to Localazy. You're also encouraged to join the Element Translators space to discuss and coordinate translation efforts.

๐Ÿ› ๏ธ Development

Dependencies

  • Node.js (e.g. via nvm)
  • Corepack (not bundled with Node.js anymore starting from 25.0.0)
  • Docker client and runtime + Docker Compose (for the backend)
    • On macOS you can install everything with brew install colima docker docker-compose

Frontend

To get started clone and set up this project:

git clone https://github.com/element-hq/element-call.git
cd element-call
corepack enable
pnpm install

To use it, create a local config by, e.g., cp ./config/config.devenv.json ./public/config.json and adapt it if necessary. The config.devenv.json config should work with the backend development environment as outlined in the next section out of box.

You're now ready to launch the development server:

pnpm dev

See also:

Element Call as a component (experimental)

Element Call can also be embedded directly into another React application rather than being loaded in an iframe as a widget. pnpm build:component builds it as a library into component/dist (the bundle, its stylesheet and type declarations), and

pnpm dev:component

serves a harness on port 3001 that stands in for such an application: it signs in twice against the development backend and shows two calls side by side, in resizable boxes, with page furniture of its own around them. Use it to see how Element Call behaves when it does not own the page โ€” the size it is given, whether it stays inside its container, and what it says to its host, which is logged along the bottom. The harness is served with the same development certificate as the app, so unless the development CA is trusted, the browser needs a certificate exception for https://localhost:3001 as well (see the note under Backend). It reads the same public/config.json as pnpm dev if one exists, and runs with Element Call's defaults otherwise.

The call lays itself out for the size of the element it is mounted in, not the window: a host that shrinks the container to a corner of its page gets the picture-in-picture layout, just as a host that shrank the whole iframe used to. The breakpoints in the stylesheets the component uses are @container element-call queries against its root element for the same reason; for the standalone app the root is the page, so they mean what the media queries they replaced did. (The standalone-only views, such as the home and login pages, still use plain media queries, since the component never shows them.)

The component's stylesheet is confined to the element it is mounted in: the build rewrites every selector so that it matches only Element Call's root or what is inside it, with html, body and :root standing for that root (see component/build/scopeStylesToRoot.ts). A host's own page keeps its styles, and Element Call brings its own fonts and design tokens along.

The component speaks every language the app does. English is bundled in; the other locales are split into chunks the host's bundler loads the first time they are needed. It starts in the browser's language, and follows the host's own language setting through the language prop (supportedLanguages lists the tags it accepts, and anything else falls back to its base language or to English). The theme prop works the same way and takes the same values as the widget's theme URL parameter: light, dark, light-high-contrast or dark-high-contrast. Both can change while a call is running without disturbing it.

A host must call and await initializeElementCall(config) once before rendering the component: it loads the Intl polyfills, applies the deployment-wide config.json-style configuration and sets up translations. The component itself takes the host's client and the roomId to call in, an intent saying what the user asked for (which decides whether to show the lobby, ring, and so on), an optional config overriding what the intent implies, and an optional hostBridge through which Element Call tells the host that the user has joined or hung up, that it wants to stay on screen, and so on. The host makes its own requests (join, hangUp, setDeviceMute) through the handle exposed on ref. The full API is documented in the type declarations (component/index.tsx and component/host.ts).

A few things differ from the widget on purpose: the component draws a solid background rather than a gradient unless told otherwise, never offers to edit the user's profile (the account is the host's), scopes its keyboard shortcuts to its own root element so that several instances can share a page, and only shows its own post-call and error screens when the host has not supplied a close() callback; with one, it asks the host to unmount it instead. The global JS controls on window are unchanged and remain page-wide, so with several instances on one page they apply to all of them.

The package is not published yet. A host installs it as a git dependency on the component directory of this repository,

"@element-hq/element-call-component": "github:element-hq/element-call#main&path:/component"

whose prepare script runs the build on install. That build needs pnpm (via Corepack) on the host's machine, runs a full pnpm install of this repository and is memory-hungry, since it inherits the --max-old-space-size setting of the app build; the host's pnpm also has to allow it to run at all (allowBuilds in its pnpm-workspace.yaml). Note that component/ is a pnpm project of its own for this reason, so pnpm commands run from inside that directory target it rather than the repository; run them from the repository root. The host imports the component from @element-hq/element-call-component and the stylesheet from @element-hq/element-call-component/style.css, and has to provide react, react-dom, matrix-js-sdk and livekit-client itself, since the bundle leaves them external.

Backend

A docker compose file docker-compose-dev.yml is provided to start the whole stack of components which is required for a local development environment including federation:

  • Minimum Synapse Setup (servernames: synapse.m.localhost, synapse.othersite.m.localhost)
  • MatrixRTC Authorization Service (Note: requires Federation API and hence a TLS reverse proxy)
  • Minimum LiveKit SFU setup using dev defaults for config
  • Minimum localhost Certificate Authority (CA) for Transport Layer Security (TLS)
  • Minimum TLS reverse proxy for
    • Synapse homeserver: synapse.m.localhost and synapse.othersite.m.localhost
    • MatrixRTC backend: matrix-rtc.m.localhost and matrix-rtc.othersite.m.localhost
    • Local Element Call development call.m.localhost via pnpm dev --host
    • Element Web app.m.localhost and app.othersite.m.localhost
    • Note certificates will expire on Thr, 20 September 2035 14:27:35 CEST

These use a test 'secret' published in this repository, so this must be used only for local development and never be exposed to the public Internet.

Make sure your Docker runtime is running (e.g. via colima start) and then start the backend components:

pnpm backend
# or for podman-compose:
# podman-compose -f docker-compose-dev.yml up

Note

To ensure your local development frontend functions properly, youโ€™ll need to add certificate exceptions in your browser for https://localhost:3000 and https://matrix-rtc.m.localhost/livekit/jwt/healthz. This can be done either by adding the minimum localhost CA (./backend/dev_tls_local-ca.crt) to your web browser's trusted certificates or by simply copying and pasting each URL into your browserโ€™s address bar and follow the prompts to add the exception.

Updating snapshots

To update snapshots used in tests, use Vitest's -u flag, e.g.:

pnpm test DeveloperSettingsTab -u

Playwright tests

Our Playwright tests run automatically as part of our CI along with our other tests, on every pull request.

You may need to follow instructions to set up your development environment for running Playwright by following https://playwright.dev/docs/browsers#install-browsers and https://playwright.dev/docs/browsers#install-system-dependencies.

However the Playwright tests are run, an element-call instance must be running on https://localhost:3000 (this is configured in playwright.config.ts) - this is what will be tested. The tests under playwright/component instead drive the component harness (pnpm dev:component) on https://localhost:3001, which Playwright starts as a second web server; it is always a Vite dev server, even when the app itself is served from Docker with USE_DOCKER.

The local backend environment should be running for the test to work: pnpm backend

There are a few different ways to run the tests yourself. The simplest is to run:

pnpm run test:playwright

This will run the Playwright tests once, non-interactively.

There is a more user-friendly way to run the tests in interactive mode:

pnpm run test:playwright:open

The easiest way to develop new test is to use the codegen feature of Playwright:

npx playwright codegen

This will record your action and write the test code for you. Use the tool bar to test visibility, text content and clicking.

Investigate a failed test from the CI

In the failed action page, click on the failed job, then scroll down to the upload-artifact step. You will find a link to download the zip report, as per:

Artifact playwright-report has been successfully uploaded! Final size is 1360358 bytes. Artifact ID is 2746265841
Artifact download URL: https://github.com/element-hq/element-call/actions/runs/13837660687/artifacts/2746265841

Unzip the report then use this command to open the report in your browser:

npx playwright show-report ~/Downloads/playwright-report/

Under the failed test there is a small icon looking like "3 columns" (next to the test name file name), click on it to see the live screenshots/console output.

Test Coverage

Add a new translation key

To add a new translation key you can do these steps:

  1. Add the new key entry to the code where the new key is used: t("some_new_key")

  2. Run pnpm i18n to extract the new key and update the translation files. This will add a skeleton entry to the locales/en/app.json file:

    {
        ...
        "some_new_key": "",
        ...
    }
  3. Update the skeleton entry in the locales/en/app.json file with the English translation:

    {
        ...
        "some_new_key": "Some new key",
        ...
    }

๐Ÿ“– Documentation

Usage and other technical details about the project can be found here:

Docs

GitHub Labels

GitHub labels in this repository are maintained in the labels.yml file and automatically synced to GitHub using the sync-labels workflow. We do this so that we can reuse the labels between repositories.

Warning

Do not manually edit labels in the GitHub UI. Any manual changes will be overridden by the workflow on its next invocation.

๐Ÿ“ Copyright & License

Copyright 2021-2026 New Vector Ltd

This software is dual-licensed by New Vector Ltd (Element). It can be used either:

(1) for free under the terms of the GNU Affero General Public License (as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version); OR

(2) under the terms of a paid-for Element Commercial License agreement between you and Element (the terms of which may vary depending on what you and Element have agreed to). Unless required by applicable law or agreed to in writing, software distributed under the Licenses is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Licenses for the specific language governing permissions and limitations under the Licenses.

About

Group calls powered by Matrix

Topics

Resources

Contributing

Security policy

Stars

996 stars

Watchers

28 watching

Forks

Releases

Packages

Used by

Contributors

Languages