mirror of
https://github.com/vector-im/element-call.git
synced 2026-02-20 04:57:03 +00:00
Prevent new devices from automatically starting unmuted in call (#2959)
This commit is contained in:
@@ -118,6 +118,7 @@ function createGroupCallView(widget: WidgetHelpers | null): {
|
||||
skipLobby={false}
|
||||
hideHeader={true}
|
||||
rtcSession={rtcSession as unknown as MatrixRTCSession}
|
||||
isJoined
|
||||
muteStates={muteState}
|
||||
widget={widget}
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user