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,
|
initTheme, toggleTheme, initAccentColor, applyAccentColor,
|
||||||
renderAccentSwatches, selectAccentColor, toggleAccentPicker, lightenColor,
|
renderAccentSwatches, selectAccentColor, toggleAccentPicker, lightenColor,
|
||||||
toggleVinylMode, applyVinylMode,
|
toggleVinylMode, applyVinylMode,
|
||||||
visualizerEnabled, visualizerAvailable,
|
visualizerEnabled, visualizerAvailable, setVisualizerEnabled,
|
||||||
checkVisualizerAvailability, toggleVisualizer, applyVisualizerMode,
|
checkVisualizerAvailability, toggleVisualizer, applyVisualizerMode,
|
||||||
loadAudioDevices, onAudioDeviceChanged,
|
loadAudioDevices, onAudioDeviceChanged,
|
||||||
setupProgressDrag, updateUI, updatePlaybackState, stopPositionInterpolation,
|
setupProgressDrag, updateUI, updatePlaybackState, stopPositionInterpolation,
|
||||||
@@ -186,14 +186,13 @@ window.addEventListener('DOMContentLoaded', async () => {
|
|||||||
// Initialize audio visualizer — auto-enable when supported so the
|
// Initialize audio visualizer — auto-enable when supported so the
|
||||||
// spectrum shows real audio out of the box.
|
// spectrum shows real audio out of the box.
|
||||||
checkVisualizerAvailability().then(() => {
|
checkVisualizerAvailability().then(() => {
|
||||||
if (visualizerAvailable && !visualizerEnabled) {
|
if (!visualizerAvailable) return;
|
||||||
// Auto-enable on first install if loopback capture works.
|
// First install: opt the user in by default since the spectrum
|
||||||
if (localStorage.getItem('visualizerEnabled') === null) {
|
// is the centerpiece of the player view.
|
||||||
localStorage.setItem('visualizerEnabled', 'true');
|
const stored = localStorage.getItem('visualizerEnabled');
|
||||||
}
|
const shouldEnable = stored === null ? true : stored === 'true';
|
||||||
}
|
if (shouldEnable) {
|
||||||
if ((visualizerEnabled || localStorage.getItem('visualizerEnabled') === 'true')
|
setVisualizerEnabled(true); // updates the let in player.js
|
||||||
&& visualizerAvailable) {
|
|
||||||
applyVisualizerMode();
|
applyVisualizerMode();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -277,6 +277,10 @@ function updateVinylSpin() {
|
|||||||
// Audio Visualizer
|
// Audio Visualizer
|
||||||
export let visualizerEnabled = localStorage.getItem('visualizerEnabled') === 'true';
|
export let visualizerEnabled = localStorage.getItem('visualizerEnabled') === 'true';
|
||||||
export let visualizerAvailable = false;
|
export let visualizerAvailable = false;
|
||||||
|
export function setVisualizerEnabled(value) {
|
||||||
|
visualizerEnabled = !!value;
|
||||||
|
localStorage.setItem('visualizerEnabled', visualizerEnabled);
|
||||||
|
}
|
||||||
let visualizerCtx = null;
|
let visualizerCtx = null;
|
||||||
let visualizerAnimFrame = null;
|
let visualizerAnimFrame = null;
|
||||||
export let frequencyData = null;
|
export let frequencyData = null;
|
||||||
@@ -506,13 +510,24 @@ export async function loadAudioDevices() {
|
|||||||
select.appendChild(opt);
|
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++) {
|
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;
|
select.selectedIndex = i;
|
||||||
break;
|
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
|
// Enhance with icon grid
|
||||||
@@ -545,6 +560,19 @@ export async function loadAudioDevices() {
|
|||||||
ws.send(JSON.stringify({ type: 'enable_visualizer' }));
|
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) {
|
} catch (e) {
|
||||||
section.style.display = 'none';
|
section.style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -572,6 +600,13 @@ export async function onAudioDeviceChanged() {
|
|||||||
|
|
||||||
const deviceName = select.value || null;
|
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 {
|
try {
|
||||||
const resp = await fetch('/api/media/visualizer/device', {
|
const resp = await fetch('/api/media/visualizer/device', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
Reference in New Issue
Block a user