diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx
index 19af03373..76e0c03ec 100644
--- a/src/settings/DeveloperSettingsTab.tsx
+++ b/src/settings/DeveloperSettingsTab.tsx
@@ -69,6 +69,23 @@ import { Slider } from "../Slider";
import { useUrlParams } from "../UrlParams";
import { type CallViewModel } from "../state/CallViewModel/CallViewModel.ts";
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
+import { useBehavior } from "../useBehavior";
+
+/**
+ * Shows whether the call is large enough that MatrixRTC has stopped rotating the media key.
+ */
+const KeyRotationStatus: FC<{ vm: CallViewModel }> = ({ vm }) => {
+ const suppressed = useBehavior(vm.keyRotationSuppressed$);
+ const participantCount = useBehavior(vm.participantCount$);
+ return (
+
+ Media key rotation:{" "}
+ {suppressed
+ ? `suppressed, participant limit reached (${participantCount} participants)`
+ : `active (${participantCount} participants)`}
+
+ );
+};
interface Props {
client: MatrixClient;
@@ -368,6 +385,7 @@ export const DeveloperSettingsTab: FC = ({
id: client.getDeviceId() || "unknown",
})}
+ {vm && }
;
+ /**
+ * Whether the call has grown large enough that MatrixRTC has stopped rotating the media
+ * encryption key. While this is true the key in use is still shared with new joiners, but no new
+ * key is generated when someone joins or leaves.
+ */
+ keyRotationSuppressed$: Behavior;
allConnections$: Behavior;
/** Participants sorted by livekit room so they can be used in the audio rendering */
livekitRoomItems$: Behavior;
@@ -856,6 +863,11 @@ export function createCallViewModel$(
matrixLivekitMembers$.pipe(map((ms) => ms.length)),
);
+ const keyRotationSuppressed$ = createKeyRotationSuppressed$(
+ scope,
+ matrixRTCSession,
+ );
+
const leaveSoundEffect$ = userMedia$.pipe(
pairwise(),
filter(
@@ -1782,6 +1794,7 @@ export function createCallViewModel$(
),
allConnections$,
participantCount$: participantCount$,
+ keyRotationSuppressed$: keyRotationSuppressed$,
handsRaised$: handsRaised$,
reactions$: reactions$,
joinSoundEffect$: joinSoundEffect$,
diff --git a/src/state/SessionBehaviors.ts b/src/state/SessionBehaviors.ts
index 652e43b5f..784e33666 100644
--- a/src/state/SessionBehaviors.ts
+++ b/src/state/SessionBehaviors.ts
@@ -88,3 +88,22 @@ export const createMemberships$ = (
new Epoch(matrixRTCSession.memberships),
);
};
+
+/**
+ * Whether the session has grown large enough that MatrixRTC has stopped rotating the media
+ * encryption key. While this is true the key in use is still shared with new joiners, but no new
+ * key is generated when someone joins or leaves.
+ */
+export const createKeyRotationSuppressed$ = (
+ scope: ObservableScope,
+ matrixRTCSession: MatrixRTCSession,
+): Behavior => {
+ return scope.behavior(
+ fromEvent(
+ matrixRTCSession,
+ MatrixRTCSessionEvent.KeyRotationSuppressedChanged,
+ (suppressed: boolean) => suppressed,
+ ),
+ matrixRTCSession.isKeyRotationSuppressed,
+ );
+};
diff --git a/src/utils/test.ts b/src/utils/test.ts
index 206db88f5..38a1bb4a5 100644
--- a/src/utils/test.ts
+++ b/src/utils/test.ts
@@ -490,6 +490,8 @@ export class MockRTCSession extends TypedEventEmitter<
return this.joined;
}
+ public isKeyRotationSuppressed = false;
+
public withMemberships(
rtcMembers$: Behavior[]>,
): MockRTCSession {