Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21adeb1070 | |||
| 68614c982d | |||
| a2a258e898 | |||
| 456eb3a881 | |||
| c586b1b518 | |||
| ee5184920d |
+21
-6
@@ -1,16 +1,31 @@
|
|||||||
## v0.1.7 (2026-04-17)
|
## v0.1.8 (2026-04-18)
|
||||||
|
|
||||||
### Changes
|
### Bug Fixes
|
||||||
- Bundle the audio visualizer by default. `soundcard` and `numpy` are now mandatory dependencies instead of gated behind the optional `[visualizer]` extra, so the visualizer works out of the box on every install.
|
|
||||||
|
- Fix numpy failing to import in the Windows installer — preserve required numpy submodules (`lib`, `linalg`, `ma`, `polynomial`, `fft`, `ctypeslib`, `matrixlib`) during build cleanup ([68614c9](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/68614c9))
|
||||||
|
- Fix numpy failing to locate `libopenblas` DLL in the Windows installer — generate `_distributor_init_local.py` at build time and call `os.add_dll_directory()` at runtime ([456eb3a](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/456eb3a))
|
||||||
|
- Fix visualizer toggle button not reflecting actual availability after audio device load ([ee51849](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/ee51849))
|
||||||
|
- Fix visualizer WebSocket re-subscription firing before availability is confirmed from the API — moved from `connectWebSocket` to `loadAudioDevices` ([ee51849](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/ee51849))
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Development / Internal
|
### Development / Internal
|
||||||
|
|
||||||
#### CI/Build
|
#### CI/Build
|
||||||
- Simplify `build-dist-linux.sh` to install `.` instead of `.[visualizer]` now that the deps are part of the base install.
|
- Generate `numpy/_distributor_init_local.py` in Windows build script to fix DLL loading in embedded Python ([456eb3a](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/456eb3a))
|
||||||
|
|
||||||
|
#### Other
|
||||||
|
- Broaden audio library import errors from `ImportError` to `Exception` and log at `warning` level with error details ([ee51849](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/ee51849))
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Contributors
|
<details>
|
||||||
- @alexei.dolgolyov — 1 change
|
<summary>All Commits</summary>
|
||||||
|
|
||||||
|
| Hash | Message | Author |
|
||||||
|
|------|---------|--------|
|
||||||
|
| [68614c9](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/68614c9) | fix(windows): keep required numpy submodules in build cleanup | alexei.dolgolyov |
|
||||||
|
| [456eb3a](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/456eb3a) | fix(windows): fix numpy DLL loading in embedded Python distribution | alexei.dolgolyov |
|
||||||
|
| [ee51849](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/ee51849) | fix(visualizer): sync state and re-subscribe from audio device load | alexei.dolgolyov |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|||||||
+5
-2
@@ -91,8 +91,11 @@ cleanup_site_packages() {
|
|||||||
find "$sp_dir" -name "*.pyi" -delete 2>/dev/null || true
|
find "$sp_dir" -name "*.pyi" -delete 2>/dev/null || true
|
||||||
rm -rf "$sp_dir"/{pip,setuptools,pkg_resources,_distutils_hack}* 2>/dev/null || true
|
rm -rf "$sp_dir"/{pip,setuptools,pkg_resources,_distutils_hack}* 2>/dev/null || true
|
||||||
|
|
||||||
# Trim numpy if present
|
# Trim numpy if present.
|
||||||
for mod in polynomial linalg ma lib distutils f2py typing _pyinstaller; do
|
# Keep only modules that numpy/__init__.py does NOT import unconditionally —
|
||||||
|
# lib, linalg, ma, polynomial, fft, ctypeslib, matrixlib are all required for
|
||||||
|
# `import numpy` to succeed, so they MUST stay.
|
||||||
|
for mod in distutils f2py typing _pyinstaller; do
|
||||||
rm -rf "$sp_dir/numpy/$mod" 2>/dev/null || true
|
rm -rf "$sp_dir/numpy/$mod" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -119,6 +119,22 @@ for whl in "$WHEEL_DIR"/*.whl; do
|
|||||||
unzip -qo "$whl" -d "$SITE_PACKAGES"
|
unzip -qo "$whl" -d "$SITE_PACKAGES"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# numpy wheels from PyPI don't include _distributor_init_local.py unless
|
||||||
|
# patched by delvewheel. In embedded Python, os.add_dll_directory() is never
|
||||||
|
# called, so libopenblas can't be found and numpy fails to import.
|
||||||
|
# Generate the missing loader here instead.
|
||||||
|
if [ -d "${SITE_PACKAGES}/numpy" ]; then
|
||||||
|
cat > "${SITE_PACKAGES}/numpy/_distributor_init_local.py" << 'EOF'
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
_libs = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'numpy.libs'))
|
||||||
|
if os.path.isdir(_libs):
|
||||||
|
os.add_dll_directory(_libs)
|
||||||
|
EOF
|
||||||
|
echo "Generated numpy/_distributor_init_local.py"
|
||||||
|
fi
|
||||||
|
|
||||||
cleanup_site_packages "$SITE_PACKAGES" "pyd" "dll"
|
cleanup_site_packages "$SITE_PACKAGES" "pyd" "dll"
|
||||||
verify_frontend
|
verify_frontend
|
||||||
copy_app_files "$DIST_DIR"
|
copy_app_files "$DIST_DIR"
|
||||||
|
|||||||
@@ -15,10 +15,25 @@ def _load_numpy():
|
|||||||
global _np
|
global _np
|
||||||
if _np is None:
|
if _np is None:
|
||||||
try:
|
try:
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
# Embedded Python doesn't auto-load DLLs from numpy.libs;
|
||||||
|
# add the directory explicitly so libopenblas can be found.
|
||||||
|
try:
|
||||||
|
import importlib.util
|
||||||
|
spec = importlib.util.find_spec('numpy')
|
||||||
|
if spec and spec.submodule_search_locations:
|
||||||
|
numpy_dir = list(spec.submodule_search_locations)[0]
|
||||||
|
libs_dir = os.path.join(os.path.dirname(numpy_dir), 'numpy.libs')
|
||||||
|
if os.path.isdir(libs_dir):
|
||||||
|
os.add_dll_directory(libs_dir)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
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 +43,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) => {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "media-server-frontend",
|
"name": "media-server-frontend",
|
||||||
"version": "0.1.5",
|
"version": "0.1.8",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "media-server-frontend",
|
"name": "media-server-frontend",
|
||||||
"version": "0.1.5",
|
"version": "0.1.8",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.27.4"
|
"esbuild": "^0.27.4"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "media-server-frontend",
|
"name": "media-server-frontend",
|
||||||
"version": "0.1.5",
|
"version": "0.1.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Frontend build tooling for media server WebUI",
|
"description": "Frontend build tooling for media server WebUI",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "media-server"
|
name = "media-server"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
description = "REST API server for controlling system-wide media playback"
|
description = "REST API server for controlling system-wide media playback"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
|
|||||||
Reference in New Issue
Block a user