/* Copyright 2026 Lotus Guild SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ /** * [element-call #36] Remember the per-participant volume slider across calls, * reloads and reconnects. Keyed by Matrix user id (not device), so "Bob is * loud" sticks when Bob switches devices. Local to this browser, never synced. * Only voice is remembered; screenshare audio stays per-share. */ const STORAGE_KEY = "lotus-per-user-volume"; /** Most recently set entries kept; older ones are dropped. */ export const MAX_REMEMBERED = 50; type VolumeMap = Record; const isVolume = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 4; function load(): VolumeMap { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return {}; const parsed: unknown = JSON.parse(raw); if (typeof parsed !== "object" || parsed === null) return {}; const out: VolumeMap = {}; for (const [k, v] of Object.entries(parsed)) if (isVolume(v)) out[k] = v; return out; } catch { return {}; } } function save(map: VolumeMap): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(map)); } catch { // Storage unavailable (private mode, quota): the slider just won't stick. } } /** The remembered volume for `userId`, or 1 (100 %) if none. */ export function getRememberedVolume(userId: string): number { return load()[userId] ?? 1; } /** * Remember `volume` for `userId`. 1 (the default) forgets the entry. The entry * moves to the end so the map is in least-recently-set order, trimmed to * MAX_REMEMBERED. */ export function rememberVolume(userId: string, volume: number): void { if (!isVolume(volume)) return; const map = load(); delete map[userId]; if (volume !== 1) map[userId] = volume; const keys = Object.keys(map); for (const k of keys.slice(0, Math.max(0, keys.length - MAX_REMEMBERED))) delete map[k]; save(map); }