Prevent new devices from automatically starting unmuted in call (#2959)

This commit is contained in:
Robin
2025-01-17 10:30:28 -05:00
committed by GitHub
parent cda802a2e9
commit e636542b1e
6 changed files with 59 additions and 35 deletions

View File

@@ -118,6 +118,7 @@ function createGroupCallView(widget: WidgetHelpers | null): {
skipLobby={false}
hideHeader={true}
rtcSession={rtcSession as unknown as MatrixRTCSession}
isJoined
muteStates={muteState}
widget={widget}
/>

View File

@@ -46,7 +46,6 @@ import {
} from "../livekit/MediaDevicesContext";
import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships";
import { enterRTCSession, leaveRTCSession } from "../rtcSessionHelpers";
import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState";
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
import { useRoomAvatar } from "./useRoomAvatar";
import { useRoomName } from "./useRoomName";
@@ -74,6 +73,7 @@ interface Props {
skipLobby: boolean;
hideHeader: boolean;
rtcSession: MatrixRTCSession;
isJoined: boolean;
muteStates: MuteStates;
widget: WidgetHelpers | null;
}
@@ -86,11 +86,11 @@ export const GroupCallView: FC<Props> = ({
skipLobby,
hideHeader,
rtcSession,
isJoined,
muteStates,
widget,
}) => {
const memberships = useMatrixRTCSessionMemberships(rtcSession);
const isJoined = useMatrixRTCSessionJoinState(rtcSession);
const leaveSoundContext = useLatest(
useAudioContext({
sounds: callEventAudioSounds,

View File

@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
*/
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import { type FC, useCallback, useState, type ReactNode } from "react";
import { type FC, useCallback, useState } from "react";
import { render, screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import userEvent from "@testing-library/user-event";
@@ -20,8 +20,12 @@ import {
} from "../livekit/MediaDevicesContext";
import { mockConfig } from "../utils/test";
function TestComponent(): ReactNode {
const muteStates = useMuteStates();
interface TestComponentProps {
isJoined?: boolean;
}
const TestComponent: FC<TestComponentProps> = ({ isJoined = false }) => {
const muteStates = useMuteStates(isJoined);
const onToggleAudio = useCallback(
() => muteStates.audio.setEnabled?.(!muteStates.audio.enabled),
[muteStates],
@@ -37,7 +41,7 @@ function TestComponent(): ReactNode {
</div>
</div>
);
}
};
const mockMicrophone: MediaDeviceInfo = {
deviceId: "",
@@ -134,7 +138,7 @@ describe("useMuteStates", () => {
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
});
it("should be enabled by default", () => {
it("enables devices by default in the lobby", () => {
mockConfig();
render(
@@ -148,6 +152,22 @@ describe("useMuteStates", () => {
expect(screen.getByTestId("video-enabled").textContent).toBe("true");
});
it("disables devices by default in the call", () => {
// Disabling new devices in the call ensures that connecting a webcam
// mid-call won't cause it to suddenly be enabled without user input
mockConfig();
render(
<MemoryRouter>
<MediaDevicesContext.Provider value={mockMediaDevices()}>
<TestComponent isJoined />
</MediaDevicesContext.Provider>
</MemoryRouter>,
);
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
});
it("uses defaults from config", () => {
mockConfig({
media_devices: {

View File

@@ -74,17 +74,17 @@ function useMuteState(
);
}
export function useMuteStates(): MuteStates {
export function useMuteStates(isJoined: boolean): MuteStates {
const devices = useMediaDevices();
const { skipLobby } = useUrlParams();
const audio = useMuteState(devices.audioInput, () => {
return Config.get().media_devices.enable_audio && !skipLobby;
return Config.get().media_devices.enable_audio && !skipLobby && !isJoined;
});
const video = useMuteState(
devices.videoInput,
() => Config.get().media_devices.enable_video && !skipLobby,
() => Config.get().media_devices.enable_video && !skipLobby && !isJoined,
);
useEffect(() => {

View File

@@ -40,6 +40,7 @@ import { useOptInAnalytics } from "../settings/settings";
import { Config } from "../config/Config";
import { Link } from "../button/Link";
import { ErrorView } from "../ErrorView";
import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState";
export const RoomPage: FC = () => {
const {
@@ -66,7 +67,10 @@ export const RoomPage: FC = () => {
const { avatarUrl, displayName: userDisplayName } = useProfile(client);
const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers);
const muteStates = useMuteStates();
const isJoined = useMatrixRTCSessionJoinState(
groupCallState.kind === "loaded" ? groupCallState.rtcSession : undefined,
);
const muteStates = useMuteStates(isJoined);
useEffect(() => {
// If we've finished loading, are not already authed and we've been given a display name as
@@ -111,6 +115,7 @@ export const RoomPage: FC = () => {
widget={widget}
client={client!}
rtcSession={groupCallState.rtcSession}
isJoined={isJoined}
isPasswordlessUser={passwordlessUser}
confineToRoom={confineToRoom}
preload={preload}

View File

@@ -10,35 +10,33 @@ import {
type MatrixRTCSession,
MatrixRTCSessionEvent,
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { useCallback, useEffect, useState } from "react";
import { useEffect, useState } from "react";
export function useMatrixRTCSessionJoinState(
rtcSession: MatrixRTCSession,
rtcSession: MatrixRTCSession | undefined,
): boolean {
const [isJoined, setJoined] = useState(rtcSession.isJoined());
const onJoinStateChanged = useCallback(
(isJoined: boolean) => {
logger.info(
`Session in room ${rtcSession.room.roomId} changed to ${
isJoined ? "joined" : "left"
}`,
);
setJoined(isJoined);
},
[rtcSession],
);
const [, setNumUpdates] = useState(0);
useEffect(() => {
rtcSession.on(MatrixRTCSessionEvent.JoinStateChanged, onJoinStateChanged);
if (rtcSession !== undefined) {
const onJoinStateChanged = (isJoined: boolean): void => {
logger.info(
`Session in room ${rtcSession.room.roomId} changed to ${
isJoined ? "joined" : "left"
}`,
);
setNumUpdates((n) => n + 1); // Force an update
};
rtcSession.on(MatrixRTCSessionEvent.JoinStateChanged, onJoinStateChanged);
return (): void => {
rtcSession.off(
MatrixRTCSessionEvent.JoinStateChanged,
onJoinStateChanged,
);
};
}, [rtcSession, onJoinStateChanged]);
return (): void => {
rtcSession.off(
MatrixRTCSessionEvent.JoinStateChanged,
onJoinStateChanged,
);
};
}
}, [rtcSession]);
return isJoined;
return rtcSession?.isJoined() ?? false;
}