fix(visualizer): auto-enable actually starts capture; persist audio device
Auto-enable was a no-op - Writing 'visualizerEnabled'='true' to localStorage from app.js did not update the exported `let visualizerEnabled` in player.js. So applyVisualizerMode() saw the stale `false` and went into the DISABLE branch — leaving the device 'available, not capturing'. - Add a setVisualizerEnabled() setter exported from player.js and call it before applyVisualizerMode() during boot. Audio device persistence - Save the selected device name to localStorage on change. - On loadAudioDevices(), prefer status.current_device (server's current state) but fall back to the localStorage value if the server doesn't know one (e.g. after a server restart). - If the saved device wasn't recognized by the server, push it back via POST /api/media/visualizer/device so capture lands on it immediately. Best-effort; no toast on failure.
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
initTheme, toggleTheme, initAccentColor, applyAccentColor,
|
||||
renderAccentSwatches, selectAccentColor, toggleAccentPicker, lightenColor,
|
||||
toggleVinylMode, applyVinylMode,
|
||||
visualizerEnabled, visualizerAvailable,
|
||||
visualizerEnabled, visualizerAvailable, setVisualizerEnabled,
|
||||
checkVisualizerAvailability, toggleVisualizer, applyVisualizerMode,
|
||||
loadAudioDevices, onAudioDeviceChanged,
|
||||
setupProgressDrag, updateUI, updatePlaybackState, stopPositionInterpolation,
|
||||
@@ -186,14 +186,13 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
// Initialize audio visualizer — auto-enable when supported so the
|
||||
// spectrum shows real audio out of the box.
|
||||
checkVisualizerAvailability().then(() => {
|
||||
if (visualizerAvailable && !visualizerEnabled) {
|
||||
// Auto-enable on first install if loopback capture works.
|
||||
if (localStorage.getItem('visualizerEnabled') === null) {
|
||||
localStorage.setItem('visualizerEnabled', 'true');
|
||||
}
|
||||
}
|
||||
if ((visualizerEnabled || localStorage.getItem('visualizerEnabled') === 'true')
|
||||
&& visualizerAvailable) {
|
||||
if (!visualizerAvailable) return;
|
||||
// First install: opt the user in by default since the spectrum
|
||||
// is the centerpiece of the player view.
|
||||
const stored = localStorage.getItem('visualizerEnabled');
|
||||
const shouldEnable = stored === null ? true : stored === 'true';
|
||||
if (shouldEnable) {
|
||||
setVisualizerEnabled(true); // updates the let in player.js
|
||||
applyVisualizerMode();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -277,6 +277,10 @@ function updateVinylSpin() {
|
||||
// Audio Visualizer
|
||||
export let visualizerEnabled = localStorage.getItem('visualizerEnabled') === 'true';
|
||||
export let visualizerAvailable = false;
|
||||
export function setVisualizerEnabled(value) {
|
||||
visualizerEnabled = !!value;
|
||||
localStorage.setItem('visualizerEnabled', visualizerEnabled);
|
||||
}
|
||||
let visualizerCtx = null;
|
||||
let visualizerAnimFrame = null;
|
||||
export let frequencyData = null;
|
||||
@@ -506,13 +510,24 @@ export async function loadAudioDevices() {
|
||||
select.appendChild(opt);
|
||||
}
|
||||
|
||||
if (status.current_device) {
|
||||
// Prefer server-reported device; fall back to the last user choice
|
||||
// saved in localStorage (so reloads persist even if the server
|
||||
// forgets between restarts).
|
||||
const savedDevice = localStorage.getItem('audioDevice') || '';
|
||||
const targetDevice = status.current_device || savedDevice;
|
||||
let pendingPushToServer = false;
|
||||
if (targetDevice) {
|
||||
for (let i = 0; i < select.options.length; i++) {
|
||||
if (select.options[i].value === status.current_device) {
|
||||
if (select.options[i].value === targetDevice) {
|
||||
select.selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If the saved device wasn't on the server, push it back so
|
||||
// capture starts on the right one.
|
||||
if (!status.current_device && savedDevice) {
|
||||
pendingPushToServer = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Enhance with icon grid
|
||||
@@ -545,6 +560,19 @@ export async function loadAudioDevices() {
|
||||
ws.send(JSON.stringify({ type: 'enable_visualizer' }));
|
||||
}
|
||||
}
|
||||
|
||||
// If the user's previously-chosen device wasn't recognized by
|
||||
// the server (e.g. server restart cleared in-memory state),
|
||||
// push it back so capture lands on the right one.
|
||||
if (pendingPushToServer && savedDevice) {
|
||||
try {
|
||||
await fetch('/api/media/visualizer/device', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ device_name: savedDevice })
|
||||
});
|
||||
} catch (_) { /* best-effort */ }
|
||||
}
|
||||
} catch (e) {
|
||||
section.style.display = 'none';
|
||||
}
|
||||
@@ -572,6 +600,13 @@ export async function onAudioDeviceChanged() {
|
||||
|
||||
const deviceName = select.value || null;
|
||||
|
||||
// Persist locally so reloads survive even if the server doesn't.
|
||||
if (deviceName) {
|
||||
localStorage.setItem('audioDevice', deviceName);
|
||||
} else {
|
||||
localStorage.removeItem('audioDevice');
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/media/visualizer/device', {
|
||||
method: 'POST',
|
||||
|
||||
Reference in New Issue
Block a user