Add sticky event support (#3513)
* add sticky event support - use new js-sdk - use custom synapse - don't filter rooms by existing call state events Signed-off-by: Timo K <toger5@hotmail.de> * enable sticky events in the joinSessionConfig Signed-off-by: Timo K <toger5@hotmail.de> * Remove unused useNewMembershipmanager setting * Add prefer sticky setting] * Fixup call detection logic to allow sticky events * lint * update docker image * More tidy * update checksum * bump js-sdk fix sticky events type Signed-off-by: Timo K <toger5@hotmail.de> * fix demo Signed-off-by: Timo K <toger5@hotmail.de> * always use multi sfu if we are using sticky events. Signed-off-by: Timo K <toger5@hotmail.de> * review Signed-off-by: Timo K <toger5@hotmail.de> * lint Signed-off-by: Timo K <toger5@hotmail.de> * Always consider multi-SFU mode enabled when using sticky events CallViewModel would pass the wrong transport to enterRtcSession when the user enabled sticky events but didn't manually enable multi-SFU mode as well. This likely would've added some confusion to our attempts to test these modes. * Fix test type errors * add todo comment Signed-off-by: Timo K <toger5@hotmail.de> --------- Signed-off-by: Timo K <toger5@hotmail.de> Co-authored-by: Half-Shot <will@half-shot.uk> Co-authored-by: Robin <robin@robin.town>
This commit is contained in:
@@ -38,6 +38,8 @@ experimental_features:
|
|||||||
# MSC4222 needed for syncv2 state_after. This allow clients to
|
# MSC4222 needed for syncv2 state_after. This allow clients to
|
||||||
# correctly track the state of the room.
|
# correctly track the state of the room.
|
||||||
msc4222_enabled: true
|
msc4222_enabled: true
|
||||||
|
# sticky events for matrixRTC user state
|
||||||
|
msc4354_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
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ services:
|
|||||||
|
|
||||||
synapse:
|
synapse:
|
||||||
hostname: homeserver
|
hostname: homeserver
|
||||||
image: docker.io/matrixdotorg/synapse:latest
|
image: ghcr.io/element-hq/synapse:msc4354-5
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
environment:
|
environment:
|
||||||
- SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml
|
- SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml
|
||||||
|
|||||||
@@ -74,9 +74,12 @@
|
|||||||
"matrix_id": "Matrix ID: {{id}}",
|
"matrix_id": "Matrix ID: {{id}}",
|
||||||
"multi_sfu": "Multi-SFU media transport",
|
"multi_sfu": "Multi-SFU media transport",
|
||||||
"mute_all_audio": "Mute all audio (participants, reactions, join sounds)",
|
"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"
|
||||||
|
},
|
||||||
"show_connection_stats": "Show connection statistics",
|
"show_connection_stats": "Show connection statistics",
|
||||||
"url_params": "URL parameters",
|
"url_params": "URL parameters",
|
||||||
"use_new_membership_manager": "Use the new implementation of the call MembershipManager",
|
|
||||||
"use_to_device_key_transport": "Use to device key transport. This will fallback to room key transport when another call member sent a room key"
|
"use_to_device_key_transport": "Use to device key transport. This will fallback to room key transport when another call member sent a room key"
|
||||||
},
|
},
|
||||||
"disconnected_banner": "Connectivity to the server has been lost.",
|
"disconnected_banner": "Connectivity to the server has been lost.",
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
"livekit-client": "^2.13.0",
|
"livekit-client": "^2.13.0",
|
||||||
"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#head=develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#head=toger5/sticky-events&commit=e7f5bec51b6f70501a025b79fe5021c933385b21",
|
||||||
"matrix-widget-api": "^1.13.0",
|
"matrix-widget-api": "^1.13.0",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"observable-hooks": "^4.2.3",
|
"observable-hooks": "^4.2.3",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
MatrixRTCSessionManagerEvents,
|
MatrixRTCSessionManagerEvents,
|
||||||
type MatrixRTCSession,
|
type MatrixRTCSession,
|
||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
} from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
|
||||||
|
|
||||||
import { getKeyForRoom } from "../e2ee/sharedKeyManagement";
|
import { getKeyForRoom } from "../e2ee/sharedKeyManagement";
|
||||||
|
|
||||||
@@ -114,19 +113,49 @@ const roomIsJoinable = (room: Room): boolean => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a given room has call events in it, and therefore
|
||||||
|
* is likely to be a call room.
|
||||||
|
* @param room The Matrix room instance.
|
||||||
|
* @returns `true` if the room has call events.
|
||||||
|
*/
|
||||||
const roomHasCallMembershipEvents = (room: Room): boolean => {
|
const roomHasCallMembershipEvents = (room: Room): boolean => {
|
||||||
switch (room.getMyMembership()) {
|
// Check our room membership first, to rule out any rooms
|
||||||
case KnownMembership.Join:
|
// we can't have a call in.
|
||||||
return !!room
|
const myMembership = room.getMyMembership();
|
||||||
.getLiveTimeline()
|
if (myMembership === KnownMembership.Knock) {
|
||||||
.getState(EventTimeline.FORWARDS)
|
// Assume that a room you've knocked on is able to hold calls
|
||||||
?.events?.get(EventType.GroupCallMemberPrefix);
|
return true;
|
||||||
case KnownMembership.Knock:
|
} else if (myMembership !== KnownMembership.Join) {
|
||||||
// Assume that a room you've knocked on is able to hold calls
|
// Otherwise, non-joined rooms should never show up.
|
||||||
return true;
|
return false;
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Legacy member state checks (cheaper to check.)
|
||||||
|
const timeline = room.getLiveTimeline();
|
||||||
|
if (
|
||||||
|
timeline
|
||||||
|
.getState(EventTimeline.FORWARDS)
|
||||||
|
?.events?.has(EventType.GroupCallMemberPrefix)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for *active* calls using sticky events.
|
||||||
|
for (const sticky of room._unstable_getStickyEvents()) {
|
||||||
|
if (sticky.getType() === EventType.RTCMembership) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, check recent event history to see if anyone had
|
||||||
|
// sent a call membership in here.
|
||||||
|
return timeline.getEvents().some(
|
||||||
|
(e) =>
|
||||||
|
// Membership events only count if both of these are true
|
||||||
|
e.unstableStickyInfo && e.getType() === EventType.GroupCallMemberPrefix,
|
||||||
|
);
|
||||||
|
// Otherwise, it's *unlikely* this room was ever a call.
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
||||||
@@ -140,24 +169,22 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
|||||||
.filter(roomHasCallMembershipEvents)
|
.filter(roomHasCallMembershipEvents)
|
||||||
.filter(roomIsJoinable);
|
.filter(roomIsJoinable);
|
||||||
const sortedRooms = sortRooms(client, rooms);
|
const sortedRooms = sortRooms(client, rooms);
|
||||||
Promise.all(
|
const items = sortedRooms.map((room) => {
|
||||||
sortedRooms.map((room) => {
|
const session = client.matrixRTC.getRoomSession(room);
|
||||||
const session = client.matrixRTC.getRoomSession(room);
|
return {
|
||||||
return {
|
roomAlias: room.getCanonicalAlias() ?? undefined,
|
||||||
roomAlias: room.getCanonicalAlias() ?? undefined,
|
roomName: room.name,
|
||||||
roomName: room.name,
|
avatarUrl: room.getMxcAvatarUrl()!,
|
||||||
avatarUrl: room.getMxcAvatarUrl()!,
|
room,
|
||||||
room,
|
session,
|
||||||
session,
|
participants: session.memberships
|
||||||
participants: session.memberships
|
.filter((m) => m.sender)
|
||||||
.filter((m) => m.userId)
|
.map((m) => room.getMember(m.sender!))
|
||||||
.map((m) => room.getMember(m.userId!))
|
.filter((m) => m) as RoomMember[],
|
||||||
.filter((m) => m) as RoomMember[],
|
};
|
||||||
};
|
});
|
||||||
}),
|
|
||||||
)
|
setRooms(items);
|
||||||
.then((items) => setRooms(items))
|
|
||||||
.catch(logger.error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRooms();
|
updateRooms();
|
||||||
|
|||||||
@@ -70,10 +70,6 @@ import {
|
|||||||
UnknownCallError,
|
UnknownCallError,
|
||||||
} from "../utils/errors.ts";
|
} from "../utils/errors.ts";
|
||||||
import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary.tsx";
|
import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary.tsx";
|
||||||
import {
|
|
||||||
useNewMembershipManager as useNewMembershipManagerSetting,
|
|
||||||
useSetting,
|
|
||||||
} from "../settings/settings";
|
|
||||||
import { useTypedEventEmitter } from "../useEvents";
|
import { useTypedEventEmitter } from "../useEvents";
|
||||||
import { muteAllAudio$ } from "../state/MuteAllAudioModel.ts";
|
import { muteAllAudio$ } from "../state/MuteAllAudioModel.ts";
|
||||||
import { useAppBarTitle } from "../AppBar.tsx";
|
import { useAppBarTitle } from "../AppBar.tsx";
|
||||||
@@ -186,7 +182,6 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
password: passwordFromUrl,
|
password: passwordFromUrl,
|
||||||
} = useUrlParams();
|
} = useUrlParams();
|
||||||
const e2eeSystem = useRoomEncryptionSystem(room.roomId);
|
const e2eeSystem = useRoomEncryptionSystem(room.roomId);
|
||||||
const [useNewMembershipManager] = useSetting(useNewMembershipManagerSetting);
|
|
||||||
|
|
||||||
// Save the password once we start the groupCallView
|
// Save the password once we start the groupCallView
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -310,7 +305,6 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
mediaDevices,
|
mediaDevices,
|
||||||
latestMuteStates,
|
latestMuteStates,
|
||||||
setJoined,
|
setJoined,
|
||||||
useNewMembershipManager,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// TODO refactor this + "joined" to just one callState
|
// TODO refactor this + "joined" to just one callState
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ test("It joins the correct Session", async () => {
|
|||||||
{
|
{
|
||||||
encryptMedia: true,
|
encryptMedia: true,
|
||||||
useMultiSfu: USE_MUTI_SFU,
|
useMultiSfu: USE_MUTI_SFU,
|
||||||
|
preferStickyEvents: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -111,7 +112,6 @@ test("It joins the correct Session", async () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
manageMediaKeys: true,
|
manageMediaKeys: true,
|
||||||
useLegacyMemberEvents: false,
|
useLegacyMemberEvents: false,
|
||||||
useNewMembershipManager: true,
|
|
||||||
useExperimentalToDeviceTransport: false,
|
useExperimentalToDeviceTransport: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -197,6 +197,7 @@ test("It should not fail with configuration error if homeserver config has livek
|
|||||||
{
|
{
|
||||||
encryptMedia: true,
|
encryptMedia: true,
|
||||||
useMultiSfu: USE_MUTI_SFU,
|
useMultiSfu: USE_MUTI_SFU,
|
||||||
|
preferStickyEvents: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -99,12 +99,11 @@ export async function makeTransport(
|
|||||||
|
|
||||||
export interface EnterRTCSessionOptions {
|
export interface EnterRTCSessionOptions {
|
||||||
encryptMedia: boolean;
|
encryptMedia: boolean;
|
||||||
// TODO: remove this flag, the new membership manager is stable enough
|
|
||||||
useNewMembershipManager?: boolean;
|
|
||||||
// TODO: remove this flag, to-device transport is stable enough now
|
// TODO: remove this flag, to-device transport is stable enough now
|
||||||
useExperimentalToDeviceTransport?: boolean;
|
useExperimentalToDeviceTransport?: boolean;
|
||||||
/** EXPERIMENTAL: If true, will use the multi-sfu codepath where each member connects to its SFU instead of everyone connecting to an elected on. */
|
/** EXPERIMENTAL: If true, will use the multi-sfu codepath where each member connects to its SFU instead of everyone connecting to an elected on. */
|
||||||
useMultiSfu?: boolean;
|
useMultiSfu: boolean;
|
||||||
|
preferStickyEvents: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,20 +115,13 @@ export interface EnterRTCSessionOptions {
|
|||||||
export async function enterRTCSession(
|
export async function enterRTCSession(
|
||||||
rtcSession: MatrixRTCSession,
|
rtcSession: MatrixRTCSession,
|
||||||
transport: LivekitTransport,
|
transport: LivekitTransport,
|
||||||
options: EnterRTCSessionOptions = {
|
{
|
||||||
encryptMedia: true,
|
|
||||||
useNewMembershipManager: true,
|
|
||||||
useExperimentalToDeviceTransport: false,
|
|
||||||
useMultiSfu: true,
|
|
||||||
},
|
|
||||||
): Promise<void> {
|
|
||||||
const {
|
|
||||||
encryptMedia,
|
encryptMedia,
|
||||||
useNewMembershipManager = true,
|
|
||||||
useExperimentalToDeviceTransport = false,
|
useExperimentalToDeviceTransport = false,
|
||||||
useMultiSfu = true,
|
useMultiSfu,
|
||||||
} = options;
|
preferStickyEvents,
|
||||||
|
}: EnterRTCSessionOptions,
|
||||||
|
): Promise<void> {
|
||||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||||
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
||||||
|
|
||||||
@@ -148,7 +140,6 @@ export async function enterRTCSession(
|
|||||||
{
|
{
|
||||||
notificationType,
|
notificationType,
|
||||||
callIntent,
|
callIntent,
|
||||||
useNewMembershipManager,
|
|
||||||
manageMediaKeys: encryptMedia,
|
manageMediaKeys: encryptMedia,
|
||||||
...(useDeviceSessionMemberEvents !== undefined && {
|
...(useDeviceSessionMemberEvents !== undefined && {
|
||||||
useLegacyMemberEvents: !useDeviceSessionMemberEvents,
|
useLegacyMemberEvents: !useDeviceSessionMemberEvents,
|
||||||
@@ -164,6 +155,7 @@ export async function enterRTCSession(
|
|||||||
membershipEventExpiryMs:
|
membershipEventExpiryMs:
|
||||||
matrixRtcSessionConfig?.membership_event_expiry_ms,
|
matrixRtcSessionConfig?.membership_event_expiry_ms,
|
||||||
useExperimentalToDeviceTransport,
|
useExperimentalToDeviceTransport,
|
||||||
|
unstableSendStickyEvents: preferStickyEvents,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (widget) {
|
if (widget) {
|
||||||
|
|||||||
@@ -5,8 +5,20 @@ 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 ChangeEvent, type FC, useCallback, useMemo } from "react";
|
import {
|
||||||
|
type ChangeEvent,
|
||||||
|
type FC,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
UNSTABLE_MSC4354_STICKY_EVENTS,
|
||||||
|
type MatrixClient,
|
||||||
|
} from "matrix-js-sdk";
|
||||||
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
import { FieldRow, InputField } from "../input/Input";
|
import { FieldRow, InputField } from "../input/Input";
|
||||||
import {
|
import {
|
||||||
@@ -14,16 +26,16 @@ import {
|
|||||||
duplicateTiles as duplicateTilesSetting,
|
duplicateTiles as duplicateTilesSetting,
|
||||||
debugTileLayout as debugTileLayoutSetting,
|
debugTileLayout as debugTileLayoutSetting,
|
||||||
showConnectionStats as showConnectionStatsSetting,
|
showConnectionStats as showConnectionStatsSetting,
|
||||||
useNewMembershipManager as useNewMembershipManagerSetting,
|
|
||||||
useExperimentalToDeviceTransport as useExperimentalToDeviceTransportSetting,
|
useExperimentalToDeviceTransport as useExperimentalToDeviceTransportSetting,
|
||||||
multiSfu as multiSfuSetting,
|
multiSfu as multiSfuSetting,
|
||||||
muteAllAudio as muteAllAudioSetting,
|
muteAllAudio as muteAllAudioSetting,
|
||||||
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
||||||
|
preferStickyEvents as preferStickyEventsSetting,
|
||||||
} from "./settings";
|
} from "./settings";
|
||||||
import type { MatrixClient } from "matrix-js-sdk";
|
|
||||||
import type { Room as LivekitRoom } from "livekit-client";
|
import type { Room as LivekitRoom } from "livekit-client";
|
||||||
import styles from "./DeveloperSettingsTab.module.css";
|
import styles from "./DeveloperSettingsTab.module.css";
|
||||||
import { useUrlParams } from "../UrlParams";
|
import { useUrlParams } from "../UrlParams";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
livekitRooms?: { room: LivekitRoom; url: string; isLocal?: boolean }[];
|
livekitRooms?: { room: LivekitRoom; url: string; isLocal?: boolean }[];
|
||||||
@@ -36,12 +48,24 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRooms }) => {
|
|||||||
debugTileLayoutSetting,
|
debugTileLayoutSetting,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [showConnectionStats, setShowConnectionStats] = useSetting(
|
const [stickyEventsSupported, setStickyEventsSupported] = useState(false);
|
||||||
showConnectionStatsSetting,
|
useEffect(() => {
|
||||||
|
client
|
||||||
|
.doesServerSupportUnstableFeature(UNSTABLE_MSC4354_STICKY_EVENTS)
|
||||||
|
.then((result) => {
|
||||||
|
setStickyEventsSupported(result);
|
||||||
|
})
|
||||||
|
.catch((ex) => {
|
||||||
|
logger.warn("Failed to check if sticky events are supported", ex);
|
||||||
|
});
|
||||||
|
}, [client]);
|
||||||
|
|
||||||
|
const [preferStickyEvents, setPreferStickyEvents] = useSetting(
|
||||||
|
preferStickyEventsSetting,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [useNewMembershipManager, setNewMembershipManager] = useSetting(
|
const [showConnectionStats, setShowConnectionStats] = useSetting(
|
||||||
useNewMembershipManagerSetting,
|
showConnectionStatsSetting,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [alwaysShowIphoneEarpiece, setAlwaysShowIphoneEarpiece] = useSetting(
|
const [alwaysShowIphoneEarpiece, setAlwaysShowIphoneEarpiece] = useSetting(
|
||||||
@@ -126,6 +150,22 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRooms }) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id="preferStickyEvents"
|
||||||
|
type="checkbox"
|
||||||
|
label={t("developer_mode.prefer_sticky_events.label")}
|
||||||
|
disabled={!stickyEventsSupported}
|
||||||
|
description={t("developer_mode.prefer_sticky_events.description")}
|
||||||
|
checked={!!preferStickyEvents}
|
||||||
|
onChange={useCallback(
|
||||||
|
(event: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
setPreferStickyEvents(event.target.checked);
|
||||||
|
},
|
||||||
|
[setPreferStickyEvents],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<InputField
|
<InputField
|
||||||
id="showConnectionStats"
|
id="showConnectionStats"
|
||||||
@@ -140,20 +180,6 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRooms }) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
<FieldRow>
|
|
||||||
<InputField
|
|
||||||
id="useNewMembershipManager"
|
|
||||||
type="checkbox"
|
|
||||||
label={t("developer_mode.use_new_membership_manager")}
|
|
||||||
checked={!!useNewMembershipManager}
|
|
||||||
onChange={useCallback(
|
|
||||||
(event: ChangeEvent<HTMLInputElement>): void => {
|
|
||||||
setNewMembershipManager(event.target.checked);
|
|
||||||
},
|
|
||||||
[setNewMembershipManager],
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FieldRow>
|
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<InputField
|
<InputField
|
||||||
id="useToDeviceKeyTransport"
|
id="useToDeviceKeyTransport"
|
||||||
@@ -173,7 +199,9 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRooms }) => {
|
|||||||
id="multiSfu"
|
id="multiSfu"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
label={t("developer_mode.multi_sfu")}
|
label={t("developer_mode.multi_sfu")}
|
||||||
checked={multiSfu}
|
// If using sticky events we implicitly prefer use multi-sfu
|
||||||
|
checked={multiSfu || preferStickyEvents}
|
||||||
|
disabled={preferStickyEvents}
|
||||||
onChange={useCallback(
|
onChange={useCallback(
|
||||||
(event: ChangeEvent<HTMLInputElement>): void => {
|
(event: ChangeEvent<HTMLInputElement>): void => {
|
||||||
setMultiSfu(event.target.checked);
|
setMultiSfu(event.target.checked);
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ export const showConnectionStats = new Setting<boolean>(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const preferStickyEvents = new Setting<boolean>(
|
||||||
|
"prefer-sticky-events",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
export const audioInput = new Setting<string | undefined>(
|
export const audioInput = new Setting<string | undefined>(
|
||||||
"audio-input",
|
"audio-input",
|
||||||
undefined,
|
undefined,
|
||||||
@@ -115,11 +120,6 @@ export const soundEffectVolume = new Setting<number>(
|
|||||||
0.5,
|
0.5,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useNewMembershipManager = new Setting<boolean>(
|
|
||||||
"new-membership-manager",
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const useExperimentalToDeviceTransport = new Setting<boolean>(
|
export const useExperimentalToDeviceTransport = new Setting<boolean>(
|
||||||
"experimental-to-device-transport",
|
"experimental-to-device-transport",
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ import {
|
|||||||
duplicateTiles,
|
duplicateTiles,
|
||||||
multiSfu,
|
multiSfu,
|
||||||
playReactionsSound,
|
playReactionsSound,
|
||||||
|
preferStickyEvents,
|
||||||
showReactions,
|
showReactions,
|
||||||
} from "../settings/settings";
|
} from "../settings/settings";
|
||||||
import { isFirefox } from "../Platform";
|
import { isFirefox } from "../Platform";
|
||||||
@@ -265,23 +266,33 @@ export class CallViewModel {
|
|||||||
/**
|
/**
|
||||||
* Lists the transports used by ourselves, plus all other MatrixRTC session
|
* Lists the transports used by ourselves, plus all other MatrixRTC session
|
||||||
* members. For completeness this also lists the preferred transport and
|
* members. For completeness this also lists the preferred transport and
|
||||||
* whether we are in multi-SFU mode (because advertisedTransport$ wants to
|
* whether we are in multi-SFU mode or sticky events mode (because
|
||||||
* read them at the same time, and bundling data together when it might change
|
* advertisedTransport$ wants to read them at the same time, and bundling data
|
||||||
* together is what you have to do in RxJS to avoid reading inconsistent state
|
* together when it might change together is what you have to do in RxJS to
|
||||||
* or observing too many changes.)
|
* avoid reading inconsistent state or observing too many changes.)
|
||||||
*/
|
*/
|
||||||
|
// TODO-MULTI-SFU find a better name for this. with the addition of sticky events it's no longer just about transports.
|
||||||
private readonly transports$: Behavior<{
|
private readonly transports$: Behavior<{
|
||||||
local: Async<LivekitTransport>;
|
local: Async<LivekitTransport>;
|
||||||
remote: { membership: CallMembership; transport: LivekitTransport }[];
|
remote: { membership: CallMembership; transport: LivekitTransport }[];
|
||||||
preferred: Async<LivekitTransport>;
|
preferred: Async<LivekitTransport>;
|
||||||
multiSfu: boolean;
|
multiSfu: boolean;
|
||||||
|
preferStickyEvents: boolean;
|
||||||
} | null> = this.scope.behavior(
|
} | null> = this.scope.behavior(
|
||||||
this.joined$.pipe(
|
this.joined$.pipe(
|
||||||
switchMap((joined) =>
|
switchMap((joined) =>
|
||||||
joined
|
joined
|
||||||
? combineLatest(
|
? combineLatest(
|
||||||
[this.preferredTransport$, this.memberships$, multiSfu.value$],
|
[
|
||||||
(preferred, memberships, multiSfu) => {
|
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;
|
||||||
|
|
||||||
const oldestMembership =
|
const oldestMembership =
|
||||||
this.matrixRTCSession.getOldestMembership();
|
this.matrixRTCSession.getOldestMembership();
|
||||||
const remote = memberships.flatMap((m) => {
|
const remote = memberships.flatMap((m) => {
|
||||||
@@ -292,6 +303,7 @@ export class CallViewModel {
|
|||||||
? [{ membership: m, transport: t }]
|
? [{ membership: m, transport: t }]
|
||||||
: [];
|
: [];
|
||||||
});
|
});
|
||||||
|
|
||||||
let local = preferred;
|
let local = preferred;
|
||||||
if (!multiSfu) {
|
if (!multiSfu) {
|
||||||
const oldest = this.matrixRTCSession.getOldestMembership();
|
const oldest = this.matrixRTCSession.getOldestMembership();
|
||||||
@@ -302,6 +314,7 @@ export class CallViewModel {
|
|||||||
local = ready(selection);
|
local = ready(selection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local.state === "error") {
|
if (local.state === "error") {
|
||||||
this._configError$.next(
|
this._configError$.next(
|
||||||
local.value instanceof ElementCallError
|
local.value instanceof ElementCallError
|
||||||
@@ -309,7 +322,14 @@ export class CallViewModel {
|
|||||||
: new UnknownCallError(local.value),
|
: new UnknownCallError(local.value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { local, remote, preferred, multiSfu };
|
|
||||||
|
return {
|
||||||
|
local,
|
||||||
|
remote,
|
||||||
|
preferred,
|
||||||
|
multiSfu,
|
||||||
|
preferStickyEvents,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: of(null),
|
: of(null),
|
||||||
@@ -339,10 +359,11 @@ export class CallViewModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The transport we should advertise in our MatrixRTC membership (plus whether
|
* The transport we should advertise in our MatrixRTC membership (plus whether
|
||||||
* it is a multi-SFU transport).
|
* it is a multi-SFU transport and whether we should use sticky events).
|
||||||
*/
|
*/
|
||||||
private readonly advertisedTransport$: Behavior<{
|
private readonly advertisedTransport$: Behavior<{
|
||||||
multiSfu: boolean;
|
multiSfu: boolean;
|
||||||
|
preferStickyEvents: boolean;
|
||||||
transport: LivekitTransport;
|
transport: LivekitTransport;
|
||||||
} | null> = this.scope.behavior(
|
} | null> = this.scope.behavior(
|
||||||
this.transports$.pipe(
|
this.transports$.pipe(
|
||||||
@@ -351,6 +372,7 @@ export class CallViewModel {
|
|||||||
transports.preferred.state === "ready"
|
transports.preferred.state === "ready"
|
||||||
? {
|
? {
|
||||||
multiSfu: transports.multiSfu,
|
multiSfu: transports.multiSfu,
|
||||||
|
preferStickyEvents: transports.preferStickyEvents,
|
||||||
// In non-multi-SFU mode we should always advertise the preferred
|
// In non-multi-SFU mode we should always advertise the preferred
|
||||||
// SFU to minimize the number of membership updates
|
// SFU to minimize the number of membership updates
|
||||||
transport: transports.multiSfu
|
transport: transports.multiSfu
|
||||||
@@ -361,6 +383,7 @@ export class CallViewModel {
|
|||||||
),
|
),
|
||||||
distinctUntilChanged<{
|
distinctUntilChanged<{
|
||||||
multiSfu: boolean;
|
multiSfu: boolean;
|
||||||
|
preferStickyEvents: boolean;
|
||||||
transport: LivekitTransport;
|
transport: LivekitTransport;
|
||||||
} | null>(deepCompare),
|
} | null>(deepCompare),
|
||||||
),
|
),
|
||||||
@@ -1796,8 +1819,8 @@ export class CallViewModel {
|
|||||||
await enterRTCSession(this.matrixRTCSession, advertised.transport, {
|
await enterRTCSession(this.matrixRTCSession, advertised.transport, {
|
||||||
encryptMedia: this.options.encryptionSystem.kind !== E2eeType.NONE,
|
encryptMedia: this.options.encryptionSystem.kind !== E2eeType.NONE,
|
||||||
useExperimentalToDeviceTransport: true,
|
useExperimentalToDeviceTransport: true,
|
||||||
useNewMembershipManager: true,
|
|
||||||
useMultiSfu: advertised.multiSfu,
|
useMultiSfu: advertised.multiSfu,
|
||||||
|
preferStickyEvents: advertised.preferStickyEvents,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Error entering RTC session", e);
|
logger.error("Error entering RTC session", e);
|
||||||
|
|||||||
@@ -7545,7 +7545,7 @@ __metadata:
|
|||||||
livekit-client: "npm:^2.13.0"
|
livekit-client: "npm:^2.13.0"
|
||||||
lodash-es: "npm:^4.17.21"
|
lodash-es: "npm:^4.17.21"
|
||||||
loglevel: "npm:^1.9.1"
|
loglevel: "npm:^1.9.1"
|
||||||
matrix-js-sdk: "github:matrix-org/matrix-js-sdk#head=develop"
|
matrix-js-sdk: "github:matrix-org/matrix-js-sdk#head=toger5/sticky-events&commit=e7f5bec51b6f70501a025b79fe5021c933385b21"
|
||||||
matrix-widget-api: "npm:^1.13.0"
|
matrix-widget-api: "npm:^1.13.0"
|
||||||
normalize.css: "npm:^8.0.1"
|
normalize.css: "npm:^8.0.1"
|
||||||
observable-hooks: "npm:^4.2.3"
|
observable-hooks: "npm:^4.2.3"
|
||||||
@@ -10335,9 +10335,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=develop":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=toger5/sticky-events&commit=e7f5bec51b6f70501a025b79fe5021c933385b21":
|
||||||
version: 38.4.0
|
version: 38.4.0
|
||||||
resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=fd949fe486038099ee111a72b57ce711e85bc352"
|
resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=e7f5bec51b6f70501a025b79fe5021c933385b21"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.12.5"
|
"@babel/runtime": "npm:^7.12.5"
|
||||||
"@matrix-org/matrix-sdk-crypto-wasm": "npm:^15.3.0"
|
"@matrix-org/matrix-sdk-crypto-wasm": "npm:^15.3.0"
|
||||||
@@ -10353,7 +10353,7 @@ __metadata:
|
|||||||
sdp-transform: "npm:^2.14.1"
|
sdp-transform: "npm:^2.14.1"
|
||||||
unhomoglyph: "npm:^1.0.6"
|
unhomoglyph: "npm:^1.0.6"
|
||||||
uuid: "npm:13"
|
uuid: "npm:13"
|
||||||
checksum: 10c0/d334811074726482b58089fef6c9e98a462fbc1d91c63798307648bdc1349d2e154aa31f391690e2c9cd90eee61a3be9fe8872873e7f828b529d9268d2a25b78
|
checksum: 10c0/7adffdc183affd2d3ee1e8497cad6ca7904a37f98328ff7bc15aa6c1829dc9f9a92f8e1bd6260432a33626ff2a839644de938270163e73438b7294675cd954e4
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user