mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
Say where background effects run slowly
- Where the browser has no MediaStreamTrackProcessor, effects draw each frame through a canvas: slower, and the first build stalls the page. They stay offered and choosable there, with a notice under the grid that says so and describes the group. - Decided in the view model, beside whether effects can run at all, so the lobby and the call give the same answer. - The notice stays within the menu's width however long the sentence, which the story checks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
"blur": "Blur",
|
"blur": "Blur",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"numbered": "Background {{n}}",
|
"numbered": "Background {{n}}",
|
||||||
|
"slow": "Background effects run slowly on this platform, which may cause your video to stutter.",
|
||||||
"unavailable": "Background effects are not supported on this platform."
|
"unavailable": "Background effects are not supported on this platform."
|
||||||
},
|
},
|
||||||
"call_ended_view": {
|
"call_ended_view": {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export interface FooterState {
|
|||||||
/** Every effect on offer, in the order they are shown. */
|
/** Every effect on offer, in the order they are shown. */
|
||||||
backgroundEffects: BackgroundEffectChoice[];
|
backgroundEffects: BackgroundEffectChoice[];
|
||||||
/** What the user is told about effects here, if anything. */
|
/** What the user is told about effects here, if anything. */
|
||||||
backgroundEffectNotice: "unavailable" | undefined;
|
backgroundEffectNotice: "unavailable" | "slow" | undefined;
|
||||||
showFooter: boolean;
|
showFooter: boolean;
|
||||||
|
|
||||||
/* This is needed for WindowMode = "flat" */
|
/* This is needed for WindowMode = "flat" */
|
||||||
@@ -243,7 +243,9 @@ export const CallFooter: FC<FooterProps> = ({
|
|||||||
backgroundEffectNotice={
|
backgroundEffectNotice={
|
||||||
backgroundEffectNotice === "unavailable"
|
backgroundEffectNotice === "unavailable"
|
||||||
? t("background_effects.unavailable")
|
? t("background_effects.unavailable")
|
||||||
: undefined
|
: backgroundEffectNotice === "slow"
|
||||||
|
? t("background_effects.slow")
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ vi.mock("../Platform", () => ({
|
|||||||
|
|
||||||
// The SDK's own check needs WebGL and canvas APIs jsdom does not have.
|
// The SDK's own check needs WebGL and canvas APIs jsdom does not have.
|
||||||
const sdkSupportMock = vi.hoisted(() => vi.fn(() => false));
|
const sdkSupportMock = vi.hoisted(() => vi.fn(() => false));
|
||||||
|
const modernRouteMock = vi.hoisted(() => vi.fn(() => true));
|
||||||
vi.mock("@livekit/track-processors", () => ({
|
vi.mock("@livekit/track-processors", () => ({
|
||||||
supportsBackgroundProcessors: (): boolean => sdkSupportMock(),
|
supportsBackgroundProcessors: (): boolean => sdkSupportMock(),
|
||||||
|
supportsModernBackgroundProcessors: (): boolean => modernRouteMock(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const outputSelectionMock = vi.hoisted(() => vi.fn(() => true));
|
const outputSelectionMock = vi.hoisted(() => vi.fn(() => true));
|
||||||
@@ -280,6 +282,16 @@ describe("createCallFooterViewModel", () => {
|
|||||||
expect(backgroundEffectSetting.getValue()).toBe("none");
|
expect(backgroundEffectSetting.getValue()).toBe("none");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("says they run slowly where only the slower route exists", () => {
|
||||||
|
sdkSupportMock.mockReturnValue(true);
|
||||||
|
modernRouteMock.mockReturnValue(false);
|
||||||
|
const slow = lobbyOn("desktop");
|
||||||
|
expect(slow.backgroundEffectNotice$.value).toBe("slow");
|
||||||
|
expect(slow.selectBackgroundEffect$.value).toBeDefined();
|
||||||
|
modernRouteMock.mockReturnValue(true);
|
||||||
|
expect(lobbyOn("desktop").backgroundEffectNotice$.value).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("offers every effect in order", () => {
|
it("offers every effect in order", () => {
|
||||||
sdkSupportMock.mockReturnValue(true);
|
sdkSupportMock.mockReturnValue(true);
|
||||||
expect(lobbyOn("desktop").backgroundEffects$.value).toEqual([
|
expect(lobbyOn("desktop").backgroundEffects$.value).toEqual([
|
||||||
|
|||||||
@@ -8,7 +8,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 { supportsAudioOutputSelection } from "livekit-client";
|
import { supportsAudioOutputSelection } from "livekit-client";
|
||||||
|
|
||||||
import { supportsBackgroundProcessors } from "../livekit/backgroundProcessing";
|
import {
|
||||||
|
supportsBackgroundProcessors,
|
||||||
|
usesFallbackProcessing,
|
||||||
|
} from "../livekit/backgroundProcessing";
|
||||||
import {
|
import {
|
||||||
parseEffect,
|
parseEffect,
|
||||||
serializeEffect,
|
serializeEffect,
|
||||||
@@ -101,6 +104,7 @@ function buildDeviceBehaviors(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const supported = supportsBackgroundProcessors();
|
const supported = supportsBackgroundProcessors();
|
||||||
|
const slow = usesFallbackProcessing();
|
||||||
const offered$ = disableSwitcher$.pipe(
|
const offered$ = disableSwitcher$.pipe(
|
||||||
map((switcherDisabled) => !switcherDisabled && supported),
|
map((switcherDisabled) => !switcherDisabled && supported),
|
||||||
);
|
);
|
||||||
@@ -147,7 +151,11 @@ function buildDeviceBehaviors(
|
|||||||
),
|
),
|
||||||
backgroundEffects$: constant(backgroundEffectChoices()),
|
backgroundEffects$: constant(backgroundEffectChoices()),
|
||||||
backgroundEffectNotice$: scope.behavior(
|
backgroundEffectNotice$: scope.behavior(
|
||||||
offered$.pipe(map((offered) => (offered ? undefined : "unavailable"))),
|
offered$.pipe(
|
||||||
|
map((offered) =>
|
||||||
|
!offered ? "unavailable" : slow ? "slow" : undefined,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,6 +268,43 @@ export const BackgroundEffectsUnavailable: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Where only the slower route exists: offered and choosable, with the cost said. */
|
||||||
|
export const BackgroundEffectsSlowInThisBrowser: Story = {
|
||||||
|
args: {
|
||||||
|
...VideoUnmute.args,
|
||||||
|
backgroundEffectNotice:
|
||||||
|
"Background effects run slowly on this platform, which may cause your video to stutter.",
|
||||||
|
},
|
||||||
|
play: async ({ args, canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
await userEvent.click(canvas.getByRole("button", { name: "Camera" }));
|
||||||
|
const menu = within(document.body);
|
||||||
|
const section = await menu.findByRole("group", {
|
||||||
|
name: "Background effects",
|
||||||
|
});
|
||||||
|
await expect(section).toHaveAccessibleDescription(
|
||||||
|
"Background effects run slowly on this platform, which may cause your video to stutter.",
|
||||||
|
);
|
||||||
|
for (const tile of within(section).getAllByRole("menuitemradio"))
|
||||||
|
await expect(tile).not.toHaveAttribute("aria-disabled");
|
||||||
|
await userEvent.click(
|
||||||
|
within(section).getByRole("menuitemradio", { name: "Background 1" }),
|
||||||
|
);
|
||||||
|
await expect(args.onSelectBackgroundEffect).toHaveBeenCalledWith(
|
||||||
|
"image:arc",
|
||||||
|
);
|
||||||
|
|
||||||
|
const notice = document.body.querySelector(`.${styles.notice}`)!;
|
||||||
|
const frame = document.body
|
||||||
|
.querySelector("[role='menu']")!
|
||||||
|
.getBoundingClientRect();
|
||||||
|
await expect(frame.width).toBeLessThanOrEqual(296);
|
||||||
|
await expect(notice.getBoundingClientRect().right).toBeLessThanOrEqual(
|
||||||
|
frame.right,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/** In a short call the effects scroll into view with the list. */
|
/** In a short call the effects scroll into view with the list. */
|
||||||
export const BackgroundEffectsScrollWhenTheyDoNotFit: Story = {
|
export const BackgroundEffectsScrollWhenTheyDoNotFit: Story = {
|
||||||
args: {
|
args: {
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { supportsBackgroundProcessors as supportsBackgroundProcessorsLivekitSdk } from "@livekit/track-processors";
|
import {
|
||||||
|
supportsBackgroundProcessors as supportsBackgroundProcessorsLivekitSdk,
|
||||||
|
supportsModernBackgroundProcessors,
|
||||||
|
} from "@livekit/track-processors";
|
||||||
|
|
||||||
import { platform } from "../Platform";
|
import { platform } from "../Platform";
|
||||||
|
|
||||||
@@ -13,3 +16,12 @@ import { platform } from "../Platform";
|
|||||||
export function supportsBackgroundProcessors(): boolean {
|
export function supportsBackgroundProcessors(): boolean {
|
||||||
return supportsBackgroundProcessorsLivekitSdk() && platform === "desktop";
|
return supportsBackgroundProcessorsLivekitSdk() && platform === "desktop";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether effects, where they run at all, draw every frame through a canvas:
|
||||||
|
* the slower route, which costs frame rate and stalls the page while the
|
||||||
|
* first one is built.
|
||||||
|
*/
|
||||||
|
export function usesFallbackProcessing(): boolean {
|
||||||
|
return !supportsModernBackgroundProcessors();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user