mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-07 21:45:18 +00:00
When someone starts or stops speaking, the layout will often be recomputed only to find out that there is ultimately no layout change. We can ignore these redundant updates to avoid re-rendering the InCallView and Grid components, which are relatively slow. For that specific, common case, this reduces JS CPU usage by as much as 70% in my testing.
171 lines
4.5 KiB
TypeScript
171 lines
4.5 KiB
TypeScript
/*
|
|
Copyright 2025 New Vector Ltd.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import { type BehaviorSubject } from "rxjs";
|
|
|
|
import { type LocalUserMediaViewModel } from "./media/LocalUserMediaViewModel.ts";
|
|
import { type MediaViewModel } from "./media/MediaViewModel.ts";
|
|
import { type RingingMediaViewModel } from "./media/RingingMediaViewModel.ts";
|
|
import { type UserMediaViewModel } from "./media/UserMediaViewModel.ts";
|
|
import {
|
|
type GridTileViewModel,
|
|
type SpotlightTileViewModel,
|
|
} from "./TileViewModel.ts";
|
|
import { type Behavior } from "./Behavior.ts";
|
|
import { shallowEquals as arrayShallowEquals } from "../utils/array.ts";
|
|
|
|
export interface GridLayoutMedia {
|
|
type: "grid";
|
|
edgeToEdge: false;
|
|
spotlight?: MediaViewModel[];
|
|
grid: UserMediaViewModel[];
|
|
}
|
|
|
|
export interface SpotlightLandscapeLayoutMedia {
|
|
type: "spotlight-landscape";
|
|
edgeToEdge: boolean;
|
|
spotlight: MediaViewModel[];
|
|
grid: UserMediaViewModel[];
|
|
}
|
|
|
|
export interface SpotlightPortraitLayoutMedia {
|
|
type: "spotlight-portrait";
|
|
edgeToEdge: false;
|
|
spotlight: MediaViewModel[];
|
|
grid: UserMediaViewModel[];
|
|
}
|
|
|
|
export interface SpotlightExpandedLayoutMedia {
|
|
type: "spotlight-expanded";
|
|
edgeToEdge: boolean;
|
|
spotlight: MediaViewModel[];
|
|
pip?: UserMediaViewModel;
|
|
}
|
|
|
|
export interface OneOnOneDesktopLayoutMedia {
|
|
type: "one-on-one-desktop";
|
|
edgeToEdge: false;
|
|
spotlight: UserMediaViewModel;
|
|
pip: LocalUserMediaViewModel | RingingMediaViewModel;
|
|
}
|
|
|
|
export interface OneOnOneMobileLayoutMedia {
|
|
type: "one-on-one-mobile";
|
|
edgeToEdge: true;
|
|
spotlight: UserMediaViewModel | RingingMediaViewModel;
|
|
pip?: LocalUserMediaViewModel;
|
|
}
|
|
|
|
export interface PipLayoutMedia {
|
|
type: "pip";
|
|
edgeToEdge: boolean;
|
|
spotlight: MediaViewModel[];
|
|
}
|
|
|
|
export type LayoutMedia =
|
|
| GridLayoutMedia
|
|
| SpotlightLandscapeLayoutMedia
|
|
| SpotlightPortraitLayoutMedia
|
|
| SpotlightExpandedLayoutMedia
|
|
| OneOnOneDesktopLayoutMedia
|
|
| OneOnOneMobileLayoutMedia
|
|
| PipLayoutMedia;
|
|
|
|
export interface Alignment {
|
|
inline: "start" | "end";
|
|
block: "start" | "end";
|
|
}
|
|
|
|
export interface GridLayout {
|
|
type: "grid";
|
|
spotlight?: SpotlightTileViewModel;
|
|
grid: GridTileViewModel[];
|
|
spotlightAlignment$: BehaviorSubject<Alignment>;
|
|
setVisibleTiles: (value: number) => void;
|
|
}
|
|
|
|
export interface SpotlightLandscapeLayout {
|
|
type: "spotlight-landscape";
|
|
spotlight: SpotlightTileViewModel;
|
|
grid: GridTileViewModel[];
|
|
setVisibleTiles: (value: number) => void;
|
|
}
|
|
|
|
export interface SpotlightPortraitLayout {
|
|
type: "spotlight-portrait";
|
|
spotlight: SpotlightTileViewModel;
|
|
grid: GridTileViewModel[];
|
|
setVisibleTiles: (value: number) => void;
|
|
}
|
|
|
|
export interface SpotlightExpandedLayout {
|
|
type: "spotlight-expanded";
|
|
spotlight: SpotlightTileViewModel;
|
|
pip?: GridTileViewModel;
|
|
pipAlignment$: BehaviorSubject<Alignment>;
|
|
}
|
|
|
|
export interface OneOnOneDesktopLayout {
|
|
type: "one-on-one-desktop";
|
|
spotlight: GridTileViewModel;
|
|
pip: GridTileViewModel;
|
|
pipAlignment$: BehaviorSubject<Alignment>;
|
|
}
|
|
|
|
export interface OneOnOneMobileLayout {
|
|
type: "one-on-one-mobile";
|
|
spotlight: SpotlightTileViewModel;
|
|
pip?: GridTileViewModel;
|
|
pipSize$: Behavior<"sm" | "lg">;
|
|
pipAlignment$: BehaviorSubject<Alignment>;
|
|
}
|
|
|
|
export interface PipLayout {
|
|
type: "pip";
|
|
spotlight: SpotlightTileViewModel;
|
|
}
|
|
|
|
/**
|
|
* A layout defining the media tiles present on screen and their visual
|
|
* arrangement.
|
|
*/
|
|
export type Layout =
|
|
| GridLayout
|
|
| SpotlightLandscapeLayout
|
|
| SpotlightPortraitLayout
|
|
| SpotlightExpandedLayout
|
|
| OneOnOneDesktopLayout
|
|
| OneOnOneMobileLayout
|
|
| PipLayout;
|
|
|
|
/**
|
|
* Tests whether the top-level properties and array elements of layout `a` are
|
|
* equal to those of layout `b`. Useful for deduping redundant layout updates.
|
|
*/
|
|
export function layoutShallowEquals(a: Layout, b: Layout): boolean {
|
|
// If a and b have the same number of keys and every key in a is also in b,
|
|
// then they have the same keys.
|
|
const aKeys = Object.keys(a);
|
|
const bKeys = Object.keys(b);
|
|
if (aKeys.length !== bKeys.length) return false;
|
|
|
|
for (const key of aKeys) {
|
|
if (!(key in b)) return false;
|
|
|
|
// Now check that they have the same values.
|
|
const aValue = (a as any)[key];
|
|
const bValue = (b as any)[key];
|
|
if (Array.isArray(aValue) && Array.isArray(bValue)) {
|
|
// Special case for arrays so we can detect when the grid tiles arrays are
|
|
// essentially the same.
|
|
if (!arrayShallowEquals(aValue, bValue)) return false;
|
|
} else if (aValue !== bValue) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|