Files
element-call/src/lotus/lotusVolumeMemory.ts
T

66 lines
2.0 KiB
TypeScript
Raw Normal View History

/*
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<string, number>;
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);
}