fix(visualizer): sync state and re-subscribe from audio device load
- Broaden audio import errors from ImportError to Exception, log at warning - Move visualizer WS re-subscription into loadAudioDevices() so it runs after availability is confirmed from the API - Show/hide the visualizer toggle button based on fetched availability
This commit is contained in:
@@ -17,8 +17,8 @@ def _load_numpy():
|
|||||||
try:
|
try:
|
||||||
import numpy as np
|
import numpy as np
|
||||||
_np = np
|
_np = np
|
||||||
except ImportError:
|
except Exception as e:
|
||||||
logger.info("numpy not installed - audio visualizer unavailable")
|
logger.warning("numpy unavailable - audio visualizer disabled: %s", e)
|
||||||
return _np
|
return _np
|
||||||
|
|
||||||
|
|
||||||
@@ -28,8 +28,8 @@ def _load_soundcard():
|
|||||||
try:
|
try:
|
||||||
import soundcard as sc
|
import soundcard as sc
|
||||||
_sc = sc
|
_sc = sc
|
||||||
except ImportError:
|
except Exception as e:
|
||||||
logger.info("soundcard not installed - audio visualizer unavailable")
|
logger.warning("soundcard unavailable - audio visualizer disabled: %s", e)
|
||||||
return _sc
|
return _sc
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -485,7 +485,20 @@ export async function loadAudioDevices() {
|
|||||||
});
|
});
|
||||||
_audioDeviceIconSelect.setValue(select.value, false);
|
_audioDeviceIconSelect.setValue(select.value, false);
|
||||||
|
|
||||||
|
// Sync visualizerAvailable from the fetched status so that
|
||||||
|
// applyVisualizerMode() and the toggle button are consistent.
|
||||||
|
visualizerAvailable = status.available;
|
||||||
|
const btn = document.getElementById('visualizerToggle');
|
||||||
|
if (btn) btn.style.display = visualizerAvailable ? '' : 'none';
|
||||||
|
|
||||||
updateAudioDeviceStatus(status);
|
updateAudioDeviceStatus(status);
|
||||||
|
|
||||||
|
// Re-subscribe the WebSocket if the user had the visualizer enabled.
|
||||||
|
if (visualizerEnabled && visualizerAvailable) {
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send(JSON.stringify({ type: 'enable_visualizer' }));
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
section.style.display = 'none';
|
section.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
WS_MAX_RECONNECT_ATTEMPTS, WS_PING_INTERVAL_MS,
|
WS_MAX_RECONNECT_ATTEMPTS, WS_PING_INTERVAL_MS,
|
||||||
authRequired, showUpdateBanner,
|
authRequired, showUpdateBanner,
|
||||||
} from './core.js';
|
} from './core.js';
|
||||||
import { updateUI, visualizerEnabled, visualizerAvailable, setFrequencyData, stopPositionInterpolation, loadAudioDevices } from './player.js';
|
import { updateUI, setFrequencyData, stopPositionInterpolation, loadAudioDevices } from './player.js';
|
||||||
import { loadScripts, loadScriptsTable, displayQuickAccess } from './scripts.js';
|
import { loadScripts, loadScriptsTable, displayQuickAccess } from './scripts.js';
|
||||||
import { loadCallbacksTable } from './callbacks.js';
|
import { loadCallbacksTable } from './callbacks.js';
|
||||||
import { loadHeaderLinks, loadLinksTable } from './links.js';
|
import { loadHeaderLinks, loadLinksTable } from './links.js';
|
||||||
@@ -81,9 +81,6 @@ export function connectWebSocket(token) {
|
|||||||
loadLinksTable();
|
loadLinksTable();
|
||||||
loadHeaderLinks();
|
loadHeaderLinks();
|
||||||
loadAudioDevices();
|
loadAudioDevices();
|
||||||
if (visualizerEnabled && visualizerAvailable) {
|
|
||||||
newWs.send(JSON.stringify({ type: 'enable_visualizer' }));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
newWs.onmessage = (event) => {
|
newWs.onmessage = (event) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user