mirror of
https://github.com/vector-im/element-call.git
synced 2026-03-31 07:00:26 +00:00
This commit is contained in:
@@ -128,7 +128,7 @@ const devicesStub: MediaDevices = {
|
||||
stopUsingDeviceNames: () => {},
|
||||
};
|
||||
|
||||
const MediaDevicesContext = createContext<MediaDevices>(devicesStub);
|
||||
export const MediaDevicesContext = createContext<MediaDevices>(devicesStub);
|
||||
|
||||
interface Props {
|
||||
children: JSX.Element;
|
||||
|
||||
@@ -131,48 +131,46 @@ export const GroupCallView: FC<Props> = ({
|
||||
const latestDevices = useRef<MediaDevices>();
|
||||
latestDevices.current = deviceContext;
|
||||
|
||||
// TODO: why do we use a ref here instead of using muteStates directly?
|
||||
const latestMuteStates = useRef<MuteStates>();
|
||||
latestMuteStates.current = muteStates;
|
||||
|
||||
useEffect(() => {
|
||||
const defaultDeviceSetup = async (
|
||||
requestedDeviceData: JoinCallData,
|
||||
): Promise<void> => {
|
||||
const defaultDeviceSetup = async ({
|
||||
audioInput,
|
||||
videoInput,
|
||||
}: JoinCallData): Promise<void> => {
|
||||
// XXX: I think this is broken currently - LiveKit *won't* request
|
||||
// permissions and give you device names unless you specify a kind, but
|
||||
// here we want all kinds of devices. This needs a fix in livekit-client
|
||||
// for the following name-matching logic to do anything useful.
|
||||
const devices = await Room.getLocalDevices(undefined, true);
|
||||
const { audioInput, videoInput } = requestedDeviceData;
|
||||
if (audioInput === null) {
|
||||
latestMuteStates.current!.audio.setEnabled?.(false);
|
||||
} else {
|
||||
|
||||
if (audioInput) {
|
||||
const deviceId = findDeviceByName(audioInput, "audioinput", devices);
|
||||
if (!deviceId) {
|
||||
logger.warn("Unknown audio input: " + audioInput);
|
||||
// override the default mute state
|
||||
latestMuteStates.current!.audio.setEnabled?.(false);
|
||||
} else {
|
||||
logger.debug(
|
||||
`Found audio input ID ${deviceId} for name ${audioInput}`,
|
||||
);
|
||||
latestDevices.current!.audioInput.select(deviceId);
|
||||
latestMuteStates.current!.audio.setEnabled?.(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (videoInput === null) {
|
||||
latestMuteStates.current!.video.setEnabled?.(false);
|
||||
} else {
|
||||
if (videoInput) {
|
||||
const deviceId = findDeviceByName(videoInput, "videoinput", devices);
|
||||
if (!deviceId) {
|
||||
logger.warn("Unknown video input: " + videoInput);
|
||||
// override the default mute state
|
||||
latestMuteStates.current!.video.setEnabled?.(false);
|
||||
} else {
|
||||
logger.debug(
|
||||
`Found video input ID ${deviceId} for name ${videoInput}`,
|
||||
);
|
||||
latestDevices.current!.videoInput.select(deviceId);
|
||||
latestMuteStates.current!.video.setEnabled?.(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -199,7 +197,6 @@ export const GroupCallView: FC<Props> = ({
|
||||
} else {
|
||||
// No lobby and no preload: we enter the rtc session right away
|
||||
(async (): Promise<void> => {
|
||||
await defaultDeviceSetup({ audioInput: null, videoInput: null });
|
||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
})().catch((e) => {
|
||||
logger.error("Error joining RTC session", e);
|
||||
|
||||
172
src/room/MuteStates.test.tsx
Normal file
172
src/room/MuteStates.test.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
|
||||
import React, { ReactNode } from "react";
|
||||
import { beforeEach } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
|
||||
import { useMuteStates } from "./MuteStates";
|
||||
import {
|
||||
MediaDevice,
|
||||
MediaDevices,
|
||||
MediaDevicesContext,
|
||||
} from "../livekit/MediaDevicesContext";
|
||||
import { mockConfig } from "../utils/test";
|
||||
|
||||
function TestComponent(): ReactNode {
|
||||
const muteStates = useMuteStates();
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="audio-enabled">
|
||||
{muteStates.audio.enabled.toString()}
|
||||
</div>
|
||||
<div data-testid="video-enabled">
|
||||
{muteStates.video.enabled.toString()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mockMicrophone: MediaDeviceInfo = {
|
||||
deviceId: "",
|
||||
kind: "audioinput",
|
||||
label: "",
|
||||
groupId: "",
|
||||
toJSON() {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
|
||||
const mockSpeaker: MediaDeviceInfo = {
|
||||
deviceId: "",
|
||||
kind: "audiooutput",
|
||||
label: "",
|
||||
groupId: "",
|
||||
toJSON() {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
|
||||
const mockCamera: MediaDeviceInfo = {
|
||||
deviceId: "",
|
||||
kind: "videoinput",
|
||||
label: "",
|
||||
groupId: "",
|
||||
toJSON() {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
|
||||
function mockDevices(available: MediaDeviceInfo[]): MediaDevice {
|
||||
return {
|
||||
available,
|
||||
selectedId: "",
|
||||
select: (): void => {},
|
||||
};
|
||||
}
|
||||
|
||||
function mockMediaDevices(
|
||||
{
|
||||
microphone,
|
||||
speaker,
|
||||
camera,
|
||||
}: {
|
||||
microphone?: boolean;
|
||||
speaker?: boolean;
|
||||
camera?: boolean;
|
||||
} = { microphone: true, speaker: true, camera: true },
|
||||
): MediaDevices {
|
||||
return {
|
||||
audioInput: mockDevices(microphone ? [mockMicrophone] : []),
|
||||
audioOutput: mockDevices(speaker ? [mockSpeaker] : []),
|
||||
videoInput: mockDevices(camera ? [mockCamera] : []),
|
||||
startUsingDeviceNames: (): void => {},
|
||||
stopUsingDeviceNames: (): void => {},
|
||||
};
|
||||
}
|
||||
|
||||
describe("useMuteStates", () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(React, "useContext").mockReturnValue({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("disabled when no input devices", () => {
|
||||
mockConfig();
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MediaDevicesContext.Provider
|
||||
value={mockMediaDevices({
|
||||
microphone: false,
|
||||
camera: false,
|
||||
})}
|
||||
>
|
||||
<TestComponent />
|
||||
</MediaDevicesContext.Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
||||
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
|
||||
});
|
||||
|
||||
it("should be enabled by default", () => {
|
||||
mockConfig();
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MediaDevicesContext.Provider value={mockMediaDevices()}>
|
||||
<TestComponent />
|
||||
</MediaDevicesContext.Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(screen.getByTestId("audio-enabled").textContent).toBe("true");
|
||||
expect(screen.getByTestId("video-enabled").textContent).toBe("true");
|
||||
});
|
||||
|
||||
it("uses defaults from config", () => {
|
||||
mockConfig({
|
||||
media_devices: {
|
||||
enable_audio: false,
|
||||
enable_video: false,
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MediaDevicesContext.Provider value={mockMediaDevices()}>
|
||||
<TestComponent />
|
||||
</MediaDevicesContext.Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
||||
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
|
||||
});
|
||||
|
||||
it("skipLobby mutes inputs", () => {
|
||||
mockConfig();
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={["/room/?skipLobby=true"]}>
|
||||
<MediaDevicesContext.Provider value={mockMediaDevices()}>
|
||||
<TestComponent />
|
||||
</MediaDevicesContext.Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
||||
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,7 @@ import { MediaDevice, useMediaDevices } from "../livekit/MediaDevicesContext";
|
||||
import { useReactiveState } from "../useReactiveState";
|
||||
import { ElementWidgetActions, widget } from "../widget";
|
||||
import { Config } from "../config/Config";
|
||||
import { useUrlParams } from "../UrlParams";
|
||||
|
||||
/**
|
||||
* If there already are this many participants in the call, we automatically mute
|
||||
@@ -72,13 +73,14 @@ function useMuteState(
|
||||
export function useMuteStates(): MuteStates {
|
||||
const devices = useMediaDevices();
|
||||
|
||||
const audio = useMuteState(
|
||||
devices.audioInput,
|
||||
() => Config.get().media_devices.enable_audio,
|
||||
);
|
||||
const { skipLobby } = useUrlParams();
|
||||
|
||||
const audio = useMuteState(devices.audioInput, () => {
|
||||
return Config.get().media_devices.enable_audio && !skipLobby;
|
||||
});
|
||||
const video = useMuteState(
|
||||
devices.videoInput,
|
||||
() => Config.get().media_devices.enable_video,
|
||||
() => Config.get().media_devices.enable_video && !skipLobby,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -9,8 +9,7 @@ import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { expect, test, vi } from "vitest";
|
||||
|
||||
import { enterRTCSession } from "../src/rtcSessionHelpers";
|
||||
import { Config } from "../src/config/Config";
|
||||
import { DEFAULT_CONFIG } from "./config/ConfigOptions";
|
||||
import { mockConfig } from "./utils/test";
|
||||
|
||||
test("It joins the correct Session", async () => {
|
||||
const focusFromOlderMembership = {
|
||||
@@ -34,8 +33,7 @@ test("It joins the correct Session", async () => {
|
||||
],
|
||||
};
|
||||
|
||||
vi.spyOn(Config, "get").mockReturnValue({
|
||||
...DEFAULT_CONFIG,
|
||||
mockConfig({
|
||||
livekit: { livekit_service_url: "http://my-default-service-url.com" },
|
||||
});
|
||||
const mockedSession = vi.mocked({
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
RemoteUserMediaViewModel,
|
||||
} from "../state/MediaViewModel";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { DEFAULT_CONFIG, ResolvedConfigOptions } from "../config/ConfigOptions";
|
||||
import { Config } from "../config/Config";
|
||||
|
||||
export function withFakeTimers(continuation: () => void): void {
|
||||
vi.useFakeTimers();
|
||||
@@ -197,3 +199,10 @@ export async function withRemoteMedia(
|
||||
vm.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export function mockConfig(config: Partial<ResolvedConfigOptions> = {}): void {
|
||||
vi.spyOn(Config, "get").mockReturnValue({
|
||||
...DEFAULT_CONFIG,
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user