mirror of
https://github.com/vector-im/element-call.git
synced 2026-11-10 01:02:27 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad3511aaf | ||
|
|
9714412a43 |
+1
-1
@@ -107,7 +107,7 @@
|
||||
"livekit-client": "^2.18.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"loglevel": "^1.9.1",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#3f05dc4d104a3dfc753c5e3549a18c74fab4b076",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#b3f17e3ee8ca53f5625f7d8c482155d5a82ef4a2",
|
||||
"matrix-widget-api": "^1.18.0",
|
||||
"node-stdlib-browser": "^1.3.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
|
||||
Generated
+33
-56
@@ -66,7 +66,7 @@ importers:
|
||||
version: 10.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@rolldown/plugin-babel':
|
||||
specifier: ^0.2.3
|
||||
version: 0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.0)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))
|
||||
version: 0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.8)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))
|
||||
'@sentry/react':
|
||||
specifier: ^8.0.0
|
||||
version: 8.55.2(react@19.2.8)
|
||||
@@ -138,7 +138,7 @@ importers:
|
||||
version: 10.4.0(@fontsource/inconsolata@5.3.0)(@fontsource/inter@5.3.0)(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(@vector-im/compound-design-tokens@11.3.1(@types/react@19.2.18)(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^6.0.2
|
||||
version: 6.1.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.0)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))
|
||||
version: 6.1.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.8)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))
|
||||
'@vitest/browser-playwright':
|
||||
specifier: ^4.1.5
|
||||
version: 4.1.11(playwright@1.62.1)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.11)
|
||||
@@ -191,8 +191,8 @@ importers:
|
||||
specifier: ^1.9.1
|
||||
version: 1.9.2
|
||||
matrix-js-sdk:
|
||||
specifier: github:matrix-org/matrix-js-sdk#3f05dc4d104a3dfc753c5e3549a18c74fab4b076
|
||||
version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3f05dc4d104a3dfc753c5e3549a18c74fab4b076
|
||||
specifier: github:matrix-org/matrix-js-sdk#b3f17e3ee8ca53f5625f7d8c482155d5a82ef4a2
|
||||
version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b3f17e3ee8ca53f5625f7d8c482155d5a82ef4a2
|
||||
matrix-widget-api:
|
||||
specifier: ^1.18.0
|
||||
version: 1.20.0
|
||||
@@ -397,8 +397,8 @@ packages:
|
||||
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@8.0.0':
|
||||
resolution: {integrity: sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==}
|
||||
'@babel/runtime@8.0.8':
|
||||
resolution: {integrity: sha512-dEu8nGlRqjsJsvviUUoSMfcRHpwomCiRlXsc2mPfJ79+ffTpDm+JtfUtTMzUrqCApHQnCsVPD3gT1ZT8RgBRCQ==}
|
||||
|
||||
'@babel/template@7.29.7':
|
||||
resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
|
||||
@@ -408,10 +408,6 @@ packages:
|
||||
resolution: {integrity: sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/traverse@7.29.8':
|
||||
resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.29.11':
|
||||
resolution: {integrity: sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -3421,11 +3417,6 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
acorn@8.18.0:
|
||||
resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
acorn@8.19.0:
|
||||
resolution: {integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -4759,9 +4750,9 @@ packages:
|
||||
matrix-events-sdk@0.0.1:
|
||||
resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==}
|
||||
|
||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3f05dc4d104a3dfc753c5e3549a18c74fab4b076:
|
||||
resolution: {gitHosted: true, integrity: sha512-Ly8eS7fNNKDo9KmPvTkE49KvD8iMUmrVWJMNERNuxc8UksMLMG36NLz3fs8HytdwrB7AKPfyVo392VZsxILDIg==, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3f05dc4d104a3dfc753c5e3549a18c74fab4b076}
|
||||
version: 44.0.0
|
||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b3f17e3ee8ca53f5625f7d8c482155d5a82ef4a2:
|
||||
resolution: {gitHosted: true, integrity: sha512-AXD5ClohyPLBY30QfmkD99fAGlAMsQ+2flR8rWqTy8aNpLHnTUisNhAClDgkAaDTnnFbOX/vZ8hwVzmYSefqTA==, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b3f17e3ee8ca53f5625f7d8c482155d5a82ef4a2}
|
||||
version: 45.0.0
|
||||
engines: {node: '>=22.0.0'}
|
||||
|
||||
matrix-widget-api@1.20.0:
|
||||
@@ -6357,10 +6348,10 @@ snapshots:
|
||||
'@babel/helper-compilation-targets': 7.29.7
|
||||
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)
|
||||
'@babel/helpers': 7.29.7
|
||||
'@babel/parser': 7.29.8
|
||||
'@babel/parser': 7.29.11
|
||||
'@babel/template': 7.29.7
|
||||
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||
'@babel/types': 7.29.8
|
||||
'@babel/traverse': 7.29.10(supports-color@7.2.0)
|
||||
'@babel/types': 7.29.11
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
@@ -6372,8 +6363,8 @@ snapshots:
|
||||
|
||||
'@babel/generator@7.29.8':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.8
|
||||
'@babel/types': 7.29.8
|
||||
'@babel/parser': 7.29.11
|
||||
'@babel/types': 7.29.11
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
jsesc: 3.1.0
|
||||
@@ -6390,8 +6381,8 @@ snapshots:
|
||||
|
||||
'@babel/helper-module-imports@7.29.7(supports-color@7.2.0)':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||
'@babel/types': 7.29.8
|
||||
'@babel/traverse': 7.29.10(supports-color@7.2.0)
|
||||
'@babel/types': 7.29.11
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -6400,7 +6391,7 @@ snapshots:
|
||||
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||
'@babel/helper-module-imports': 7.29.7(supports-color@7.2.0)
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||
'@babel/traverse': 7.29.10(supports-color@7.2.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -6417,7 +6408,7 @@ snapshots:
|
||||
'@babel/helpers@7.29.7':
|
||||
dependencies:
|
||||
'@babel/template': 7.29.7
|
||||
'@babel/types': 7.29.8
|
||||
'@babel/types': 7.29.11
|
||||
|
||||
'@babel/parser@7.29.11':
|
||||
dependencies:
|
||||
@@ -6433,13 +6424,13 @@ snapshots:
|
||||
|
||||
'@babel/runtime@7.29.7': {}
|
||||
|
||||
'@babel/runtime@8.0.0': {}
|
||||
'@babel/runtime@8.0.8': {}
|
||||
|
||||
'@babel/template@7.29.7':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.7
|
||||
'@babel/parser': 7.29.8
|
||||
'@babel/types': 7.29.8
|
||||
'@babel/parser': 7.29.11
|
||||
'@babel/types': 7.29.11
|
||||
|
||||
'@babel/traverse@7.29.10(supports-color@7.2.0)':
|
||||
dependencies:
|
||||
@@ -6448,19 +6439,7 @@ snapshots:
|
||||
'@babel/helper-globals': 7.29.7
|
||||
'@babel/parser': 7.29.11
|
||||
'@babel/template': 7.29.7
|
||||
'@babel/types': 7.29.8
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/traverse@7.29.8(supports-color@7.2.0)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.7
|
||||
'@babel/generator': 7.29.8
|
||||
'@babel/helper-globals': 7.29.7
|
||||
'@babel/parser': 7.29.8
|
||||
'@babel/template': 7.29.7
|
||||
'@babel/types': 7.29.8
|
||||
'@babel/types': 7.29.11
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -8167,13 +8146,13 @@ snapshots:
|
||||
'@rolldown/binding-win32-x64-msvc@1.2.15':
|
||||
optional: true
|
||||
|
||||
'@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.0)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))':
|
||||
'@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.8)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||
picomatch: 4.0.4
|
||||
rolldown: 1.2.15
|
||||
optionalDependencies:
|
||||
'@babel/runtime': 8.0.0
|
||||
'@babel/runtime': 8.0.8
|
||||
vite: 8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0)
|
||||
|
||||
'@rolldown/pluginutils@1.0.1': {}
|
||||
@@ -8925,12 +8904,12 @@ snapshots:
|
||||
- '@types/react-dom'
|
||||
- react-dom
|
||||
|
||||
'@vitejs/plugin-react@6.1.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.0)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))':
|
||||
'@vitejs/plugin-react@6.1.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.8)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.1
|
||||
vite: 8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0)
|
||||
optionalDependencies:
|
||||
'@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.0)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))
|
||||
'@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@8.0.8)(rolldown@1.2.15)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))
|
||||
babel-plugin-react-compiler: 1.0.0
|
||||
|
||||
'@vitest/browser-playwright@4.1.11(playwright@1.62.1)(vite@8.2.4(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.103.1)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.11)':
|
||||
@@ -9055,14 +9034,12 @@ snapshots:
|
||||
|
||||
'@webcontainer/env@1.1.1': {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.18.0):
|
||||
acorn-jsx@5.3.2(acorn@8.19.0):
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
acorn: 8.19.0
|
||||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
acorn@8.18.0: {}
|
||||
|
||||
acorn@8.19.0: {}
|
||||
|
||||
agent-base@6.0.2(supports-color@7.2.0):
|
||||
@@ -9733,8 +9710,8 @@ snapshots:
|
||||
|
||||
espree@9.6.1:
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
acorn-jsx: 5.3.2(acorn@8.18.0)
|
||||
acorn: 8.19.0
|
||||
acorn-jsx: 5.3.2(acorn@8.19.0)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
esprima@4.0.1: {}
|
||||
@@ -10468,9 +10445,9 @@ snapshots:
|
||||
|
||||
matrix-events-sdk@0.0.1: {}
|
||||
|
||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3f05dc4d104a3dfc753c5e3549a18c74fab4b076:
|
||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b3f17e3ee8ca53f5625f7d8c482155d5a82ef4a2:
|
||||
dependencies:
|
||||
'@babel/runtime': 8.0.0
|
||||
'@babel/runtime': 8.0.8
|
||||
'@matrix-org/matrix-sdk-crypto-wasm': 18.9.0
|
||||
another-json: 0.2.0
|
||||
bs58: 6.0.0
|
||||
@@ -11701,7 +11678,7 @@ snapshots:
|
||||
terser@5.46.1:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.11
|
||||
acorn: 8.18.0
|
||||
acorn: 8.19.0
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
|
||||
|
||||
+10
-4
@@ -66,9 +66,15 @@ media backend: whatever carries the media, behind the `MediaBackend` interface i
|
||||
`sdk/src/media-backend/api.ts`. The client is the join of the two: a member exists
|
||||
once its membership does, and gets its media once the backend has something
|
||||
for that membership on its transport; `state$` is the membership state combined
|
||||
with the local media connection. LiveKit is the one backend today; a
|
||||
cascading SFU or full mesh is another folder under `sdk/src/media-backend/`, handed
|
||||
in through `options.backend`, and nothing in the left box changes.
|
||||
with the local media connection. LiveKit (`livekit/`) is the one backend today,
|
||||
and the client runs it inside a combination (`combineBackends.ts`). It publishes
|
||||
on the backend of the type of `options.transport`, LiveKit without one, and
|
||||
hands every remote member to the backend of that member's transport. A backend
|
||||
nobody is on connects nowhere. LiveKit joins it twice (`createLivekitBackends`),
|
||||
once for the members on `livekit` and once for those on `m.livekit`, sharing
|
||||
its connections, so members of either type on one SFU share one. A cascading
|
||||
SFU or full mesh is another folder under `sdk/src/media-backend/`, added to the combination, and nothing in the
|
||||
left box changes; `options.backend` replaces the combination altogether.
|
||||
|
||||
### How Element Call uses it
|
||||
|
||||
@@ -149,7 +155,7 @@ the sections below still describe `MatrixRTCClient` until that lands.
|
||||
| publish request | a `PublishRequest`: a source, where to capture it from and how to encode it. Given at the join in `publish`, or later to `publish` on the participation | |
|
||||
| attach, detach | handing a `<video>` or `<audio>` element to a track and taking it back. The SDK owns the stream and the observers on the element | LiveKit `Track.attach` |
|
||||
| transport | an `RTCTransport`: where media is exchanged, a LiveKit service url today. Carries the raw js-sdk `Transport` from the membership and, in `resolved$`, what the backend fetched to connect to it | MSC4143 "focus" |
|
||||
| media backend | a `MediaBackend`: what carries the media, behind `sdk/src/media-backend/api.ts`. One per participation, chosen with `backend` in the join options; LiveKit under `sdk/src/media-backend/livekit/` is the one that exists | "SFU", a LiveKit room, a peer connection |
|
||||
| media backend | a `MediaBackend`: what carries the media, behind `sdk/src/media-backend/api.ts`. One per participation: the SDK's backends combined, publishing on the type of `transport` in the join options, unless `backend` replaces them | "SFU", a LiveKit room, a peer connection |
|
||||
|
||||
Names use `RTC` in capitals, as the js-sdk does: `RTCSlot`, `RTCParticipation`,
|
||||
`RTCMember`, `matrixRTCMode`.
|
||||
|
||||
@@ -50,7 +50,8 @@ import {
|
||||
type MediaBackendContext,
|
||||
type MediaKey,
|
||||
} from "./media-backend/api";
|
||||
import { createLivekitBackend } from "./media-backend/livekit/LivekitBackend";
|
||||
import { combineBackends } from "./media-backend/combineBackends";
|
||||
import { createLivekitBackends } from "./media-backend/livekit/LivekitBackend";
|
||||
import { findRemotePublishLivekitTransport } from "./media-backend/livekit/RemotePublishTransport";
|
||||
import { createHomeserverConnected$ } from "./matrixrtc/HomeserverConnected";
|
||||
import { joinJsSdkSession } from "./matrixrtc/joinJsSdkSession";
|
||||
@@ -136,7 +137,7 @@ export function createRTCParticipation(
|
||||
// fallback: another member's SFU may let us publish (MSC4195). LiveKit only,
|
||||
// so it stays out of the backend interface
|
||||
const transportFromBackends = async (): Promise<Transport | null> =>
|
||||
backend.transportType === "livekit"
|
||||
backend.transportType === "livekit" || backend.transportType === "m.livekit"
|
||||
? findRemotePublishLivekitTransport({
|
||||
client,
|
||||
// A snapshot: if nobody with a usable SFU is here yet, the user has
|
||||
@@ -310,15 +311,23 @@ export function createRTCParticipation(
|
||||
};
|
||||
}
|
||||
|
||||
/** The host's backend, or LiveKit configured from the participation options. */
|
||||
/**
|
||||
* The host's backend, or every backend the SDK has, combined: we publish on
|
||||
* the type of the transport the host named (LiveKit without one) and receive
|
||||
* from members on any of them.
|
||||
*/
|
||||
function createBackend(
|
||||
scope: ObservableScope,
|
||||
client: MatrixClient,
|
||||
{ backend, mediaQuality }: RTCParticipationOptions,
|
||||
{ backend, mediaQuality, transport }: RTCParticipationOptions,
|
||||
context: MediaBackendContext,
|
||||
): MediaBackend {
|
||||
if (backend) return backend(scope, context);
|
||||
return createLivekitBackend(scope, context, { client, mediaQuality });
|
||||
return combineBackends(
|
||||
scope,
|
||||
createLivekitBackends(scope, context, { client, mediaQuality }),
|
||||
transport?.type ?? "livekit",
|
||||
);
|
||||
}
|
||||
|
||||
/** The slot's per-participant keys: the ones it already holds on subscribe, then each new one. */
|
||||
|
||||
+1
-1
@@ -230,7 +230,7 @@ export interface DataMessage {
|
||||
* backend-specific developer panel and for connection diagnostics.
|
||||
*/
|
||||
export interface TransportMetadata {
|
||||
/** `"livekit"` today. */
|
||||
/** `"m.livekit"` or `"livekit"` today. */
|
||||
type: string;
|
||||
/** Stable key, unique per transport in the slot: the raw transport, serialised with sorted keys. */
|
||||
id: string;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { BehaviorSubject, NEVER } from "rxjs";
|
||||
|
||||
import { type RTCMember, type TransportMetadata } from "../api";
|
||||
import { type MemberMedia } from "../media-api";
|
||||
import { constant } from "../reactive/Behavior";
|
||||
import { ObservableScope } from "../reactive/ObservableScope";
|
||||
import { type MediaBackend } from "./api";
|
||||
import { combineBackends } from "./combineBackends";
|
||||
|
||||
function fakeBackend(type: string): MediaBackend & { ended: string[] } {
|
||||
const ended: string[] = [];
|
||||
return {
|
||||
ended,
|
||||
transportType: type,
|
||||
prepareLocalTransport: vi.fn(async () =>
|
||||
Promise.resolve({ canDelegateDelayedLeave: false }),
|
||||
),
|
||||
delegateDelayedLeave: vi.fn(),
|
||||
local: {} as MediaBackend["local"],
|
||||
mediaFor: (scope, member): MemberMedia => {
|
||||
scope.onEnd(() => ended.push(member.memberId));
|
||||
return {
|
||||
tracks$: constant([{ id: `${type}:${member.memberId}` }] as never),
|
||||
encryptionError$: NEVER,
|
||||
};
|
||||
},
|
||||
connections$: constant([
|
||||
{ transport: { type }, state: "connected" as never, resolved: undefined },
|
||||
]),
|
||||
setAudioOutputDeviceId: vi.fn(),
|
||||
sendData: vi.fn(),
|
||||
data$: NEVER,
|
||||
};
|
||||
}
|
||||
|
||||
function transport(type: string): TransportMetadata {
|
||||
return { type, id: type, raw: { type }, resolved$: constant(undefined) };
|
||||
}
|
||||
|
||||
describe("combineBackends", () => {
|
||||
it("routes each member to the backend of its transport type", () => {
|
||||
const scope = new ObservableScope();
|
||||
const livekit = fakeBackend("livekit");
|
||||
const other = fakeBackend("other");
|
||||
const combined = combineBackends(scope, [livekit, other], "livekit");
|
||||
expect(combined.transportType).toBe("livekit");
|
||||
expect(combined.connections$.value).toHaveLength(2);
|
||||
|
||||
const transport$ = new BehaviorSubject<TransportMetadata | undefined>(
|
||||
transport("other"),
|
||||
);
|
||||
const memberScope = new ObservableScope();
|
||||
const media = combined.mediaFor(memberScope, {
|
||||
memberId: "m1",
|
||||
transport$,
|
||||
} as unknown as RTCMember);
|
||||
expect(media.tracks$.value).toEqual([{ id: "other:m1" }]);
|
||||
|
||||
transport$.next(transport("livekit"));
|
||||
expect(other.ended).toEqual(["m1"]);
|
||||
expect(media.tracks$.value).toEqual([{ id: "livekit:m1" }]);
|
||||
|
||||
transport$.next(transport("unknown"));
|
||||
expect(media.tracks$.value).toBeNull();
|
||||
// The publishing backend keeps every member for as long as it is one
|
||||
expect(livekit.ended).toEqual([]);
|
||||
memberScope.end();
|
||||
expect(livekit.ended).toEqual(["m1"]);
|
||||
scope.end();
|
||||
});
|
||||
|
||||
it("publishes on the backend of the publish type", () => {
|
||||
const scope = new ObservableScope();
|
||||
const livekit = fakeBackend("livekit");
|
||||
const other = fakeBackend("other");
|
||||
const combined = combineBackends(scope, [livekit, other], "other");
|
||||
expect(combined.transportType).toBe("other");
|
||||
expect(() => combineBackends(scope, [livekit, other], "unknown")).toThrow(
|
||||
"No media backend for transport unknown",
|
||||
);
|
||||
scope.end();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { combineLatest, map, merge, NEVER, of, switchMap } from "rxjs";
|
||||
|
||||
import { type RTCMember } from "../api";
|
||||
import { MatrixRTCError } from "../errors";
|
||||
import { type MemberMedia } from "../media-api";
|
||||
import { type ObservableScope } from "../reactive/ObservableScope";
|
||||
import { mapScoped } from "../utils/mapScoped";
|
||||
import { type MediaBackend } from "./api";
|
||||
|
||||
/**
|
||||
* Several backends as one: we publish on the backend of `publishType`, and
|
||||
* receive from every member on a transport any of the backends serves. A
|
||||
* member is handed to the backend of its current transport type. A backend
|
||||
* nobody is on costs nothing: it connects only for the members handed to it.
|
||||
*/
|
||||
export function combineBackends(
|
||||
scope: ObservableScope,
|
||||
all: MediaBackend[],
|
||||
publishType: string,
|
||||
): MediaBackend {
|
||||
const byType = new Map(all.map((b) => [b.transportType, b]));
|
||||
const publish = byType.get(publishType);
|
||||
if (publish === undefined)
|
||||
throw new MatrixRTCError(`No media backend for transport ${publishType}`);
|
||||
|
||||
const mediaFor = (
|
||||
memberScope: ObservableScope,
|
||||
member: RTCMember,
|
||||
): MemberMedia => {
|
||||
// The publishing backend sees every member, whatever its transport, so
|
||||
// that it can tell members from strangers when a backend is asked for our
|
||||
// media by members on other transports. It connects nowhere for them
|
||||
const publishMedia = publish.mediaFor(memberScope, member);
|
||||
const type$ = memberScope.behavior(
|
||||
member.transport$.pipe(map((transport) => transport?.type)),
|
||||
);
|
||||
const media$ = mapScoped(memberScope, type$, (s, type) =>
|
||||
type === publish.transportType
|
||||
? publishMedia
|
||||
: byType.get(type)?.mediaFor(s, member),
|
||||
);
|
||||
return {
|
||||
tracks$: memberScope.behavior(
|
||||
media$.pipe(switchMap((media) => media?.tracks$ ?? of(null))),
|
||||
),
|
||||
encryptionError$: media$.pipe(
|
||||
switchMap((media) => media?.encryptionError$ ?? NEVER),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
transportType: publish.transportType,
|
||||
prepareLocalTransport: async (transport) =>
|
||||
publish.prepareLocalTransport(transport),
|
||||
delegateDelayedLeave: async (delayId) =>
|
||||
publish.delegateDelayedLeave(delayId),
|
||||
local: publish.local,
|
||||
mediaFor,
|
||||
connections$: scope.behavior(
|
||||
combineLatest(all.map((b) => b.connections$)).pipe(
|
||||
map((lists) => lists.flat()),
|
||||
),
|
||||
),
|
||||
setAudioOutputDeviceId: async (deviceId) => {
|
||||
await Promise.all(
|
||||
all.map(async (b) => b.setAudioOutputDeviceId(deviceId)),
|
||||
);
|
||||
},
|
||||
sendData: async (topic, text) => publish.sendData(topic, text),
|
||||
data$: merge(...all.map((b) => b.data$)),
|
||||
};
|
||||
}
|
||||
@@ -101,8 +101,11 @@ export class ConnectionMap {
|
||||
interface Props {
|
||||
scope: ObservableScope;
|
||||
connectionFactory: ConnectionFactory;
|
||||
/** The transport we publish on; the one connection with the publisher role. */
|
||||
localTransport: TransportLocator;
|
||||
/**
|
||||
* The transport we publish on; the one connection with the publisher role.
|
||||
* Null for a backend that only subscribes, while another backend publishes.
|
||||
*/
|
||||
localTransport: TransportLocator | null;
|
||||
/** Every transport some member is on, deduplicated against the local one. */
|
||||
remoteTransports$: Behavior<Epoch<TransportLocator[]>>;
|
||||
logger: Logger;
|
||||
@@ -128,7 +131,11 @@ export function createConnectionMap$({
|
||||
scope.behavior(
|
||||
remoteTransports$.pipe(
|
||||
mapEpoch((remoteTransports) =>
|
||||
removeDuplicateTransports([localTransport, ...remoteTransports]),
|
||||
removeDuplicateTransports(
|
||||
localTransport
|
||||
? [localTransport, ...remoteTransports]
|
||||
: remoteTransports,
|
||||
),
|
||||
),
|
||||
tap((transports) =>
|
||||
logger.debug("localAndRemoteTransports$ = ", transports),
|
||||
@@ -136,7 +143,7 @@ export function createConnectionMap$({
|
||||
),
|
||||
);
|
||||
|
||||
const localTransportKey = keyFor(localTransport);
|
||||
const localTransportKey = localTransport && keyFor(localTransport);
|
||||
|
||||
const connections$ = scope.behavior(
|
||||
localAndRemoteTransports$.pipe(
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
ownMemberMock,
|
||||
testScope,
|
||||
} from "../../utils/test";
|
||||
import { MediaConnectionState } from "../api";
|
||||
import { MediaConnectionState, type MediaBackendContext } from "../api";
|
||||
import * as auth from "./auth";
|
||||
import { type TransportLocator } from "./auth";
|
||||
import { type Connection, type ConnectionState } from "./Connection";
|
||||
@@ -36,6 +36,7 @@ import * as connectionFactory from "./ConnectionFactory";
|
||||
import { toMediaConnectionState } from "./connectionState";
|
||||
import {
|
||||
createLivekitBackend,
|
||||
createLivekitBackends,
|
||||
createTransportRegistry,
|
||||
supportsDelegation,
|
||||
} from "./LivekitBackend";
|
||||
@@ -214,21 +215,19 @@ describe("createLivekitBackend", () => {
|
||||
fetchMock.reset();
|
||||
});
|
||||
|
||||
const context: MediaBackendContext = {
|
||||
roomId: "!room:example.org",
|
||||
slotId: "m.call#ROOM",
|
||||
ownMembershipIdentity: ownMemberMock,
|
||||
publish: [],
|
||||
encryptionSystem: { kind: E2eeType.NONE },
|
||||
mediaKeys$: NEVER,
|
||||
timings: defaultSessionTimings,
|
||||
logger,
|
||||
};
|
||||
|
||||
function backend(): ReturnType<typeof createLivekitBackend> {
|
||||
return createLivekitBackend(
|
||||
testScope(),
|
||||
{
|
||||
roomId: "!room:example.org",
|
||||
slotId: "m.call#ROOM",
|
||||
ownMembershipIdentity: ownMemberMock,
|
||||
publish: [],
|
||||
encryptionSystem: { kind: E2eeType.NONE },
|
||||
mediaKeys$: NEVER,
|
||||
timings: defaultSessionTimings,
|
||||
logger,
|
||||
},
|
||||
{ client },
|
||||
);
|
||||
return createLivekitBackend(testScope(), context, { client });
|
||||
}
|
||||
|
||||
// Delegation goes through whichever service authenticates us as a
|
||||
@@ -475,6 +474,70 @@ describe("createLivekitBackend", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("serves members on either type", async () => {
|
||||
const b = await prepared();
|
||||
const stableTransport: UnstableLivekitTransport = {
|
||||
...otherTransport,
|
||||
type: "m.livekit",
|
||||
};
|
||||
const media = b.mediaFor(testScope(), bob(stableTransport, [legacyId]));
|
||||
await flushPromises();
|
||||
const remote = connections.find(
|
||||
(c) => c.connection.role === "subscriber",
|
||||
);
|
||||
expect(remote?.connection.transport).toEqual(stableTransport);
|
||||
|
||||
remote!.participants$.next([
|
||||
mockRemoteParticipant({
|
||||
identity: legacyId,
|
||||
trackPublications: new Map(),
|
||||
}),
|
||||
]);
|
||||
await flushPromises();
|
||||
expect(media.tracks$.value).toEqual([]);
|
||||
});
|
||||
|
||||
it("splits members by type across the two backends, on shared connections", async () => {
|
||||
const [legacy, stable] = createLivekitBackends(testScope(), context, {
|
||||
client,
|
||||
});
|
||||
expect([legacy.transportType, stable.transportType]).toEqual([
|
||||
"livekit",
|
||||
"m.livekit",
|
||||
]);
|
||||
// Bob and Carl are on the same SFU, each under a type of his own
|
||||
const carlId = "@carl:example.org:DEV000";
|
||||
const bobOnLegacy = bob(otherTransport, [legacyId]);
|
||||
const carlOnStable = {
|
||||
...bob({ ...otherTransport, type: "m.livekit" } as const, [carlId]),
|
||||
rtcBackendIdentity: carlId,
|
||||
memberId: carlId,
|
||||
};
|
||||
const media = {
|
||||
bob: legacy.mediaFor(testScope(), bobOnLegacy),
|
||||
carl: stable.mediaFor(testScope(), carlOnStable),
|
||||
// Each backend leaves the members of the other type alone
|
||||
carlOnLegacy: legacy.mediaFor(testScope(), carlOnStable),
|
||||
bobOnStable: stable.mediaFor(testScope(), bobOnLegacy),
|
||||
};
|
||||
await flushPromises();
|
||||
expect(connections).toHaveLength(1);
|
||||
|
||||
connections[0].participants$.next(
|
||||
[legacyId, carlId].map((identity) =>
|
||||
mockRemoteParticipant({ identity, trackPublications: new Map() }),
|
||||
),
|
||||
);
|
||||
await flushPromises();
|
||||
expect(media.bob.tracks$.value).toEqual([]);
|
||||
expect(media.carl.tracks$.value).toEqual([]);
|
||||
expect(media.carlOnLegacy.tracks$.value).toBeNull();
|
||||
expect(media.bobOnStable.tracks$.value).toBeNull();
|
||||
// The shared connection is reported once, by the `livekit` backend
|
||||
expect(legacy.connections$.value).toHaveLength(1);
|
||||
expect(stable.connections$.value).toEqual([]);
|
||||
});
|
||||
|
||||
it("has no media for a member without a transport, and opens no connection", async () => {
|
||||
const b = await prepared();
|
||||
const media = b.mediaFor(testScope(), {
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
filter,
|
||||
firstValueFrom,
|
||||
map,
|
||||
NEVER,
|
||||
type Observable,
|
||||
of,
|
||||
switchMap,
|
||||
@@ -27,7 +28,7 @@ import { type ResolvedTransport, type RTCMember } from "../../api";
|
||||
import { type MediaQuality } from "../../config";
|
||||
import { MatrixRTCError } from "../../errors";
|
||||
import { type MemberMedia } from "../../media-api";
|
||||
import { type Behavior } from "../../reactive/Behavior";
|
||||
import { type Behavior, constant } from "../../reactive/Behavior";
|
||||
import {
|
||||
Epoch,
|
||||
type ObservableScope,
|
||||
@@ -81,9 +82,44 @@ export interface LivekitBackendOptions {
|
||||
/**
|
||||
* MatrixRTC media over LiveKit SFUs: one room per transport, the local one
|
||||
* published on, every remote one subscribed to, each authenticated for its
|
||||
* role through the homeserver or the transport's JWT service.
|
||||
* role through the homeserver or the transport's JWT service. Serves members
|
||||
* on either LiveKit transport type, `livekit` and `m.livekit`.
|
||||
*/
|
||||
export function createLivekitBackend(
|
||||
scope: ObservableScope,
|
||||
context: MediaBackendContext,
|
||||
options: LivekitBackendOptions,
|
||||
): MediaBackend {
|
||||
return livekitBackend(scope, context, options).backend;
|
||||
}
|
||||
|
||||
/**
|
||||
* The LiveKit backend once per transport type, for a combination of backends:
|
||||
* `livekit` of the earlier versions of MSC4195 and `m.livekit` of the merged
|
||||
* one. Each takes the members on its own type, and both share the connections,
|
||||
* so members of either type on one SFU share a connection. The `livekit` one
|
||||
* reports the shared connections and data.
|
||||
*/
|
||||
export function createLivekitBackends(
|
||||
scope: ObservableScope,
|
||||
context: MediaBackendContext,
|
||||
options: LivekitBackendOptions,
|
||||
): MediaBackend[] {
|
||||
const { backend, mediaForType } = livekitBackend(scope, context, options);
|
||||
return [
|
||||
{ ...backend, mediaFor: mediaForType("livekit") },
|
||||
{
|
||||
...backend,
|
||||
transportType: "m.livekit",
|
||||
mediaFor: mediaForType("m.livekit"),
|
||||
connections$: constant([]),
|
||||
setAudioOutputDeviceId: async () => Promise.resolve(),
|
||||
data$: NEVER,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function livekitBackend(
|
||||
scope: ObservableScope,
|
||||
{
|
||||
roomId,
|
||||
@@ -96,7 +132,11 @@ export function createLivekitBackend(
|
||||
logger: parentLogger,
|
||||
}: MediaBackendContext,
|
||||
{ client, mediaQuality }: LivekitBackendOptions,
|
||||
): MediaBackend {
|
||||
): {
|
||||
backend: MediaBackend;
|
||||
/** `mediaFor` for the members on transports of `type`, or of either type. */
|
||||
mediaForType: (type?: string) => MediaBackend["mediaFor"];
|
||||
} {
|
||||
const logger = parentLogger.getChild("[LivekitBackend]");
|
||||
const keyProvider = createKeyProvider(encryptionSystem, mediaKeys$, logger);
|
||||
const transports = createTransportRegistry(scope);
|
||||
@@ -115,9 +155,14 @@ export function createLivekitBackend(
|
||||
);
|
||||
|
||||
// A new map, in a scope of its own, whenever the local transport changes:
|
||||
// the publisher role sits on one connection, and which one is fixed per map
|
||||
// the publisher role sits on one connection, and which one is fixed per map.
|
||||
// Without a local transport the map only subscribes, for when another
|
||||
// backend publishes; boxed so that `mapScoped` builds a map for null too
|
||||
const boxedLocalTransport$ = scope.behavior(
|
||||
localTransport$.pipe(map((localTransport) => ({ localTransport }))),
|
||||
);
|
||||
const connectionMap$ = scope.behavior<Epoch<ConnectionMap>>(
|
||||
mapScoped(scope, localTransport$, (mapScope, localTransport) =>
|
||||
mapScoped(scope, boxedLocalTransport$, (mapScope, { localTransport }) =>
|
||||
createConnectionMap$({
|
||||
scope: mapScope,
|
||||
connectionFactory,
|
||||
@@ -139,12 +184,15 @@ export function createLivekitBackend(
|
||||
logger,
|
||||
});
|
||||
|
||||
// Probed once: the answer cannot change within a session
|
||||
const homeserverSupportsDelegation = supportsDelegation(
|
||||
`${client.baseUrl}/_matrix/client/unstable/io.element.msc4195/rtc/livekit/delegate_delayed_leave`,
|
||||
"homeserver",
|
||||
logger,
|
||||
);
|
||||
// Probed once, when first needed: the answer cannot change within a
|
||||
// session, and a backend that only subscribes never needs it
|
||||
let homeserverProbe: Promise<boolean> | undefined;
|
||||
const homeserverSupportsDelegation = async (): Promise<boolean> =>
|
||||
(homeserverProbe ??= supportsDelegation(
|
||||
`${client.baseUrl}/_matrix/client/unstable/io.element.msc4195/rtc/livekit/delegate_delayed_leave`,
|
||||
"homeserver",
|
||||
logger,
|
||||
));
|
||||
|
||||
const ownServerName = serverNameOf(ownMembershipIdentity.userId);
|
||||
|
||||
@@ -176,7 +224,7 @@ export function createLivekitBackend(
|
||||
`transport ${flow.serviceUrl}`,
|
||||
logger,
|
||||
)
|
||||
: homeserverSupportsDelegation;
|
||||
: homeserverSupportsDelegation();
|
||||
}
|
||||
localTransport$.next(locator);
|
||||
// The membership is only sent once the publisher connection holds its
|
||||
@@ -209,13 +257,15 @@ export function createLivekitBackend(
|
||||
const mediaFor = (
|
||||
memberScope: ObservableScope,
|
||||
member: RTCMember,
|
||||
type?: string,
|
||||
): MemberMedia => {
|
||||
const { backendIdentities } = member;
|
||||
const locator$ = memberScope.behavior(
|
||||
member.transport$.pipe(
|
||||
map((transport): TransportLocator | undefined => {
|
||||
const raw = transport?.raw;
|
||||
return isUnstableLivekitTransport(raw)
|
||||
return isUnstableLivekitTransport(raw) &&
|
||||
(type === undefined || raw.type === type)
|
||||
? {
|
||||
transport: raw,
|
||||
serverName: serverNameOfTransport(raw, member.userId),
|
||||
@@ -253,6 +303,10 @@ export function createLivekitBackend(
|
||||
});
|
||||
return createLivekitMemberMedia(memberScope, source$, encryptionSystem);
|
||||
};
|
||||
const mediaForType =
|
||||
(type?: string): MediaBackend["mediaFor"] =>
|
||||
(memberScope, member) =>
|
||||
mediaFor(memberScope, member, type);
|
||||
|
||||
const connections$ = scope.behavior<BackendConnection[]>(
|
||||
connectionMap$.pipe(
|
||||
@@ -271,15 +325,18 @@ export function createLivekitBackend(
|
||||
});
|
||||
|
||||
return {
|
||||
transportType: "livekit",
|
||||
prepareLocalTransport,
|
||||
delegateDelayedLeave: delegateLeave,
|
||||
local: localMedia_,
|
||||
mediaFor,
|
||||
connections$,
|
||||
setAudioOutputDeviceId: localMedia_.setAudioOutputDeviceId,
|
||||
sendData,
|
||||
data$,
|
||||
backend: {
|
||||
transportType: "livekit",
|
||||
prepareLocalTransport,
|
||||
delegateDelayedLeave: delegateLeave,
|
||||
local: localMedia_,
|
||||
mediaFor: mediaForType(),
|
||||
connections$,
|
||||
setAudioOutputDeviceId: localMedia_.setAudioOutputDeviceId,
|
||||
sendData,
|
||||
data$,
|
||||
},
|
||||
mediaForType,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||
|
||||
import { serverNameOfTransport } from "../../utils/serverName";
|
||||
import { isCsApiUnavailable } from "./auth";
|
||||
import * as csApi from "./auth/csApi";
|
||||
import { decodeSFUJWTPayload } from "./auth/types";
|
||||
@@ -59,7 +60,7 @@ export async function findRemotePublishLivekitTransport({
|
||||
if (!isUnstableLivekitTransport(transport) || !("url" in transport))
|
||||
continue;
|
||||
const { url } = transport;
|
||||
const serverName = membership.getTransportServerName(transport);
|
||||
const serverName = serverNameOfTransport(transport, membership.userId);
|
||||
const key = JSON.stringify([url, serverName]);
|
||||
if (tried.has(key)) continue;
|
||||
tried.add(key);
|
||||
|
||||
Reference in New Issue
Block a user