diff --git a/dev-backend-docker-compose.yml b/dev-backend-docker-compose.yml index 50498c7a4..27b22b4ee 100644 --- a/dev-backend-docker-compose.yml +++ b/dev-backend-docker-compose.yml @@ -88,7 +88,7 @@ services: synapse: hostname: homeserver - image: docker.io/matrixdotorg/synapse:latest + image: ghcr.io/element-hq/synapse:msc4354-5 pull_policy: always environment: - SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml @@ -106,7 +106,7 @@ services: synapse-1: hostname: homeserver-1 - image: docker.io/matrixdotorg/synapse:latest + image: ghcr.io/element-hq/synapse:msc4354-5 pull_policy: always environment: - SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml diff --git a/docs/url-params.md b/docs/url-params.md index b2af8416f..34485d46f 100644 --- a/docs/url-params.md +++ b/docs/url-params.md @@ -72,6 +72,7 @@ These parameters are relevant to both [widget](./embedded-standalone.md) and [st | `sendNotificationType` | `ring` or `notification` | No | No | Will send a "ring" or "notification" `m.rtc.notification` event if the user is the first one in the call. | | `autoLeaveWhenOthersLeft` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Whether the app should automatically leave the call when there is no one left in the call. | | `waitForCallPickup` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | When sending a notification, show UI that the app is awaiting an answer, play a dial tone, and (in widget mode) auto-close the widget once the notification expires. | +| `multiSFU` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Enables experimental new multiSFU support. | ### Widget-only parameters diff --git a/locales/en/app.json b/locales/en/app.json index 11267439f..cebb1b38e 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -72,12 +72,11 @@ "livekit_server_info": "LiveKit Server Info", "livekit_sfu": "LiveKit SFU: {{url}}", "matrix_id": "Matrix ID: {{id}}", - "multi_sfu": "Multi-SFU media transport", - "mute_all_audio": "Mute all audio (participants, reactions, join sounds)", - "prefer_sticky_events": { - "description": "Improves reliability of calls (requires homeserver support)", - "label": "Prefer sticky events" + "multi_sfu": { + "description": "Allows multiple SFUs to be present in a call (requires homeserver support)", + "label": "Multi-SFU media transport" }, + "mute_all_audio": "Mute all audio (participants, reactions, join sounds)", "show_connection_stats": "Show connection statistics", "url_params": "URL parameters" }, diff --git a/package.json b/package.json index 35468c21f..795e5fe4d 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "livekit-client": "^2.13.0", "lodash-es": "^4.17.21", "loglevel": "^1.9.1", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#head=toger5/sticky-events&commit=e7f5bec51b6f70501a025b79fe5021c933385b21", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#head=hs/rtc-slots&commit=c56fe525aa4dcb8c3b8629dc303b95f4dd4988bd", "matrix-widget-api": "^1.13.0", "normalize.css": "^8.0.1", "observable-hooks": "^4.2.3", diff --git a/src/UrlParams.ts b/src/UrlParams.ts index 4eb692986..873e4d9b4 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -141,6 +141,11 @@ export interface UrlProperties { * can be "light", "dark", "light-high-contrast" or "dark-high-contrast". */ theme: string | null; + /** + * Whether or not the call should be held using the sticky event implementation, + * where possible. + */ + multiSFU: boolean; } /** @@ -501,6 +506,7 @@ export const computeUrlParams = (search = "", hash = ""): UrlParams => { sentryDsn: parser.getParam("sentryDsn"), sentryEnvironment: parser.getParam("sentryEnvironment"), e2eEnabled: parser.getFlagParam("enableE2EE", true), + multiSFU: parser.getFlagParam("multiSFU", false), }; const configuration: Partial = { diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index 08c22557c..28178cfb0 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -29,7 +29,6 @@ import { multiSfu as multiSfuSetting, muteAllAudio as muteAllAudioSetting, alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting, - preferStickyEvents as preferStickyEventsSetting, } from "./settings"; import type { Room as LivekitRoom } from "livekit-client"; import styles from "./DeveloperSettingsTab.module.css"; @@ -59,10 +58,6 @@ export const DeveloperSettingsTab: FC = ({ client, livekitRooms }) => { }); }, [client]); - const [preferStickyEvents, setPreferStickyEvents] = useSetting( - preferStickyEventsSetting, - ); - const [showConnectionStats, setShowConnectionStats] = useSetting( showConnectionStatsSetting, ); @@ -146,22 +141,6 @@ export const DeveloperSettingsTab: FC = ({ client, livekitRooms }) => { } /> - - ): void => { - setPreferStickyEvents(event.target.checked); - }, - [setPreferStickyEvents], - )} - /> - = ({ client, livekitRooms }) => { ): void => { setMultiSfu(event.target.checked); diff --git a/src/settings/settings.ts b/src/settings/settings.ts index b58db9837..6d1f7ff29 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -83,11 +83,6 @@ export const showConnectionStats = new Setting( false, ); -export const preferStickyEvents = new Setting( - "prefer-sticky-events", - false, -); - export const audioInput = new Setting( "audio-input", undefined, diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index d7735b260..fcdce6b20 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -91,7 +91,6 @@ import { duplicateTiles, multiSfu, playReactionsSound, - preferStickyEvents, showReactions, } from "../settings/settings"; import { isFirefox } from "../Platform"; @@ -282,22 +281,13 @@ export class CallViewModel { remote: { membership: CallMembership; transport: LivekitTransport }[]; preferred: Async; multiSfu: boolean; - preferStickyEvents: boolean; } | null> = this.scope.behavior( this.joined$.pipe( switchMap((joined) => joined ? combineLatest( - [ - this.preferredTransport$, - this.memberships$, - multiSfu.value$, - preferStickyEvents.value$, - ], - (preferred, memberships, preferMultiSfu, preferStickyEvents) => { - // Multi-SFU must be implicitly enabled when using sticky events - const multiSfu = preferStickyEvents || preferMultiSfu; - + [this.preferredTransport$, this.memberships$, multiSfu.value$], + (preferred, memberships, multiSfu) => { const oldestMembership = this.matrixRTCSession.getOldestMembership(); const remote = memberships.flatMap((m) => { @@ -333,7 +323,6 @@ export class CallViewModel { remote, preferred, multiSfu, - preferStickyEvents, }; }, ) @@ -368,7 +357,6 @@ export class CallViewModel { */ private readonly advertisedTransport$: Behavior<{ multiSfu: boolean; - preferStickyEvents: boolean; transport: LivekitTransport; } | null> = this.scope.behavior( this.transports$.pipe( @@ -377,7 +365,6 @@ export class CallViewModel { transports.preferred.state === "ready" ? { multiSfu: transports.multiSfu, - preferStickyEvents: transports.preferStickyEvents, // In non-multi-SFU mode we should always advertise the preferred // SFU to minimize the number of membership updates transport: transports.multiSfu @@ -388,7 +375,6 @@ export class CallViewModel { ), distinctUntilChanged<{ multiSfu: boolean; - preferStickyEvents: boolean; transport: LivekitTransport; } | null>(deepCompare), ), @@ -1834,7 +1820,8 @@ export class CallViewModel { await enterRTCSession(this.matrixRTCSession, advertised.transport, { encryptMedia: this.options.encryptionSystem.kind !== E2eeType.NONE, useMultiSfu: advertised.multiSfu, - preferStickyEvents: advertised.preferStickyEvents, + // Multi-SFU enables sticky events. + preferStickyEvents: advertised.multiSfu ?? this.urlParams.multiSFU, }); } catch (e) { logger.error("Error entering RTC session", e); diff --git a/src/utils/matrix.ts b/src/utils/matrix.ts index 0a2b5c1a5..2292c3061 100644 --- a/src/utils/matrix.ts +++ b/src/utils/matrix.ts @@ -13,6 +13,7 @@ import { MemoryStore, Preset, Visibility, + EventType, } from "matrix-js-sdk"; import { type ISyncStateData, type SyncState } from "matrix-js-sdk/lib/sync"; import { logger } from "matrix-js-sdk/lib/logger"; @@ -28,6 +29,10 @@ import { type EncryptionSystem, saveKeyForRoom, } from "../e2ee/sharedKeyManagement"; +import { + DefaultCallApplicationSlot, + RtcSlotEventContent, +} from "matrix-js-sdk/lib/matrixrtc"; export const fallbackICEServerAllowed = import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true"; @@ -236,6 +241,16 @@ export async function createRoom( preset: Preset.PublicChat, name, room_alias_name: e2ee ? undefined : roomAliasLocalpartFromRoomName(name), + initial_state: [ + // Always create a slot + { + type: EventType.RTCSlot, + content: { + slot_id: DefaultCallApplicationSlot.slot_id, + application: { ...DefaultCallApplicationSlot.application }, + } satisfies RtcSlotEventContent, + }, + ], power_level_content_override: { invite: 100, kick: 100, diff --git a/yarn.lock b/yarn.lock index e78dbbf21..0185e6687 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7545,7 +7545,7 @@ __metadata: livekit-client: "npm:^2.13.0" lodash-es: "npm:^4.17.21" loglevel: "npm:^1.9.1" - matrix-js-sdk: "github:matrix-org/matrix-js-sdk#head=toger5/sticky-events&commit=e7f5bec51b6f70501a025b79fe5021c933385b21" + matrix-js-sdk: "github:matrix-org/matrix-js-sdk#head=hs/rtc-slots&commit=c56fe525aa4dcb8c3b8629dc303b95f4dd4988bd" matrix-widget-api: "npm:^1.13.0" normalize.css: "npm:^8.0.1" observable-hooks: "npm:^4.2.3" @@ -10343,9 +10343,9 @@ __metadata: languageName: node linkType: hard -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=toger5/sticky-events&commit=e7f5bec51b6f70501a025b79fe5021c933385b21": - version: 38.4.0 - resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=e7f5bec51b6f70501a025b79fe5021c933385b21" +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=hs/rtc-slots&commit=c56fe525aa4dcb8c3b8629dc303b95f4dd4988bd": + version: 39.0.0 + resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=c56fe525aa4dcb8c3b8629dc303b95f4dd4988bd" dependencies: "@babel/runtime": "npm:^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm": "npm:^15.3.0" @@ -10358,10 +10358,10 @@ __metadata: matrix-widget-api: "npm:^1.10.0" oidc-client-ts: "npm:^3.0.1" p-retry: "npm:7" - sdp-transform: "npm:^2.14.1" + sdp-transform: "npm:^3.0.0" unhomoglyph: "npm:^1.0.6" uuid: "npm:13" - checksum: 10c0/7adffdc183affd2d3ee1e8497cad6ca7904a37f98328ff7bc15aa6c1829dc9f9a92f8e1bd6260432a33626ff2a839644de938270163e73438b7294675cd954e4 + checksum: 10c0/f8267c2a1fac67076400f886259d9b58b597f975865ce49aefe774d2f56a5c7caef66307f02fe24dcad35829e747bd7e563ecbb9a694d1f80b2439eebe0724fa languageName: node linkType: hard @@ -12544,7 +12544,7 @@ __metadata: languageName: node linkType: hard -"sdp-transform@npm:^2.14.1, sdp-transform@npm:^2.15.0": +"sdp-transform@npm:^2.15.0": version: 2.15.0 resolution: "sdp-transform@npm:2.15.0" bin: @@ -12553,6 +12553,15 @@ __metadata: languageName: node linkType: hard +"sdp-transform@npm:^3.0.0": + version: 3.0.0 + resolution: "sdp-transform@npm:3.0.0" + bin: + sdp-verify: checker.js + checksum: 10c0/828a4595041ba64c86b29075aa4007ab384519b1fa29882db59ccb83b54b2b2a33b60848293f8da537fe151c52f5844fc17c8325396cac309fb19e2e81ec5bf4 + languageName: node + linkType: hard + "sdp@npm:^3.2.0": version: 3.2.0 resolution: "sdp@npm:3.2.0"