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 { Alignment, Layout } from "../state/layout-types";
|
||||||
import type { SpotlightTileViewModel } from "../state/TileViewModel";
|
import type { SpotlightTileViewModel } from "../state/TileViewModel";
|
||||||
import type { DeviceLabel } from "../state/MediaDevices";
|
import type { DeviceLabel } from "../state/MediaDevices";
|
||||||
import { createCallFooterViewModel } from "./CallFooterViewModel";
|
import {
|
||||||
|
createCallFooterViewModel,
|
||||||
|
createLobbyFooterViewModel,
|
||||||
|
} from "./CallFooterViewModel";
|
||||||
import { HeaderStyle } from "../UrlParams";
|
import { HeaderStyle } from "../UrlParams";
|
||||||
|
|
||||||
const platformMock = vi.hoisted(() => vi.fn(() => "desktop"));
|
const platformMock = vi.hoisted(() => vi.fn(() => "desktop"));
|
||||||
@@ -24,10 +27,12 @@ vi.mock("../Platform", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Prevent supportsBackgroundProcessors from throwing in jsdom – it is not
|
// The SDK's own check needs WebGL and canvas APIs jsdom does not have. The
|
||||||
// exercised by these tests (only used in `videoToggles`, not `videoOptions`).
|
// 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", () => ({
|
vi.mock("@livekit/track-processors", () => ({
|
||||||
supportsBackgroundProcessors: (): boolean => false,
|
supportsBackgroundProcessors: (): boolean => sdkSupportMock(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const outputSelectionMock = vi.hoisted(() => vi.fn(() => true));
|
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 { combineLatest, map, type Observable, switchMap } from "rxjs";
|
||||||
import { supportsBackgroundProcessors } from "@livekit/track-processors";
|
|
||||||
import { supportsAudioOutputSelection } from "livekit-client";
|
import { supportsAudioOutputSelection } from "livekit-client";
|
||||||
|
|
||||||
|
import { supportsBackgroundProcessors } from "../livekit/backgroundProcessing";
|
||||||
|
|
||||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||||
import { type MenuOptions } from "./MediaMuteAndSwitchButton";
|
import { type MenuOptions } from "./MediaMuteAndSwitchButton";
|
||||||
import { type MediaDevices } from "../state/MediaDevices";
|
import { type MediaDevices } from "../state/MediaDevices";
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import {
|
import {
|
||||||
BackgroundProcessorWrapper,
|
BackgroundProcessorWrapper,
|
||||||
type ProcessorWrapper,
|
type ProcessorWrapper,
|
||||||
supportsBackgroundProcessors as supportsBackgroundProcessorsLivekitSdk,
|
|
||||||
type BackgroundOptions,
|
type BackgroundOptions,
|
||||||
type SwitchBackgroundProcessorOptions,
|
type SwitchBackgroundProcessorOptions,
|
||||||
} from "@livekit/track-processors";
|
} from "@livekit/track-processors";
|
||||||
@@ -40,9 +39,9 @@ import {
|
|||||||
type BackgroundEffect,
|
type BackgroundEffect,
|
||||||
} from "./backgroundEffects";
|
} from "./backgroundEffects";
|
||||||
import { BackgroundImageStore } from "./backgroundImages";
|
import { BackgroundImageStore } from "./backgroundImages";
|
||||||
|
import { supportsBackgroundProcessors } from "./backgroundProcessing";
|
||||||
import { type Behavior } from "../state/Behavior";
|
import { type Behavior } from "../state/Behavior";
|
||||||
import { type ObservableScope } from "../state/ObservableScope";
|
import { type ObservableScope } from "../state/ObservableScope";
|
||||||
import { platform } from "../Platform";
|
|
||||||
|
|
||||||
//TODO-MULTI-SFU: This is not yet fully there.
|
//TODO-MULTI-SFU: This is not yet fully there.
|
||||||
// it is a combination of exposing observable and react hooks.
|
// it is a combination of exposing observable and react hooks.
|
||||||
@@ -175,10 +174,6 @@ interface Props {
|
|||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
function supportsBackgroundProcessors(): boolean {
|
|
||||||
return supportsBackgroundProcessorsLivekitSdk() && platform === "desktop";
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Translates a chosen effect into the pipeline's own vocabulary. */
|
/** Translates a chosen effect into the pipeline's own vocabulary. */
|
||||||
function switchOptionsFor(
|
function switchOptionsFor(
|
||||||
effect: BackgroundEffect,
|
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