mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
Merge branch 'livekit' into more-more-performance
This commit is contained in:
34
README.md
34
README.md
@@ -115,30 +115,33 @@ For more details on the packages, see the
|
|||||||
For operating and deploying Element Call on your own server, refer to the
|
For operating and deploying Element Call on your own server, refer to the
|
||||||
[**Self-Hosting Guide**](./docs/self_hosting.md).
|
[**Self-Hosting Guide**](./docs/self_hosting.md).
|
||||||
|
|
||||||
## 🧭 MatrixRTC Backend Discovery and Selection
|
## MatrixRTC Transports
|
||||||
|
|
||||||
For proper Element Call operation each site deployment needs a MatrixRTC backend
|
For proper operation of Element Call, each deployment needs to set up a
|
||||||
setup as outlined in the [Self-Hosting Guide](./docs/self_hosting.md). A typical
|
MatrixRTC transport in the form of a LiveKit server as outlined in the
|
||||||
federated site deployment for three different sites A, B and C is depicted below.
|
[Self-Hosting Guide](./docs/self_hosting.md). A typical federated site
|
||||||
|
deployment for three different sites A, B and C is depicted below.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./docs/Federated_Setup.drawio.png" alt="Element Call federated setup">
|
<img src="./docs/Federated_Setup.drawio.png" alt="Element Call federated setup">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### Backend Discovery
|
### Transport Discovery
|
||||||
|
|
||||||
The MatrixRTC backend (according to
|
Element Call discovers the available MatrixRTC transports (as defined by
|
||||||
[MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)) is
|
[MSC4519](https://github.com/matrix-org/matrix-spec-proposals/pull/4519)) by
|
||||||
announced by the Matrix site's `.well-known/matrix/client` file and discovered
|
hitting the `GET /_matrix/client/unstable/org.matrix.msc4143/rtc/transports`
|
||||||
via the `org.matrix.msc4143.rtc_foci` key, e.g.:
|
endpoint of the Client-Server API. An example response:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
{
|
||||||
|
"rtc_transports": [
|
||||||
{
|
{
|
||||||
"type": "livekit",
|
"type": "livekit",
|
||||||
"livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt"
|
"livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt"
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
where the format for MatrixRTC using LiveKit backend is defined in
|
where the format for MatrixRTC using LiveKit backend is defined in
|
||||||
@@ -149,7 +152,7 @@ via `livekit_service_url`.
|
|||||||
|
|
||||||
### Backend Selection
|
### Backend Selection
|
||||||
|
|
||||||
- Each call participant proposes their discovered MatrixRTC backend from
|
- Each call participant proposes their discovered MatrixRTC transport from
|
||||||
`org.matrix.msc4143.rtc_foci` in their `org.matrix.msc3401.call.member` state event.
|
`org.matrix.msc4143.rtc_foci` in their `org.matrix.msc3401.call.member` state event.
|
||||||
- For the **LiveKit** MatrixRTC backend
|
- For the **LiveKit** MatrixRTC backend
|
||||||
([MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)),
|
([MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)),
|
||||||
@@ -244,10 +247,9 @@ pnpm backend
|
|||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> To ensure your local development frontend functions properly, you’ll need to
|
> To ensure your local development frontend functions properly, you’ll need to
|
||||||
> add certificate exceptions in your browser for `https://localhost:3000`,
|
> add certificate exceptions in your browser for `https://localhost:3000` and
|
||||||
> `https://matrix-rtc.m.localhost/livekit/jwt/healthz` and
|
> `https://matrix-rtc.m.localhost/livekit/jwt/healthz`. This can be done either
|
||||||
> `https://synapse.m.localhost/.well-known/matrix/client`. This can be either
|
> by adding the minimum localhost CA
|
||||||
> done by adding the minimum localhost CA
|
|
||||||
> ([./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt)) to your web
|
> ([./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt)) to your web
|
||||||
> browser's trusted certificates or by simply copying and pasting each URL into
|
> 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.
|
> your browser’s address bar and follow the prompts to add the exception.
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ experimental_features:
|
|||||||
msc4222_enabled: true
|
msc4222_enabled: true
|
||||||
# sticky events for MatrixRTC user state
|
# sticky events for MatrixRTC user state
|
||||||
msc4354_enabled: true
|
msc4354_enabled: true
|
||||||
|
# MatrixRTC
|
||||||
|
msc4143_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140. Must be a positive value if set. Defaults to no
|
# per MSC4140. Must be a positive value if set. Defaults to no
|
||||||
@@ -54,7 +56,6 @@ enable_registration_without_verification: true
|
|||||||
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
|
||||||
|
|
||||||
# Ratelimiting settings for client actions (registration, login, messaging).
|
# Ratelimiting settings for client actions (registration, login, messaging).
|
||||||
#
|
#
|
||||||
@@ -67,3 +68,8 @@ rc_message:
|
|||||||
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
||||||
per_second: 0.5
|
per_second: 0.5
|
||||||
burst_count: 30
|
burst_count: 30
|
||||||
|
|
||||||
|
matrix_rtc:
|
||||||
|
transports:
|
||||||
|
- type: livekit
|
||||||
|
livekit_service_url: https://matrix-rtc.othersite.m.localhost/livekit/jwt
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ experimental_features:
|
|||||||
msc4222_enabled: true
|
msc4222_enabled: true
|
||||||
# sticky events for MatrixRTC user state
|
# sticky events for MatrixRTC user state
|
||||||
msc4354_enabled: true
|
msc4354_enabled: true
|
||||||
|
# MatrixRTC
|
||||||
|
msc4143_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140. Must be a positive value if set. Defaults to no
|
# per MSC4140. Must be a positive value if set. Defaults to no
|
||||||
@@ -54,7 +56,6 @@ enable_registration_without_verification: true
|
|||||||
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
|
||||||
|
|
||||||
# Ratelimiting settings for client actions (registration, login, messaging).
|
# Ratelimiting settings for client actions (registration, login, messaging).
|
||||||
#
|
#
|
||||||
@@ -67,3 +68,8 @@ rc_message:
|
|||||||
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
# Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s
|
||||||
per_second: 0.5
|
per_second: 0.5
|
||||||
burst_count: 30
|
burst_count: 30
|
||||||
|
|
||||||
|
matrix_rtc:
|
||||||
|
transports:
|
||||||
|
- type: livekit
|
||||||
|
livekit_service_url: https://matrix-rtc.m.localhost/livekit/jwt
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Synapse reverse proxy including .well-known/matrix/client
|
# Synapse reverse proxy
|
||||||
# domain synapse.m.localhost
|
# domain synapse.m.localhost
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
@@ -11,18 +11,6 @@ server {
|
|||||||
ssl_certificate /root/ssl/cert.pem;
|
ssl_certificate /root/ssl/cert.pem;
|
||||||
ssl_certificate_key /root/ssl/key.pem;
|
ssl_certificate_key /root/ssl/key.pem;
|
||||||
|
|
||||||
# well-known config adding rtc_foci backend
|
|
||||||
# Note well-known is currently not effective due to:
|
|
||||||
# https://spec.matrix.org/v1.12/client-server-api/#well-known-uri the spec
|
|
||||||
# says it must be at https://$server_name/... (implied port 443) Hence, we
|
|
||||||
# currently rely for local development environment on deprecated config.json
|
|
||||||
# setting for livekit_service_url
|
|
||||||
location /.well-known/matrix/client {
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
return 200 '{"m.homeserver": {"base_url": "https://synapse.m.localhost"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://matrix-rtc.m.localhost/livekit/jwt"}]}';
|
|
||||||
default_type application/json;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Reverse proxy for Matrix Synapse Homeserver
|
# Reverse proxy for Matrix Synapse Homeserver
|
||||||
# This is also required for development environment.
|
# This is also required for development environment.
|
||||||
# Reason: the lk-jwt-service uses the federation API for the openid token
|
# Reason: the lk-jwt-service uses the federation API for the openid token
|
||||||
@@ -44,7 +32,7 @@ server {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Synapse reverse proxy including .well-known/matrix/client
|
# Synapse reverse proxy
|
||||||
# domain synapse.othersite.m.localhost
|
# domain synapse.othersite.m.localhost
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
@@ -57,18 +45,6 @@ server {
|
|||||||
ssl_certificate /root/ssl/cert.pem;
|
ssl_certificate /root/ssl/cert.pem;
|
||||||
ssl_certificate_key /root/ssl/key.pem;
|
ssl_certificate_key /root/ssl/key.pem;
|
||||||
|
|
||||||
# well-known config adding rtc_foci backend
|
|
||||||
# Note well-known is currently not effective due to:
|
|
||||||
# https://spec.matrix.org/v1.12/client-server-api/#well-known-uri the spec
|
|
||||||
# says it must be at https://$server_name/... (implied port 443) Hence, we
|
|
||||||
# currently rely for local development environment on deprecated config.json
|
|
||||||
# setting for livekit_service_url
|
|
||||||
location /.well-known/matrix/client {
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
return 200 '{"m.homeserver": {"base_url": "https://synapse.othersite.m.localhost"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://matrix-rtc.othersite.m.localhost/livekit/jwt"}]}';
|
|
||||||
default_type application/json;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Reverse proxy for Matrix Synapse Homeserver
|
# Reverse proxy for Matrix Synapse Homeserver
|
||||||
# This is also required for development environment.
|
# This is also required for development environment.
|
||||||
# Reason: the lk-jwt-service uses the federation API for the openid token
|
# Reason: the lk-jwt-service uses the federation API for the openid token
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ experimental_features:
|
|||||||
msc4222_enabled: true
|
msc4222_enabled: true
|
||||||
# sticky events for MatrixRTC user state
|
# sticky events for MatrixRTC user state
|
||||||
msc4354_enabled: true
|
msc4354_enabled: true
|
||||||
|
# MatrixRTC
|
||||||
|
msc4143_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140. Must be a positive value if set. Defaults to no
|
# per MSC4140. Must be a positive value if set. Defaults to no
|
||||||
@@ -54,7 +56,6 @@ enable_registration_without_verification: true
|
|||||||
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
|
||||||
|
|
||||||
# Ratelimiting settings for client actions (registration, login, messaging).
|
# Ratelimiting settings for client actions (registration, login, messaging).
|
||||||
#
|
#
|
||||||
@@ -84,3 +85,8 @@ rc_login:
|
|||||||
rc_registration:
|
rc_registration:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
|
|
||||||
|
matrix_rtc:
|
||||||
|
transports:
|
||||||
|
- type: livekit
|
||||||
|
livekit_service_url: https://matrix-rtc.othersite.m.localhost/livekit/jwt
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ experimental_features:
|
|||||||
msc4222_enabled: true
|
msc4222_enabled: true
|
||||||
# sticky events for MatrixRTC user state
|
# sticky events for MatrixRTC user state
|
||||||
msc4354_enabled: true
|
msc4354_enabled: true
|
||||||
|
# MatrixRTC
|
||||||
|
msc4143_enabled: true
|
||||||
|
|
||||||
# The maximum allowed duration by which sent events can be delayed, as
|
# The maximum allowed duration by which sent events can be delayed, as
|
||||||
# per MSC4140. Must be a positive value if set. Defaults to no
|
# per MSC4140. Must be a positive value if set. Defaults to no
|
||||||
@@ -54,7 +56,6 @@ enable_registration_without_verification: true
|
|||||||
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
|
||||||
|
|
||||||
# Ratelimiting settings for client actions (registration, login, messaging).
|
# Ratelimiting settings for client actions (registration, login, messaging).
|
||||||
#
|
#
|
||||||
@@ -84,3 +85,8 @@ rc_login:
|
|||||||
rc_registration:
|
rc_registration:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
|
|
||||||
|
matrix_rtc:
|
||||||
|
transports:
|
||||||
|
- type: livekit
|
||||||
|
livekit_service_url: https://matrix-rtc.m.localhost/livekit/jwt
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ services:
|
|||||||
- ./backend/playwright_homeserver-othersite.yaml:/data/cfg/homeserver.yaml:Z
|
- ./backend/playwright_homeserver-othersite.yaml:/data/cfg/homeserver.yaml:Z
|
||||||
element-web:
|
element-web:
|
||||||
# Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates.
|
# Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates.
|
||||||
image: ghcr.io/element-hq/element-web:develop@sha256:b4e2d2d0919f26362755d4df29dbc7a55075880fdef329f53a7937241712c526
|
image: ghcr.io/element-hq/element-web:develop@sha256:af44e1e18bdd363a3332a577c3af66a9dde4ad352508a42ee5012c4a9528b30d
|
||||||
element-web-1:
|
element-web-1:
|
||||||
# Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates.
|
# Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates.
|
||||||
image: ghcr.io/element-hq/element-web:develop@sha256:b4e2d2d0919f26362755d4df29dbc7a55075880fdef329f53a7937241712c526
|
image: ghcr.io/element-hq/element-web:develop@sha256:af44e1e18bdd363a3332a577c3af66a9dde4ad352508a42ee5012c4a9528b30d
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ As a prerequisite for the
|
|||||||
make sure that your Synapse server has either a `federation` or `openid`
|
make sure that your Synapse server has either a `federation` or `openid`
|
||||||
[listener configured](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#listeners).
|
[listener configured](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#listeners).
|
||||||
|
|
||||||
### MatrixRTC Backend
|
### LiveKit backend
|
||||||
|
|
||||||
In order to **guarantee smooth operation** of Element Call, a MatrixRTC backend is
|
In order to **guarantee smooth operation** of Element Call, a dedicated LiveKit
|
||||||
required for each site deployment.
|
backend is required for each site deployment.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ Using Haproxy, you can achieve this by:
|
|||||||
use_backend mxrtc_auth_backend if is_mxrtc_auth matrixrtc_domain
|
use_backend mxrtc_auth_backend if is_mxrtc_auth matrixrtc_domain
|
||||||
|
|
||||||
# Backend
|
# Backend
|
||||||
## MatrixRTC backend
|
## LiveKit backend
|
||||||
backend sfu_backend
|
backend sfu_backend
|
||||||
server livekit 127.0.0.1:7880
|
server livekit 127.0.0.1:7880
|
||||||
http-request set-path %[path,regsub(^/livekit/sfu/,/)]
|
http-request set-path %[path,regsub(^/livekit/sfu/,/)]
|
||||||
@@ -187,43 +187,22 @@ backend mxrtc_auth_backend
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### MatrixRTC backend announcement
|
#### MatrixRTC transport announcement
|
||||||
|
|
||||||
> [!IMPORTANT]
|
Enable the unstable feature flag `msc4143_enabled`, and update the
|
||||||
> As defined in
|
[`matrix_rtc` section](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#matrix_rtc)
|
||||||
> [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143),
|
of your Synapse config file:
|
||||||
> the MatrixRTC backend(s) must be announced to the client via your **Matrix site's
|
|
||||||
> `.well-known/matrix/client`** file (e.g.
|
|
||||||
> `example.com/.well-known/matrix/client` matching the site deployment example
|
|
||||||
> from above). The configuration is a list of Foci configs:
|
|
||||||
|
|
||||||
```json
|
```yaml
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
matrix_rtc:
|
||||||
{
|
transports:
|
||||||
"type": "livekit",
|
- type: livekit
|
||||||
"livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt"
|
livekit_service_url: https://matrix-rtc.example.com/livekit/jwt
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "livekit",
|
|
||||||
"livekit_service_url": "https://matrix-rtc-2.example.com/livekit/jwt"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure this file is served with the correct MIME type (`application/json`).
|
The transport you specify will be made available to clients over the
|
||||||
Additionally, ensure the appropriate CORS headers are set to allow web clients
|
`/_matrix/client/unstable/org.matrix.msc4143/rtc/transports` endpoint as defined
|
||||||
to access it across origins. For more details, refer to the
|
in [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143).
|
||||||
[Matrix Client-Server API: 2. Web Browser Clients](https://spec.matrix.org/latest/client-server-api/#web-browser-clients).
|
|
||||||
|
|
||||||
```
|
|
||||||
Access-Control-Allow-Origin: *
|
|
||||||
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
|
|
||||||
Access-Control-Allow-Headers: X-Requested-With, Content-Type, Authorization
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Most `org.matrix.msc4143.rtc_foci` configurations will only have one entry in
|
|
||||||
> the array.
|
|
||||||
|
|
||||||
## Building Element Call
|
## Building Element Call
|
||||||
|
|
||||||
@@ -278,7 +257,7 @@ runtime. Documentation and default values for `public/config.json` can be found
|
|||||||
in [ConfigOptions.ts](../src/config/ConfigOptions.ts).
|
in [ConfigOptions.ts](../src/config/ConfigOptions.ts).
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> Please note configuring MatrixRTC backend via `config.json` of
|
> Please note configuring LiveKit backend via `config.json` of
|
||||||
> Element Call is only available for developing and debug purposes. Relying on
|
> Element Call is only available for developing and debug purposes. Relying on
|
||||||
> it might break Element Call going forward!
|
> it might break Element Call going forward!
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
"crypto_version": "Crypto version: {{version}}",
|
"crypto_version": "Crypto version: {{version}}",
|
||||||
"custom_livekit_url": {
|
"custom_livekit_url": {
|
||||||
"current_url": "Currently set to: ",
|
"current_url": "Currently set to: ",
|
||||||
"from_config": "Currently, no overwrite is set. Url from well-known or config is used.",
|
"from_config": "Currently, no overwrite is set. Url from config is used.",
|
||||||
"label": "Custom Livekit-url",
|
"label": "Custom Livekit-url",
|
||||||
"reset": "Reset overwrite",
|
"reset": "Reset overwrite",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -196,13 +196,23 @@
|
|||||||
"room_auth_view_ssla_caption": "By clicking \"Join call now\", you agree to our <2>Software and Services License Agreement (SSLA)</2>",
|
"room_auth_view_ssla_caption": "By clicking \"Join call now\", you agree to our <2>Software and Services License Agreement (SSLA)</2>",
|
||||||
"screenshare_button_label": "Share screen",
|
"screenshare_button_label": "Share screen",
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"advanced_camera_description": "Configure resolution, framerate, bitrate, and codec for camera video. Changes apply on next call join.",
|
||||||
|
"advanced_camera_label": "Advanced camera settings",
|
||||||
|
"advanced_screen_share_description": "Configure resolution, framerate, bitrate, and codec for screen sharing",
|
||||||
|
"advanced_screen_share_label": "Advanced screen share settings",
|
||||||
|
"audio_processing_description": "Changes apply on next call join.",
|
||||||
|
"audio_processing_header": "Audio processing",
|
||||||
"audio_tab": {
|
"audio_tab": {
|
||||||
"effect_volume_description": "Adjust the volume at which reactions and hand raised effects play.",
|
"effect_volume_description": "Adjust the volume at which reactions and hand raised effects play.",
|
||||||
"effect_volume_label": "Sound effect volume"
|
"effect_volume_label": "Sound effect volume"
|
||||||
},
|
},
|
||||||
|
"auto_gain_control_label": "Automatic gain control",
|
||||||
"background_blur_header": "Background",
|
"background_blur_header": "Background",
|
||||||
"background_blur_label": "Blur the background of the video",
|
"background_blur_label": "Blur the background of the video",
|
||||||
|
"bitrate_label": "Bitrate",
|
||||||
"blur_not_supported_by_browser": "(Background blur is not supported by this device.)",
|
"blur_not_supported_by_browser": "(Background blur is not supported by this device.)",
|
||||||
|
"camera_header": "Camera quality",
|
||||||
|
"codec_label": "Codec",
|
||||||
"developer_tab_title": "Developer",
|
"developer_tab_title": "Developer",
|
||||||
"devices": {
|
"devices": {
|
||||||
"activating": "Activating…",
|
"activating": "Activating…",
|
||||||
@@ -218,12 +228,15 @@
|
|||||||
"speaker": "Speaker",
|
"speaker": "Speaker",
|
||||||
"speaker_numbered": "Speaker {{n}}"
|
"speaker_numbered": "Speaker {{n}}"
|
||||||
},
|
},
|
||||||
|
"echo_cancellation_label": "Echo cancellation",
|
||||||
"feedback_tab_body": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.",
|
"feedback_tab_body": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.",
|
||||||
"feedback_tab_description_label": "Your feedback",
|
"feedback_tab_description_label": "Your feedback",
|
||||||
"feedback_tab_h4": "Submit feedback",
|
"feedback_tab_h4": "Submit feedback",
|
||||||
"feedback_tab_send_logs_label": "Include debug logs",
|
"feedback_tab_send_logs_label": "Include debug logs",
|
||||||
"feedback_tab_thank_you": "Thanks, we received your feedback!",
|
"feedback_tab_thank_you": "Thanks, we received your feedback!",
|
||||||
"feedback_tab_title": "Feedback",
|
"feedback_tab_title": "Feedback",
|
||||||
|
"framerate_label": "Framerate",
|
||||||
|
"noise_suppression_label": "Noise suppression",
|
||||||
"opt_in_description": "<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.",
|
"opt_in_description": "<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.",
|
||||||
"preferences_tab": {
|
"preferences_tab": {
|
||||||
"developer_mode_label": "Developer mode",
|
"developer_mode_label": "Developer mode",
|
||||||
@@ -235,7 +248,9 @@
|
|||||||
"reactions_show_label": "Show reactions",
|
"reactions_show_label": "Show reactions",
|
||||||
"show_hand_raised_timer_description": "Show a timer when a participant raises their hand",
|
"show_hand_raised_timer_description": "Show a timer when a participant raises their hand",
|
||||||
"show_hand_raised_timer_label": "Show hand raise duration"
|
"show_hand_raised_timer_label": "Show hand raise duration"
|
||||||
}
|
},
|
||||||
|
"resolution_label": "Resolution",
|
||||||
|
"screen_share_header": "Screen sharing"
|
||||||
},
|
},
|
||||||
"star_rating_input_label_one": "{{count}} star",
|
"star_rating_input_label_one": "{{count}} star",
|
||||||
"star_rating_input_label_other": "{{count}} stars",
|
"star_rating_input_label_other": "{{count}} stars",
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
"@typescript-eslint/utils": "^8.61.0",
|
"@typescript-eslint/utils": "^8.61.0",
|
||||||
"@use-gesture/react": "^10.2.11",
|
"@use-gesture/react": "^10.2.11",
|
||||||
"@vector-im/compound-design-tokens": "^10.0.0",
|
"@vector-im/compound-design-tokens": "^10.0.0",
|
||||||
"@vector-im/compound-web": "^9.3.0",
|
"@vector-im/compound-web": "^10.0.0",
|
||||||
"@vitejs/plugin-react": "^6.0.2",
|
"@vitejs/plugin-react": "^6.0.2",
|
||||||
"@vitest/browser-playwright": "^4.1.5",
|
"@vitest/browser-playwright": "^4.1.5",
|
||||||
"@vitest/coverage-v8": "^4.0.18",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"loglevel": "^1.9.1",
|
"loglevel": "^1.9.1",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-widget-api": "^1.16.1",
|
"matrix-widget-api": "^1.18.0",
|
||||||
"node-stdlib-browser": "^1.3.1",
|
"node-stdlib-browser": "^1.3.1",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"observable-hooks": "^4.2.3",
|
"observable-hooks": "^4.2.3",
|
||||||
@@ -131,5 +131,5 @@
|
|||||||
"vitest": "^4.1.5",
|
"vitest": "^4.1.5",
|
||||||
"vitest-axe": "^1.0.0-pre.3"
|
"vitest-axe": "^1.0.0-pre.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.6.0+sha512.9a36518224080c6fe5165afdcfe79bfa118c29be703f3f462b1e32efe1e98e47e8750b148e08286250aad4113cc7993ca413c4e2cd447752708c2ee5751bc95f"
|
"packageManager": "pnpm@11.21.0+sha512.521705bce689924eac72f5a3587122f362689ef6571e55ba80076fd637c11132ecffada26fad4ea79c485bfddbfd3d5a2a5b05805a77e893de71ec8a6cca3bb1"
|
||||||
}
|
}
|
||||||
|
|||||||
368
pnpm-lock.yaml
generated
368
pnpm-lock.yaml
generated
@@ -39,10 +39,10 @@ importers:
|
|||||||
version: 11.7.12
|
version: 11.7.12
|
||||||
'@livekit/components-core':
|
'@livekit/components-core':
|
||||||
specifier: ^0.12.0
|
specifier: ^0.12.0
|
||||||
version: 0.12.14(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1)
|
version: 0.12.15(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1)
|
||||||
'@livekit/components-react':
|
'@livekit/components-react':
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.9.23(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1)
|
version: 2.9.24(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1)
|
||||||
'@livekit/track-processors':
|
'@livekit/track-processors':
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.2(@types/dom-mediacapture-transform@0.1.11)(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))
|
version: 0.7.2(@types/dom-mediacapture-transform@0.1.11)(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))
|
||||||
@@ -66,13 +66,13 @@ importers:
|
|||||||
version: 10.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
version: 10.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
'@rolldown/plugin-babel':
|
'@rolldown/plugin-babel':
|
||||||
specifier: ^0.2.3
|
specifier: ^0.2.3
|
||||||
version: 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
version: 0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
'@sentry/react':
|
'@sentry/react':
|
||||||
specifier: ^8.0.0
|
specifier: ^8.0.0
|
||||||
version: 8.55.2(react@19.2.8)
|
version: 8.55.2(react@19.2.8)
|
||||||
'@sentry/vite-plugin':
|
'@sentry/vite-plugin':
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.6.1
|
version: 3.6.1(supports-color@7.2.0)
|
||||||
'@storybook/addon-docs':
|
'@storybook/addon-docs':
|
||||||
specifier: ^10.3.6
|
specifier: ^10.3.6
|
||||||
version: 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
version: 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
@@ -81,7 +81,7 @@ importers:
|
|||||||
version: 10.5.5(@vitest/browser-playwright@4.1.10)(@vitest/browser@4.1.10)(@vitest/runner@4.1.10)(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(vitest@4.1.10)
|
version: 10.5.5(@vitest/browser-playwright@4.1.10)(@vitest/browser@4.1.10)(@vitest/runner@4.1.10)(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(vitest@4.1.10)
|
||||||
'@storybook/react-vite':
|
'@storybook/react-vite':
|
||||||
specifier: ^10.3.6
|
specifier: ^10.3.6
|
||||||
version: 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
version: 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
'@testing-library/dom':
|
'@testing-library/dom':
|
||||||
specifier: ^10.1.0
|
specifier: ^10.1.0
|
||||||
version: 10.4.1
|
version: 10.4.1
|
||||||
@@ -126,7 +126,7 @@ importers:
|
|||||||
version: 2.15.0
|
version: 2.15.0
|
||||||
'@typescript-eslint/utils':
|
'@typescript-eslint/utils':
|
||||||
specifier: ^8.61.0
|
specifier: ^8.61.0
|
||||||
version: 8.65.0(eslint@8.57.1)(typescript@5.9.3)
|
version: 8.65.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||||
'@use-gesture/react':
|
'@use-gesture/react':
|
||||||
specifier: ^10.2.11
|
specifier: ^10.2.11
|
||||||
version: 10.3.1(react@19.2.8)
|
version: 10.3.1(react@19.2.8)
|
||||||
@@ -134,11 +134,11 @@ importers:
|
|||||||
specifier: ^10.0.0
|
specifier: ^10.0.0
|
||||||
version: 10.2.3(@types/react@19.2.17)(react@19.2.8)
|
version: 10.2.3(@types/react@19.2.17)(react@19.2.8)
|
||||||
'@vector-im/compound-web':
|
'@vector-im/compound-web':
|
||||||
specifier: ^9.3.0
|
specifier: ^10.0.0
|
||||||
version: 9.9.0(@fontsource/inconsolata@5.3.0)(@fontsource/inter@5.3.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@vector-im/compound-design-tokens@10.2.3(@types/react@19.2.17)(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
version: 10.0.1(@fontsource/inconsolata@5.3.0)(@fontsource/inter@5.3.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@vector-im/compound-design-tokens@10.2.3(@types/react@19.2.17)(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.5(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
version: 6.0.5(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
'@vitest/browser-playwright':
|
'@vitest/browser-playwright':
|
||||||
specifier: ^4.1.5
|
specifier: ^4.1.5
|
||||||
version: 4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
version: 4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
||||||
@@ -162,10 +162,10 @@ importers:
|
|||||||
version: link:eslint
|
version: link:eslint
|
||||||
eslint-plugin-storybook:
|
eslint-plugin-storybook:
|
||||||
specifier: ^10.3.6
|
specifier: ^10.3.6
|
||||||
version: 10.5.5(eslint@8.57.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3)
|
version: 10.5.5(eslint@8.57.1(supports-color@7.2.0))(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3)
|
||||||
fetch-mock:
|
fetch-mock:
|
||||||
specifier: 11.1.5
|
specifier: 11.1.5
|
||||||
version: 11.1.5
|
version: 11.1.5(node-fetch@2.7.0)
|
||||||
i18next:
|
i18next:
|
||||||
specifier: ^26.0.0
|
specifier: ^26.0.0
|
||||||
version: 26.3.6(typescript@5.9.3)
|
version: 26.3.6(typescript@5.9.3)
|
||||||
@@ -177,7 +177,7 @@ importers:
|
|||||||
version: 1.67.3(@types/node@24.13.3)(react-dom@19.2.8(react@19.2.8))(typescript@5.9.3)
|
version: 1.67.3(@types/node@24.13.3)(react-dom@19.2.8(react@19.2.8))(typescript@5.9.3)
|
||||||
jsdom:
|
jsdom:
|
||||||
specifier: ^26.0.0
|
specifier: ^26.0.0
|
||||||
version: 26.1.0
|
version: 26.1.0(supports-color@7.2.0)
|
||||||
knip:
|
knip:
|
||||||
specifier: ^5.86.0
|
specifier: ^5.86.0
|
||||||
version: 5.88.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.13.3)(typescript@5.9.3)
|
version: 5.88.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.13.3)(typescript@5.9.3)
|
||||||
@@ -192,9 +192,9 @@ importers:
|
|||||||
version: 1.9.2
|
version: 1.9.2
|
||||||
matrix-js-sdk:
|
matrix-js-sdk:
|
||||||
specifier: github:matrix-org/matrix-js-sdk#develop
|
specifier: github:matrix-org/matrix-js-sdk#develop
|
||||||
version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7
|
version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/178dc6528ebc6a15e2f6fab38410127f16b500d0
|
||||||
matrix-widget-api:
|
matrix-widget-api:
|
||||||
specifier: ^1.16.1
|
specifier: ^1.18.0
|
||||||
version: 1.18.0
|
version: 1.18.0
|
||||||
node-stdlib-browser:
|
node-stdlib-browser:
|
||||||
specifier: ^1.3.1
|
specifier: ^1.3.1
|
||||||
@@ -282,13 +282,13 @@ importers:
|
|||||||
version: 0.2.1(node-stdlib-browser@1.3.1)(rollup@4.60.1)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
version: 0.2.1(node-stdlib-browser@1.3.1)(rollup@4.60.1)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
vite-plugin-svgr:
|
vite-plugin-svgr:
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.5.0(rollup@4.60.1)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
version: 4.5.0(rollup@4.60.1)(supports-color@7.2.0)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
vite-plugin-wasm:
|
vite-plugin-wasm:
|
||||||
specifier: ^3.6.0
|
specifier: ^3.6.0
|
||||||
version: 3.6.0(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
version: 3.6.0(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^4.1.5
|
specifier: ^4.1.5
|
||||||
version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
vitest-axe:
|
vitest-axe:
|
||||||
specifier: ^1.0.0-pre.3
|
specifier: ^1.0.0-pre.3
|
||||||
version: 1.0.0-pre.5(vitest@4.1.10)
|
version: 1.0.0-pre.5(vitest@4.1.10)
|
||||||
@@ -332,10 +332,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==}
|
resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/generator@7.29.7':
|
|
||||||
resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/generator@7.29.8':
|
'@babel/generator@7.29.8':
|
||||||
resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==}
|
resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -391,14 +387,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
|
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/runtime@8.0.0':
|
||||||
|
resolution: {integrity: sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==}
|
||||||
|
|
||||||
'@babel/template@7.29.7':
|
'@babel/template@7.29.7':
|
||||||
resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
|
resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/traverse@7.29.7':
|
|
||||||
resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/traverse@7.29.8':
|
'@babel/traverse@7.29.8':
|
||||||
resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==}
|
resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -1164,15 +1159,15 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.31':
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||||
|
|
||||||
'@livekit/components-core@0.12.14':
|
'@livekit/components-core@0.12.15':
|
||||||
resolution: {integrity: sha512-6OKP/1Ok2fCZewDLKd3SzaTb7KvfZl/6hjggI+TgUdhp0Y7HBg3+tHA7YmhZRc0BO8wIyVy4VgTPn7qkLHt2nQ==}
|
resolution: {integrity: sha512-bDkK+jkPMgyT4a5lZmdcuc/hZ8fZUrXW+qQPNHVeSpXCBMzk5q9QWzx+HPtTsPJ8hnncWGfoT81RnXHBsy+Z8g==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
livekit-client: ^2.20.1
|
livekit-client: ^2.20.1
|
||||||
tslib: ^2.6.2
|
tslib: ^2.6.2
|
||||||
|
|
||||||
'@livekit/components-react@2.9.23':
|
'@livekit/components-react@2.9.24':
|
||||||
resolution: {integrity: sha512-clO+0g/u3YBpuOvnAjUSAJBH/o7w+RpUAseswjiSML9rHrXlTUhwBNm8LPk+qN5GOU3A6lzsNnFYVpUHUBVOkg==}
|
resolution: {integrity: sha512-qw5Oy1EfPg1f/xrKYrFielSrmtxY8BZ9mDt6299G5aikotQIztmldBfTNosrVS7VSNit7qeYfOFSyKaML20W6Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@livekit/krisp-noise-filter': ^0.2.12 || ^0.3.0 || ^0.4.0
|
'@livekit/krisp-noise-filter': ^0.2.12 || ^0.3.0 || ^0.4.0
|
||||||
@@ -1196,8 +1191,8 @@ packages:
|
|||||||
'@types/dom-mediacapture-transform': ^0.1.9
|
'@types/dom-mediacapture-transform': ^0.1.9
|
||||||
livekit-client: ^1.12.0 || ^2.1.0
|
livekit-client: ^1.12.0 || ^2.1.0
|
||||||
|
|
||||||
'@matrix-org/matrix-sdk-crypto-wasm@18.3.1':
|
'@matrix-org/matrix-sdk-crypto-wasm@18.4.0':
|
||||||
resolution: {integrity: sha512-VRjWhE1UgHnPpJ3b9B5+8z71ZC/HICFngPPFIN6ktzmUBKI5RusPujzbAQUoB3CgZ0yU58L99AfSQS4YTztSWw==}
|
resolution: {integrity: sha512-osxkU1DQ+05+anGHapjWyvZqdHUb94Id37gy54mCKn1Cq/D7iGT5oEUEhjp4oTnCLo4TOtI6ULJ/LHsapaIptQ==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
'@mdx-js/react@3.1.1':
|
'@mdx-js/react@3.1.1':
|
||||||
@@ -3256,8 +3251,8 @@ packages:
|
|||||||
react:
|
react:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@vector-im/compound-web@9.9.0':
|
'@vector-im/compound-web@10.0.1':
|
||||||
resolution: {integrity: sha512-GjAEhzB/mt9MA7Y1ySe6/AlW26vodDu4hvTi1ThB5rTrt4KAiHMpvk+T56KV5Lwo0VvBxAyHMtm/IY5tkyH12A==}
|
resolution: {integrity: sha512-CT3aGMalPSf53o3JUbBF7EA10Rfp+auftIuBwOUSna+hfZ/zPxGLTFcuCuOJ7L8hrzijM+V4I8L0PUkfyOczpg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@fontsource/inconsolata': ^5
|
'@fontsource/inconsolata': ^5
|
||||||
'@fontsource/inter': ^5
|
'@fontsource/inter': ^5
|
||||||
@@ -4381,8 +4376,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==}
|
resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
is-network-error@1.3.1:
|
is-network-error@1.3.2:
|
||||||
resolution: {integrity: sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==}
|
resolution: {integrity: sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
is-number@7.0.0:
|
is-number@7.0.0:
|
||||||
@@ -4467,6 +4462,9 @@ packages:
|
|||||||
jose@6.2.4:
|
jose@6.2.4:
|
||||||
resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==}
|
resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==}
|
||||||
|
|
||||||
|
jose@6.2.9:
|
||||||
|
resolution: {integrity: sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==}
|
||||||
|
|
||||||
js-tokens@10.0.0:
|
js-tokens@10.0.0:
|
||||||
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||||
|
|
||||||
@@ -4518,10 +4516,6 @@ packages:
|
|||||||
jsonfile@6.2.0:
|
jsonfile@6.2.0:
|
||||||
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
|
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
|
||||||
|
|
||||||
jwt-decode@4.0.0:
|
|
||||||
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
|
|
||||||
@@ -4685,9 +4679,9 @@ packages:
|
|||||||
matrix-events-sdk@0.0.1:
|
matrix-events-sdk@0.0.1:
|
||||||
resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==}
|
resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==}
|
||||||
|
|
||||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7:
|
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/178dc6528ebc6a15e2f6fab38410127f16b500d0:
|
||||||
resolution: {gitHosted: true, integrity: sha512-ThGe61xx0d1L1weA8hvodzZOcATv2jR+LXd/WKwSsVT2GcR8AADY4pPxgaNvhFfl9tLMi2qMHeNay36RjDa6Zw==, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7}
|
resolution: {gitHosted: true, integrity: sha512-UmlkoXs9VMZvNaDeQqBb2k+gLifnIqEZc5cxrLeEwdYEDakQLbRqrFtmvIp4ipUCjdIPkCgtzb7QLlpPBLAwqA==, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/178dc6528ebc6a15e2f6fab38410127f16b500d0}
|
||||||
version: 41.7.0
|
version: 42.1.0
|
||||||
engines: {node: '>=22.0.0'}
|
engines: {node: '>=22.0.0'}
|
||||||
|
|
||||||
matrix-widget-api@1.18.0:
|
matrix-widget-api@1.18.0:
|
||||||
@@ -4829,10 +4823,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
|
resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
|
||||||
engines: {node: '>=12.20.0'}
|
engines: {node: '>=12.20.0'}
|
||||||
|
|
||||||
oidc-client-ts@3.5.0:
|
|
||||||
resolution: {integrity: sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
@@ -6254,34 +6244,26 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/compat-data@7.29.7': {}
|
'@babel/compat-data@7.29.7': {}
|
||||||
|
|
||||||
'@babel/core@7.29.7':
|
'@babel/core@7.29.7(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.29.7
|
'@babel/code-frame': 7.29.7
|
||||||
'@babel/generator': 7.29.7
|
'@babel/generator': 7.29.8
|
||||||
'@babel/helper-compilation-targets': 7.29.7
|
'@babel/helper-compilation-targets': 7.29.7
|
||||||
'@babel/helper-module-transforms': 7.29.7(@babel/core@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/helpers': 7.29.7
|
||||||
'@babel/parser': 7.29.8
|
'@babel/parser': 7.29.8
|
||||||
'@babel/template': 7.29.7
|
'@babel/template': 7.29.7
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||||
'@babel/types': 7.29.8
|
'@babel/types': 7.29.8
|
||||||
'@jridgewell/remapping': 2.3.5
|
'@jridgewell/remapping': 2.3.5
|
||||||
convert-source-map: 2.0.0
|
convert-source-map: 2.0.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
gensync: 1.0.0-beta.2
|
gensync: 1.0.0-beta.2
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/generator@7.29.7':
|
|
||||||
dependencies:
|
|
||||||
'@babel/parser': 7.29.8
|
|
||||||
'@babel/types': 7.29.8
|
|
||||||
'@jridgewell/gen-mapping': 0.3.13
|
|
||||||
'@jridgewell/trace-mapping': 0.3.31
|
|
||||||
jsesc: 3.1.0
|
|
||||||
|
|
||||||
'@babel/generator@7.29.8':
|
'@babel/generator@7.29.8':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.29.8
|
'@babel/parser': 7.29.8
|
||||||
@@ -6300,19 +6282,19 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/helper-globals@7.29.7': {}
|
'@babel/helper-globals@7.29.7': {}
|
||||||
|
|
||||||
'@babel/helper-module-imports@7.29.7':
|
'@babel/helper-module-imports@7.29.7(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||||
'@babel/types': 7.29.8
|
'@babel/types': 7.29.8
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/helper-module-transforms@7.29.7(@babel/core@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)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
'@babel/helper-module-imports': 7.29.7
|
'@babel/helper-module-imports': 7.29.7(supports-color@7.2.0)
|
||||||
'@babel/helper-validator-identifier': 7.29.7
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -6337,25 +6319,15 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/runtime@7.29.7': {}
|
'@babel/runtime@7.29.7': {}
|
||||||
|
|
||||||
|
'@babel/runtime@8.0.0': {}
|
||||||
|
|
||||||
'@babel/template@7.29.7':
|
'@babel/template@7.29.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.29.7
|
'@babel/code-frame': 7.29.7
|
||||||
'@babel/parser': 7.29.8
|
'@babel/parser': 7.29.8
|
||||||
'@babel/types': 7.29.8
|
'@babel/types': 7.29.8
|
||||||
|
|
||||||
'@babel/traverse@7.29.7':
|
'@babel/traverse@7.29.8(supports-color@7.2.0)':
|
||||||
dependencies:
|
|
||||||
'@babel/code-frame': 7.29.7
|
|
||||||
'@babel/generator': 7.29.7
|
|
||||||
'@babel/helper-globals': 7.29.7
|
|
||||||
'@babel/parser': 7.29.8
|
|
||||||
'@babel/template': 7.29.7
|
|
||||||
'@babel/types': 7.29.8
|
|
||||||
debug: 4.4.3
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@babel/traverse@7.29.8':
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.29.7
|
'@babel/code-frame': 7.29.7
|
||||||
'@babel/generator': 7.29.8
|
'@babel/generator': 7.29.8
|
||||||
@@ -6363,7 +6335,7 @@ snapshots:
|
|||||||
'@babel/parser': 7.29.8
|
'@babel/parser': 7.29.8
|
||||||
'@babel/template': 7.29.7
|
'@babel/template': 7.29.7
|
||||||
'@babel/types': 7.29.8
|
'@babel/types': 7.29.8
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -6829,17 +6801,17 @@ snapshots:
|
|||||||
'@esbuild/win32-x64@0.28.1':
|
'@esbuild/win32-x64@0.28.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.10.1(eslint@8.57.1)':
|
'@eslint-community/eslint-utils@4.10.1(eslint@8.57.1(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1(supports-color@7.2.0)
|
||||||
eslint-visitor-keys: 3.4.3
|
eslint-visitor-keys: 3.4.3
|
||||||
|
|
||||||
'@eslint-community/regexpp@4.12.2': {}
|
'@eslint-community/regexpp@4.12.2': {}
|
||||||
|
|
||||||
'@eslint/eslintrc@2.1.4':
|
'@eslint/eslintrc@2.1.4(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.15.0
|
ajv: 6.15.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
espree: 9.6.1
|
espree: 9.6.1
|
||||||
globals: 13.24.0
|
globals: 13.24.0
|
||||||
ignore: 5.3.2
|
ignore: 5.3.2
|
||||||
@@ -6920,10 +6892,10 @@ snapshots:
|
|||||||
'@formatjs/intl-localematcher': 0.6.2
|
'@formatjs/intl-localematcher': 0.6.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@humanwhocodes/config-array@0.13.0':
|
'@humanwhocodes/config-array@0.13.0(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@humanwhocodes/object-schema': 2.0.3
|
'@humanwhocodes/object-schema': 2.0.3
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
minimatch: 10.2.6
|
minimatch: 10.2.6
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -7092,7 +7064,7 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
'@livekit/components-core@0.12.14(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1)':
|
'@livekit/components-core@0.12.15(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/dom': 1.7.6
|
'@floating-ui/dom': 1.7.6
|
||||||
livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22)
|
livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22)
|
||||||
@@ -7100,12 +7072,12 @@ snapshots:
|
|||||||
rxjs: 7.8.2
|
rxjs: 7.8.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@livekit/components-react@2.9.23(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1)':
|
'@livekit/components-react@2.9.24(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@livekit/components-core': 0.12.14(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1)
|
'@livekit/components-core': 0.12.15(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1)
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
jose: 6.2.4
|
jose: 6.2.9
|
||||||
livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22)
|
livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22)
|
||||||
react: 19.2.8
|
react: 19.2.8
|
||||||
react-dom: 19.2.8(react@19.2.8)
|
react-dom: 19.2.8(react@19.2.8)
|
||||||
@@ -7124,7 +7096,7 @@ snapshots:
|
|||||||
'@types/dom-mediacapture-transform': 0.1.11
|
'@types/dom-mediacapture-transform': 0.1.11
|
||||||
livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22)
|
livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22)
|
||||||
|
|
||||||
'@matrix-org/matrix-sdk-crypto-wasm@18.3.1': {}
|
'@matrix-org/matrix-sdk-crypto-wasm@18.4.0': {}
|
||||||
|
|
||||||
'@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.8)':
|
'@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.8)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8075,9 +8047,9 @@ snapshots:
|
|||||||
'@rolldown/binding-win32-x64-msvc@1.1.5':
|
'@rolldown/binding-win32-x64-msvc@1.1.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(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@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
rolldown: 1.1.5
|
rolldown: 1.1.5
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -8220,11 +8192,11 @@ snapshots:
|
|||||||
'@sentry-internal/replay-canvas': 8.55.2
|
'@sentry-internal/replay-canvas': 8.55.2
|
||||||
'@sentry/core': 8.55.2
|
'@sentry/core': 8.55.2
|
||||||
|
|
||||||
'@sentry/bundler-plugin-core@3.6.1':
|
'@sentry/bundler-plugin-core@3.6.1(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
'@sentry/babel-plugin-component-annotate': 3.6.1
|
'@sentry/babel-plugin-component-annotate': 3.6.1
|
||||||
'@sentry/cli': 2.58.5
|
'@sentry/cli': 2.58.5(supports-color@7.2.0)
|
||||||
dotenv: 16.6.1
|
dotenv: 16.6.1
|
||||||
find-up: 5.0.0
|
find-up: 5.0.0
|
||||||
glob: 10.5.0
|
glob: 10.5.0
|
||||||
@@ -8258,9 +8230,9 @@ snapshots:
|
|||||||
'@sentry/cli-win32-x64@2.58.5':
|
'@sentry/cli-win32-x64@2.58.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@sentry/cli@2.58.5':
|
'@sentry/cli@2.58.5(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
https-proxy-agent: 5.0.1
|
https-proxy-agent: 5.0.1(supports-color@7.2.0)
|
||||||
node-fetch: 2.7.0
|
node-fetch: 2.7.0
|
||||||
progress: 2.0.3
|
progress: 2.0.3
|
||||||
proxy-from-env: 1.1.0
|
proxy-from-env: 1.1.0
|
||||||
@@ -8287,9 +8259,9 @@ snapshots:
|
|||||||
hoist-non-react-statics: 3.3.2
|
hoist-non-react-statics: 3.3.2
|
||||||
react: 19.2.8
|
react: 19.2.8
|
||||||
|
|
||||||
'@sentry/vite-plugin@3.6.1':
|
'@sentry/vite-plugin@3.6.1(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/bundler-plugin-core': 3.6.1
|
'@sentry/bundler-plugin-core': 3.6.1(supports-color@7.2.0)
|
||||||
unplugin: 1.0.1
|
unplugin: 1.0.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
@@ -8327,7 +8299,7 @@ snapshots:
|
|||||||
'@vitest/browser': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
'@vitest/browser': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
||||||
'@vitest/browser-playwright': 4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
'@vitest/browser-playwright': 4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
||||||
'@vitest/runner': 4.1.10
|
'@vitest/runner': 4.1.10
|
||||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- react
|
- react
|
||||||
|
|
||||||
@@ -8366,16 +8338,16 @@ snapshots:
|
|||||||
'@types/react': 19.2.17
|
'@types/react': 19.2.17
|
||||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||||
|
|
||||||
'@storybook/react-vite@10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))':
|
'@storybook/react-vite@10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
'@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
'@rollup/pluginutils': 5.4.0(rollup@4.60.1)
|
'@rollup/pluginutils': 5.4.0(rollup@4.60.1)
|
||||||
'@storybook/builder-vite': 10.5.5(esbuild@0.28.1)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
'@storybook/builder-vite': 10.5.5(esbuild@0.28.1)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
'@storybook/react': 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3)
|
'@storybook/react': 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3)
|
||||||
empathic: 2.0.1
|
empathic: 2.0.1
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
react: 19.2.8
|
react: 19.2.8
|
||||||
react-docgen: 8.0.3
|
react-docgen: 8.0.3(supports-color@7.2.0)
|
||||||
react-dom: 19.2.8(react@19.2.8)
|
react-dom: 19.2.8(react@19.2.8)
|
||||||
resolve: 1.22.12
|
resolve: 1.22.12
|
||||||
storybook: 10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8)
|
storybook: 10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8)
|
||||||
@@ -8391,12 +8363,12 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- webpack
|
- webpack
|
||||||
|
|
||||||
'@storybook/react@10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3)':
|
'@storybook/react@10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@storybook/global': 5.0.0
|
'@storybook/global': 5.0.0
|
||||||
'@storybook/react-dom-shim': 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))
|
'@storybook/react-dom-shim': 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))
|
||||||
react: 19.2.8
|
react: 19.2.8
|
||||||
react-docgen: 8.0.3
|
react-docgen: 8.0.3(supports-color@7.2.0)
|
||||||
react-docgen-typescript: 2.4.0(typescript@5.9.3)
|
react-docgen-typescript: 2.4.0(typescript@5.9.3)
|
||||||
react-dom: 19.2.8(react@19.2.8)
|
react-dom: 19.2.8(react@19.2.8)
|
||||||
storybook: 10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8)
|
storybook: 10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8)
|
||||||
@@ -8407,54 +8379,54 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7)':
|
'@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
|
|
||||||
'@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7)':
|
'@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
|
|
||||||
'@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7)':
|
'@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
|
|
||||||
'@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7)':
|
'@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
|
|
||||||
'@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7)':
|
'@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
|
|
||||||
'@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7)':
|
'@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
|
|
||||||
'@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7)':
|
'@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
|
|
||||||
'@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7)':
|
'@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
|
|
||||||
'@svgr/babel-preset@8.1.0(@babel/core@7.29.7)':
|
'@svgr/babel-preset@8.1.0(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
'@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7)
|
'@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
'@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7)
|
'@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
'@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7)
|
'@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
'@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7)
|
'@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
'@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7)
|
'@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
'@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7)
|
'@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
'@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7)
|
'@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
'@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7)
|
'@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
|
|
||||||
'@svgr/core@8.1.0(typescript@5.9.3)':
|
'@svgr/core@8.1.0(supports-color@7.2.0)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
'@svgr/babel-preset': 8.1.0(@babel/core@7.29.7)
|
'@svgr/babel-preset': 8.1.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
camelcase: 6.3.0
|
camelcase: 6.3.0
|
||||||
cosmiconfig: 8.3.6(typescript@5.9.3)
|
cosmiconfig: 8.3.6(typescript@5.9.3)
|
||||||
snake-case: 3.0.4
|
snake-case: 3.0.4
|
||||||
@@ -8467,11 +8439,11 @@ snapshots:
|
|||||||
'@babel/types': 7.29.8
|
'@babel/types': 7.29.8
|
||||||
entities: 4.5.0
|
entities: 4.5.0
|
||||||
|
|
||||||
'@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))':
|
'@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(supports-color@7.2.0)(typescript@5.9.3))(supports-color@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
'@svgr/babel-preset': 8.1.0(@babel/core@7.29.7)
|
'@svgr/babel-preset': 8.1.0(@babel/core@7.29.7(supports-color@7.2.0))
|
||||||
'@svgr/core': 8.1.0(typescript@5.9.3)
|
'@svgr/core': 8.1.0(supports-color@7.2.0)(typescript@5.9.3)
|
||||||
'@svgr/hast-util-to-babel-ast': 8.0.0
|
'@svgr/hast-util-to-babel-ast': 8.0.0
|
||||||
svg-parser: 2.0.4
|
svg-parser: 2.0.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -8679,11 +8651,11 @@ snapshots:
|
|||||||
'@types/trusted-types@2.0.7':
|
'@types/trusted-types@2.0.7':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@typescript-eslint/project-service@8.65.0(typescript@5.9.3)':
|
'@typescript-eslint/project-service@8.65.0(supports-color@7.2.0)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.65.0
|
'@typescript-eslint/types': 8.65.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -8699,13 +8671,13 @@ snapshots:
|
|||||||
|
|
||||||
'@typescript-eslint/types@8.65.0': {}
|
'@typescript-eslint/types@8.65.0': {}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.65.0(typescript@5.9.3)':
|
'@typescript-eslint/typescript-estree@8.65.0(supports-color@7.2.0)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/project-service': 8.65.0(typescript@5.9.3)
|
'@typescript-eslint/project-service': 8.65.0(supports-color@7.2.0)(typescript@5.9.3)
|
||||||
'@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.65.0
|
'@typescript-eslint/types': 8.65.0
|
||||||
'@typescript-eslint/visitor-keys': 8.65.0
|
'@typescript-eslint/visitor-keys': 8.65.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
minimatch: 10.2.6
|
minimatch: 10.2.6
|
||||||
semver: 7.8.5
|
semver: 7.8.5
|
||||||
tinyglobby: 0.2.17
|
tinyglobby: 0.2.17
|
||||||
@@ -8714,13 +8686,13 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.65.0(eslint@8.57.1)(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.65.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1)
|
'@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1(supports-color@7.2.0))
|
||||||
'@typescript-eslint/scope-manager': 8.65.0
|
'@typescript-eslint/scope-manager': 8.65.0
|
||||||
'@typescript-eslint/types': 8.65.0
|
'@typescript-eslint/types': 8.65.0
|
||||||
'@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@5.9.3)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1(supports-color@7.2.0)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -8744,7 +8716,7 @@ snapshots:
|
|||||||
'@types/react': 19.2.17
|
'@types/react': 19.2.17
|
||||||
react: 19.2.8
|
react: 19.2.8
|
||||||
|
|
||||||
'@vector-im/compound-web@9.9.0(@fontsource/inconsolata@5.3.0)(@fontsource/inter@5.3.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@vector-im/compound-design-tokens@10.2.3(@types/react@19.2.17)(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
'@vector-im/compound-web@10.0.1(@fontsource/inconsolata@5.3.0)(@fontsource/inter@5.3.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@vector-im/compound-design-tokens@10.2.3(@types/react@19.2.17)(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/react': 0.27.20(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
'@floating-ui/react': 0.27.20(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
'@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
'@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
@@ -8766,12 +8738,12 @@ snapshots:
|
|||||||
- '@types/react-dom'
|
- '@types/react-dom'
|
||||||
- react-dom
|
- react-dom
|
||||||
|
|
||||||
'@vitejs/plugin-react@6.0.5(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))':
|
'@vitejs/plugin-react@6.0.5(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rolldown/pluginutils': 1.0.1
|
'@rolldown/pluginutils': 1.0.1
|
||||||
vite: 8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)
|
vite: 8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(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@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
babel-plugin-react-compiler: 1.0.0
|
babel-plugin-react-compiler: 1.0.0
|
||||||
|
|
||||||
'@vitest/browser-playwright@4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)':
|
'@vitest/browser-playwright@4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)':
|
||||||
@@ -8780,7 +8752,7 @@ snapshots:
|
|||||||
'@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
'@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
playwright: 1.62.0
|
playwright: 1.62.0
|
||||||
tinyrainbow: 3.1.1
|
tinyrainbow: 3.1.1
|
||||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- msw
|
- msw
|
||||||
@@ -8796,7 +8768,7 @@ snapshots:
|
|||||||
pngjs: 7.0.0
|
pngjs: 7.0.0
|
||||||
sirv: 3.0.2
|
sirv: 3.0.2
|
||||||
tinyrainbow: 3.1.1
|
tinyrainbow: 3.1.1
|
||||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
ws: 8.21.1
|
ws: 8.21.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -8816,7 +8788,7 @@ snapshots:
|
|||||||
obug: 2.1.4
|
obug: 2.1.4
|
||||||
std-env: 4.2.0
|
std-env: 4.2.0
|
||||||
tinyrainbow: 3.1.1
|
tinyrainbow: 3.1.1
|
||||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@vitest/browser': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
'@vitest/browser': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
||||||
|
|
||||||
@@ -8880,7 +8852,7 @@ snapshots:
|
|||||||
sirv: 3.0.2
|
sirv: 3.0.2
|
||||||
tinyglobby: 0.2.17
|
tinyglobby: 0.2.17
|
||||||
tinyrainbow: 3.1.1
|
tinyrainbow: 3.1.1
|
||||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
|
|
||||||
'@vitest/utils@3.2.4':
|
'@vitest/utils@3.2.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8904,9 +8876,9 @@ snapshots:
|
|||||||
|
|
||||||
acorn@8.18.0: {}
|
acorn@8.18.0: {}
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -9234,7 +9206,7 @@ snapshots:
|
|||||||
cosmiconfig@8.3.6(typescript@5.9.3):
|
cosmiconfig@8.3.6(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
import-fresh: 3.3.1
|
import-fresh: 3.3.1
|
||||||
js-yaml: 4.3.0
|
js-yaml: 4.3.1
|
||||||
parse-json: 5.2.0
|
parse-json: 5.2.0
|
||||||
path-type: 4.0.0
|
path-type: 4.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -9329,9 +9301,11 @@ snapshots:
|
|||||||
whatwg-mimetype: 4.0.0
|
whatwg-mimetype: 4.0.0
|
||||||
whatwg-url: 14.2.0
|
whatwg-url: 14.2.0
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
optionalDependencies:
|
||||||
|
supports-color: 7.2.0
|
||||||
|
|
||||||
decamelize@1.2.0: {}
|
decamelize@1.2.0: {}
|
||||||
|
|
||||||
@@ -9507,11 +9481,11 @@ snapshots:
|
|||||||
|
|
||||||
escape-string-regexp@4.0.0: {}
|
escape-string-regexp@4.0.0: {}
|
||||||
|
|
||||||
eslint-plugin-storybook@10.5.5(eslint@8.57.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3):
|
eslint-plugin-storybook@10.5.5(eslint@8.57.1(supports-color@7.2.0))(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.65.0
|
'@typescript-eslint/types': 8.65.0
|
||||||
'@typescript-eslint/utils': 8.65.0(eslint@8.57.1)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.65.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1(supports-color@7.2.0)
|
||||||
storybook: 10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8)
|
storybook: 10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -9526,20 +9500,20 @@ snapshots:
|
|||||||
|
|
||||||
eslint-visitor-keys@5.0.1: {}
|
eslint-visitor-keys@5.0.1: {}
|
||||||
|
|
||||||
eslint@8.57.1:
|
eslint@8.57.1(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1)
|
'@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1(supports-color@7.2.0))
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@eslint/eslintrc': 2.1.4
|
'@eslint/eslintrc': 2.1.4(supports-color@7.2.0)
|
||||||
'@eslint/js': 8.57.1
|
'@eslint/js': 8.57.1
|
||||||
'@humanwhocodes/config-array': 0.13.0
|
'@humanwhocodes/config-array': 0.13.0(supports-color@7.2.0)
|
||||||
'@humanwhocodes/module-importer': 1.0.1
|
'@humanwhocodes/module-importer': 1.0.1
|
||||||
'@nodelib/fs.walk': 1.2.8
|
'@nodelib/fs.walk': 1.2.8
|
||||||
'@ungap/structured-clone': 1.3.3
|
'@ungap/structured-clone': 1.3.3
|
||||||
ajv: 6.15.0
|
ajv: 6.15.0
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
doctrine: 3.0.0
|
doctrine: 3.0.0
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint-scope: 7.2.2
|
eslint-scope: 7.2.2
|
||||||
@@ -9655,13 +9629,15 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.5
|
picomatch: 4.0.5
|
||||||
|
|
||||||
fetch-mock@11.1.5:
|
fetch-mock@11.1.5(node-fetch@2.7.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/glob-to-regexp': 0.4.4
|
'@types/glob-to-regexp': 0.4.4
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
glob-to-regexp: 0.4.1
|
glob-to-regexp: 0.4.1
|
||||||
is-subset: 0.1.1
|
is-subset: 0.1.1
|
||||||
regexparam: 3.0.0
|
regexparam: 3.0.0
|
||||||
|
optionalDependencies:
|
||||||
|
node-fetch: 2.7.0
|
||||||
|
|
||||||
fflate@0.4.8: {}
|
fflate@0.4.8: {}
|
||||||
|
|
||||||
@@ -9859,26 +9835,26 @@ snapshots:
|
|||||||
|
|
||||||
html-parse-stringify@4.0.1: {}
|
html-parse-stringify@4.0.1: {}
|
||||||
|
|
||||||
http-proxy-agent@7.0.2:
|
http-proxy-agent@7.0.2(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
https-browserify@1.0.0: {}
|
https-browserify@1.0.0: {}
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2(supports-color@7.2.0)
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
https-proxy-agent@7.0.6:
|
https-proxy-agent@7.0.6(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -10009,7 +9985,7 @@ snapshots:
|
|||||||
call-bind: 1.0.9
|
call-bind: 1.0.9
|
||||||
define-properties: 1.2.1
|
define-properties: 1.2.1
|
||||||
|
|
||||||
is-network-error@1.3.1: {}
|
is-network-error@1.3.2: {}
|
||||||
|
|
||||||
is-number@7.0.0: {}
|
is-number@7.0.0: {}
|
||||||
|
|
||||||
@@ -10079,6 +10055,8 @@ snapshots:
|
|||||||
|
|
||||||
jose@6.2.4: {}
|
jose@6.2.4: {}
|
||||||
|
|
||||||
|
jose@6.2.9: {}
|
||||||
|
|
||||||
js-tokens@10.0.0: {}
|
js-tokens@10.0.0: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
@@ -10091,14 +10069,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
|
|
||||||
jsdom@26.1.0:
|
jsdom@26.1.0(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
cssstyle: 4.6.0
|
cssstyle: 4.6.0
|
||||||
data-urls: 5.0.0
|
data-urls: 5.0.0
|
||||||
decimal.js: 10.6.0
|
decimal.js: 10.6.0
|
||||||
html-encoding-sniffer: 4.0.0
|
html-encoding-sniffer: 4.0.0
|
||||||
http-proxy-agent: 7.0.2
|
http-proxy-agent: 7.0.2(supports-color@7.2.0)
|
||||||
https-proxy-agent: 7.0.6
|
https-proxy-agent: 7.0.6(supports-color@7.2.0)
|
||||||
is-potential-custom-element-name: 1.0.1
|
is-potential-custom-element-name: 1.0.1
|
||||||
nwsapi: 2.2.23
|
nwsapi: 2.2.23
|
||||||
parse5: 7.3.0
|
parse5: 7.3.0
|
||||||
@@ -10138,8 +10116,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
|
|
||||||
jwt-decode@4.0.0: {}
|
|
||||||
|
|
||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
@@ -10293,18 +10269,16 @@ snapshots:
|
|||||||
|
|
||||||
matrix-events-sdk@0.0.1: {}
|
matrix-events-sdk@0.0.1: {}
|
||||||
|
|
||||||
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7:
|
matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/178dc6528ebc6a15e2f6fab38410127f16b500d0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.2
|
'@babel/runtime': 8.0.0
|
||||||
'@matrix-org/matrix-sdk-crypto-wasm': 18.3.1
|
'@matrix-org/matrix-sdk-crypto-wasm': 18.4.0
|
||||||
another-json: 0.2.0
|
another-json: 0.2.0
|
||||||
bs58: 6.0.0
|
bs58: 6.0.0
|
||||||
content-type: 2.0.0
|
content-type: 2.0.0
|
||||||
jwt-decode: 4.0.0
|
|
||||||
loglevel: 1.9.2
|
loglevel: 1.9.2
|
||||||
matrix-events-sdk: 0.0.1
|
matrix-events-sdk: 0.0.1
|
||||||
matrix-widget-api: 1.18.0
|
matrix-widget-api: 1.18.0
|
||||||
oidc-client-ts: 3.5.0
|
|
||||||
p-retry: 8.0.0
|
p-retry: 8.0.0
|
||||||
sdp-transform: 3.0.0
|
sdp-transform: 3.0.0
|
||||||
unhomoglyph: 1.0.6
|
unhomoglyph: 1.0.6
|
||||||
@@ -10454,10 +10428,6 @@ snapshots:
|
|||||||
|
|
||||||
obug@2.1.4: {}
|
obug@2.1.4: {}
|
||||||
|
|
||||||
oidc-client-ts@3.5.0:
|
|
||||||
dependencies:
|
|
||||||
jwt-decode: 4.0.0
|
|
||||||
|
|
||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
wrappy: 1.0.2
|
wrappy: 1.0.2
|
||||||
@@ -10642,7 +10612,7 @@ snapshots:
|
|||||||
|
|
||||||
p-retry@8.0.0:
|
p-retry@8.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-network-error: 1.3.1
|
is-network-error: 1.3.2
|
||||||
|
|
||||||
p-try@2.2.0: {}
|
p-try@2.2.0: {}
|
||||||
|
|
||||||
@@ -11060,10 +11030,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
react-docgen@8.0.3:
|
react-docgen@8.0.3(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||||
'@babel/traverse': 7.29.8
|
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||||
'@babel/types': 7.29.8
|
'@babel/types': 7.29.8
|
||||||
'@types/babel__core': 7.20.5
|
'@types/babel__core': 7.20.5
|
||||||
'@types/babel__traverse': 7.28.0
|
'@types/babel__traverse': 7.28.0
|
||||||
@@ -11516,7 +11486,7 @@ snapshots:
|
|||||||
terser@5.46.1:
|
terser@5.46.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/source-map': 0.3.11
|
'@jridgewell/source-map': 0.3.11
|
||||||
acorn: 8.16.0
|
acorn: 8.18.0
|
||||||
commander: 2.20.3
|
commander: 2.20.3
|
||||||
source-map-support: 0.5.21
|
source-map-support: 0.5.21
|
||||||
|
|
||||||
@@ -11745,11 +11715,11 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- rollup
|
- rollup
|
||||||
|
|
||||||
vite-plugin-svgr@4.5.0(rollup@4.60.1)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)):
|
vite-plugin-svgr@4.5.0(rollup@4.60.1)(supports-color@7.2.0)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
||||||
'@svgr/core': 8.1.0(typescript@5.9.3)
|
'@svgr/core': 8.1.0(supports-color@7.2.0)(typescript@5.9.3)
|
||||||
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3))
|
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(supports-color@7.2.0)(typescript@5.9.3))(supports-color@7.2.0)
|
||||||
vite: 8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)
|
vite: 8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- rollup
|
- rollup
|
||||||
@@ -11782,9 +11752,9 @@ snapshots:
|
|||||||
axe-core: 4.11.3
|
axe-core: 4.11.3
|
||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
lodash-es: 4.18.1
|
lodash-es: 4.18.1
|
||||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
|
|
||||||
vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)):
|
vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 4.1.10
|
'@vitest/expect': 4.1.10
|
||||||
'@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
'@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))
|
||||||
@@ -11812,7 +11782,7 @@ snapshots:
|
|||||||
'@vitest/browser-playwright': 4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
'@vitest/browser-playwright': 4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)
|
||||||
'@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
|
'@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
|
||||||
'@vitest/ui': 4.1.10(vitest@4.1.10)
|
'@vitest/ui': 4.1.10(vitest@4.1.10)
|
||||||
jsdom: 26.1.0
|
jsdom: 26.1.0(supports-color@7.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- msw
|
- msw
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ allowBuilds:
|
|||||||
"@swc/core": true
|
"@swc/core": true
|
||||||
"core-js": true
|
"core-js": true
|
||||||
"esbuild": true
|
"esbuild": true
|
||||||
"matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7": true
|
"matrix-js-sdk@git+https://github.com/matrix-org/matrix-js-sdk.git": true
|
||||||
"protobufjs": true
|
"protobufjs": true
|
||||||
overrides:
|
overrides:
|
||||||
# We need compatible versions of RxJS in our dependencies and LiveKit's dependencies, but
|
# We need compatible versions of RxJS in our dependencies and LiveKit's dependencies, but
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ interface Props {
|
|||||||
max: number;
|
max: number;
|
||||||
step: number;
|
step: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
/**
|
||||||
|
* Custom formatter for the tooltip label. If not provided, the value is
|
||||||
|
* displayed as a percentage.
|
||||||
|
*/
|
||||||
|
tooltipFormatter?: (value: number) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,6 +51,7 @@ export const Slider: FC<Props> = ({
|
|||||||
max,
|
max,
|
||||||
step,
|
step,
|
||||||
disabled,
|
disabled,
|
||||||
|
tooltipFormatter,
|
||||||
}) => {
|
}) => {
|
||||||
const onValueChange = useCallback(
|
const onValueChange = useCallback(
|
||||||
([v]: number[]) => onValueChangeProp(v),
|
([v]: number[]) => onValueChangeProp(v),
|
||||||
@@ -71,7 +77,14 @@ export const Slider: FC<Props> = ({
|
|||||||
<Range className={styles.highlight} />
|
<Range className={styles.highlight} />
|
||||||
</Track>
|
</Track>
|
||||||
{/* Note: This is expected not to be visible on mobile.*/}
|
{/* Note: This is expected not to be visible on mobile.*/}
|
||||||
<Tooltip placement="top" label={Math.round(value * 100).toString() + "%"}>
|
<Tooltip
|
||||||
|
placement="top"
|
||||||
|
label={
|
||||||
|
tooltipFormatter
|
||||||
|
? tooltipFormatter(value)
|
||||||
|
: Math.round(value * 100).toString() + "%"
|
||||||
|
}
|
||||||
|
>
|
||||||
<Thumb className={styles.handle} aria-label={label} />
|
<Thumb className={styles.handle} aria-label={label} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Root>
|
</Root>
|
||||||
|
|||||||
@@ -74,10 +74,7 @@ export interface ConfigOptions {
|
|||||||
livekit?: {
|
livekit?: {
|
||||||
// The link to the service that returns a livekit url and token to use it.
|
// The link to the service that returns a livekit url and token to use it.
|
||||||
// This is a fallback link in case the homeserver in use does not advertise
|
// This is a fallback link in case the homeserver in use does not advertise
|
||||||
// a livekit service url in the client well-known.
|
// a livekit service url over the transports endpoint.
|
||||||
// The well known needs to be formatted like so:
|
|
||||||
// {"type":"livekit", "livekit_service_url":"https://livekit.example.com"}
|
|
||||||
// and stored under the key: "org.matrix.msc4143.rtc_foci"
|
|
||||||
livekit_service_url: string;
|
livekit_service_url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,6 +102,61 @@ export interface ConfigOptions {
|
|||||||
*/
|
*/
|
||||||
ssla?: string;
|
ssla?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Media quality settings for video and screen sharing.
|
||||||
|
* These override the hardcoded LiveKit defaults.
|
||||||
|
*/
|
||||||
|
media_quality?: {
|
||||||
|
/**
|
||||||
|
* Video codec preference. The server must also have the codec enabled.
|
||||||
|
* @default "vp8"
|
||||||
|
*/
|
||||||
|
video_codec?: "vp8" | "vp9" | "h264" | "av1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Camera video settings.
|
||||||
|
*/
|
||||||
|
video?: {
|
||||||
|
/** Max resolution height in pixels (e.g. 720, 1080, 1440). @default 720 */
|
||||||
|
max_resolution?: number;
|
||||||
|
/** Max bitrate in bits per second. @default 1700000 */
|
||||||
|
max_bitrate?: number;
|
||||||
|
/** Max framerate. @default 30 */
|
||||||
|
max_framerate?: number;
|
||||||
|
/**
|
||||||
|
* Simulcast layers as an array of {height, bitrate} objects,
|
||||||
|
* ordered from lowest to highest quality.
|
||||||
|
* @default [{height: 180, bitrate: 160000}, {height: 360, bitrate: 450000}]
|
||||||
|
*/
|
||||||
|
simulcast_layers?: Array<{
|
||||||
|
height: number;
|
||||||
|
bitrate: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen share settings.
|
||||||
|
*/
|
||||||
|
screen_share?: {
|
||||||
|
/** Max resolution height in pixels. @default 1080 */
|
||||||
|
max_resolution?: number;
|
||||||
|
/** Max bitrate in bits per second. @default 5000000 */
|
||||||
|
max_bitrate?: number;
|
||||||
|
/** Max framerate. @default 30 */
|
||||||
|
max_framerate?: number;
|
||||||
|
/**
|
||||||
|
* Simulcast layers for screen sharing as an array of {height, bitrate, framerate} objects,
|
||||||
|
* ordered from lowest to highest quality. If omitted, LiveKit SDK defaults apply (1 extra
|
||||||
|
* layer at half resolution).
|
||||||
|
*/
|
||||||
|
simulcast_layers?: Array<{
|
||||||
|
height: number;
|
||||||
|
bitrate: number;
|
||||||
|
framerate?: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
media_devices?: {
|
media_devices?: {
|
||||||
/**
|
/**
|
||||||
* Defines whether participants should start with audio enabled by default.
|
* Defines whether participants should start with audio enabled by default.
|
||||||
@@ -185,6 +237,24 @@ export interface ConfigOptions {
|
|||||||
export interface ResolvedConfigOptions extends ConfigOptions {
|
export interface ResolvedConfigOptions extends ConfigOptions {
|
||||||
sync_disconnect_grace_period_ms: number;
|
sync_disconnect_grace_period_ms: number;
|
||||||
ssla: string;
|
ssla: string;
|
||||||
|
media_quality: Required<
|
||||||
|
Pick<NonNullable<ConfigOptions["media_quality"]>, "video_codec">
|
||||||
|
> & {
|
||||||
|
video: Required<
|
||||||
|
Pick<
|
||||||
|
NonNullable<NonNullable<ConfigOptions["media_quality"]>["video"]>,
|
||||||
|
"max_resolution" | "max_bitrate" | "max_framerate"
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
screen_share: Required<
|
||||||
|
Pick<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<ConfigOptions["media_quality"]>["screen_share"]
|
||||||
|
>,
|
||||||
|
"max_resolution" | "max_bitrate" | "max_framerate"
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
};
|
||||||
matrix_rtc_session: {
|
matrix_rtc_session: {
|
||||||
wait_for_key_rotation_ms?: number;
|
wait_for_key_rotation_ms?: number;
|
||||||
delayed_leave_event_delay_ms: number;
|
delayed_leave_event_delay_ms: number;
|
||||||
@@ -201,6 +271,19 @@ export const DEFAULT_CONFIG: ResolvedConfigOptions = {
|
|||||||
},
|
},
|
||||||
sync_disconnect_grace_period_ms: 10000,
|
sync_disconnect_grace_period_ms: 10000,
|
||||||
ssla: "https://static.element.io/legal/element-software-and-services-license-agreement-uk-1.pdf",
|
ssla: "https://static.element.io/legal/element-software-and-services-license-agreement-uk-1.pdf",
|
||||||
|
media_quality: {
|
||||||
|
video_codec: "vp8",
|
||||||
|
video: {
|
||||||
|
max_resolution: 720,
|
||||||
|
max_bitrate: 1_700_000,
|
||||||
|
max_framerate: 30,
|
||||||
|
},
|
||||||
|
screen_share: {
|
||||||
|
max_resolution: 1080,
|
||||||
|
max_bitrate: 5_000_000,
|
||||||
|
max_framerate: 30,
|
||||||
|
},
|
||||||
|
},
|
||||||
matrix_rtc_session: {
|
matrix_rtc_session: {
|
||||||
delayed_leave_event_delay_ms: 10000,
|
delayed_leave_event_delay_ms: 10000,
|
||||||
network_error_retry_ms: 1000,
|
network_error_retry_ms: 1000,
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ import { TileWrapper } from "./TileWrapper";
|
|||||||
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
|
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
|
||||||
import { useInitial } from "../useInitial";
|
import { useInitial } from "../useInitial";
|
||||||
|
|
||||||
|
const MAX_ANIMATED_TILES = 50; // Capped for performance reasons
|
||||||
|
|
||||||
interface Rect {
|
interface Rect {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
@@ -285,7 +287,6 @@ export function Grid<
|
|||||||
const [visibleTilesCallback, setVisibleTilesCallback] =
|
const [visibleTilesCallback, setVisibleTilesCallback] =
|
||||||
useState<VisibleTilesCallback | null>(null);
|
useState<VisibleTilesCallback | null>(null);
|
||||||
const tiles = useInitial(() => new Map<string, Tile<TileModel>>());
|
const tiles = useInitial(() => new Map<string, Tile<TileModel>>());
|
||||||
const prefersReducedMotion = usePrefersReducedMotion();
|
|
||||||
|
|
||||||
const Slot: FC<SlotProps<TileModel>> = useMemo(
|
const Slot: FC<SlotProps<TileModel>> = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -372,6 +373,10 @@ export function Grid<
|
|||||||
// react-spring's imperative API during gestures to improve responsiveness
|
// react-spring's imperative API during gestures to improve responsiveness
|
||||||
const dragState = useRef<DragState | null>(null);
|
const dragState = useRef<DragState | null>(null);
|
||||||
|
|
||||||
|
// If true, disables animations
|
||||||
|
const immediate =
|
||||||
|
usePrefersReducedMotion() || placedTiles.length > MAX_ANIMATED_TILES;
|
||||||
|
|
||||||
const [tileTransitions, springRef] = useTransition(
|
const [tileTransitions, springRef] = useTransition(
|
||||||
placedTiles,
|
placedTiles,
|
||||||
() => ({
|
() => ({
|
||||||
@@ -389,9 +394,9 @@ export function Grid<
|
|||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
immediate: prefersReducedMotion,
|
immediate,
|
||||||
}),
|
}),
|
||||||
enter: { opacity: 1, scale: 1, immediate: prefersReducedMotion },
|
enter: { opacity: 1, scale: 1, immediate },
|
||||||
update: ({
|
update: ({
|
||||||
id,
|
id,
|
||||||
x,
|
x,
|
||||||
@@ -406,9 +411,9 @@ export function Grid<
|
|||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
immediate: prefersReducedMotion,
|
immediate,
|
||||||
},
|
},
|
||||||
leave: { opacity: 0, scale: 0, immediate: prefersReducedMotion },
|
leave: { opacity: 0, scale: 0, immediate },
|
||||||
config: { mass: 0.7, tension: 252, friction: 25 },
|
config: { mass: 0.7, tension: 252, friction: 25 },
|
||||||
}),
|
}),
|
||||||
// react-spring's types are bugged and can't infer the spring type
|
// react-spring's types are bugged and can't infer the spring type
|
||||||
@@ -441,8 +446,7 @@ export function Grid<
|
|||||||
y: tile.y,
|
y: tile.y,
|
||||||
width: tile.width,
|
width: tile.width,
|
||||||
height: tile.height,
|
height: tile.height,
|
||||||
immediate:
|
immediate: immediate || ((key): boolean => key === "zIndex"),
|
||||||
prefersReducedMotion || ((key): boolean => key === "zIndex"),
|
|
||||||
// Allow the tile's position to settle before pushing its
|
// Allow the tile's position to settle before pushing its
|
||||||
// z-index back down
|
// z-index back down
|
||||||
delay: (key): number => (key === "zIndex" ? 500 : 0),
|
delay: (key): number => (key === "zIndex" ? 500 : 0),
|
||||||
@@ -453,7 +457,7 @@ export function Grid<
|
|||||||
x: tileX,
|
x: tileX,
|
||||||
y: tileY,
|
y: tileY,
|
||||||
immediate:
|
immediate:
|
||||||
prefersReducedMotion ||
|
immediate ||
|
||||||
((key): boolean =>
|
((key): boolean =>
|
||||||
key === "zIndex" || key === "x" || key === "y"),
|
key === "zIndex" || key === "x" || key === "y"),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ReactNode } from "react";
|
import { type FC, type ReactNode } from "react";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
@@ -13,6 +13,9 @@ import { type CallLayout } from "./CallLayout";
|
|||||||
import { type SpotlightLandscapeLayout as SpotlightLandscapeLayoutModel } from "../state/layout-types.ts";
|
import { type SpotlightLandscapeLayout as SpotlightLandscapeLayoutModel } from "../state/layout-types.ts";
|
||||||
import styles from "./SpotlightLandscapeLayout.module.css";
|
import styles from "./SpotlightLandscapeLayout.module.css";
|
||||||
import { useUpdateLayout, useVisibleTiles } from "./Grid";
|
import { useUpdateLayout, useVisibleTiles } from "./Grid";
|
||||||
|
import { type MediaViewModel } from "../state/media/MediaViewModel.ts";
|
||||||
|
import { type Behavior } from "../state/Behavior.ts";
|
||||||
|
import { useBehavior } from "../useBehavior.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of the "spotlight landscape" layout, in which the spotlight
|
* An implementation of the "spotlight landscape" layout, in which the spotlight
|
||||||
@@ -54,16 +57,10 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
|||||||
useUpdateLayout();
|
useUpdateLayout();
|
||||||
useVisibleTiles(model.setVisibleTiles);
|
useVisibleTiles(model.setVisibleTiles);
|
||||||
useObservableEagerState(minBounds$);
|
useObservableEagerState(minBounds$);
|
||||||
const withIndicators =
|
|
||||||
useObservableEagerState(model.spotlight.media$).length > 1;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={styles.layer}>
|
<div ref={ref} className={styles.layer}>
|
||||||
<div
|
<SpotlightSlot media$={model.spotlight.media$} />
|
||||||
className={classNames(styles.spotlight, {
|
|
||||||
[styles.withIndicators]: withIndicators,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{model.grid.map((m) => (
|
{model.grid.map((m) => (
|
||||||
<Slot key={m.id} className={styles.slot} id={m.id} model={m} />
|
<Slot key={m.id} className={styles.slot} id={m.id} model={m} />
|
||||||
@@ -73,3 +70,20 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface SpotlightSlotProps {
|
||||||
|
media$: Behavior<MediaViewModel[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This component isolates the subscription to the spotlight media so that it
|
||||||
|
// can change without causing the whole layout to re-render
|
||||||
|
const SpotlightSlot: FC<SpotlightSlotProps> = ({ media$ }) => {
|
||||||
|
const withIndicators = useBehavior(media$).length > 1;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(styles.spotlight, {
|
||||||
|
[styles.withIndicators]: withIndicators,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
|
|
||||||
import { getUrlParams } from "./UrlParams";
|
import { getUrlParams } from "./UrlParams";
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
|
import { seedSettingsFromConfig } from "./settings/settings";
|
||||||
import { platform } from "./Platform";
|
import { platform } from "./Platform";
|
||||||
import { isFailure } from "./utils/fetch";
|
import { isFailure } from "./utils/fetch";
|
||||||
import { initializeWidget } from "./widget";
|
import { initializeWidget } from "./widget";
|
||||||
@@ -237,6 +238,7 @@ export class Initializer {
|
|||||||
this.loadStates.config = LoadState.Loading;
|
this.loadStates.config = LoadState.Loading;
|
||||||
Config.init().then(
|
Config.init().then(
|
||||||
() => {
|
() => {
|
||||||
|
seedSettingsFromConfig(Config.get().media_quality);
|
||||||
this.loadStates.config = LoadState.Loaded;
|
this.loadStates.config = LoadState.Loaded;
|
||||||
this.initStep(resolve);
|
this.initStep(resolve);
|
||||||
},
|
},
|
||||||
|
|||||||
213
src/livekit/options.test.ts
Normal file
213
src/livekit/options.test.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
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 { VideoPresets, type VideoPreset } from "livekit-client";
|
||||||
|
|
||||||
|
import { buildLiveKitOptions, getLiveKitOptions } from "./options";
|
||||||
|
import { Config } from "../config/Config";
|
||||||
|
|
||||||
|
vi.mock("../config/Config", () => ({
|
||||||
|
Config: {
|
||||||
|
get: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("buildLiveKitOptions", () => {
|
||||||
|
it("returns sensible defaults with no config", () => {
|
||||||
|
const opts = buildLiveKitOptions();
|
||||||
|
expect(opts.adaptiveStream).toBe(true);
|
||||||
|
expect(opts.dynacast).toBe(true);
|
||||||
|
expect(opts.videoCaptureDefaults?.resolution).toEqual(
|
||||||
|
VideoPresets.h720.resolution,
|
||||||
|
);
|
||||||
|
expect(opts.publishDefaults?.videoCodec).toBe("vp8");
|
||||||
|
expect(opts.publishDefaults?.videoEncoding).toEqual({
|
||||||
|
maxBitrate: 1_700_000,
|
||||||
|
maxFramerate: 30,
|
||||||
|
});
|
||||||
|
expect(opts.publishDefaults?.screenShareEncoding).toEqual({
|
||||||
|
maxBitrate: 5_000_000,
|
||||||
|
maxFramerate: 30,
|
||||||
|
});
|
||||||
|
expect(opts.publishDefaults?.videoSimulcastLayers).toEqual([
|
||||||
|
VideoPresets.h180,
|
||||||
|
VideoPresets.h360,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies video codec from config", () => {
|
||||||
|
const opts = buildLiveKitOptions({ video_codec: "vp9" });
|
||||||
|
expect(opts.publishDefaults?.videoCodec).toBe("vp9");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies video resolution and encoding from config", () => {
|
||||||
|
const baseVideoConfig = {
|
||||||
|
max_resolution: 1080,
|
||||||
|
max_bitrate: 3_000_000,
|
||||||
|
max_framerate: 60,
|
||||||
|
};
|
||||||
|
const opts1080 = buildLiveKitOptions({
|
||||||
|
video: baseVideoConfig,
|
||||||
|
});
|
||||||
|
const opts1440 = buildLiveKitOptions({
|
||||||
|
video: { ...baseVideoConfig, max_resolution: 1440 },
|
||||||
|
});
|
||||||
|
const opts2160 = buildLiveKitOptions({
|
||||||
|
video: { ...baseVideoConfig, max_resolution: 2160 },
|
||||||
|
});
|
||||||
|
expect(opts1080.videoCaptureDefaults?.resolution).toEqual(
|
||||||
|
VideoPresets.h1080.resolution,
|
||||||
|
);
|
||||||
|
expect(opts1440.videoCaptureDefaults?.resolution).toEqual(
|
||||||
|
VideoPresets.h1440.resolution,
|
||||||
|
);
|
||||||
|
expect(opts2160.videoCaptureDefaults?.resolution).toEqual(
|
||||||
|
VideoPresets.h2160.resolution,
|
||||||
|
);
|
||||||
|
expect(opts1080.publishDefaults?.videoEncoding).toEqual({
|
||||||
|
maxBitrate: 3_000_000,
|
||||||
|
maxFramerate: 60,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies screen share encoding from config", () => {
|
||||||
|
const opts = buildLiveKitOptions({
|
||||||
|
screen_share: {
|
||||||
|
max_bitrate: 8_000_000,
|
||||||
|
max_framerate: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(opts.publishDefaults?.screenShareEncoding).toEqual({
|
||||||
|
maxBitrate: 8_000_000,
|
||||||
|
maxFramerate: 15,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses DEFAULT_CONFIG defaults when only resolution is set", () => {
|
||||||
|
const opts = buildLiveKitOptions({
|
||||||
|
screen_share: {
|
||||||
|
max_resolution: 720,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Bitrate and framerate fall back to DEFAULT_CONFIG, not the preset
|
||||||
|
expect(opts.publishDefaults?.screenShareEncoding).toEqual({
|
||||||
|
maxBitrate: 5_000_000,
|
||||||
|
maxFramerate: 30,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps low resolutions to the closest preset, rounding up", () => {
|
||||||
|
const expectations: [number, VideoPreset][] = [
|
||||||
|
[180, VideoPresets.h180],
|
||||||
|
[360, VideoPresets.h360],
|
||||||
|
[480, VideoPresets.h540],
|
||||||
|
[540, VideoPresets.h540],
|
||||||
|
[720, VideoPresets.h720],
|
||||||
|
];
|
||||||
|
for (const [height, preset] of expectations) {
|
||||||
|
const opts = buildLiveKitOptions({ video: { max_resolution: height } });
|
||||||
|
expect(opts.videoCaptureDefaults?.resolution).toEqual(preset.resolution);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("screen share layers fall back to max_framerate, then 30", () => {
|
||||||
|
const fromMax = buildLiveKitOptions({
|
||||||
|
screen_share: {
|
||||||
|
max_framerate: 15,
|
||||||
|
simulcast_layers: [{ height: 540, bitrate: 1_000_000 }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
fromMax.publishDefaults?.screenShareSimulcastLayers?.[0],
|
||||||
|
).toMatchObject({ encoding: { maxFramerate: 15 } });
|
||||||
|
|
||||||
|
const fromDefault = buildLiveKitOptions({
|
||||||
|
screen_share: {
|
||||||
|
simulcast_layers: [{ height: 540, bitrate: 1_000_000 }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
fromDefault.publishDefaults?.screenShareSimulcastLayers?.[0],
|
||||||
|
).toMatchObject({ encoding: { maxFramerate: 30 } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies custom video simulcast layers", () => {
|
||||||
|
const opts = buildLiveKitOptions({
|
||||||
|
video: {
|
||||||
|
simulcast_layers: [
|
||||||
|
{ height: 180, bitrate: 100_000 },
|
||||||
|
{ height: 360, bitrate: 300_000 },
|
||||||
|
{ height: 540, bitrate: 600_000 },
|
||||||
|
],
|
||||||
|
max_framerate: 24,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const layers = opts.publishDefaults?.videoSimulcastLayers;
|
||||||
|
expect(layers).toHaveLength(3);
|
||||||
|
expect(layers?.[0]).toMatchObject({
|
||||||
|
width: 320,
|
||||||
|
height: 180,
|
||||||
|
encoding: { maxBitrate: 100_000, maxFramerate: 24 },
|
||||||
|
});
|
||||||
|
expect(layers?.[2]).toMatchObject({
|
||||||
|
width: 960,
|
||||||
|
height: 540,
|
||||||
|
encoding: { maxBitrate: 600_000, maxFramerate: 24 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies custom screen share simulcast layers", () => {
|
||||||
|
const opts = buildLiveKitOptions({
|
||||||
|
screen_share: {
|
||||||
|
simulcast_layers: [{ height: 540, bitrate: 1_000_000, framerate: 5 }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const layers = opts.publishDefaults?.screenShareSimulcastLayers;
|
||||||
|
expect(layers).toHaveLength(1);
|
||||||
|
expect(layers?.[0]).toMatchObject({
|
||||||
|
width: 960,
|
||||||
|
height: 540,
|
||||||
|
encoding: { maxBitrate: 1_000_000, maxFramerate: 5 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not include screenShareSimulcastLayers when not configured", () => {
|
||||||
|
const opts = buildLiveKitOptions();
|
||||||
|
expect(opts.publishDefaults?.screenShareSimulcastLayers).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("backupCodec always uses stock VP8 720p encoding", () => {
|
||||||
|
const opts = buildLiveKitOptions({
|
||||||
|
video_codec: "av1",
|
||||||
|
video: { max_bitrate: 10_000_000, max_framerate: 60 },
|
||||||
|
});
|
||||||
|
const backup = opts.publishDefaults?.backupCodec as {
|
||||||
|
codec: string;
|
||||||
|
encoding: { maxBitrate: number; maxFramerate: number };
|
||||||
|
};
|
||||||
|
expect(backup.codec).toBe("vp8");
|
||||||
|
expect(backup.encoding).toEqual(VideoPresets.h720.encoding);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getLiveKitOptions", () => {
|
||||||
|
it("reads from Config singleton", () => {
|
||||||
|
vi.mocked(Config.get).mockReturnValue({
|
||||||
|
media_quality: { video_codec: "h264" },
|
||||||
|
} as ReturnType<typeof Config.get>);
|
||||||
|
const opts = getLiveKitOptions();
|
||||||
|
expect(opts.publishDefaults?.videoCodec).toBe("h264");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when Config is not initialized", () => {
|
||||||
|
vi.mocked(Config.get).mockImplementation(() => {
|
||||||
|
throw new Error("Config not initialized");
|
||||||
|
});
|
||||||
|
expect(() => getLiveKitOptions()).toThrow("Config not initialized");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,13 +9,83 @@ import {
|
|||||||
AudioPresets,
|
AudioPresets,
|
||||||
DefaultReconnectPolicy,
|
DefaultReconnectPolicy,
|
||||||
type RoomOptions,
|
type RoomOptions,
|
||||||
ScreenSharePresets,
|
|
||||||
type TrackPublishDefaults,
|
type TrackPublishDefaults,
|
||||||
type VideoPreset,
|
type VideoPreset,
|
||||||
VideoPresets,
|
VideoPresets,
|
||||||
|
VideoPreset as VideoPresetClass,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
|
|
||||||
const defaultLiveKitPublishOptions: TrackPublishDefaults = {
|
import { Config } from "../config/Config";
|
||||||
|
import { DEFAULT_CONFIG, type ConfigOptions } from "../config/ConfigOptions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the closest matching VideoPreset for a given height.
|
||||||
|
*/
|
||||||
|
function videoPresetForHeight(height: number): VideoPreset {
|
||||||
|
if (height <= 180) return VideoPresets.h180;
|
||||||
|
if (height <= 360) return VideoPresets.h360;
|
||||||
|
if (height <= 540) return VideoPresets.h540;
|
||||||
|
if (height <= 720) return VideoPresets.h720;
|
||||||
|
if (height <= 1080) return VideoPresets.h1080;
|
||||||
|
if (height <= 1440) return VideoPresets.h1440;
|
||||||
|
return VideoPresets.h2160;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build LiveKit publish options from config, falling back to sensible defaults.
|
||||||
|
*/
|
||||||
|
function buildPublishOptions(
|
||||||
|
mediaQuality: ConfigOptions["media_quality"],
|
||||||
|
): TrackPublishDefaults {
|
||||||
|
const defaults = DEFAULT_CONFIG.media_quality;
|
||||||
|
const videoConf = mediaQuality?.video;
|
||||||
|
const screenConf = mediaQuality?.screen_share;
|
||||||
|
const codec = mediaQuality?.video_codec ?? defaults.video_codec;
|
||||||
|
|
||||||
|
// Camera video encoding
|
||||||
|
const videoEncoding = {
|
||||||
|
maxBitrate: videoConf?.max_bitrate ?? defaults.video.max_bitrate,
|
||||||
|
maxFramerate: videoConf?.max_framerate ?? defaults.video.max_framerate,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Camera simulcast layers
|
||||||
|
let videoSimulcastLayers: VideoPreset[];
|
||||||
|
if (videoConf?.simulcast_layers) {
|
||||||
|
videoSimulcastLayers = videoConf.simulcast_layers.map(
|
||||||
|
(layer) =>
|
||||||
|
new VideoPresetClass(
|
||||||
|
Math.round((layer.height * 16) / 9),
|
||||||
|
layer.height,
|
||||||
|
layer.bitrate,
|
||||||
|
videoConf?.max_framerate ?? defaults.video.max_framerate,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
videoSimulcastLayers = [VideoPresets.h180, VideoPresets.h360];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screen share encoding
|
||||||
|
const screenShareEncoding = {
|
||||||
|
maxBitrate: screenConf?.max_bitrate ?? defaults.screen_share.max_bitrate,
|
||||||
|
maxFramerate:
|
||||||
|
screenConf?.max_framerate ?? defaults.screen_share.max_framerate,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Screen share simulcast layers
|
||||||
|
let screenShareSimulcastLayers: VideoPreset[] | undefined;
|
||||||
|
if (screenConf?.simulcast_layers) {
|
||||||
|
screenShareSimulcastLayers = screenConf.simulcast_layers.map(
|
||||||
|
(layer) =>
|
||||||
|
new VideoPresetClass(
|
||||||
|
Math.round((layer.height * 16) / 9),
|
||||||
|
layer.height,
|
||||||
|
layer.bitrate,
|
||||||
|
layer.framerate ?? screenConf?.max_framerate ?? 30,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
audioPreset: AudioPresets.music,
|
audioPreset: AudioPresets.music,
|
||||||
dtx: true,
|
dtx: true,
|
||||||
// disable red because the livekit server strips out red packets for clients
|
// disable red because the livekit server strips out red packets for clients
|
||||||
@@ -23,15 +93,34 @@ const defaultLiveKitPublishOptions: TrackPublishDefaults = {
|
|||||||
red: false,
|
red: false,
|
||||||
forceStereo: false,
|
forceStereo: false,
|
||||||
simulcast: true,
|
simulcast: true,
|
||||||
videoSimulcastLayers: [VideoPresets.h180, VideoPresets.h360] as VideoPreset[],
|
videoSimulcastLayers: videoSimulcastLayers as VideoPreset[],
|
||||||
screenShareEncoding: ScreenSharePresets.h1080fps30.encoding,
|
screenShareEncoding,
|
||||||
|
...(screenShareSimulcastLayers && {
|
||||||
|
screenShareSimulcastLayers: screenShareSimulcastLayers as VideoPreset[],
|
||||||
|
}),
|
||||||
stopMicTrackOnMute: false,
|
stopMicTrackOnMute: false,
|
||||||
videoCodec: "vp8",
|
videoCodec: codec,
|
||||||
videoEncoding: VideoPresets.h720.encoding,
|
videoEncoding,
|
||||||
backupCodec: { codec: "vp8", encoding: VideoPresets.h720.encoding },
|
backupCodec: {
|
||||||
} as const;
|
codec: "vp8",
|
||||||
|
encoding: VideoPresets.h720.encoding,
|
||||||
|
},
|
||||||
|
} as TrackPublishDefaults;
|
||||||
|
}
|
||||||
|
|
||||||
export const defaultLiveKitOptions: RoomOptions = {
|
/**
|
||||||
|
* Build LiveKit RoomOptions from config.
|
||||||
|
* Call this after Config.init() has resolved.
|
||||||
|
*/
|
||||||
|
export function buildLiveKitOptions(
|
||||||
|
mediaQuality?: ConfigOptions["media_quality"],
|
||||||
|
): RoomOptions {
|
||||||
|
const videoHeight =
|
||||||
|
mediaQuality?.video?.max_resolution ??
|
||||||
|
DEFAULT_CONFIG.media_quality.video.max_resolution;
|
||||||
|
const basePreset = videoPresetForHeight(videoHeight);
|
||||||
|
|
||||||
|
return {
|
||||||
// automatically manage subscribed video quality
|
// automatically manage subscribed video quality
|
||||||
adaptiveStream: true,
|
adaptiveStream: true,
|
||||||
|
|
||||||
@@ -40,11 +129,11 @@ export const defaultLiveKitOptions: RoomOptions = {
|
|||||||
|
|
||||||
// capture settings
|
// capture settings
|
||||||
videoCaptureDefaults: {
|
videoCaptureDefaults: {
|
||||||
resolution: VideoPresets.h720.resolution,
|
resolution: basePreset.resolution,
|
||||||
},
|
},
|
||||||
|
|
||||||
// publish settings
|
// publish settings
|
||||||
publishDefaults: defaultLiveKitPublishOptions,
|
publishDefaults: buildPublishOptions(mediaQuality),
|
||||||
|
|
||||||
// default LiveKit options that seem to be sane
|
// default LiveKit options that seem to be sane
|
||||||
stopLocalTrackOnUnpublish: true,
|
stopLocalTrackOnUnpublish: true,
|
||||||
@@ -52,3 +141,12 @@ export const defaultLiveKitOptions: RoomOptions = {
|
|||||||
disconnectOnPageLeave: true,
|
disconnectOnPageLeave: true,
|
||||||
webAudioMix: false,
|
webAudioMix: false,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get LiveKit options, reading from the loaded Config singleton.
|
||||||
|
* Requires Config.init() to have resolved first.
|
||||||
|
*/
|
||||||
|
export function getLiveKitOptions(): RoomOptions {
|
||||||
|
return buildLiveKitOptions(Config.get().media_quality);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import {
|
import {
|
||||||
type ChangeEvent,
|
type ChangeEvent,
|
||||||
type FC,
|
type FC,
|
||||||
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -29,12 +30,14 @@ import {
|
|||||||
InlineField,
|
InlineField,
|
||||||
Label,
|
Label,
|
||||||
RadioControl,
|
RadioControl,
|
||||||
|
Separator,
|
||||||
} from "@vector-im/compound-web";
|
} from "@vector-im/compound-web";
|
||||||
import { type Room as LivekitRoom } from "livekit-client";
|
import { type Room as LivekitRoom } from "livekit-client";
|
||||||
|
|
||||||
import { FieldRow, InputField } from "../input/Input";
|
import { FieldRow, InputField } from "../input/Input";
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
import {
|
import {
|
||||||
|
type Setting,
|
||||||
useSetting,
|
useSetting,
|
||||||
duplicateTiles as duplicateTilesSetting,
|
duplicateTiles as duplicateTilesSetting,
|
||||||
debugTileLayout as debugTileLayoutSetting,
|
debugTileLayout as debugTileLayoutSetting,
|
||||||
@@ -43,10 +46,26 @@ import {
|
|||||||
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
||||||
matrixRTCMode as matrixRTCModeSetting,
|
matrixRTCMode as matrixRTCModeSetting,
|
||||||
customLivekitUrl as customLivekitUrlSetting,
|
customLivekitUrl as customLivekitUrlSetting,
|
||||||
|
advancedScreenShare as advancedScreenShareSetting,
|
||||||
|
screenShareResolution as screenShareResolutionSetting,
|
||||||
|
screenShareFramerate as screenShareFramerateSetting,
|
||||||
|
screenShareBitrate as screenShareBitrateSetting,
|
||||||
|
screenShareCodec as screenShareCodecSetting,
|
||||||
|
advancedCamera as advancedCameraSetting,
|
||||||
|
cameraResolution as cameraResolutionSetting,
|
||||||
|
cameraFramerate as cameraFramerateSetting,
|
||||||
|
cameraBitrate as cameraBitrateSetting,
|
||||||
|
cameraCodec as cameraCodecSetting,
|
||||||
|
echoCancellationSetting,
|
||||||
|
noiseSuppressionSetting,
|
||||||
|
autoGainControlSetting,
|
||||||
|
type VideoCodec,
|
||||||
enableExtendedLivekitLogs as enableExtendedLivekitLogsSetting,
|
enableExtendedLivekitLogs as enableExtendedLivekitLogsSetting,
|
||||||
} from "./settings";
|
} from "./settings";
|
||||||
import { MatrixRTCMode } from "../config/ConfigOptions";
|
import { MatrixRTCMode } from "../config/ConfigOptions";
|
||||||
import styles from "./DeveloperSettingsTab.module.css";
|
import styles from "./DeveloperSettingsTab.module.css";
|
||||||
|
import settingsStyles from "./SettingsModal.module.css";
|
||||||
|
import { Slider } from "../Slider";
|
||||||
import { useUrlParams } from "../UrlParams";
|
import { useUrlParams } from "../UrlParams";
|
||||||
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
||||||
|
|
||||||
@@ -138,6 +157,185 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
return null;
|
return null;
|
||||||
}, [livekitRooms]);
|
}, [livekitRooms]);
|
||||||
|
|
||||||
|
const MediaQualitySettings: React.FC<{
|
||||||
|
id: string;
|
||||||
|
header: string;
|
||||||
|
toggleLabel: string;
|
||||||
|
description: string;
|
||||||
|
toggleSetting: Setting<boolean>;
|
||||||
|
resolutionSetting: Setting<string>;
|
||||||
|
framerateSetting: Setting<number>;
|
||||||
|
bitrateSetting: Setting<number>;
|
||||||
|
codecSetting: Setting<VideoCodec>;
|
||||||
|
resolutionOptions: { value: string; label: string }[];
|
||||||
|
bitrateRange: { min: number; max: number; step: number };
|
||||||
|
}> = ({
|
||||||
|
id,
|
||||||
|
header,
|
||||||
|
toggleLabel,
|
||||||
|
description,
|
||||||
|
toggleSetting,
|
||||||
|
resolutionSetting,
|
||||||
|
framerateSetting,
|
||||||
|
bitrateSetting,
|
||||||
|
codecSetting,
|
||||||
|
resolutionOptions,
|
||||||
|
bitrateRange,
|
||||||
|
}): ReactNode => {
|
||||||
|
const [advancedEnabled, setAdvancedEnabled] = useSetting(toggleSetting);
|
||||||
|
const [resolution, setResolution] = useSetting(resolutionSetting);
|
||||||
|
const [framerate, setFramerate] = useSetting(framerateSetting);
|
||||||
|
const [framerateRaw, setFramerateRaw] = useState(framerate);
|
||||||
|
const [bitrate, setBitrate] = useSetting(bitrateSetting);
|
||||||
|
const [bitrateRaw, setBitrateRaw] = useState(bitrate);
|
||||||
|
const [codec, setCodec] = useSetting(codecSetting);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4>{header}</h4>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id={`${id}Toggle`}
|
||||||
|
label={toggleLabel}
|
||||||
|
description={description}
|
||||||
|
type="checkbox"
|
||||||
|
checked={advancedEnabled}
|
||||||
|
onChange={(e): void => setAdvancedEnabled(e.target.checked)}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
{advancedEnabled && (
|
||||||
|
<>
|
||||||
|
<div className={settingsStyles.volumeSlider}>
|
||||||
|
<label htmlFor={`${id}Resolution`}>
|
||||||
|
{t("settings.resolution_label", "Resolution")}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id={`${id}Resolution`}
|
||||||
|
value={resolution}
|
||||||
|
onChange={(e): void => setResolution(e.target.value)}
|
||||||
|
>
|
||||||
|
{resolutionOptions.map((opt) => (
|
||||||
|
<option key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className={settingsStyles.volumeSlider}>
|
||||||
|
<label>
|
||||||
|
{t("settings.framerate_label", "Framerate")}
|
||||||
|
{": "}
|
||||||
|
<span className={settingsStyles.settingValue}>
|
||||||
|
{framerateRaw} fps
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<Slider
|
||||||
|
label={t("settings.framerate_label", "Framerate")}
|
||||||
|
value={framerateRaw}
|
||||||
|
onValueChange={setFramerateRaw}
|
||||||
|
onValueCommit={setFramerate}
|
||||||
|
min={5}
|
||||||
|
max={60}
|
||||||
|
step={5}
|
||||||
|
tooltipFormatter={(v): string => `${v} fps`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={settingsStyles.volumeSlider}>
|
||||||
|
<label>
|
||||||
|
{t("settings.bitrate_label", "Bitrate")}
|
||||||
|
{": "}
|
||||||
|
<span className={settingsStyles.settingValue}>
|
||||||
|
{(bitrateRaw / 1_000_000).toFixed(1)} Mbps
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<Slider
|
||||||
|
label={t("settings.bitrate_label", "Bitrate")}
|
||||||
|
value={bitrateRaw}
|
||||||
|
onValueChange={setBitrateRaw}
|
||||||
|
onValueCommit={setBitrate}
|
||||||
|
min={bitrateRange.min}
|
||||||
|
max={bitrateRange.max}
|
||||||
|
step={bitrateRange.step}
|
||||||
|
tooltipFormatter={(v): string =>
|
||||||
|
`${(v / 1_000_000).toFixed(1)} Mbps`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={settingsStyles.volumeSlider}>
|
||||||
|
<label htmlFor={`${id}Codec`}>
|
||||||
|
{t("settings.codec_label", "Codec")}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id={`${id}Codec`}
|
||||||
|
value={codec}
|
||||||
|
onChange={(e): void => setCodec(e.target.value as VideoCodec)}
|
||||||
|
>
|
||||||
|
<option value="vp8">VP8</option>
|
||||||
|
<option value="vp9">VP9</option>
|
||||||
|
<option value="h264">H.264</option>
|
||||||
|
<option value="av1">AV1</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AudioProcessingSettings: React.FC = (): ReactNode => {
|
||||||
|
const [echoCancellation, setEchoCancellation] = useSetting(
|
||||||
|
echoCancellationSetting,
|
||||||
|
);
|
||||||
|
const [noiseSuppression, setNoiseSuppression] = useSetting(
|
||||||
|
noiseSuppressionSetting,
|
||||||
|
);
|
||||||
|
const [autoGainControl, setAutoGainControl] = useSetting(
|
||||||
|
autoGainControlSetting,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4>{t("settings.audio_processing_header", "Audio processing")}</h4>
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
"settings.audio_processing_description",
|
||||||
|
"Changes apply on next call join.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id="echoCancellation"
|
||||||
|
label={t("settings.echo_cancellation_label", "Echo cancellation")}
|
||||||
|
type="checkbox"
|
||||||
|
checked={echoCancellation}
|
||||||
|
onChange={(e): void => setEchoCancellation(e.target.checked)}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id="noiseSuppression"
|
||||||
|
label={t("settings.noise_suppression_label", "Noise suppression")}
|
||||||
|
type="checkbox"
|
||||||
|
checked={noiseSuppression}
|
||||||
|
onChange={(e): void => setNoiseSuppression(e.target.checked)}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id="autoGainControl"
|
||||||
|
label={t(
|
||||||
|
"settings.auto_gain_control_label",
|
||||||
|
"Automatic gain control",
|
||||||
|
)}
|
||||||
|
type="checkbox"
|
||||||
|
checked={autoGainControl}
|
||||||
|
onChange={(e): void => setAutoGainControl(e.target.checked)}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
@@ -166,6 +364,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
id: client.getDeviceId() || "unknown",
|
id: client.getDeviceId() || "unknown",
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
|
<Separator />
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<InputField
|
<InputField
|
||||||
id="duplicateTiles"
|
id="duplicateTiles"
|
||||||
@@ -315,6 +514,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
<ErrorMessage>{customLivekitUrlUpdateError}</ErrorMessage>
|
<ErrorMessage>{customLivekitUrlUpdateError}</ErrorMessage>
|
||||||
)}
|
)}
|
||||||
</EditInPlace>
|
</EditInPlace>
|
||||||
|
<Separator />
|
||||||
<Heading as="h3" type="body" weight="semibold" size="lg">
|
<Heading as="h3" type="body" weight="semibold" size="lg">
|
||||||
{t("developer_mode.matrixRTCMode.title")}
|
{t("developer_mode.matrixRTCMode.title")}
|
||||||
</Heading>
|
</Heading>
|
||||||
@@ -403,6 +603,61 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
<Separator />
|
||||||
|
<MediaQualitySettings
|
||||||
|
id="camera"
|
||||||
|
header={t("settings.camera_header", "Camera quality")}
|
||||||
|
toggleLabel={t(
|
||||||
|
"settings.advanced_camera_label",
|
||||||
|
"Advanced camera settings",
|
||||||
|
)}
|
||||||
|
description={t(
|
||||||
|
"settings.advanced_camera_description",
|
||||||
|
"Configure resolution, framerate, bitrate, and codec for camera video. Changes apply on next call join.",
|
||||||
|
)}
|
||||||
|
toggleSetting={advancedCameraSetting}
|
||||||
|
resolutionSetting={cameraResolutionSetting}
|
||||||
|
framerateSetting={cameraFramerateSetting}
|
||||||
|
bitrateSetting={cameraBitrateSetting}
|
||||||
|
codecSetting={cameraCodecSetting}
|
||||||
|
resolutionOptions={[
|
||||||
|
{ value: "640x360", label: "360p" },
|
||||||
|
{ value: "960x540", label: "540p" },
|
||||||
|
{ value: "1280x720", label: "720p" },
|
||||||
|
{ value: "1920x1080", label: "1080p" },
|
||||||
|
{ value: "2560x1440", label: "1440p" },
|
||||||
|
]}
|
||||||
|
bitrateRange={{ min: 200_000, max: 8_000_000, step: 100_000 }}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<MediaQualitySettings
|
||||||
|
id="screenShare"
|
||||||
|
header={t("settings.screen_share_header", "Screen sharing")}
|
||||||
|
toggleLabel={t(
|
||||||
|
"settings.advanced_screen_share_label",
|
||||||
|
"Advanced screen share settings",
|
||||||
|
)}
|
||||||
|
description={t(
|
||||||
|
"settings.advanced_screen_share_description",
|
||||||
|
"Configure resolution, framerate, bitrate, and codec for screen sharing",
|
||||||
|
)}
|
||||||
|
toggleSetting={advancedScreenShareSetting}
|
||||||
|
resolutionSetting={screenShareResolutionSetting}
|
||||||
|
framerateSetting={screenShareFramerateSetting}
|
||||||
|
bitrateSetting={screenShareBitrateSetting}
|
||||||
|
codecSetting={screenShareCodecSetting}
|
||||||
|
resolutionOptions={[
|
||||||
|
{ value: "1024x576", label: "576p" },
|
||||||
|
{ value: "1280x720", label: "720p" },
|
||||||
|
{ value: "1920x1080", label: "1080p" },
|
||||||
|
{ value: "2560x1440", label: "1440p" },
|
||||||
|
{ value: "3840x2160", label: "4K" },
|
||||||
|
]}
|
||||||
|
bitrateRange={{ min: 500_000, max: 15_000_000, step: 500_000 }}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<AudioProcessingSettings />
|
||||||
|
<Separator />
|
||||||
<p>{t("developer_mode.environment_variables")}</p>
|
<p>{t("developer_mode.environment_variables")}</p>
|
||||||
<pre>{JSON.stringify(env, null, 2)}</pre>
|
<pre>{JSON.stringify(env, null, 2)}</pre>
|
||||||
<p>{t("developer_mode.url_params")}</p>
|
<p>{t("developer_mode.url_params")}</p>
|
||||||
|
|||||||
@@ -33,3 +33,27 @@ Please see LICENSE in the repository root for full details.
|
|||||||
.volumeSlider > p {
|
.volumeSlider > p {
|
||||||
color: var(--cpd-color-text-secondary);
|
color: var(--cpd-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.volumeSlider > select {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-top: var(--cpd-space-1x);
|
||||||
|
border: 1px solid var(--cpd-color-border-interactive-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--cpd-color-bg-canvas-default);
|
||||||
|
color: var(--cpd-color-text-primary);
|
||||||
|
font-size: var(--font-size-body);
|
||||||
|
font-family: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volumeSlider > select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--cpd-color-text-link-external);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingValue {
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--cpd-color-text-secondary);
|
||||||
|
}
|
||||||
|
|||||||
@@ -152,7 +152,13 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.volumeSlider}>
|
<div className={styles.volumeSlider}>
|
||||||
<label>{t("settings.audio_tab.effect_volume_label")}</label>
|
<label>
|
||||||
|
{t("settings.audio_tab.effect_volume_label")}
|
||||||
|
{": "}
|
||||||
|
<span className={styles.settingValue}>
|
||||||
|
{Math.round(soundVolumeRaw * 100)}%
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<p>{t("settings.audio_tab.effect_volume_description")}</p>
|
<p>{t("settings.audio_tab.effect_volume_description")}</p>
|
||||||
<Slider
|
<Slider
|
||||||
label={t("video_tile.volume")}
|
label={t("video_tile.volume")}
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
|||||||
<p>
|
<p>
|
||||||
Device ID: DEVICE123
|
Device ID: DEVICE123
|
||||||
</p>
|
</p>
|
||||||
|
<div
|
||||||
|
class="_separator_13qwf_8"
|
||||||
|
data-kind="primary"
|
||||||
|
data-orientation="horizontal"
|
||||||
|
role="separator"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="_fieldRow_1bd8c0"
|
class="_fieldRow_1bd8c0"
|
||||||
>
|
>
|
||||||
@@ -251,10 +257,16 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
|||||||
class="_message_1o4d9_86 _help-message_1o4d9_92"
|
class="_message_1o4d9_86 _help-message_1o4d9_92"
|
||||||
id="radix-_r_8_"
|
id="radix-_r_8_"
|
||||||
>
|
>
|
||||||
Currently, no overwrite is set. Url from well-known or config is used.
|
Currently, no overwrite is set. Url from config is used.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div
|
||||||
|
class="_separator_13qwf_8"
|
||||||
|
data-kind="primary"
|
||||||
|
data-orientation="horizontal"
|
||||||
|
role="separator"
|
||||||
|
/>
|
||||||
<h3
|
<h3
|
||||||
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
|
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
|
||||||
>
|
>
|
||||||
@@ -463,6 +475,242 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = `
|
|||||||
</p>
|
</p>
|
||||||
<ul />
|
<ul />
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="_separator_13qwf_8"
|
||||||
|
data-kind="primary"
|
||||||
|
data-orientation="horizontal"
|
||||||
|
role="separator"
|
||||||
|
/>
|
||||||
|
<h4>
|
||||||
|
Camera quality
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
class="_fieldRow_1bd8c0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-describedby="_r_k_"
|
||||||
|
id="cameraToggle"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="cameraToggle"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_checkbox_1bd8c0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="24"
|
||||||
|
stroke="#000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m20 6-11 11-5-5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Advanced camera settings
|
||||||
|
</label>
|
||||||
|
<p
|
||||||
|
class="_description_1bd8c0"
|
||||||
|
id="_r_k_"
|
||||||
|
>
|
||||||
|
Configure resolution, framerate, bitrate, and codec for camera video. Changes apply on next call join.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="_separator_13qwf_8"
|
||||||
|
data-kind="primary"
|
||||||
|
data-orientation="horizontal"
|
||||||
|
role="separator"
|
||||||
|
/>
|
||||||
|
<h4>
|
||||||
|
Screen sharing
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
class="_fieldRow_1bd8c0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-describedby="_r_l_"
|
||||||
|
id="screenShareToggle"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="screenShareToggle"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_checkbox_1bd8c0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="24"
|
||||||
|
stroke="#000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m20 6-11 11-5-5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Advanced screen share settings
|
||||||
|
</label>
|
||||||
|
<p
|
||||||
|
class="_description_1bd8c0"
|
||||||
|
id="_r_l_"
|
||||||
|
>
|
||||||
|
Configure resolution, framerate, bitrate, and codec for screen sharing
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="_separator_13qwf_8"
|
||||||
|
data-kind="primary"
|
||||||
|
data-orientation="horizontal"
|
||||||
|
role="separator"
|
||||||
|
/>
|
||||||
|
<h4>
|
||||||
|
Audio processing
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
Changes apply on next call join.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="_fieldRow_1bd8c0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-describedby="_r_m_"
|
||||||
|
checked=""
|
||||||
|
id="echoCancellation"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="echoCancellation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_checkbox_1bd8c0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="24"
|
||||||
|
stroke="#000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m20 6-11 11-5-5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Echo cancellation
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="_fieldRow_1bd8c0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-describedby="_r_n_"
|
||||||
|
checked=""
|
||||||
|
id="noiseSuppression"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="noiseSuppression"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_checkbox_1bd8c0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="24"
|
||||||
|
stroke="#000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m20 6-11 11-5-5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Noise suppression
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="_fieldRow_1bd8c0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_field_1bd8c0 _checkboxField_1bd8c0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-describedby="_r_o_"
|
||||||
|
checked=""
|
||||||
|
id="autoGainControl"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="autoGainControl"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="_checkbox_1bd8c0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="24"
|
||||||
|
stroke="#000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m20 6-11 11-5-5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
Automatic gain control
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="_separator_13qwf_8"
|
||||||
|
data-kind="primary"
|
||||||
|
data-orientation="horizontal"
|
||||||
|
role="separator"
|
||||||
|
/>
|
||||||
<p>
|
<p>
|
||||||
Environment variables
|
Environment variables
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
111
src/settings/settings.test.ts
Normal file
111
src/settings/settings.test.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
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, beforeEach } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Setting,
|
||||||
|
parseResolution,
|
||||||
|
seedSettingsFromConfig,
|
||||||
|
screenShareCodec,
|
||||||
|
screenShareResolution,
|
||||||
|
screenShareFramerate,
|
||||||
|
screenShareBitrate,
|
||||||
|
cameraCodec,
|
||||||
|
cameraResolution,
|
||||||
|
cameraFramerate,
|
||||||
|
cameraBitrate,
|
||||||
|
} from "./settings";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("parseResolution", () => {
|
||||||
|
it("parses a WIDTHxHEIGHT string", () => {
|
||||||
|
expect(parseResolution("1920x1080")).toEqual({
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses non-standard resolutions", () => {
|
||||||
|
expect(parseResolution("640x360")).toEqual({ width: 640, height: 360 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Setting", () => {
|
||||||
|
describe("seedFromConfig", () => {
|
||||||
|
it("updates value when no localStorage value exists", () => {
|
||||||
|
const setting = new Setting<string>("test-seed", "default");
|
||||||
|
setting.seedFromConfig("from-config");
|
||||||
|
expect(setting.getValue()).toBe("from-config");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not override an existing localStorage value", () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
"matrix-setting-test-seed-existing",
|
||||||
|
JSON.stringify("user-set"),
|
||||||
|
);
|
||||||
|
const setting = new Setting<string>("test-seed-existing", "default");
|
||||||
|
expect(setting.getValue()).toBe("user-set");
|
||||||
|
setting.seedFromConfig("from-config");
|
||||||
|
expect(setting.getValue()).toBe("user-set");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("seedSettingsFromConfig", () => {
|
||||||
|
it("does nothing when config is undefined", () => {
|
||||||
|
const codecBefore = screenShareCodec.getValue();
|
||||||
|
seedSettingsFromConfig(undefined);
|
||||||
|
expect(screenShareCodec.getValue()).toBe(codecBefore);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("seeds video codec to both camera and screen share", () => {
|
||||||
|
seedSettingsFromConfig({ video_codec: "av1" });
|
||||||
|
expect(screenShareCodec.getValue()).toBe("av1");
|
||||||
|
expect(cameraCodec.getValue()).toBe("av1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("seeds screen share settings from config", () => {
|
||||||
|
seedSettingsFromConfig({
|
||||||
|
screen_share: {
|
||||||
|
max_resolution: 720,
|
||||||
|
max_framerate: 15,
|
||||||
|
max_bitrate: 2_000_000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(screenShareResolution.getValue()).toBe("1280x720");
|
||||||
|
expect(screenShareFramerate.getValue()).toBe(15);
|
||||||
|
expect(screenShareBitrate.getValue()).toBe(2_000_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("seeds camera/video settings from config", () => {
|
||||||
|
seedSettingsFromConfig({
|
||||||
|
video: {
|
||||||
|
max_resolution: 1080,
|
||||||
|
max_framerate: 60,
|
||||||
|
max_bitrate: 4_000_000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(cameraResolution.getValue()).toBe("1920x1080");
|
||||||
|
expect(cameraFramerate.getValue()).toBe(60);
|
||||||
|
expect(cameraBitrate.getValue()).toBe(4_000_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("only seeds provided fields, leaves others at defaults", () => {
|
||||||
|
const defaultFramerate = screenShareFramerate.getValue();
|
||||||
|
seedSettingsFromConfig({
|
||||||
|
screen_share: {
|
||||||
|
max_bitrate: 3_000_000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(screenShareBitrate.getValue()).toBe(3_000_000);
|
||||||
|
expect(screenShareFramerate.getValue()).toBe(defaultFramerate);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -21,6 +21,7 @@ export class Setting<T> {
|
|||||||
this.key = `matrix-setting-${key}`;
|
this.key = `matrix-setting-${key}`;
|
||||||
|
|
||||||
const storedValue = localStorage.getItem(this.key);
|
const storedValue = localStorage.getItem(this.key);
|
||||||
|
this.hasStoredValue = storedValue !== null;
|
||||||
let initialValue = defaultValue;
|
let initialValue = defaultValue;
|
||||||
if (storedValue !== null) {
|
if (storedValue !== null) {
|
||||||
try {
|
try {
|
||||||
@@ -40,6 +41,7 @@ export class Setting<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly key: string;
|
private readonly key: string;
|
||||||
|
private readonly hasStoredValue: boolean;
|
||||||
|
|
||||||
private readonly _value$: BehaviorSubject<T>;
|
private readonly _value$: BehaviorSubject<T>;
|
||||||
private readonly _lastUpdateReason$: BehaviorSubject<string | null>;
|
private readonly _lastUpdateReason$: BehaviorSubject<string | null>;
|
||||||
@@ -54,6 +56,17 @@ export class Setting<T> {
|
|||||||
public readonly getValue = (): T => {
|
public readonly getValue = (): T => {
|
||||||
return this._value$.getValue();
|
return this._value$.getValue();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the setting's value from a config source, but only if the user
|
||||||
|
* hasn't explicitly set a value in localStorage. This lets admins set
|
||||||
|
* org-wide defaults in config.json that users can override.
|
||||||
|
*/
|
||||||
|
public seedFromConfig(value: T): void {
|
||||||
|
if (!this.hasStoredValue) {
|
||||||
|
this._value$.next(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,3 +157,130 @@ export const customLivekitUrl = new Setting<string | null>(
|
|||||||
"custom-livekit-url",
|
"custom-livekit-url",
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type VideoCodec = "vp8" | "vp9" | "h264" | "av1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a "WIDTHxHEIGHT" resolution string into numeric width and height.
|
||||||
|
*/
|
||||||
|
export function parseResolution(res: string): {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
} {
|
||||||
|
const [w, h] = res.split("x").map(Number);
|
||||||
|
return { width: w, height: h };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const advancedScreenShare = new Setting<boolean>(
|
||||||
|
"advanced-screen-share",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const screenShareResolution = new Setting<string>(
|
||||||
|
"screen-share-resolution",
|
||||||
|
"1920x1080",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const screenShareFramerate = new Setting<number>(
|
||||||
|
"screen-share-framerate",
|
||||||
|
30,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const screenShareBitrate = new Setting<number>(
|
||||||
|
"screen-share-bitrate",
|
||||||
|
5_000_000,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const screenShareCodec = new Setting<VideoCodec>(
|
||||||
|
"screen-share-codec",
|
||||||
|
"vp9",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Camera video quality settings
|
||||||
|
export const advancedCamera = new Setting<boolean>("advanced-camera", false);
|
||||||
|
|
||||||
|
export const cameraResolution = new Setting<string>(
|
||||||
|
"camera-resolution",
|
||||||
|
"1280x720",
|
||||||
|
);
|
||||||
|
|
||||||
|
export const cameraFramerate = new Setting<number>("camera-framerate", 30);
|
||||||
|
|
||||||
|
export const cameraBitrate = new Setting<number>("camera-bitrate", 1_700_000);
|
||||||
|
|
||||||
|
export const cameraCodec = new Setting<VideoCodec>("camera-codec", "vp8");
|
||||||
|
|
||||||
|
// Audio processing settings
|
||||||
|
export const echoCancellationSetting = new Setting<boolean>(
|
||||||
|
"echo-cancellation",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const noiseSuppressionSetting = new Setting<boolean>(
|
||||||
|
"noise-suppression",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const autoGainControlSetting = new Setting<boolean>(
|
||||||
|
"auto-gain-control",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed setting defaults from config.json's media_quality section.
|
||||||
|
* Call this after Config.init() has resolved.
|
||||||
|
* Only updates settings that the user hasn't explicitly set in localStorage.
|
||||||
|
*/
|
||||||
|
export function seedSettingsFromConfig(
|
||||||
|
mediaQuality:
|
||||||
|
| {
|
||||||
|
video_codec?: VideoCodec;
|
||||||
|
video?: {
|
||||||
|
max_resolution?: number;
|
||||||
|
max_bitrate?: number;
|
||||||
|
max_framerate?: number;
|
||||||
|
};
|
||||||
|
screen_share?: {
|
||||||
|
max_resolution?: number;
|
||||||
|
max_bitrate?: number;
|
||||||
|
max_framerate?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| undefined,
|
||||||
|
): void {
|
||||||
|
if (!mediaQuality) return;
|
||||||
|
|
||||||
|
const codec = mediaQuality.video_codec;
|
||||||
|
if (codec) {
|
||||||
|
screenShareCodec.seedFromConfig(codec);
|
||||||
|
cameraCodec.seedFromConfig(codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
const screen = mediaQuality.screen_share;
|
||||||
|
if (screen) {
|
||||||
|
if (screen.max_resolution) {
|
||||||
|
const width = Math.round((screen.max_resolution * 16) / 9);
|
||||||
|
screenShareResolution.seedFromConfig(`${width}x${screen.max_resolution}`);
|
||||||
|
}
|
||||||
|
if (screen.max_framerate) {
|
||||||
|
screenShareFramerate.seedFromConfig(screen.max_framerate);
|
||||||
|
}
|
||||||
|
if (screen.max_bitrate) {
|
||||||
|
screenShareBitrate.seedFromConfig(screen.max_bitrate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const video = mediaQuality.video;
|
||||||
|
if (video) {
|
||||||
|
if (video.max_resolution) {
|
||||||
|
const width = Math.round((video.max_resolution * 16) / 9);
|
||||||
|
cameraResolution.seedFromConfig(`${width}x${video.max_resolution}`);
|
||||||
|
}
|
||||||
|
if (video.max_framerate) {
|
||||||
|
cameraFramerate.seedFromConfig(video.max_framerate);
|
||||||
|
}
|
||||||
|
if (video.max_bitrate) {
|
||||||
|
cameraBitrate.seedFromConfig(video.max_bitrate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ import { useClient } from "../ClientContext";
|
|||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
import { type RageshakeRequestModal } from "../room/RageshakeRequestModal";
|
import { type RageshakeRequestModal } from "../room/RageshakeRequestModal";
|
||||||
import { getUrlParams } from "../UrlParams";
|
import { getUrlParams } from "../UrlParams";
|
||||||
|
import { deepCompare } from "matrix-js-sdk/lib/utils";
|
||||||
|
import { advancedCamera as advancedCameraSetting } from "./settings";
|
||||||
|
import { advancedScreenShare as advancedScreenShareSetting } from "./settings";
|
||||||
|
import { DEFAULT_CONFIG } from "../config/ConfigOptions";
|
||||||
const gzip = async (text: string): Promise<Blob> => {
|
const gzip = async (text: string): Promise<Blob> => {
|
||||||
// pako is relatively large (200KB), so we only import it when needed
|
// pako is relatively large (200KB), so we only import it when needed
|
||||||
const { gzip: pakoGzip } = await import("pako");
|
const { gzip: pakoGzip } = await import("pako");
|
||||||
@@ -245,6 +248,20 @@ export function useSubmitRageshake(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom media related information to the rageshake issue description.
|
||||||
|
// Used to quickly identify issues due to untested configurations.
|
||||||
|
if (
|
||||||
|
!deepCompare(Config.get().media_quality, DEFAULT_CONFIG.media_quality)
|
||||||
|
) {
|
||||||
|
body.append("custom_media_quality_in_config", "true");
|
||||||
|
}
|
||||||
|
if (advancedCameraSetting.getValue()) {
|
||||||
|
body.append("devTools_advancedCameraSettings", "true");
|
||||||
|
}
|
||||||
|
if (advancedScreenShareSetting.getValue()) {
|
||||||
|
body.append("devTools_advancedScreenShareSetting", "true");
|
||||||
|
}
|
||||||
|
|
||||||
if (navigator.storage && navigator.storage.estimate) {
|
if (navigator.storage && navigator.storage.estimate) {
|
||||||
try {
|
try {
|
||||||
const estimate: {
|
const estimate: {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import { useSubmitRageshake, getRageshakeSubmitUrl } from "./submit-rageshake";
|
|||||||
import { ClientContextProvider } from "../ClientContext";
|
import { ClientContextProvider } from "../ClientContext";
|
||||||
import { getUrlParams } from "../UrlParams";
|
import { getUrlParams } from "../UrlParams";
|
||||||
import { mockConfig } from "../utils/test";
|
import { mockConfig } from "../utils/test";
|
||||||
|
import { DEFAULT_CONFIG } from "../config/ConfigOptions";
|
||||||
|
import { advancedCamera, advancedScreenShare } from "./settings";
|
||||||
|
|
||||||
vi.mock("../UrlParams", () => ({ getUrlParams: vi.fn() }));
|
vi.mock("../UrlParams", () => ({ getUrlParams: vi.fn() }));
|
||||||
|
|
||||||
@@ -201,6 +203,60 @@ describe("useSubmitRageshake", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("media quality metadata", () => {
|
||||||
|
const submitAndGetBody = async (): Promise<FormData> => {
|
||||||
|
const fetchFn = vi.fn().mockResolvedValue({
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
vi.stubGlobal("fetch", fetchFn);
|
||||||
|
|
||||||
|
renderWithMockClient(() => "https://rageshake.localhost/foo", false);
|
||||||
|
screen.getByTestId("submit").click();
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("sent").textContent).toBe("true");
|
||||||
|
});
|
||||||
|
return fetchFn.mock.calls[0][1].body as FormData;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
advancedCamera.setValue(advancedCamera.defaultValue);
|
||||||
|
advancedScreenShare.setValue(advancedScreenShare.defaultValue);
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits media quality fields when config and settings are default", async () => {
|
||||||
|
mockConfig({});
|
||||||
|
const body = await submitAndGetBody();
|
||||||
|
expect(body.get("custom_media_quality_in_config")).toBeNull();
|
||||||
|
expect(body.get("devTools_advancedCameraSettings")).toBeNull();
|
||||||
|
expect(body.get("devTools_advancedScreenShareSetting")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes custom_media_quality_in_config when media_quality differs from default", async () => {
|
||||||
|
mockConfig({
|
||||||
|
media_quality: {
|
||||||
|
...DEFAULT_CONFIG.media_quality,
|
||||||
|
video_codec: "h264",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const body = await submitAndGetBody();
|
||||||
|
expect(body.get("custom_media_quality_in_config")).toBe("true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes devTools flags when advanced media settings are enabled", async () => {
|
||||||
|
mockConfig({});
|
||||||
|
advancedCamera.setValue(true);
|
||||||
|
advancedScreenShare.setValue(true);
|
||||||
|
const body = await submitAndGetBody();
|
||||||
|
expect(body.get("devTools_advancedCameraSettings")).toBe("true");
|
||||||
|
expect(body.get("devTools_advancedScreenShareSetting")).toBe("true");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("when rageshake is not available", () => {
|
describe("when rageshake is not available", () => {
|
||||||
it("starts unsent", () => {
|
it("starts unsent", () => {
|
||||||
renderWithMockClient(() => undefined, false);
|
renderWithMockClient(() => undefined, false);
|
||||||
|
|||||||
@@ -513,8 +513,6 @@ export function createCallViewModel$(
|
|||||||
livekitKeyProvider,
|
livekitKeyProvider,
|
||||||
getUrlParams().controlledAudioDevices,
|
getUrlParams().controlledAudioDevices,
|
||||||
options.livekitRoomFactory,
|
options.livekitRoomFactory,
|
||||||
getUrlParams().echoCancellation,
|
|
||||||
getUrlParams().noiseSuppression,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const connectionManager = createConnectionManager$({
|
const connectionManager = createConnectionManager$({
|
||||||
|
|||||||
@@ -138,9 +138,6 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
public getSyncState(): SyncState {
|
public getSyncState(): SyncState {
|
||||||
return syncState;
|
return syncState;
|
||||||
}
|
}
|
||||||
public getAccessToken(): string | null {
|
|
||||||
return "a-token";
|
|
||||||
}
|
|
||||||
})() as Partial<MatrixClient> as MatrixClient,
|
})() as Partial<MatrixClient> as MatrixClient,
|
||||||
getMembers: () => roomMembers,
|
getMembers: () => roomMembers,
|
||||||
getMembersWithMembership: () => roomMembers,
|
getMembersWithMembership: () => roomMembers,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
afterAll,
|
afterAll,
|
||||||
beforeEach,
|
beforeEach,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
|
||||||
import { BehaviorSubject, map, of } from "rxjs";
|
import { BehaviorSubject, map, of } from "rxjs";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { type LocalParticipant, type LocalTrack } from "livekit-client";
|
import { type LocalParticipant, type LocalTrack } from "livekit-client";
|
||||||
@@ -78,34 +77,10 @@ describe("LocalMembership", () => {
|
|||||||
livekit_alias: "my-oldest-member-service-alias",
|
livekit_alias: "my-oldest-member-service-alias",
|
||||||
};
|
};
|
||||||
|
|
||||||
const focusConfigFromWellKnown = {
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
|
||||||
};
|
|
||||||
const focusConfigFromWellKnown2 = {
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "http://my-well-known-service-url2.com",
|
|
||||||
};
|
|
||||||
const clientWellKnown = {
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
focusConfigFromWellKnown,
|
|
||||||
focusConfigFromWellKnown2,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
mockConfig({
|
mockConfig({
|
||||||
livekit: { livekit_service_url: "http://my-default-service-url.com" },
|
livekit: { livekit_service_url: "http://my-default-service-url.com" },
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(AutoDiscovery, "getRawClientConfig").mockImplementation(
|
|
||||||
async (domain) => {
|
|
||||||
if (domain === "example.org") {
|
|
||||||
return Promise.resolve(clientWellKnown);
|
|
||||||
}
|
|
||||||
return Promise.resolve({});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockedSession = vi.mocked({
|
const mockedSession = vi.mocked({
|
||||||
room: {
|
room: {
|
||||||
roomId: "roomId",
|
roomId: "roomId",
|
||||||
@@ -132,7 +107,7 @@ describe("LocalMembership", () => {
|
|||||||
ownMemberMock,
|
ownMemberMock,
|
||||||
{
|
{
|
||||||
livekit_alias: "roomId",
|
livekit_alias: "roomId",
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
livekit_service_url: "http://my-livekit-service-url.com",
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -150,7 +125,7 @@ describe("LocalMembership", () => {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
livekit_alias: "roomId",
|
livekit_alias: "roomId",
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
livekit_service_url: "http://my-livekit-service-url.com",
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -161,50 +136,6 @@ describe("LocalMembership", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("It should not fail with configuration error if homeserver config has livekit url but not fallback", () => {
|
|
||||||
mockConfig({});
|
|
||||||
vi.spyOn(AutoDiscovery, "getRawClientConfig").mockResolvedValue({
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
{
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const mockedSession = vi.mocked({
|
|
||||||
room: {
|
|
||||||
roomId: "roomId",
|
|
||||||
client: {
|
|
||||||
getDomain: vi.fn().mockReturnValue("example.org"),
|
|
||||||
getOpenIdToken: vi.fn().mockResolvedValue({
|
|
||||||
access_token: "ACCCESS_TOKEN",
|
|
||||||
token_type: "Bearer",
|
|
||||||
matrix_server_name: "localhost",
|
|
||||||
expires_in: 10000,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
memberships: [],
|
|
||||||
getFocusInUse: vi.fn(),
|
|
||||||
joinRTCSession: vi.fn(),
|
|
||||||
}) as unknown as MatrixRTCSession;
|
|
||||||
|
|
||||||
enterRTCSession(
|
|
||||||
mockedSession,
|
|
||||||
ownMemberMock,
|
|
||||||
{
|
|
||||||
livekit_alias: "roomId",
|
|
||||||
livekit_service_url: "http://my-well-known-service-url.com",
|
|
||||||
type: "livekit",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
encryptMedia: true,
|
|
||||||
matrixRTCMode: MATRIX_RTC_MODE,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultCreateLocalMemberValues = {
|
const defaultCreateLocalMemberValues = {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ParticipantEvent,
|
ParticipantEvent,
|
||||||
type LocalParticipant,
|
type LocalParticipant,
|
||||||
type ScreenShareCaptureOptions,
|
type ScreenShareCaptureOptions,
|
||||||
|
type TrackPublishOptions,
|
||||||
RoomEvent,
|
RoomEvent,
|
||||||
MediaDeviceFailure,
|
MediaDeviceFailure,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
@@ -53,6 +54,14 @@ import {
|
|||||||
import { ElementWidgetActions, widget } from "../../../widget.ts";
|
import { ElementWidgetActions, widget } from "../../../widget.ts";
|
||||||
import { getUrlParams } from "../../../UrlParams.ts";
|
import { getUrlParams } from "../../../UrlParams.ts";
|
||||||
import { PosthogAnalytics } from "../../../analytics/PosthogAnalytics.ts";
|
import { PosthogAnalytics } from "../../../analytics/PosthogAnalytics.ts";
|
||||||
|
import {
|
||||||
|
advancedScreenShare,
|
||||||
|
screenShareResolution,
|
||||||
|
screenShareFramerate,
|
||||||
|
screenShareBitrate,
|
||||||
|
screenShareCodec,
|
||||||
|
parseResolution,
|
||||||
|
} from "../../../settings/settings.ts";
|
||||||
import { MatrixRTCMode } from "../../../config/ConfigOptions.ts";
|
import { MatrixRTCMode } from "../../../config/ConfigOptions.ts";
|
||||||
import { Config } from "../../../config/Config.ts";
|
import { Config } from "../../../config/Config.ts";
|
||||||
import {
|
import {
|
||||||
@@ -108,7 +117,6 @@ export type LocalMemberState =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* - get well known
|
|
||||||
* - get oldest membership
|
* - get oldest membership
|
||||||
* - get transport to use
|
* - get transport to use
|
||||||
* - get openId + jwt token
|
* - get openId + jwt token
|
||||||
@@ -719,6 +727,43 @@ export const createLocalMembership$ = ({
|
|||||||
surfaceSwitching: "include",
|
surfaceSwitching: "include",
|
||||||
systemAudio: "include",
|
systemAudio: "include",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let publishOptions: TrackPublishOptions | undefined;
|
||||||
|
|
||||||
|
if (advancedScreenShare.getValue()) {
|
||||||
|
// User has advanced screen share settings enabled
|
||||||
|
const { width, height } = parseResolution(
|
||||||
|
screenShareResolution.getValue(),
|
||||||
|
);
|
||||||
|
const fps = screenShareFramerate.getValue();
|
||||||
|
const bps = screenShareBitrate.getValue();
|
||||||
|
const codec = screenShareCodec.getValue();
|
||||||
|
|
||||||
|
screenshareSettings.resolution = {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
frameRate: fps,
|
||||||
|
};
|
||||||
|
|
||||||
|
publishOptions = {
|
||||||
|
screenShareEncoding: {
|
||||||
|
maxBitrate: bps,
|
||||||
|
maxFramerate: fps,
|
||||||
|
},
|
||||||
|
videoCodec: codec,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Fall back to config.json settings if available
|
||||||
|
const screenConf = Config.get().media_quality?.screen_share;
|
||||||
|
if (screenConf?.max_resolution) {
|
||||||
|
screenshareSettings.resolution = {
|
||||||
|
width: Math.round((screenConf.max_resolution * 16) / 9),
|
||||||
|
height: screenConf.max_resolution,
|
||||||
|
frameRate: screenConf.max_framerate ?? 30,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const targetScreenshareState = !sharingScreen$.value;
|
const targetScreenshareState = !sharingScreen$.value;
|
||||||
logger.info(
|
logger.info(
|
||||||
`toggleScreenSharing called. Switching ${
|
`toggleScreenSharing called. Switching ${
|
||||||
@@ -734,7 +779,11 @@ export const createLocalMembership$ = ({
|
|||||||
// is still initializing or publishing tracks, because there's no
|
// is still initializing or publishing tracks, because there's no
|
||||||
// technical reason to disallow this. LiveKit will publish if it can.
|
// technical reason to disallow this. LiveKit will publish if it can.
|
||||||
participant$.value
|
participant$.value
|
||||||
?.setScreenShareEnabled(targetScreenshareState, screenshareSettings)
|
?.setScreenShareEnabled(
|
||||||
|
targetScreenshareState,
|
||||||
|
screenshareSettings,
|
||||||
|
publishOptions,
|
||||||
|
)
|
||||||
.catch(logger.error);
|
.catch(logger.error);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ describe("LocalTransport", () => {
|
|||||||
client: {
|
client: {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
getDomain: () => "example.org",
|
||||||
getDomain: () => "",
|
|
||||||
baseUrl: "example.org",
|
baseUrl: "example.org",
|
||||||
// These won't be called in this error path but satisfy the type
|
// These won't be called in this error path but satisfy the type
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
@@ -77,9 +76,11 @@ describe("LocalTransport", () => {
|
|||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
expect(() => advertised$.value).toThrow(
|
expect(() => advertised$.value).toThrow(
|
||||||
new MatrixRTCTransportMissingError(""),
|
new MatrixRTCTransportMissingError("example.org"),
|
||||||
|
);
|
||||||
|
expect(() => active$.value).toThrow(
|
||||||
|
new MatrixRTCTransportMissingError("example.org"),
|
||||||
);
|
);
|
||||||
expect(() => active$.value).toThrow(new MatrixRTCTransportMissingError(""));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws FailToGetOpenIdToken when OpenID fetch fails", async () => {
|
it("throws FailToGetOpenIdToken when OpenID fetch fails", async () => {
|
||||||
@@ -103,10 +104,8 @@ describe("LocalTransport", () => {
|
|||||||
useOldestMember: false,
|
useOldestMember: false,
|
||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
baseUrl: "https://lk.example.org",
|
baseUrl: "https://example.org",
|
||||||
// Use empty domain to skip .well-known and use config directly
|
getDomain: () => "example.org",
|
||||||
getDomain: () => "",
|
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
@@ -150,11 +149,10 @@ describe("LocalTransport", () => {
|
|||||||
client: {
|
client: {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
baseUrl: "https://lk.example.org",
|
baseUrl: "https://example.org",
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
},
|
},
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
||||||
@@ -221,13 +219,12 @@ describe("LocalTransport", () => {
|
|||||||
useOldestMember: true,
|
useOldestMember: true,
|
||||||
memberships$: scope.behavior(memberships$.pipe(trackEpoch())),
|
memberships$: scope.behavior(memberships$.pipe(trackEpoch())),
|
||||||
client: {
|
client: {
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
baseUrl: "https://lk.example.org",
|
baseUrl: "https://example.org",
|
||||||
},
|
},
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
||||||
@@ -278,14 +275,13 @@ describe("LocalTransport", () => {
|
|||||||
useOldestMember: true,
|
useOldestMember: true,
|
||||||
memberships$: scope.behavior(memberships$.pipe(trackEpoch())),
|
memberships$: scope.behavior(memberships$.pipe(trackEpoch())),
|
||||||
client: {
|
client: {
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () =>
|
_unstable_getRTCTransports: async () =>
|
||||||
Promise.resolve([aliceTransport]),
|
Promise.resolve([aliceTransport]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
baseUrl: "https://lk.example.org",
|
baseUrl: "https://example.org",
|
||||||
},
|
},
|
||||||
ownMembershipIdentity: ownMemberMock,
|
ownMembershipIdentity: ownMemberMock,
|
||||||
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
forceJwtEndpoint: JwtEndpointVersion.Legacy,
|
||||||
@@ -330,10 +326,9 @@ describe("LocalTransport", () => {
|
|||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
baseUrl: "https://example.org",
|
baseUrl: "https://example.org",
|
||||||
getDomain: vi.fn().mockReturnValue(""),
|
getDomain: vi.fn().mockReturnValue("example.org"),
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
},
|
},
|
||||||
@@ -421,42 +416,6 @@ describe("LocalTransport", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should not call _unstable_getRTCTransports in widget mode but use well-known", async () => {
|
|
||||||
mockConfig({
|
|
||||||
livekit: { livekit_service_url: "https://do-not-use.lk.example.org" },
|
|
||||||
});
|
|
||||||
|
|
||||||
localTransportOpts.client.getDomain.mockReturnValue("example.org");
|
|
||||||
|
|
||||||
fetchMock.getOnce("https://example.org/.well-known/matrix/client", {
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
{
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "https://use-me.jwt.call.example.org",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
localTransportOpts.client.getAccessToken.mockReturnValue(null);
|
|
||||||
const { advertised$, active$ } =
|
|
||||||
createLocalTransport$(localTransportOpts);
|
|
||||||
openIdResolver.resolve?.(openIdResponse);
|
|
||||||
expect(advertised$.value).toBe(null);
|
|
||||||
expect(active$.value).toBe(null);
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
localTransportOpts.client._unstable_getRTCTransports,
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
const expectedTransport = {
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "https://use-me.jwt.call.example.org",
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(advertised$.value).toStrictEqual(expectedTransport);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fails fast if the openID request fails for backend config", async () => {
|
it("fails fast if the openID request fails for backend config", async () => {
|
||||||
localTransportOpts.client._unstable_getRTCTransports.mockResolvedValue([
|
localTransportOpts.client._unstable_getRTCTransports.mockResolvedValue([
|
||||||
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
||||||
@@ -469,51 +428,6 @@ describe("LocalTransport", () => {
|
|||||||
).rejects.toThrow(expect.any(FailToGetOpenIdToken));
|
).rejects.toThrow(expect.any(FailToGetOpenIdToken));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("supports getting transport via well-known", async () => {
|
|
||||||
localTransportOpts.client.getDomain.mockReturnValue("example.org");
|
|
||||||
fetchMock.getOnce("https://example.org/.well-known/matrix/client", {
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const { advertised$, active$ } =
|
|
||||||
createLocalTransport$(localTransportOpts);
|
|
||||||
openIdResolver.resolve?.(openIdResponse);
|
|
||||||
expect(advertised$.value).toBe(null);
|
|
||||||
expect(active$.value).toBe(null);
|
|
||||||
await flushPromises();
|
|
||||||
const expectedTransport = {
|
|
||||||
livekit_service_url: "https://lk.example.org",
|
|
||||||
type: "livekit",
|
|
||||||
};
|
|
||||||
expect(advertised$.value).toStrictEqual(expectedTransport);
|
|
||||||
expect(active$.value).toStrictEqual({
|
|
||||||
transport: expectedTransport,
|
|
||||||
sfuConfig: {
|
|
||||||
jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=",
|
|
||||||
livekitAlias: "Akph4alDMhen",
|
|
||||||
livekitIdentity: "@lk_user:ABCDEF",
|
|
||||||
url: "https://lk.example.org",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(fetchMock.done()).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fails fast if the openId request fails for the well-known config", async () => {
|
|
||||||
localTransportOpts.client.getDomain.mockReturnValue("example.org");
|
|
||||||
fetchMock.getOnce("https://example.org/.well-known/matrix/client", {
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
{ type: "livekit", livekit_service_url: "https://lk.example.org" },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
openIdResolver.reject(
|
|
||||||
new FailToGetOpenIdToken(new Error("Test driven error")),
|
|
||||||
);
|
|
||||||
await expect(async () =>
|
|
||||||
lastValueFrom(createLocalTransport$(localTransportOpts).active$),
|
|
||||||
).rejects.toThrow(expect.any(FailToGetOpenIdToken));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws if no options are available", async () => {
|
it("throws if no options are available", async () => {
|
||||||
const { advertised$, active$ } = createLocalTransport$({
|
const { advertised$, active$ } = createLocalTransport$({
|
||||||
scope: testScope(),
|
scope: testScope(),
|
||||||
@@ -524,11 +438,10 @@ describe("LocalTransport", () => {
|
|||||||
delayId$: constant(null),
|
delayId$: constant(null),
|
||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
baseUrl: "https://example.org",
|
baseUrl: "https://example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
// These won't be called in this error path but satisfy the type
|
// These won't be called in this error path but satisfy the type
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
@@ -537,10 +450,10 @@ describe("LocalTransport", () => {
|
|||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
expect(() => advertised$.value).toThrow(
|
expect(() => advertised$.value).toThrow(
|
||||||
new MatrixRTCTransportMissingError(""),
|
new MatrixRTCTransportMissingError("example.org"),
|
||||||
);
|
);
|
||||||
expect(() => active$.value).toThrow(
|
expect(() => active$.value).toThrow(
|
||||||
new MatrixRTCTransportMissingError(""),
|
new MatrixRTCTransportMissingError("example.org"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -565,11 +478,10 @@ describe("LocalTransport", () => {
|
|||||||
delayId$: delayId$,
|
delayId$: delayId$,
|
||||||
memberships$: constant(new Epoch<CallMembership[]>([])),
|
memberships$: constant(new Epoch<CallMembership[]>([])),
|
||||||
client: {
|
client: {
|
||||||
getDomain: () => "",
|
getDomain: () => "example.org",
|
||||||
baseUrl: "https://example.org",
|
baseUrl: "https://example.org",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
_unstable_getRTCTransports: async () => Promise.resolve([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
// These won't be called in this error path but satisfy the type
|
// These won't be called in this error path but satisfy the type
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
tap,
|
tap,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger";
|
import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
|
||||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
|
|
||||||
import { type Behavior } from "../../Behavior.ts";
|
import { type Behavior } from "../../Behavior.ts";
|
||||||
@@ -57,7 +56,7 @@ interface Props {
|
|||||||
memberships$: Behavior<Epoch<CallMembership[]>>;
|
memberships$: Behavior<Epoch<CallMembership[]>>;
|
||||||
client: Pick<
|
client: Pick<
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
"getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken"
|
"getDomain" | "baseUrl" | "_unstable_getRTCTransports"
|
||||||
> &
|
> &
|
||||||
OpenIDClientParts;
|
OpenIDClientParts;
|
||||||
// Used by the jwt service to create the livekit room and compute the livekit alias.
|
// Used by the jwt service to create the livekit room and compute the livekit alias.
|
||||||
@@ -150,7 +149,6 @@ export const createLocalTransport$ = ({
|
|||||||
const transportDiscovery = new RtcTransportAutoDiscovery({
|
const transportDiscovery = new RtcTransportAutoDiscovery({
|
||||||
client: client,
|
client: client,
|
||||||
resolvedConfig: Config.get(),
|
resolvedConfig: Config.get(),
|
||||||
wellKnownFetcher: AutoDiscovery.getRawClientConfig.bind(AutoDiscovery),
|
|
||||||
logger: logger,
|
logger: logger,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -309,7 +307,7 @@ async function doOpenIdAndJWTFromUrl(
|
|||||||
roomId: string,
|
roomId: string,
|
||||||
client: Pick<
|
client: Pick<
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
"getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken"
|
"getDomain" | "baseUrl" | "_unstable_getRTCTransports"
|
||||||
> &
|
> &
|
||||||
OpenIDClientParts,
|
OpenIDClientParts,
|
||||||
delayId?: string,
|
delayId?: string,
|
||||||
@@ -339,7 +337,7 @@ function observeLocalTransportForOldestMembership(
|
|||||||
preferredTransport$: Observable<LocalTransportWithSFUConfig>,
|
preferredTransport$: Observable<LocalTransportWithSFUConfig>,
|
||||||
client: Pick<
|
client: Pick<
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
"getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken"
|
"getDomain" | "baseUrl" | "_unstable_getRTCTransports"
|
||||||
> &
|
> &
|
||||||
OpenIDClientParts,
|
OpenIDClientParts,
|
||||||
ownMembershipIdentity: CallMembershipIdentityParts,
|
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
type MockedObject,
|
type MockedObject,
|
||||||
vi,
|
vi,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
import { type IClientWellKnown, MatrixError } from "matrix-js-sdk";
|
import { MatrixError } from "matrix-js-sdk";
|
||||||
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||||
import {
|
import {
|
||||||
type LivekitTransportConfig,
|
type LivekitTransportConfig,
|
||||||
@@ -33,9 +33,9 @@ const backendTransport: LivekitTransportConfig = {
|
|||||||
livekit_service_url: "https://backend.example.org",
|
livekit_service_url: "https://backend.example.org",
|
||||||
};
|
};
|
||||||
|
|
||||||
const wellKnownTransport: LivekitTransportConfig = {
|
const configTransport: LivekitTransportConfig = {
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
livekit_service_url: "https://well-known.example.org",
|
livekit_service_url: "https://config.example.org",
|
||||||
};
|
};
|
||||||
|
|
||||||
function makeClient(): MockedObject<DiscoveryClient> {
|
function makeClient(): MockedObject<DiscoveryClient> {
|
||||||
@@ -43,7 +43,6 @@ function makeClient(): MockedObject<DiscoveryClient> {
|
|||||||
getDomain: vi.fn().mockReturnValue("example.org"),
|
getDomain: vi.fn().mockReturnValue("example.org"),
|
||||||
baseUrl: "https://matrix.example.org",
|
baseUrl: "https://matrix.example.org",
|
||||||
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
|
||||||
getAccessToken: vi.fn().mockReturnValue("access_token"),
|
|
||||||
getOpenIdToken: vi.fn(),
|
getOpenIdToken: vi.fn(),
|
||||||
getDeviceId: vi.fn(),
|
getDeviceId: vi.fn(),
|
||||||
} as unknown as MockedObject<DiscoveryClient>;
|
} as unknown as MockedObject<DiscoveryClient>;
|
||||||
@@ -59,12 +58,6 @@ function makeResolvedConfig(livekitServiceUrl?: string): ResolvedConfigOptions {
|
|||||||
} as ResolvedConfigOptions;
|
} as ResolvedConfigOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeWellKnown(rtcFoci?: Transport[]): IClientWellKnown {
|
|
||||||
return {
|
|
||||||
"org.matrix.msc4143.rtc_foci": rtcFoci,
|
|
||||||
} as unknown as IClientWellKnown;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("RtcTransportAutoDiscovery", () => {
|
describe("RtcTransportAutoDiscovery", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -75,33 +68,27 @@ describe("RtcTransportAutoDiscovery", () => {
|
|||||||
{ transports: [{ type: "not_livekit" }, backendTransport] },
|
{ transports: [{ type: "not_livekit" }, backendTransport] },
|
||||||
];
|
];
|
||||||
it.each(VALID_TEST_CASES)(
|
it.each(VALID_TEST_CASES)(
|
||||||
"prefers backend transport over well-known and app config $transports",
|
"prefers backend transport other app config $transports",
|
||||||
async ({ transports }) => {
|
async ({ transports }) => {
|
||||||
// it("prefers backend transport over well-known and app config", async () => {
|
|
||||||
const client = makeClient();
|
const client = makeClient();
|
||||||
client._unstable_getRTCTransports.mockResolvedValue(transports);
|
client._unstable_getRTCTransports.mockResolvedValue(transports);
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue(makeWellKnown([wellKnownTransport]));
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client,
|
client,
|
||||||
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
resolvedConfig: makeResolvedConfig(configTransport.livekit_service_url),
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
logger: rootLogger,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
const discoveredTransport = await discovery.discoverPreferredTransport();
|
||||||
discovery.discoverPreferredTransport(),
|
|
||||||
).resolves.toStrictEqual(backendTransport);
|
expect(discoveredTransport).toStrictEqual(backendTransport);
|
||||||
|
expect(discoveredTransport).not.toStrictEqual(configTransport);
|
||||||
|
|
||||||
expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(1);
|
expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(1);
|
||||||
expect(wellKnownFetcher).not.toHaveBeenCalled();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it("Retries limit_exceeded backend transport over well-known", async () => {
|
it("Retries limit_exceeded backend transport", async () => {
|
||||||
const client = makeClient();
|
const client = makeClient();
|
||||||
client._unstable_getRTCTransports
|
client._unstable_getRTCTransports
|
||||||
.mockRejectedValueOnce(
|
.mockRejectedValueOnce(
|
||||||
@@ -116,14 +103,9 @@ describe("RtcTransportAutoDiscovery", () => {
|
|||||||
)
|
)
|
||||||
.mockResolvedValue([backendTransport]);
|
.mockResolvedValue([backendTransport]);
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue(makeWellKnown([wellKnownTransport]));
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client,
|
client,
|
||||||
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
logger: rootLogger,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,7 +114,6 @@ describe("RtcTransportAutoDiscovery", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(2);
|
expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(2);
|
||||||
expect(wellKnownFetcher).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const INVALID_TEST_CASES: Array<{ transports: Transport[] }> = [
|
const INVALID_TEST_CASES: Array<{ transports: Transport[] }> = [
|
||||||
@@ -140,91 +121,30 @@ describe("RtcTransportAutoDiscovery", () => {
|
|||||||
{ transports: [{ type: "not_livekit" }] },
|
{ transports: [{ type: "not_livekit" }] },
|
||||||
];
|
];
|
||||||
it.each(INVALID_TEST_CASES)(
|
it.each(INVALID_TEST_CASES)(
|
||||||
"falls back to well-known when backend has no (valid) livekit transports $transports",
|
"falls back to config when backend has no (valid) livekit transports $transports",
|
||||||
async ({ transports }) => {
|
async ({ transports }) => {
|
||||||
const client = makeClient();
|
const client = makeClient();
|
||||||
client._unstable_getRTCTransports.mockResolvedValue(transports);
|
client._unstable_getRTCTransports.mockResolvedValue(transports);
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue(makeWellKnown([wellKnownTransport]));
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client,
|
client,
|
||||||
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
resolvedConfig: makeResolvedConfig(configTransport.livekit_service_url),
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
logger: rootLogger,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
const discoveredTransport = await discovery.discoverPreferredTransport();
|
||||||
discovery.discoverPreferredTransport(),
|
expect(discoveredTransport).not.toStrictEqual(backendTransport);
|
||||||
).resolves.toStrictEqual(wellKnownTransport);
|
expect(discoveredTransport).toStrictEqual(configTransport);
|
||||||
|
|
||||||
expect(wellKnownFetcher).toHaveBeenCalledWith("example.org");
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it("skips backend discovery in widget mode and uses well-known", async () => {
|
it("returns null when backend and config are all unavailable", async () => {
|
||||||
const client = makeClient();
|
|
||||||
// widget mode is detected by the absence of an access token
|
|
||||||
client.getAccessToken.mockReturnValue(null);
|
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue(makeWellKnown([wellKnownTransport]));
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
|
||||||
client,
|
|
||||||
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(discovery.discoverPreferredTransport()).resolves.toStrictEqual(
|
|
||||||
wellKnownTransport,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(client._unstable_getRTCTransports).not.toHaveBeenCalled();
|
|
||||||
expect(wellKnownFetcher).toHaveBeenCalledWith("example.org");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("falls back to app config when backend fails and well-known has no rtc_foci", async () => {
|
|
||||||
const client = makeClient();
|
|
||||||
client._unstable_getRTCTransports.mockRejectedValue(
|
|
||||||
new MatrixError({ errcode: "M_UNKNOWN" }, 404),
|
|
||||||
);
|
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue({} as IClientWellKnown);
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
|
||||||
client,
|
|
||||||
resolvedConfig: makeResolvedConfig("https://config.example.org"),
|
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(discovery.discoverPreferredTransport()).resolves.toStrictEqual(
|
|
||||||
{
|
|
||||||
type: "livekit",
|
|
||||||
livekit_service_url: "https://config.example.org",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns null when backend, well-known and config are all unavailable", async () => {
|
|
||||||
const client = makeClient();
|
const client = makeClient();
|
||||||
client._unstable_getRTCTransports.mockResolvedValue([]);
|
client._unstable_getRTCTransports.mockResolvedValue([]);
|
||||||
|
|
||||||
const wellKnownFetcher = vi
|
|
||||||
.fn<(domain: string) => Promise<IClientWellKnown>>()
|
|
||||||
.mockResolvedValue({} as IClientWellKnown);
|
|
||||||
|
|
||||||
const discovery = new RtcTransportAutoDiscovery({
|
const discovery = new RtcTransportAutoDiscovery({
|
||||||
client,
|
client,
|
||||||
resolvedConfig: makeResolvedConfig(undefined),
|
resolvedConfig: makeResolvedConfig(undefined),
|
||||||
wellKnownFetcher,
|
|
||||||
logger: rootLogger,
|
logger: rootLogger,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
isLivekitTransportConfig,
|
isLivekitTransportConfig,
|
||||||
type LivekitTransportConfig,
|
type LivekitTransportConfig,
|
||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
} from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import { type IClientWellKnown, type MatrixClient } from "matrix-js-sdk";
|
import { type MatrixClient } from "matrix-js-sdk";
|
||||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
import type { ResolvedConfigOptions } from "../../../config/ConfigOptions.ts";
|
import type { ResolvedConfigOptions } from "../../../config/ConfigOptions.ts";
|
||||||
@@ -16,33 +16,27 @@ import { doNetworkOperationWithRetry } from "../../../utils/matrix.ts";
|
|||||||
|
|
||||||
type TransportDiscoveryClient = Pick<
|
type TransportDiscoveryClient = Pick<
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
"getDomain" | "_unstable_getRTCTransports" | "getAccessToken"
|
"getDomain" | "_unstable_getRTCTransports"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface RtcTransportAutoDiscoveryProps {
|
export interface RtcTransportAutoDiscoveryProps {
|
||||||
client: TransportDiscoveryClient;
|
client: TransportDiscoveryClient;
|
||||||
resolvedConfig: ResolvedConfigOptions;
|
resolvedConfig: ResolvedConfigOptions;
|
||||||
wellKnownFetcher: (domain: string) => Promise<IClientWellKnown>;
|
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RtcTransportAutoDiscovery {
|
export class RtcTransportAutoDiscovery {
|
||||||
private readonly client: TransportDiscoveryClient;
|
private readonly client: TransportDiscoveryClient;
|
||||||
private readonly resolvedConfig: ResolvedConfigOptions;
|
private readonly resolvedConfig: ResolvedConfigOptions;
|
||||||
private readonly wellKnownFetcher: (
|
|
||||||
domain: string,
|
|
||||||
) => Promise<IClientWellKnown>;
|
|
||||||
private readonly logger: Logger;
|
private readonly logger: Logger;
|
||||||
|
|
||||||
public constructor({
|
public constructor({
|
||||||
client,
|
client,
|
||||||
resolvedConfig,
|
resolvedConfig,
|
||||||
wellKnownFetcher,
|
|
||||||
logger,
|
logger,
|
||||||
}: RtcTransportAutoDiscoveryProps) {
|
}: RtcTransportAutoDiscoveryProps) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.resolvedConfig = resolvedConfig;
|
this.resolvedConfig = resolvedConfig;
|
||||||
this.wellKnownFetcher = wellKnownFetcher;
|
|
||||||
this.logger = logger.getChild("[RtcTransportAutoDiscovery]");
|
this.logger = logger.getChild("[RtcTransportAutoDiscovery]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,21 +50,7 @@ export class RtcTransportAutoDiscovery {
|
|||||||
return backendTransport;
|
return backendTransport;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info("No backend transport found, falling back to well-known");
|
// 2) app config URL
|
||||||
// 2) .well-known transports
|
|
||||||
const wellKnownTransport = await this.tryWellKnownTransports();
|
|
||||||
if (wellKnownTransport) {
|
|
||||||
this.logger.info(
|
|
||||||
`Found .well-known transport: ${wellKnownTransport.livekit_service_url}`,
|
|
||||||
);
|
|
||||||
return wellKnownTransport;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info(
|
|
||||||
"No .well-known transport found, falling back to app config",
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3) app config URL
|
|
||||||
const configTransport = this.tryConfigTransport();
|
const configTransport = this.tryConfigTransport();
|
||||||
if (configTransport) {
|
if (configTransport) {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
@@ -90,13 +70,6 @@ export class RtcTransportAutoDiscovery {
|
|||||||
private async tryBackendTransports(): Promise<LivekitTransportConfig | null> {
|
private async tryBackendTransports(): Promise<LivekitTransportConfig | null> {
|
||||||
const client = this.client;
|
const client = this.client;
|
||||||
// MSC4143: Attempt to fetch transports from backend.
|
// MSC4143: Attempt to fetch transports from backend.
|
||||||
// TODO: Workaround for an issue in the js-sdk RoomWidgetClient that
|
|
||||||
// is not yet implementing _unstable_getRTCTransports properly (via widget API new action).
|
|
||||||
// For now we just skip this call if we are in a widget.
|
|
||||||
// In widget mode the client is a `RoomWidgetClient` which has no access token (it is using the widget API).
|
|
||||||
// Could be removed once the js-sdk is fixed (https://github.com/matrix-org/matrix-js-sdk/issues/5245)
|
|
||||||
const isSPA = !!client.getAccessToken();
|
|
||||||
if (isSPA && "_unstable_getRTCTransports" in client) {
|
|
||||||
this.logger.info("First try to use getRTCTransports end point ...");
|
this.logger.info("First try to use getRTCTransports end point ...");
|
||||||
try {
|
try {
|
||||||
const transportList = await doNetworkOperationWithRetry(async () =>
|
const transportList = await doNetworkOperationWithRetry(async () =>
|
||||||
@@ -114,48 +87,6 @@ export class RtcTransportAutoDiscovery {
|
|||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
this.logger.info(`Failed to use getRTCTransports end point: ${ex}`);
|
this.logger.info(`Failed to use getRTCTransports end point: ${ex}`);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.logger.debug(`getRTCTransports end point not available`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the first rtc_foci from the .well-known/matrix/client.
|
|
||||||
* This will not throw errors, but instead just log them and return null if the expected config is not found or malformed.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async tryWellKnownTransports(): Promise<LivekitTransportConfig | null> {
|
|
||||||
// Legacy MSC4143 (to be removed) WELL_KNOWN: Prioritize the .well-known/matrix/client, if available.
|
|
||||||
const client = this.client;
|
|
||||||
const domain = client.getDomain();
|
|
||||||
if (domain) {
|
|
||||||
// we use AutoDiscovery instead of relying on the MatrixClient having already
|
|
||||||
// been fully configured and started
|
|
||||||
|
|
||||||
const wellKnownFoci = await this.wellKnownFetcher(domain);
|
|
||||||
|
|
||||||
const fociConfig = wellKnownFoci["org.matrix.msc4143.rtc_foci"];
|
|
||||||
if (fociConfig) {
|
|
||||||
if (!Array.isArray(fociConfig)) {
|
|
||||||
this.logger.warn(
|
|
||||||
`org.matrix.msc4143.rtc_foci is not an array in .well-known`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return fociConfig[0];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.logger.info(
|
|
||||||
`No .well-known "org.matrix.msc4143.rtc_foci" found for ${domain}`,
|
|
||||||
wellKnownFoci,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Should never happen, but just in case
|
|
||||||
this.logger.warn(`No domain configured for client`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
type E2EEManagerOptions,
|
type E2EEManagerOptions,
|
||||||
type BaseE2EEManager,
|
type BaseE2EEManager,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
import { logger, type Logger } from "matrix-js-sdk/lib/logger";
|
||||||
// imported as inline to support worker when loaded from a cdn (cross domain)
|
// imported as inline to support worker when loaded from a cdn (cross domain)
|
||||||
import E2EEWorker from "livekit-client/e2ee-worker?worker&inline";
|
import E2EEWorker from "livekit-client/e2ee-worker?worker&inline";
|
||||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||||
@@ -27,7 +27,18 @@ import type {
|
|||||||
import type { MediaDevices } from "../../MediaDevices.ts";
|
import type { MediaDevices } from "../../MediaDevices.ts";
|
||||||
import type { Behavior } from "../../Behavior.ts";
|
import type { Behavior } from "../../Behavior.ts";
|
||||||
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
||||||
import { defaultLiveKitOptions } from "../../../livekit/options.ts";
|
import { getLiveKitOptions } from "../../../livekit/options.ts";
|
||||||
|
import {
|
||||||
|
advancedCamera,
|
||||||
|
cameraResolution,
|
||||||
|
cameraFramerate,
|
||||||
|
cameraBitrate,
|
||||||
|
cameraCodec,
|
||||||
|
parseResolution,
|
||||||
|
echoCancellationSetting,
|
||||||
|
noiseSuppressionSetting,
|
||||||
|
autoGainControlSetting,
|
||||||
|
} from "../../../settings/settings.ts";
|
||||||
|
|
||||||
// TODO evaluate if this should be done like the Publisher Factory
|
// TODO evaluate if this should be done like the Publisher Factory
|
||||||
export interface ConnectionFactory {
|
export interface ConnectionFactory {
|
||||||
@@ -53,8 +64,6 @@ export class ECConnectionFactory implements ConnectionFactory {
|
|||||||
* @param livekitKeyProvider - Optional key provider for end-to-end encryption.
|
* @param livekitKeyProvider - Optional key provider for end-to-end encryption.
|
||||||
* @param controlledAudioDevices - Option to indicate whether audio output device is controlled externally (native mobile app).
|
* @param controlledAudioDevices - Option to indicate whether audio output device is controlled externally (native mobile app).
|
||||||
* @param livekitRoomFactory - Optional factory function (for testing) to create LivekitRoom instances. If not provided, a default factory is used.
|
* @param livekitRoomFactory - Optional factory function (for testing) to create LivekitRoom instances. If not provided, a default factory is used.
|
||||||
* @param echoCancellation - Whether to enable echo cancellation for audio capture.
|
|
||||||
* @param noiseSuppression - Whether to enable noise suppression for audio capture.
|
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
private client: OpenIDClientParts,
|
private client: OpenIDClientParts,
|
||||||
@@ -64,12 +73,9 @@ export class ECConnectionFactory implements ConnectionFactory {
|
|||||||
livekitKeyProvider: BaseKeyProvider | undefined,
|
livekitKeyProvider: BaseKeyProvider | undefined,
|
||||||
private controlledAudioDevices: boolean,
|
private controlledAudioDevices: boolean,
|
||||||
livekitRoomFactory?: () => LivekitRoom,
|
livekitRoomFactory?: () => LivekitRoom,
|
||||||
echoCancellation: boolean = true,
|
|
||||||
noiseSuppression: boolean = true,
|
|
||||||
) {
|
) {
|
||||||
const defaultFactory = (): LivekitRoom =>
|
const defaultFactory = (): LivekitRoom => {
|
||||||
new LivekitRoom(
|
const roomOptions = generateRoomOption({
|
||||||
generateRoomOption({
|
|
||||||
devices: this.devices,
|
devices: this.devices,
|
||||||
processorState: this.processorState$.value,
|
processorState: this.processorState$.value,
|
||||||
e2eeLivekitOptions: livekitKeyProvider && {
|
e2eeLivekitOptions: livekitKeyProvider && {
|
||||||
@@ -79,10 +85,10 @@ export class ECConnectionFactory implements ConnectionFactory {
|
|||||||
worker: new E2EEWorker(),
|
worker: new E2EEWorker(),
|
||||||
},
|
},
|
||||||
controlledAudioDevices: this.controlledAudioDevices,
|
controlledAudioDevices: this.controlledAudioDevices,
|
||||||
echoCancellation,
|
});
|
||||||
noiseSuppression,
|
logger.info("[ECConnectionFactory] livekit room options: ", roomOptions);
|
||||||
}),
|
return new LivekitRoom(roomOptions);
|
||||||
);
|
};
|
||||||
this.livekitRoomFactory = livekitRoomFactory ?? defaultFactory;
|
this.livekitRoomFactory = livekitRoomFactory ?? defaultFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,14 +125,13 @@ export class ECConnectionFactory implements ConnectionFactory {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the initial LiveKit RoomOptions based on the current media devices and processor state.
|
* Generate the initial LiveKit RoomOptions based on the current media devices and processor state.
|
||||||
|
* Reads audio processing and camera quality settings directly from Settings.
|
||||||
*/
|
*/
|
||||||
function generateRoomOption({
|
function generateRoomOption({
|
||||||
devices,
|
devices,
|
||||||
processorState,
|
processorState,
|
||||||
e2eeLivekitOptions,
|
e2eeLivekitOptions,
|
||||||
controlledAudioDevices,
|
controlledAudioDevices,
|
||||||
echoCancellation,
|
|
||||||
noiseSuppression,
|
|
||||||
}: {
|
}: {
|
||||||
devices: MediaDevices;
|
devices: MediaDevices;
|
||||||
processorState: ProcessorState;
|
processorState: ProcessorState;
|
||||||
@@ -135,21 +140,44 @@ function generateRoomOption({
|
|||||||
| { e2eeManager: BaseE2EEManager }
|
| { e2eeManager: BaseE2EEManager }
|
||||||
| undefined;
|
| undefined;
|
||||||
controlledAudioDevices: boolean;
|
controlledAudioDevices: boolean;
|
||||||
echoCancellation: boolean;
|
|
||||||
noiseSuppression: boolean;
|
|
||||||
}): RoomOptions {
|
}): RoomOptions {
|
||||||
return {
|
const liveKitOptions = getLiveKitOptions();
|
||||||
...defaultLiveKitOptions,
|
|
||||||
videoCaptureDefaults: {
|
// Apply advanced camera settings if enabled
|
||||||
...defaultLiveKitOptions.videoCaptureDefaults,
|
let videoCaptureDefaults = {
|
||||||
|
...liveKitOptions.videoCaptureDefaults,
|
||||||
deviceId: devices.videoInput.selected$.value?.id,
|
deviceId: devices.videoInput.selected$.value?.id,
|
||||||
processor: processorState.processor,
|
processor: processorState.processor,
|
||||||
},
|
};
|
||||||
|
let publishDefaults = liveKitOptions.publishDefaults;
|
||||||
|
|
||||||
|
if (advancedCamera.getValue()) {
|
||||||
|
const { width, height } = parseResolution(cameraResolution.getValue());
|
||||||
|
const fps = cameraFramerate.getValue();
|
||||||
|
const bps = cameraBitrate.getValue();
|
||||||
|
const codec = cameraCodec.getValue();
|
||||||
|
|
||||||
|
videoCaptureDefaults = {
|
||||||
|
...videoCaptureDefaults,
|
||||||
|
resolution: { width, height, frameRate: fps },
|
||||||
|
};
|
||||||
|
publishDefaults = {
|
||||||
|
...publishDefaults,
|
||||||
|
videoEncoding: { maxBitrate: bps, maxFramerate: fps },
|
||||||
|
videoCodec: codec,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...liveKitOptions,
|
||||||
|
videoCaptureDefaults,
|
||||||
|
publishDefaults,
|
||||||
audioCaptureDefaults: {
|
audioCaptureDefaults: {
|
||||||
...defaultLiveKitOptions.audioCaptureDefaults,
|
...liveKitOptions.audioCaptureDefaults,
|
||||||
deviceId: devices.audioInput.selected$.value?.id,
|
deviceId: devices.audioInput.selected$.value?.id,
|
||||||
echoCancellation,
|
echoCancellation: echoCancellationSetting.getValue(),
|
||||||
noiseSuppression,
|
noiseSuppression: noiseSuppressionSetting.getValue(),
|
||||||
|
autoGainControl: autoGainControlSetting.getValue(),
|
||||||
},
|
},
|
||||||
audioOutput: {
|
audioOutput: {
|
||||||
// When using controlled audio devices, we don't want to set the
|
// When using controlled audio devices, we don't want to set the
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ import {
|
|||||||
} from "../../../utils/test.ts";
|
} from "../../../utils/test.ts";
|
||||||
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
import type { ProcessorState } from "../../../livekit/TrackProcessorContext.tsx";
|
||||||
import { constant } from "../../Behavior";
|
import { constant } from "../../Behavior";
|
||||||
|
import {
|
||||||
|
echoCancellationSetting,
|
||||||
|
noiseSuppressionSetting,
|
||||||
|
autoGainControlSetting,
|
||||||
|
advancedCamera,
|
||||||
|
cameraResolution,
|
||||||
|
cameraFramerate,
|
||||||
|
cameraBitrate,
|
||||||
|
cameraCodec,
|
||||||
|
} from "../../../settings/settings.ts";
|
||||||
|
|
||||||
// At the top of your test file, after imports
|
// At the top of your test file, after imports
|
||||||
vi.mock("livekit-client", async (importOriginal) => {
|
vi.mock("livekit-client", async (importOriginal) => {
|
||||||
@@ -58,11 +68,14 @@ describe("ECConnectionFactory - Audio inputs options", () => {
|
|||||||
{ echo: false, noise: true },
|
{ echo: false, noise: true },
|
||||||
{ echo: false, noise: false },
|
{ echo: false, noise: false },
|
||||||
])(
|
])(
|
||||||
"it sets echoCancellation=$echo and noiseSuppression=$noise based on constructor parameters",
|
"it sets echoCancellation=$echo and noiseSuppression=$noise based on settings",
|
||||||
({ echo, noise }) => {
|
({ echo, noise }) => {
|
||||||
// test("it sets echoCancellation and noiseSuppression based on constructor parameters", () => {
|
|
||||||
const RoomConstructor = vi.mocked(LivekitRoom);
|
const RoomConstructor = vi.mocked(LivekitRoom);
|
||||||
|
|
||||||
|
// Set audio processing settings
|
||||||
|
echoCancellationSetting.setValue(echo);
|
||||||
|
noiseSuppressionSetting.setValue(noise);
|
||||||
|
|
||||||
const ecConnectionFactory = new ECConnectionFactory(
|
const ecConnectionFactory = new ECConnectionFactory(
|
||||||
mockClient,
|
mockClient,
|
||||||
"!roomid:example.org",
|
"!roomid:example.org",
|
||||||
@@ -73,9 +86,6 @@ describe("ECConnectionFactory - Audio inputs options", () => {
|
|||||||
}),
|
}),
|
||||||
undefined,
|
undefined,
|
||||||
false,
|
false,
|
||||||
undefined,
|
|
||||||
echo,
|
|
||||||
noise,
|
|
||||||
);
|
);
|
||||||
ecConnectionFactory.createConnection(
|
ecConnectionFactory.createConnection(
|
||||||
testScope,
|
testScope,
|
||||||
@@ -101,9 +111,13 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => {
|
|||||||
test.each([{ controlled: true }, { controlled: false }])(
|
test.each([{ controlled: true }, { controlled: false }])(
|
||||||
"it sets controlledAudioDevice=$controlled then uses deviceId accordingly",
|
"it sets controlledAudioDevice=$controlled then uses deviceId accordingly",
|
||||||
({ controlled }) => {
|
({ controlled }) => {
|
||||||
// test("it sets echoCancellation and noiseSuppression based on constructor parameters", () => {
|
|
||||||
const RoomConstructor = vi.mocked(LivekitRoom);
|
const RoomConstructor = vi.mocked(LivekitRoom);
|
||||||
|
|
||||||
|
// Explicitly set audio settings so the test doesn't depend on defaults
|
||||||
|
echoCancellationSetting.setValue(true);
|
||||||
|
noiseSuppressionSetting.setValue(true);
|
||||||
|
autoGainControlSetting.setValue(true);
|
||||||
|
|
||||||
const ecConnectionFactory = new ECConnectionFactory(
|
const ecConnectionFactory = new ECConnectionFactory(
|
||||||
mockClient,
|
mockClient,
|
||||||
"!roomid:example.org",
|
"!roomid:example.org",
|
||||||
@@ -120,9 +134,6 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => {
|
|||||||
}),
|
}),
|
||||||
undefined,
|
undefined,
|
||||||
controlled,
|
controlled,
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
ecConnectionFactory.createConnection(
|
ecConnectionFactory.createConnection(
|
||||||
testScope,
|
testScope,
|
||||||
@@ -143,6 +154,114 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("ECConnectionFactory - Camera quality settings", () => {
|
||||||
|
test("it uses default video options when advancedCamera is disabled", () => {
|
||||||
|
const RoomConstructor = vi.mocked(LivekitRoom);
|
||||||
|
advancedCamera.setValue(false);
|
||||||
|
|
||||||
|
const ecConnectionFactory = new ECConnectionFactory(
|
||||||
|
mockClient,
|
||||||
|
"!roomid:example.org",
|
||||||
|
mockMediaDevices({}),
|
||||||
|
new BehaviorSubject<ProcessorState>({
|
||||||
|
supported: true,
|
||||||
|
processor: undefined,
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
ecConnectionFactory.createConnection(
|
||||||
|
testScope,
|
||||||
|
exampleTransport,
|
||||||
|
ownMemberMock,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
|
||||||
|
// publishDefaults should use config defaults (vp8), not custom settings
|
||||||
|
expect(RoomConstructor).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
publishDefaults: expect.objectContaining({
|
||||||
|
videoCodec: "vp8",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it applies custom camera resolution, encoding, and codec when advancedCamera is enabled", () => {
|
||||||
|
const RoomConstructor = vi.mocked(LivekitRoom);
|
||||||
|
|
||||||
|
advancedCamera.setValue(true);
|
||||||
|
cameraResolution.setValue("1920x1080");
|
||||||
|
cameraFramerate.setValue(60);
|
||||||
|
cameraBitrate.setValue(4_000_000);
|
||||||
|
cameraCodec.setValue("vp9");
|
||||||
|
|
||||||
|
const ecConnectionFactory = new ECConnectionFactory(
|
||||||
|
mockClient,
|
||||||
|
"!roomid:example.org",
|
||||||
|
mockMediaDevices({}),
|
||||||
|
new BehaviorSubject<ProcessorState>({
|
||||||
|
supported: true,
|
||||||
|
processor: undefined,
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
ecConnectionFactory.createConnection(
|
||||||
|
testScope,
|
||||||
|
exampleTransport,
|
||||||
|
ownMemberMock,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(RoomConstructor).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
videoCaptureDefaults: expect.objectContaining({
|
||||||
|
resolution: { width: 1920, height: 1080, frameRate: 60 },
|
||||||
|
}),
|
||||||
|
publishDefaults: expect.objectContaining({
|
||||||
|
videoEncoding: { maxBitrate: 4_000_000, maxFramerate: 60 },
|
||||||
|
videoCodec: "vp9",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it applies autoGainControl from settings", () => {
|
||||||
|
const RoomConstructor = vi.mocked(LivekitRoom);
|
||||||
|
|
||||||
|
autoGainControlSetting.setValue(false);
|
||||||
|
echoCancellationSetting.setValue(true);
|
||||||
|
noiseSuppressionSetting.setValue(true);
|
||||||
|
|
||||||
|
const ecConnectionFactory = new ECConnectionFactory(
|
||||||
|
mockClient,
|
||||||
|
"!roomid:example.org",
|
||||||
|
mockMediaDevices({}),
|
||||||
|
new BehaviorSubject<ProcessorState>({
|
||||||
|
supported: true,
|
||||||
|
processor: undefined,
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
ecConnectionFactory.createConnection(
|
||||||
|
testScope,
|
||||||
|
exampleTransport,
|
||||||
|
ownMemberMock,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(RoomConstructor).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
audioCaptureDefaults: expect.objectContaining({
|
||||||
|
autoGainControl: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
testScope.end();
|
testScope.end();
|
||||||
fetchMock.reset();
|
fetchMock.reset();
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type RemoteTrackPublication } from "livekit-client";
|
import {
|
||||||
|
type LocalTrackPublication,
|
||||||
|
type RemoteTrackPublication,
|
||||||
|
} from "livekit-client";
|
||||||
import { test, expect } from "vitest";
|
import { test, expect } from "vitest";
|
||||||
import { act, render, screen } from "@testing-library/react";
|
import { act, render, screen } from "@testing-library/react";
|
||||||
import { axe } from "vitest-axe";
|
import { axe } from "vitest-axe";
|
||||||
@@ -17,6 +20,9 @@ import {
|
|||||||
mockRtcMembership,
|
mockRtcMembership,
|
||||||
mockRemoteMedia,
|
mockRemoteMedia,
|
||||||
mockRemoteParticipant,
|
mockRemoteParticipant,
|
||||||
|
mockLocalMedia,
|
||||||
|
mockLocalParticipant,
|
||||||
|
mockMediaDevices,
|
||||||
} from "../utils/test";
|
} from "../utils/test";
|
||||||
import { GridTileViewModel } from "../state/TileViewModel";
|
import { GridTileViewModel } from "../state/TileViewModel";
|
||||||
import { ReactionsSenderProvider } from "../reactions/useReactionsSender";
|
import { ReactionsSenderProvider } from "../reactions/useReactionsSender";
|
||||||
@@ -54,7 +60,7 @@ const callVm = {
|
|||||||
handsRaised$: constant({}),
|
handsRaised$: constant({}),
|
||||||
} as Partial<CallViewModel> as CallViewModel;
|
} as Partial<CallViewModel> as CallViewModel;
|
||||||
|
|
||||||
test("GridTile is accessible", async () => {
|
test("GridTile displays remote media", async () => {
|
||||||
const vm = mockRemoteMedia(
|
const vm = mockRemoteMedia(
|
||||||
mockRtcMembership("@alice:example.org", "AAAA"),
|
mockRtcMembership("@alice:example.org", "AAAA"),
|
||||||
{
|
{
|
||||||
@@ -88,6 +94,40 @@ test("GridTile is accessible", async () => {
|
|||||||
screen.getByText("Alice");
|
screen.getByText("Alice");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("GridTile displays local media", async () => {
|
||||||
|
const vm = mockLocalMedia(
|
||||||
|
mockRtcMembership("@alice:example.org", "AAAA"),
|
||||||
|
{
|
||||||
|
rawDisplayName: "Alice",
|
||||||
|
getMxcAvatarUrl: () => "mxc://adfsg",
|
||||||
|
},
|
||||||
|
mockLocalParticipant({
|
||||||
|
getTrackPublication: () =>
|
||||||
|
({}) as Partial<LocalTrackPublication> as LocalTrackPublication,
|
||||||
|
}),
|
||||||
|
mockMediaDevices({}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<ReactionsSenderProvider vm={callVm} rtcSession={fakeRtcSession}>
|
||||||
|
<GridTile
|
||||||
|
vm={new GridTileViewModel(constant(vm))}
|
||||||
|
onOpenProfile={() => {}}
|
||||||
|
targetWidth={300}
|
||||||
|
targetHeight={200}
|
||||||
|
showSpeakingIndicators
|
||||||
|
showNameTags
|
||||||
|
showRingingStatus
|
||||||
|
showOutline
|
||||||
|
focusable
|
||||||
|
/>
|
||||||
|
</ReactionsSenderProvider>,
|
||||||
|
);
|
||||||
|
expect(await axe(container)).toHaveNoViolations();
|
||||||
|
// Name should be visible
|
||||||
|
screen.getByText("Alice");
|
||||||
|
});
|
||||||
|
|
||||||
test("GridTile displays ringing media", async () => {
|
test("GridTile displays ringing media", async () => {
|
||||||
const pickupState$ = new BehaviorSubject<
|
const pickupState$ = new BehaviorSubject<
|
||||||
RingingMediaViewModel["pickupState$"]["value"]
|
RingingMediaViewModel["pickupState$"]["value"]
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
|
useMemo,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { type animated } from "@react-spring/web";
|
import { type animated } from "@react-spring/web";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
@@ -103,20 +104,22 @@ interface UserMediaTileProps extends TileProps {
|
|||||||
playbackMuted: boolean;
|
playbackMuted: boolean;
|
||||||
waitingForMedia?: boolean;
|
waitingForMedia?: boolean;
|
||||||
primaryButton?: ReactNode;
|
primaryButton?: ReactNode;
|
||||||
menuStart?: ReactNode;
|
|
||||||
menuEnd?: ReactNode;
|
|
||||||
focusUrl: string | undefined;
|
focusUrl: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserMediaTile: FC<UserMediaTileProps> = ({
|
/**
|
||||||
|
* A user media tile without a context menu.
|
||||||
|
*/
|
||||||
|
// The context menu is kept separate from this component for performance
|
||||||
|
// reasons (c.f. UserMediaTile)
|
||||||
|
const UserMediaTileInner: FC<UserMediaTileProps & { menu: ReactNode }> = ({
|
||||||
ref,
|
ref,
|
||||||
vm,
|
vm,
|
||||||
showSpeakingIndicators,
|
showSpeakingIndicators,
|
||||||
playbackMuted,
|
playbackMuted,
|
||||||
waitingForMedia,
|
waitingForMedia,
|
||||||
primaryButton,
|
primaryButton,
|
||||||
menuStart,
|
menu,
|
||||||
menuEnd,
|
|
||||||
className,
|
className,
|
||||||
focusUrl,
|
focusUrl,
|
||||||
displayName,
|
displayName,
|
||||||
@@ -156,24 +159,26 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
|||||||
: t("microphone_off");
|
: t("microphone_off");
|
||||||
|
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const menu = (
|
const menuTrigger = useMemo(
|
||||||
<>
|
() => (
|
||||||
{menuStart}
|
<button
|
||||||
{/*
|
aria-label={t("common.options")}
|
||||||
No additional menu item (used to be the manual fit to frame.
|
tabIndex={focusable ? undefined : -1}
|
||||||
Placeholder for future menu items that should be placed here.
|
>
|
||||||
*/}
|
<OverflowHorizontalIcon aria-hidden width={20} height={20} />
|
||||||
{menuEnd}
|
</button>
|
||||||
</>
|
),
|
||||||
|
[t, focusable],
|
||||||
);
|
);
|
||||||
|
|
||||||
const raisedHandOnClick = vm.local
|
const raisedHandOnClick = useMemo(
|
||||||
? (): void => void toggleRaisedHand()
|
() => (vm.local ? (): void => void toggleRaisedHand() : undefined),
|
||||||
: undefined;
|
[vm.local, toggleRaisedHand],
|
||||||
|
);
|
||||||
|
|
||||||
const showSpeaking = showSpeakingIndicators && speaking;
|
const showSpeaking = showSpeakingIndicators && speaking;
|
||||||
|
|
||||||
const tile = (
|
return (
|
||||||
<MediaView
|
<MediaView
|
||||||
ref={ref}
|
ref={ref}
|
||||||
video={video}
|
video={video}
|
||||||
@@ -202,14 +207,7 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
|||||||
open={menuOpen}
|
open={menuOpen}
|
||||||
onOpenChange={setMenuOpen}
|
onOpenChange={setMenuOpen}
|
||||||
title={displayName}
|
title={displayName}
|
||||||
trigger={
|
trigger={menuTrigger}
|
||||||
<button
|
|
||||||
aria-label={t("common.options")}
|
|
||||||
tabIndex={focusable ? undefined : -1}
|
|
||||||
>
|
|
||||||
<OverflowHorizontalIcon aria-hidden width={20} height={20} />
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
side="left"
|
side="left"
|
||||||
align="start"
|
align="start"
|
||||||
>
|
>
|
||||||
@@ -231,9 +229,37 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user media tile enhanced with a context menu.
|
||||||
|
*/
|
||||||
|
const UserMediaTile: FC<
|
||||||
|
UserMediaTileProps & { menuStart?: ReactNode; menuEnd?: ReactNode }
|
||||||
|
> = ({ menuStart, menuEnd, ...props }) => {
|
||||||
|
const menu = useMemo(
|
||||||
|
() => (
|
||||||
|
<>
|
||||||
|
{menuStart}
|
||||||
|
{/*
|
||||||
|
No additional menu item (used to be the manual fit to frame.
|
||||||
|
Placeholder for future menu items that should be placed here.
|
||||||
|
*/}
|
||||||
|
{menuEnd}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[menuStart, menuEnd],
|
||||||
|
);
|
||||||
|
|
||||||
|
// ContextMenu is expensive to render, so we avoid subscribing to any
|
||||||
|
// frequently-changing behaviors here and instead keep them isolated in the
|
||||||
|
// UserMediaTileInner component
|
||||||
return (
|
return (
|
||||||
<ContextMenu title={displayName} trigger={tile} hasAccessibleAlternative>
|
<ContextMenu
|
||||||
|
title={props.displayName}
|
||||||
|
trigger={<UserMediaTileInner {...props} menu={menu} />}
|
||||||
|
hasAccessibleAlternative
|
||||||
|
>
|
||||||
{menu}
|
{menu}
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
@@ -269,6 +295,29 @@ const LocalUserMediaTile: FC<LocalUserMediaTileProps> = ({
|
|||||||
[vm, latestAlwaysShow],
|
[vm, latestAlwaysShow],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const menuStart = useMemo(
|
||||||
|
() => (
|
||||||
|
<ToggleMenuItem
|
||||||
|
Icon={VisibilityOnIcon}
|
||||||
|
label={t("video_tile.always_show")}
|
||||||
|
checked={alwaysShow}
|
||||||
|
onSelect={onSelectAlwaysShow}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[t, alwaysShow, onSelectAlwaysShow],
|
||||||
|
);
|
||||||
|
const menuEnd = useMemo(
|
||||||
|
() =>
|
||||||
|
onOpenProfile && (
|
||||||
|
<MenuItem
|
||||||
|
Icon={UserProfileIcon}
|
||||||
|
label={t("common.profile")}
|
||||||
|
onSelect={onOpenProfile}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[t, onOpenProfile],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserMediaTile
|
<UserMediaTile
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -287,23 +336,8 @@ const LocalUserMediaTile: FC<LocalUserMediaTileProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
menuStart={
|
menuStart={menuStart}
|
||||||
<ToggleMenuItem
|
menuEnd={menuEnd}
|
||||||
Icon={VisibilityOnIcon}
|
|
||||||
label={t("video_tile.always_show")}
|
|
||||||
checked={alwaysShow}
|
|
||||||
onSelect={onSelectAlwaysShow}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
menuEnd={
|
|
||||||
onOpenProfile && (
|
|
||||||
<MenuItem
|
|
||||||
Icon={UserProfileIcon}
|
|
||||||
label={t("common.profile")}
|
|
||||||
onSelect={onOpenProfile}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
focusable={focusable}
|
focusable={focusable}
|
||||||
focusUrl={focusUrl}
|
focusUrl={focusUrl}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { i18nKey } from "./i18n";
|
|||||||
|
|
||||||
export enum ErrorCode {
|
export enum ErrorCode {
|
||||||
/**
|
/**
|
||||||
* Configuration problem due to no MatrixRTC backend/SFU is exposed via .well-known and no fallback configured.
|
* Configuration problem due to no MatrixRTC transport provided by homeserver and no fallback configured.
|
||||||
*/
|
*/
|
||||||
MISSING_MATRIX_RTC_TRANSPORT = "MISSING_MATRIX_RTC_TRANSPORT",
|
MISSING_MATRIX_RTC_TRANSPORT = "MISSING_MATRIX_RTC_TRANSPORT",
|
||||||
CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR",
|
CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR",
|
||||||
@@ -67,7 +67,7 @@ export class ElementCallError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration problem due to no MatrixRTC backend/SFU is exposed via .well-known and no fallback configured.
|
* Configuration problem due to no MatrixRTC transport provided by homeserver and no fallback configured.
|
||||||
*/
|
*/
|
||||||
export class MatrixRTCTransportMissingError extends ElementCallError {
|
export class MatrixRTCTransportMissingError extends ElementCallError {
|
||||||
public domain: string;
|
public domain: string;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const createRoomWidgetClientSpy = vi.mocked(createRoomWidgetClient);
|
|||||||
vi.mock("./config/Config", () => ({
|
vi.mock("./config/Config", () => ({
|
||||||
Config: {
|
Config: {
|
||||||
init: vi.fn().mockImplementation(async () => Promise.resolve()),
|
init: vi.fn().mockImplementation(async () => Promise.resolve()),
|
||||||
|
get: vi.fn().mockReturnValue({}),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
const configInitSpy = vi.mocked(Config.init);
|
const configInitSpy = vi.mocked(Config.init);
|
||||||
@@ -109,6 +110,7 @@ describe("widget", () => {
|
|||||||
sendToDevice: sendRecvToDevice,
|
sendToDevice: sendRecvToDevice,
|
||||||
receiveToDevice: sendRecvToDevice,
|
receiveToDevice: sendRecvToDevice,
|
||||||
turnServers: false,
|
turnServers: false,
|
||||||
|
rtcTransports: true,
|
||||||
sendDelayedEvents: true,
|
sendDelayedEvents: true,
|
||||||
updateDelayedEvents: true,
|
updateDelayedEvents: true,
|
||||||
sendSticky: true,
|
sendSticky: true,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import type { IWidgetApiRequest } from "matrix-widget-api";
|
|||||||
import { LazyEventEmitter } from "./LazyEventEmitter";
|
import { LazyEventEmitter } from "./LazyEventEmitter";
|
||||||
import { getUrlParams } from "./UrlParams";
|
import { getUrlParams } from "./UrlParams";
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
|
import { seedSettingsFromConfig } from "./settings/settings";
|
||||||
import { ElementCallReactionEventType } from "./reactions";
|
import { ElementCallReactionEventType } from "./reactions";
|
||||||
|
|
||||||
// Subset of the actions in element-web
|
// Subset of the actions in element-web
|
||||||
@@ -173,6 +174,7 @@ export const initializeWidget = (
|
|||||||
sendToDevice: sendRecvToDevice,
|
sendToDevice: sendRecvToDevice,
|
||||||
receiveToDevice: sendRecvToDevice,
|
receiveToDevice: sendRecvToDevice,
|
||||||
turnServers: false,
|
turnServers: false,
|
||||||
|
rtcTransports: true,
|
||||||
sendDelayedEvents: true,
|
sendDelayedEvents: true,
|
||||||
updateDelayedEvents: true,
|
updateDelayedEvents: true,
|
||||||
sendSticky: true,
|
sendSticky: true,
|
||||||
@@ -195,7 +197,8 @@ export const initializeWidget = (
|
|||||||
// Wait for the config file to be ready (we load very early on so it might not
|
// Wait for the config file to be ready (we load very early on so it might not
|
||||||
// be otherwise)
|
// be otherwise)
|
||||||
await Config.init();
|
await Config.init();
|
||||||
await client.startClient({ clientWellKnownPollPeriod: 60 * 10 });
|
seedSettingsFromConfig(Config.get().media_quality);
|
||||||
|
await client.startClient();
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user