mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
Ask one question about whether effects can run
- The menu asked the browser for the APIs; the pipeline asked for those and a desktop. A phone browser sat between the two: every effect offered, none applied, nothing said. - Both now ask the same function, so what is offered and what can be delivered cannot drift apart again. - Tested against the lobby, where the switcher stays enabled on a phone — in a call it is already withheld there, so a check would pass whatever the verdict said. The test fails on the old wiring. - This also fixes the blur toggle, which had the same split. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,10 @@ import type { CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
import type { Alignment, Layout } from "../state/layout-types";
|
||||
import type { SpotlightTileViewModel } from "../state/TileViewModel";
|
||||
import type { DeviceLabel } from "../state/MediaDevices";
|
||||
import { createCallFooterViewModel } from "./CallFooterViewModel";
|
||||
import {
|
||||
createCallFooterViewModel,
|
||||
createLobbyFooterViewModel,
|
||||
} from "./CallFooterViewModel";
|
||||
import { HeaderStyle } from "../UrlParams";
|
||||
|
||||
const platformMock = vi.hoisted(() => vi.fn(() => "desktop"));
|
||||
@@ -24,10 +27,12 @@ vi.mock("../Platform", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// Prevent supportsBackgroundProcessors from throwing in jsdom – it is not
|
||||
// exercised by these tests (only used in `videoToggles`, not `videoOptions`).
|
||||
// The SDK's own check needs WebGL and canvas APIs jsdom does not have. The
|
||||
// tests below drive it directly, because what they are about is the answer the
|
||||
// app gives on top of it.
|
||||
const sdkSupportMock = vi.hoisted(() => vi.fn(() => true));
|
||||
vi.mock("@livekit/track-processors", () => ({
|
||||
supportsBackgroundProcessors: (): boolean => false,
|
||||
supportsBackgroundProcessors: (): boolean => sdkSupportMock(),
|
||||
}));
|
||||
|
||||
const outputSelectionMock = vi.hoisted(() => vi.fn(() => true));
|
||||
@@ -228,4 +233,70 @@ describe("createCallFooterViewModel", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe("background effects", () => {
|
||||
// The lobby, deliberately: in a call the whole switcher is already
|
||||
// withheld on a phone, so a check there passes whatever the verdict says.
|
||||
// The lobby keeps its switcher, which is where a phone browser was offered
|
||||
// every effect, given none of them, and told nothing.
|
||||
function lobbyFor(
|
||||
platform: string,
|
||||
): ReturnType<typeof createLobbyFooterViewModel> {
|
||||
platformMock.mockReturnValue(platform);
|
||||
return createLobbyFooterViewModel(
|
||||
testScope(),
|
||||
mockMuteStates(),
|
||||
twoMicsAndOneCamMediaDevices,
|
||||
/* openSettings */ undefined,
|
||||
/* hangup */ undefined,
|
||||
/* showLogo */ false,
|
||||
);
|
||||
}
|
||||
|
||||
it("offers nothing the pipeline would refuse to honour", () => {
|
||||
sdkSupportMock.mockReturnValue(true);
|
||||
const vm = lobbyFor("ios");
|
||||
|
||||
expect(vm.selectBackgroundEffect$.value).toBeUndefined();
|
||||
expect(vm.toggleBlur$.value).toBeUndefined();
|
||||
});
|
||||
|
||||
it("offers them where the pipeline will honour them", () => {
|
||||
sdkSupportMock.mockReturnValue(true);
|
||||
const vm = lobbyFor("desktop");
|
||||
|
||||
expect(vm.selectBackgroundEffect$.value).toBeDefined();
|
||||
expect(vm.toggleBlur$.value).toBeDefined();
|
||||
});
|
||||
|
||||
it("offers nothing where the browser itself cannot run them", () => {
|
||||
sdkSupportMock.mockReturnValue(false);
|
||||
const vm = lobbyFor("desktop");
|
||||
|
||||
expect(vm.selectBackgroundEffect$.value).toBeUndefined();
|
||||
expect(vm.toggleBlur$.value).toBeUndefined();
|
||||
});
|
||||
|
||||
it("availability is the same before and during a call", () => {
|
||||
for (const supported of [true, false]) {
|
||||
sdkSupportMock.mockReturnValue(supported);
|
||||
platformMock.mockReturnValue("desktop");
|
||||
const inCall = createCallFooterViewModel(
|
||||
testScope(),
|
||||
buildMinimalCallViewModel(gridLayout),
|
||||
mockMuteStates(),
|
||||
twoMicsAndOneCamMediaDevices,
|
||||
/* reactionIdentifier */ undefined,
|
||||
{ showControls: true, header: HeaderStyle.Standard },
|
||||
);
|
||||
const lobby = lobbyFor("desktop");
|
||||
|
||||
const offeredInCall =
|
||||
inCall.selectBackgroundEffect$.value !== undefined;
|
||||
const offeredInLobby =
|
||||
lobby.selectBackgroundEffect$.value !== undefined;
|
||||
expect(offeredInLobby).toBe(offeredInCall);
|
||||
expect(offeredInLobby).toBe(supported);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,9 +6,10 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { combineLatest, map, type Observable, switchMap } from "rxjs";
|
||||
import { supportsBackgroundProcessors } from "@livekit/track-processors";
|
||||
import { supportsAudioOutputSelection } from "livekit-client";
|
||||
|
||||
import { supportsBackgroundProcessors } from "../livekit/backgroundProcessing";
|
||||
|
||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
import { type MenuOptions } from "./MediaMuteAndSwitchButton";
|
||||
import { type MediaDevices } from "../state/MediaDevices";
|
||||
|
||||
@@ -8,7 +8,6 @@ Please see LICENSE in the repository root for full details.
|
||||
import {
|
||||
BackgroundProcessorWrapper,
|
||||
type ProcessorWrapper,
|
||||
supportsBackgroundProcessors as supportsBackgroundProcessorsLivekitSdk,
|
||||
type BackgroundOptions,
|
||||
type SwitchBackgroundProcessorOptions,
|
||||
} from "@livekit/track-processors";
|
||||
@@ -40,9 +39,9 @@ import {
|
||||
type BackgroundEffect,
|
||||
} from "./backgroundEffects";
|
||||
import { BackgroundImageStore } from "./backgroundImages";
|
||||
import { supportsBackgroundProcessors } from "./backgroundProcessing";
|
||||
import { type Behavior } from "../state/Behavior";
|
||||
import { type ObservableScope } from "../state/ObservableScope";
|
||||
import { platform } from "../Platform";
|
||||
|
||||
//TODO-MULTI-SFU: This is not yet fully there.
|
||||
// it is a combination of exposing observable and react hooks.
|
||||
@@ -175,10 +174,6 @@ interface Props {
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
function supportsBackgroundProcessors(): boolean {
|
||||
return supportsBackgroundProcessorsLivekitSdk() && platform === "desktop";
|
||||
}
|
||||
|
||||
/** Translates a chosen effect into the pipeline's own vocabulary. */
|
||||
function switchOptionsFor(
|
||||
effect: BackgroundEffect,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { supportsBackgroundProcessors as supportsBackgroundProcessorsLivekitSdk } from "@livekit/track-processors";
|
||||
|
||||
import { platform } from "../Platform";
|
||||
|
||||
/**
|
||||
* Whether this device can run background effects at all.
|
||||
*
|
||||
* The one answer, for everything that asks. The controls and the pipeline used
|
||||
* to work it out separately, and differently: the pipeline required a desktop
|
||||
* while the controls asked only whether the browser had the APIs. A phone
|
||||
* browser has them, so it was offered a choice the pipeline then refused to
|
||||
* honour, and the video simply never changed — the worst of both, since it
|
||||
* neither worked nor said why.
|
||||
*
|
||||
* Anything that offers a background effect must ask this, so that what is
|
||||
* offered and what can be delivered cannot drift apart again.
|
||||
*/
|
||||
export function supportsBackgroundProcessors(): boolean {
|
||||
return supportsBackgroundProcessorsLivekitSdk() && platform === "desktop";
|
||||
}
|
||||
Reference in New Issue
Block a user