default mute states (unmuted!) in widget mode (embedded + intent) (#3494)

* default mute states (unmuted!) in widget mode (embedded + intent)

Signed-off-by: Timo K <toger5@hotmail.de>

* review

Signed-off-by: Timo K <toger5@hotmail.de>

* introduce a cache for the url params.

Signed-off-by: Timo K <toger5@hotmail.de>

* Add an option to skip the cache.

Signed-off-by: Timo K <toger5@hotmail.de>

---------

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo
2025-09-18 13:21:12 +02:00
committed by GitHub
parent 63122c7f6b
commit db5c7cf9c7
3 changed files with 156 additions and 30 deletions

View File

@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
import {
afterAll,
afterEach,
beforeEach,
describe,
expect,
it,
@@ -26,7 +27,6 @@ import { MediaDevicesContext } from "../MediaDevicesContext";
import { mockConfig } from "../utils/test";
import { MediaDevices } from "../state/MediaDevices";
import { ObservableScope } from "../state/ObservableScope";
vi.mock("@livekit/components-core");
interface TestComponentProps {
@@ -110,9 +110,10 @@ function mockMediaDevices(
return new MediaDevices(scope);
}
describe("useMuteStates", () => {
describe("useMuteStates VITE_PACKAGE='full' (SPA) mode", () => {
afterEach(() => {
vi.clearAllMocks();
vi.stubEnv("VITE_PACKAGE", "full");
});
afterAll(() => {
@@ -256,3 +257,67 @@ describe("useMuteStates", () => {
expect(screen.getByTestId("video-enabled").textContent).toBe("true");
});
});
describe("useMuteStates in VITE_PACKAGE='embedded' (widget) mode", () => {
beforeEach(() => {
vi.stubEnv("VITE_PACKAGE", "embedded");
});
it("uses defaults from config", () => {
mockConfig({
media_devices: {
enable_audio: false,
enable_video: false,
},
});
render(
<MemoryRouter>
<MediaDevicesContext value={mockMediaDevices()}>
<TestComponent />
</MediaDevicesContext>
</MemoryRouter>,
);
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
});
it("skipLobby does not mute inputs", () => {
mockConfig();
render(
<MemoryRouter
initialEntries={[
"/room/?skipLobby=true&widgetId=1234&parentUrl=www.parent.org",
]}
>
<MediaDevicesContext value={mockMediaDevices()}>
<TestComponent />
</MediaDevicesContext>
</MemoryRouter>,
);
expect(screen.getByTestId("audio-enabled").textContent).toBe("true");
expect(screen.getByTestId("video-enabled").textContent).toBe("true");
});
it("url params win over config", () => {
// The config sets audio and video to disabled
mockConfig({ media_devices: { enable_audio: false, enable_video: false } });
render(
<MemoryRouter
initialEntries={[
// The Intent sets both audio and video enabled to true via the url param configuration
"/room/?intent=start_call_dm&widgetId=1234&parentUrl=www.parent.org",
]}
>
<MediaDevicesContext value={mockMediaDevices()}>
<TestComponent />
</MediaDevicesContext>
</MemoryRouter>,
);
// At the end we expect the url param to take precedence, resulting in true
expect(screen.getByTestId("audio-enabled").textContent).toBe("true");
expect(screen.getByTestId("video-enabled").textContent).toBe("true");
});
});

View File

@@ -81,11 +81,15 @@ function useMuteState(
export function useMuteStates(isJoined: boolean): MuteStates {
const devices = useMediaDevices();
const { skipLobby } = useUrlParams();
const { skipLobby, defaultAudioEnabled, defaultVideoEnabled } =
useUrlParams();
const audio = useMuteState(devices.audioInput, () => {
return Config.get().media_devices.enable_audio && !skipLobby && !isJoined;
});
const audio = useMuteState(
devices.audioInput,
() =>
(defaultAudioEnabled ?? Config.get().media_devices.enable_audio) &&
allowJoinUnmuted(skipLobby, isJoined),
);
useEffect(() => {
// If audio is enabled, we need to request the device names again,
// because iOS will not be able to switch to the correct device after un-muting.
@@ -97,7 +101,9 @@ export function useMuteStates(isJoined: boolean): MuteStates {
const isEarpiece = useIsEarpiece();
const video = useMuteState(
devices.videoInput,
() => Config.get().media_devices.enable_video && !skipLobby && !isJoined,
() =>
(defaultVideoEnabled ?? Config.get().media_devices.enable_video) &&
allowJoinUnmuted(skipLobby, isJoined),
isEarpiece, // Force video to be unavailable if using earpiece
);
@@ -164,3 +170,9 @@ export function useMuteStates(isJoined: boolean): MuteStates {
return useMemo(() => ({ audio, video }), [audio, video]);
}
function allowJoinUnmuted(skipLobby: boolean, isJoined: boolean): boolean {
return (
(!skipLobby && !isJoined) || import.meta.env.VITE_PACKAGE === "embedded"
);
}