feat: configurable ambient background FPS in settings
Add setting for immersive background animation frame rate: - Disabled, 15 FPS (default), 30 FPS, 60 FPS, 120 FPS - Persisted via userStorage, read at canvas init - Disabled renders a single static frame - Setting placed in Appearance section after immersive default view - i18n keys added to all 4 locales (en, es, de, fr) Visualizer panels reverted to 30fps (unrelated to ambient bg).
This commit is contained in:
13 files changed
+94
-12
No files matched your search
@@ -45,7 +45,7 @@
|
||||
|
||||
let animationFrame: number | null = null;
|
||||
let lastRenderTime = 0;
|
||||
const FRAME_INTERVAL = 1000 / 60;
|
||||
const FRAME_INTERVAL = 1000 / 30;
|
||||
|
||||
// Artwork-derived glow color
|
||||
let glowColor = $state('rgba(100, 100, 255, 0.3)');
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
const energyData = new Float32Array(NUM_BANDS);
|
||||
|
||||
let lastRenderTime = 0;
|
||||
const FRAME_INTERVAL = 1000 / 60;
|
||||
const FRAME_INTERVAL = 1000 / 30;
|
||||
|
||||
let bandColors = $state([
|
||||
{ r: 255, g: 60, b: 60 },
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
let trailIndex = 0;
|
||||
|
||||
let lastRenderTime = 0;
|
||||
const FRAME_INTERVAL = 1000 / 60;
|
||||
const FRAME_INTERVAL = 1000 / 30;
|
||||
|
||||
// Primary color extracted from artwork
|
||||
let plotColor = $state({ r: 0, g: 220, b: 200 });
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
const SMOOTHING = 0.3;
|
||||
|
||||
let lastRenderTime = 0;
|
||||
const FRAME_INTERVAL = 1000 / 60;
|
||||
const FRAME_INTERVAL = 1000 / 30;
|
||||
|
||||
// Colors extracted from artwork
|
||||
let colorLeft = $state({ r: 0, g: 220, b: 160 });
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
let isInitialized = false;
|
||||
|
||||
let lastRenderTime = 0;
|
||||
const FRAME_INTERVAL = 1000 / 60;
|
||||
const FRAME_INTERVAL = 1000 / 30;
|
||||
|
||||
// Ring particles - expanding circles triggered by transients
|
||||
interface Ring {
|
||||
|
||||
@@ -50,9 +50,9 @@
|
||||
// Smoothing for visual continuity
|
||||
const SMOOTHING = 0.6;
|
||||
|
||||
// Throttle rendering to 60fps
|
||||
// Throttle rendering to 30fps max
|
||||
let lastRenderTime = 0;
|
||||
const FRAME_INTERVAL = 1000 / 60;
|
||||
const FRAME_INTERVAL = 1000 / 30;
|
||||
|
||||
// Colors extracted from artwork (Material You style)
|
||||
let colorPrimary = $state({ r: 0, g: 220, b: 200 }); // Default cyan
|
||||
|
||||
@@ -1040,6 +1040,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Immersive ambient background FPS
|
||||
const AMBIENT_FPS_KEY = 'qbz-immersive-ambient-fps';
|
||||
const AMBIENT_FPS_OPTIONS = ['0', '15', '30', '60', '120'] as const;
|
||||
let ambientFps = $state(getUserItem(AMBIENT_FPS_KEY) || '15');
|
||||
|
||||
function getAmbientFpsOptions(): string[] {
|
||||
return AMBIENT_FPS_OPTIONS.map(val =>
|
||||
$t(`settings.appearance.ambientFpsOptions.${val === '0' ? 'disabled' : val}`)
|
||||
);
|
||||
}
|
||||
|
||||
function getAmbientFpsDisplayValue(): string {
|
||||
const key = ambientFps === '0' ? 'disabled' : ambientFps;
|
||||
return $t(`settings.appearance.ambientFpsOptions.${key}`);
|
||||
}
|
||||
|
||||
function handleAmbientFpsChange(displayValue: string) {
|
||||
const options = getAmbientFpsOptions();
|
||||
const index = options.indexOf(displayValue);
|
||||
if (index >= 0) {
|
||||
ambientFps = AMBIENT_FPS_OPTIONS[index];
|
||||
setUserItem(AMBIENT_FPS_KEY, ambientFps);
|
||||
}
|
||||
}
|
||||
|
||||
// Mini player default view
|
||||
const MINIPLAYER_VIEW_KEYS = ['remember', 'micro', 'compact', 'artwork', 'queue', 'lyrics'] as const;
|
||||
let miniPlayerDefaultView = $state(
|
||||
@@ -4099,6 +4124,17 @@
|
||||
onchange={handleImmersiveViewChange}
|
||||
/>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">{$t('settings.appearance.ambientFps')}</span>
|
||||
<span class="setting-desc">{$t('settings.appearance.ambientFpsDesc')}</span>
|
||||
</div>
|
||||
<Dropdown
|
||||
value={getAmbientFpsDisplayValue()}
|
||||
options={getAmbientFpsOptions()}
|
||||
onchange={handleAmbientFpsChange}
|
||||
/>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<span class="setting-label">{$t('settings.appearance.miniplayerDefaultView')}</span>
|
||||
<Dropdown
|
||||
|
||||
@@ -760,6 +760,15 @@
|
||||
"split-suggestions": "Geteilt: Vorschläge",
|
||||
"split-queue": "Geteilt: Warteschlange"
|
||||
},
|
||||
"ambientFps": "Immersive Hintergrundanimation",
|
||||
"ambientFpsDesc": "Bildrate für den Hintergrundeffekt im Immersiv-Modus. Niedrigere Werte sparen CPU. Erfordert erneutes Aufrufen des Immersiv-Modus.",
|
||||
"ambientFpsOptions": {
|
||||
"disabled": "Deaktiviert",
|
||||
"15": "15 FPS (Niedrig)",
|
||||
"30": "30 FPS (Mittel)",
|
||||
"60": "60 FPS (Flüssig)",
|
||||
"120": "120 FPS (Ultra)"
|
||||
},
|
||||
"startupPage": "Startseite",
|
||||
"startupPageDesc": "Wähle welche Seite beim App-Start angezeigt wird",
|
||||
"startupPages": {
|
||||
|
||||
@@ -788,6 +788,15 @@
|
||||
"split-suggestions": "Split: Suggestions",
|
||||
"split-queue": "Split: Queue"
|
||||
},
|
||||
"ambientFps": "Immersive background animation",
|
||||
"ambientFpsDesc": "Frame rate for the ambient background effect in immersive mode. Lower values save CPU. Requires re-entering immersive mode.",
|
||||
"ambientFpsOptions": {
|
||||
"disabled": "Disabled",
|
||||
"15": "15 FPS (Low)",
|
||||
"30": "30 FPS (Medium)",
|
||||
"60": "60 FPS (Smooth)",
|
||||
"120": "120 FPS (Ultra)"
|
||||
},
|
||||
"miniplayerDefaultView": "Mini player default view",
|
||||
"miniplayerViews": {
|
||||
"remember": "Remember last used",
|
||||
|
||||
@@ -788,6 +788,15 @@
|
||||
"split-suggestions": "Dividido: Sugerencias",
|
||||
"split-queue": "Dividido: Cola"
|
||||
},
|
||||
"ambientFps": "Animación de fondo inmersivo",
|
||||
"ambientFpsDesc": "Tasa de cuadros del efecto de fondo en el modo inmersivo. Valores bajos ahorran CPU. Requiere volver a entrar al modo inmersivo.",
|
||||
"ambientFpsOptions": {
|
||||
"disabled": "Desactivado",
|
||||
"15": "15 FPS (Bajo)",
|
||||
"30": "30 FPS (Medio)",
|
||||
"60": "60 FPS (Fluido)",
|
||||
"120": "120 FPS (Ultra)"
|
||||
},
|
||||
"miniplayerDefaultView": "Vista predeterminada del mini reproductor",
|
||||
"miniplayerViews": {
|
||||
"remember": "Recordar la última usada",
|
||||
|
||||
@@ -760,6 +760,15 @@
|
||||
"split-suggestions": "Divisé : Suggestions",
|
||||
"split-queue": "Divisé : File d'attente"
|
||||
},
|
||||
"ambientFps": "Animation de fond immersif",
|
||||
"ambientFpsDesc": "Fréquence d'images de l'effet de fond en mode immersif. Des valeurs basses économisent le CPU. Nécessite de ré-entrer en mode immersif.",
|
||||
"ambientFpsOptions": {
|
||||
"disabled": "Désactivé",
|
||||
"15": "15 FPS (Bas)",
|
||||
"30": "30 FPS (Moyen)",
|
||||
"60": "60 FPS (Fluide)",
|
||||
"120": "120 FPS (Ultra)"
|
||||
},
|
||||
"startupPage": "Page de démarrage",
|
||||
"startupPageDesc": "Choisissez quelle page afficher au lancement de l'application",
|
||||
"startupPages": {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
handleContextRestored,
|
||||
getConfig,
|
||||
} from './ImmersiveRenderer';
|
||||
import { getUserItem } from '$lib/utils/userStorage';
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
@@ -80,9 +81,10 @@
|
||||
let lastFrameTime = 0;
|
||||
let isVisible = true;
|
||||
|
||||
// Frame throttling
|
||||
const TARGET_FPS = 60;
|
||||
const FRAME_INTERVAL = 1000 / TARGET_FPS;
|
||||
// Frame throttling — read user preference (0 = disabled, default 15)
|
||||
const AMBIENT_FPS_KEY = 'qbz-immersive-ambient-fps';
|
||||
const ambientFps = parseInt(getUserItem(AMBIENT_FPS_KEY) || '15', 10);
|
||||
const FRAME_INTERVAL = ambientFps > 0 ? 1000 / ambientFps : 0;
|
||||
|
||||
/**
|
||||
* Initialize WebGL resources.
|
||||
@@ -250,11 +252,19 @@
|
||||
*/
|
||||
function startAnimation(): void {
|
||||
if (isAnimating || !isInitialized) return;
|
||||
|
||||
// Animation disabled — render a single static frame
|
||||
if (ambientFps === 0) {
|
||||
render();
|
||||
console.log('[ImmersiveCanvas] Animation disabled, rendered static frame');
|
||||
return;
|
||||
}
|
||||
|
||||
isAnimating = true;
|
||||
lastFrameTime = performance.now();
|
||||
startTime = performance.now(); // Reset time for animation
|
||||
animationFrameId = requestAnimationFrame(animationLoop);
|
||||
console.log('[ImmersiveCanvas] Animation started, intensity:', intensity ?? getConfig().ambientIntensity);
|
||||
console.log('[ImmersiveCanvas] Animation started at', ambientFps, 'fps, intensity:', intensity ?? getConfig().ambientIntensity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,7 +56,7 @@ export interface ImmersiveConfig {
|
||||
|
||||
/** Default configuration */
|
||||
export const DEFAULT_IMMERSIVE_CONFIG: ImmersiveConfig = {
|
||||
targetFps: 60,
|
||||
targetFps: 15,
|
||||
ambientIntensity: 1.0, // Full intensity for maximum ambient motion
|
||||
pauseWhenHidden: true,
|
||||
debugOverlay: false,
|
||||
|
||||
Reference in new issue
Block a user