feat: adaptive video grid sizing (PR-C1)
Add computeGridSize helper in call.ts that computes optimal row/column counts for a video tile grid given tile count and container dimensions, maximizing tile size while maintaining ~16:9 cells. Apply via computed grid-template-columns/rows on the multi-grid path (no-spotlight) in VideoCallContent.svelte, replacing the fixed grid-cols-1 / grid-cols-1 sm:grid-cols-2 heuristic. The multi-grid now handles all tile counts from 1 to 12+ smoothly. - computeGridSize(tileCount, containerWidth, containerHeight) -> GridSize - Container dimensions bound via clientWidth/clientHeight - useMultiGrid: dropped the > 2 check, now !useSpotlightLayout - gridStyle: computed grid-template-columns/rows string from gridSize - Scope: multi-grid only, no spotlight/strip/breakpoint changes
This commit is contained in:
parent
91a8995a98
commit
664a57d4eb
2 changed files with 58 additions and 4 deletions
|
|
@ -31,6 +31,45 @@ export const LIVEKIT_PARTICIPANTS = 39004
|
|||
|
||||
export {supportsAudioOutputSelection}
|
||||
|
||||
export type GridSize = {rows: number; cols: number}
|
||||
|
||||
/**
|
||||
* Given a tile count and available container dimensions, returns the optimal
|
||||
* number of rows and columns to maximize tile size while maintaining ~16:9
|
||||
* cells. Handles 0 to 12+ tiles smoothly.
|
||||
*/
|
||||
export const computeGridSize = (
|
||||
tileCount: number,
|
||||
containerWidth: number,
|
||||
containerHeight: number,
|
||||
): GridSize => {
|
||||
if (tileCount <= 0) return {rows: 0, cols: 0}
|
||||
if (tileCount === 1) return {rows: 1, cols: 1}
|
||||
|
||||
const TILE_ASPECT = 16 / 9
|
||||
let best: GridSize = {rows: 1, cols: tileCount}
|
||||
let bestArea = 0
|
||||
|
||||
// Guard against unmeasured container — fall back to a landscape-ish default
|
||||
const w = containerWidth > 0 ? containerWidth : 800
|
||||
const h = containerHeight > 0 ? containerHeight : 600
|
||||
|
||||
for (let cols = 1; cols <= tileCount; cols++) {
|
||||
const rows = Math.ceil(tileCount / cols)
|
||||
// Tile width constrained by both axes while maintaining 16:9
|
||||
const tileWidth = Math.min(w / cols, (h / rows) * TILE_ASPECT)
|
||||
const tileHeight = tileWidth / TILE_ASPECT
|
||||
const area = tileWidth * tileHeight
|
||||
|
||||
if (area > bestArea) {
|
||||
bestArea = area
|
||||
best = {rows, cols}
|
||||
}
|
||||
}
|
||||
|
||||
return best
|
||||
}
|
||||
|
||||
const LIVEKIT_DEFAULT_DEVICE_ID = "default"
|
||||
const VISUAL_SOURCES = [Track.Source.Camera, Track.Source.ScreenShare] as const
|
||||
const RECONNECT_DELAYS = [1000, 2000, 4000, 8000, 16000]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@
|
|||
participantMediaState,
|
||||
pubkeyFromLiveKitIdentity,
|
||||
videoTrackRevision,
|
||||
computeGridSize,
|
||||
type GridSize,
|
||||
} from "@app/call"
|
||||
import {profiles} from "@app/core"
|
||||
|
||||
|
|
@ -173,10 +175,19 @@
|
|||
return videoTiles.filter(t => tileKey(t) !== pk)
|
||||
})
|
||||
|
||||
let gridWidth = $state(0)
|
||||
let gridHeight = $state(0)
|
||||
|
||||
const useSpotlightLayout = $derived(primaryTile !== undefined)
|
||||
const useMultiGrid = $derived(!useSpotlightLayout && videoTiles.length > 2)
|
||||
const multiGridClass = $derived(
|
||||
layout === VideoCallLayout.Split ? "grid-cols-1" : "grid-cols-1 sm:grid-cols-2",
|
||||
const useMultiGrid = $derived(!useSpotlightLayout)
|
||||
|
||||
const gridSize = $derived<GridSize>(
|
||||
useMultiGrid ? computeGridSize(videoTiles.length, gridWidth, gridHeight) : {rows: 0, cols: 0},
|
||||
)
|
||||
const gridStyle = $derived(
|
||||
gridSize.rows > 0 && gridSize.cols > 0
|
||||
? `grid-template-columns: repeat(${gridSize.cols}, 1fr); grid-template-rows: repeat(${gridSize.rows}, 1fr)`
|
||||
: "",
|
||||
)
|
||||
|
||||
$effect(() => {
|
||||
|
|
@ -283,7 +294,11 @@
|
|||
{/if}
|
||||
</div>
|
||||
{:else if useMultiGrid}
|
||||
<div class={cx("grid min-h-0 flex-1 content-start gap-2 overflow-y-auto", multiGridClass)}>
|
||||
<div
|
||||
bind:clientWidth={gridWidth}
|
||||
bind:clientHeight={gridHeight}
|
||||
style={gridStyle}
|
||||
class="grid min-h-0 flex-1 content-start gap-2 overflow-y-auto">
|
||||
{#each videoTiles as tile (tileKey(tile))}
|
||||
{@render videoTile(tile, "default")}
|
||||
{/each}
|
||||
|
|
|
|||
Loading…
Reference in a new issue