Releases: aroundmyroom/mStream
v6.14.6-velvet — Syncthing & Sonos Stability Fixes
v6.14.6-velvet — May 2026 — Syncthing & Sonos Stability Fixes
Overview
Two stability fixes: the Syncthing federation worker no longer causes an infinite boot loop when the binary is missing or crashing, and Sonos polling/cast sessions are now properly stopped when Sonos is disabled in the admin panel.
Bug fixes
Syncthing — prevent boot loop when binary is missing or crashing
- Root cause: enabling the Syncthing federation feature with no binary present (or a binary that crashes immediately) caused
bootProgram()to restart Syncthing at 4-second intervals forever, making the server unusable and requiring a manual config edit to recover - Binary existence check:
setup()now verifies the Syncthing binary exists on disk before doing anything; if missing it logs a clear error and returns without scheduling any restarts - Unsupported platform guard: same early-return if the current OS/arch is not in the supported platform map
- Crash retry cap:
bootProgram()now tracks consecutive failures viabootFailCount; after 5 consecutive crashes it logs a clear error and stops retrying — the counter resets on a successful spawn
Sonos — disabled state not respected on page load (polling + cast session leak)
- Server-remote polling: when Sonos was turned off in the admin panel, the
/server-remote/page could restore_output = 'sonos'fromlocalStorageand start pollingGET /api/v1/sonos/transport-statusevery 2 seconds indefinitely — even though Sonos was explicitly disabled.initOutput()now checksd.enabled === falseand silently falls back to Web output, clears the stalelocalStorageentry, and callswebInitAudio() - Main player cast session:
S.castingToSonos = truewas being restored fromcasting_sonoslocalStorage on every page load regardless ofS.sonosEnabled. When Sonos was disabled, the player still routed seek/play/pause/next calls to Sonos API endpoints. The restore block is now gated onS.sonosEnabled; if disabled, the stale localStorage entry is removed silently
Documentation
- Rewrote
docs/sonos.md— replaced the original design/planning document with full feature documentation covering requirements, output picker, admin panel setup, user permissions, queue behaviour, all API endpoints, config format, and troubleshooting - Added Sonos section to
docs/API.mdindex with all 11 endpoints listed
v6.14.5-velvet — Server Remote Output Picker & Sonos Fixes
v6.14.5-velvet — April 2026 — Server Remote Output Picker & Sonos Fixes
Overview
Adds a full output picker to the /server-remote/ mobile remote — choose between Browser, Server Speaker (MPV), or Sonos directly from the remote. Also fixes several Sonos bugs discovered during testing: crossfade was not being disabled when casting to Sonos (breaking waveform display), and songs added via the file browser were failing to cast with a 400 error.
New features
Server Remote — output picker (Browser / Server / Sonos)
- Three-button segmented group below the volume slider: Browser, Server, Sonos
- Browser — audio plays via
<audio>on the remote itself; Web Audio GainNode applies ReplayGain normalisation so loudness matches the main player - Server — routes to the MPV server-speaker (unchanged behaviour, now selectable without leaving the remote)
- Sonos — casts to a discovered Sonos room; pill row shows available rooms; re-scan button refreshes the list
- Topbar badge shows current output at a glance ("Browser" / "Server" / "Sonos")
- Server and Sonos buttons are hidden for users without the
allow-mpv-castpermission; those users always use Browser mode - Output selection persists in
localStorage; Sonos room selection is also remembered - All controls (prev/play-pause/next, seek, volume) are fully output-routed
- Sonos mode polls
GET /api/v1/sonos/transport-status?ip=every 2 s and auto-advances the queue when a track ends SONOS_MAX_VOLraised 35 → 50 (applies to both the main player and the server-remote)
Bug fixes
Crossfade not disabled when casting to Sonos
- Selecting Sonos in the main player now disables crossfade and saves the previous value, exactly matching the MPV cast behaviour
- Restoring a Sonos cast session from
localStorageon page reload now also disables crossfade at that point (previously missed) - Added
!castingToMpv && !castingToSonosguard to thetimeupdatecrossfade timer — the crossfade can never fire while any cast output is active, preventing waveform display corruption
400 error casting songs added via file browser
- File-browser paths include a leading
/(e.g./Music/song.flac); the cast endpoint rejected these becauseindexOf('/') === 0 < 1 - Fixed at three layers:
addToQueue()strips leading slashes before storingrelPath; the/castendpoint strips them on receipt;sonosCastCurrent()strips them client-side as a defensive measure
v6.14.3-velvet — MPV Full Audio Sync & CUE Artist Images
v6.14.3-velvet — April 2026 — MPV Full Audio Sync & CUE Artist Images
Overview
This release completes the MPV casting experience by bringing the browser player's audio controls (volume, balance, mute, loudness normalisation, pre-amp) into full real-time sync with MPV. It also adds live per-track artist images to both the Now Playing modal and the Playing Now view for CUE-sheet / VA compilation albums, with a smooth 2.5-second fade-in animation on every artist change.
New features
MPV — full real-time audio sync from the browser player
All audio controls in the browser now immediately update MPV while casting:
- Volume slider — dragging updates MPV volume in real time
- Mute button — click mute/unmute and MPV follows
- Balance slider — drag to offset stereo image; double-click resets to centre — all applied to MPV via
lavfi=[pan=stereo|…](thelavfi=wrapper is required; barepan=silently fails in MPV) - Pre-amp slider — moving the Loudness Normalisation pre-amp slider updates MPV's gain immediately without waiting for the next track to load
- All 4 normalisation toggles (enable, mode, clip, pre-amp) — any change is applied to the currently playing MPV track on the fly via
POST /api/v1/server-playback/reapply-gain - Server persists
_currentBalanceand_currentVolumePctbetween tracks so settings survive without the browser re-sending them - On every MPV
file-loadedevent the server re-applies volume + the full af chain (RG gain + balance)
Previously the separate loudnorm/dynaudnorm/acompressor/alimiter toggles in the server-remote Audio tab were a disconnected system that ignored the browser's settings. Those are replaced by read-only status info that reflects what the browser player is actually doing.
MPV — tag edit via NP modal reloads MPV
After saving tags via the Now Playing modal while casting to MPV, the rewritten file is now reloaded on MPV from position 0 — the same behaviour as the browser player. Previously MPV kept streaming the old in-memory file.
CUE / VA albums — live artist images with fade-in
For CUE-sheet files (e.g. a single FLAC with 35 tracks embedded as chapter markers), the artist image now updates automatically as playback crosses each track boundary:
- Chapter titles follow the
"Artist - Track Title"convention; the artist portion is extracted automatically - Now Playing modal — circular 40×40 px avatar next to the artist name; updates on each chapter change
- Playing Now view — sidebar artist photo (
pnow-tadb-img) updates on each chapter change - Both images fade in over 2.5 seconds with a CSS
@keyframesanimation that replays on every swap - A stale-guard key (
filepath:artist) prevents a slow initial fetch from overwriting a faster chapter-change fetch - For non-CUE files the artist image works as before
Home view — Recently Added shelf uses folder-based grouping
The "Recently Added" shelf on the Home screen now uses the same folder-based logic as the full Recently Added view:
- Groups by physical directory instead of album/artist tag
- CD1/CD2 sub-folders collapse into the parent folder card
- Clicking a card loads the full folder and highlights newly-added songs with a blue border
- Fixes the "two albums with the same title merge into one card" bug
Fixes
Normalisation Workshop — stats always zero after restart; auto-start silently failed
- Root cause:
getRgStatus(),resetRgFailed(), andresetRgAll()were called but never defined in the DB layer - Result: UI showed 0/0 for all counters after every restart; auto-start at 60 s never fired; Reset buttons did nothing
- Fix: all three functions added to
sqlite-backend.jsand exported frommanager.js getRgStatus()now also returnshas_tags,measured_rsgain,measured_ffmpegbreakdowns even when the worker is idle
Fix Missing Metadata — shows preview before applying
- "Fix Missing Metadata" now auto-loads a count + up to 8 example derivations (artist, album, title, filename) so you can verify results before committing
- New backend endpoint:
GET /api/v1/admin/wrapped/backfill-folder-metadata/preview - Apply button disabled when nothing can be derived
Recently Added — missing locale key player.recent.gridHint
- The key was used in the view but never added to any locale file — fixed across all 12 locales
Upgrading
docker compose pull
docker compose down
docker compose up -dv6.14.1-velvet — Tag Workshop Fixes & Docker User Mapping
v6.14.1-velvet — April 2026 — Tag Workshop Fixes & Docker User Mapping
Overview
Patch release fixing several Tag Workshop regressions introduced in v6.14.0, plus a new PUID/PGID environment variable for Docker users running on NAS systems.
Bug fixes
Tag Workshop fully restored
The entire Tag Workshop feature (introduced in v6.13.8) was accidentally deleted during the v6.14.0 merge. Fully restored:
viewTagWorkshop/renderTagWorkshopfunctions (475 lines of JS)- Tag Workshop button in the File Explorer toolbar
- All 38
player.tw.*locale keys across all 12 languages - Tag Workshop CSS (64 rules)
Tag Workshop — Accept MB now works for previously-skipped tracks
Clicking Accept MB on tracks that had been previously skipped did nothing — the server returned {ok: true, skipped: true} without writing any tags. Root cause: the DB query only matched tag_status IN ('needs_review', 'confirmed'), excluding 'skipped'. Fixed — previously-skipped tracks can now be re-accepted and written to disk.
ReplayGain badge — visible after browser refresh
The RG gain badge now correctly re-appears after a page reload. Queue entries restored from localStorage had no rg object (serialised before the fix); the player now lazy-fetches metadata for any entry missing rg and caches the result.
New feature
Docker PUID/PGID user mapping
The container now supports PUID and PGID environment variables. mStream Velvet will run as that user so tag edits, recordings, and downloads are written with the correct ownership — essential for NAS setups where music files belong to a specific user (e.g. soulseek:soulseek).
environment:
PUID: 1000 # uid of your music-owning user
PGID: 1000 # gid of your music-owning groupHow to find your UID/GID:
id <your-music-user>
# uid=1000(soulseek) gid=1000(soulseek)Default is PUID=0 PGID=0 (root) — fully backward compatible, existing deployments need no changes unless they want correct file ownership.
Migration from a root container
docker compose down
# Fix ownership of mStream's data directories (NOT your music files)
chown -R 1000:1000 /path/to/save \
/path/to/image-cache \
/path/to/waveform-cache
# Add PUID/PGID to compose.yaml, then:
docker compose up -dThe SQLite database (save/db/mstream.sqlite) is inside save/ — chown updates only the file ownership, the data is completely untouched. No re-scan needed.
See docs/docker.md for the full guide.
Upgrading
docker compose pull
docker compose down
docker compose up -dv6.14.0-velvet — The Normalize Version
v6.14.0-velvet — April 2026 — The Normalize Version
Overview
This release ships a full ReplayGain 2.0 / EBU R128 loudness normalisation system — automatic measurement of every audio file in your library, real-time gain correction in the browser player, and an admin Normalisation Workshop to monitor progress. It also fixes Firefox queue performance with virtual scroll and cleans up several minor regressions.
New — ReplayGain 2.0 / EBU R128 loudness normalisation
Background measurement
mStream Velvet now measures every audio file in your library using the EBU R128 loudness standard:
- rsgain (v3.7+) is the primary measurement tool — it reads embedded ReplayGain/R128 tags already on the file and measures loudness at native speed. It is downloaded automatically on first boot (x86_64 only; Docker image includes it pre-built).
- ffmpeg is the fallback for arm64 or when rsgain is unavailable.
Measurement runs as a background worker thread (50 files per batch, 250 ms yield between batches). Album gain is computed once all tracks in a folder group are measured. The worker auto-starts 60 s after boot if unmeasured files exist.
Values stored per file:
| Column | Meaning |
|---|---|
rg_integrated_lufs |
Integrated loudness (LUFS) |
rg_true_peak_dbfs |
True peak (dBFS) |
rg_track_gain_db |
EBU R128 track gain (dB) |
rg_lra |
Loudness range (LU) |
rg_album_gain_db |
Album gain (dB) |
rg_album_peak_dbfs |
Album peak (dBFS) |
rg_measured_ts |
Unix timestamp of measurement |
rg_measurement_tool |
"rsgain" or "ffmpeg" |
rg_tag_track_gain |
Existing ReplayGain track tag |
rg_tag_track_peak |
Existing ReplayGain peak tag |
rg_tag_album_gain |
Existing ReplayGain album tag |
rg_tag_album_peak |
Existing ReplayGain album peak |
r128_track_gain_db |
Existing R128_TRACK_GAIN tag |
r128_album_gain_db |
Existing R128_ALBUM_GAIN tag |
All 14 columns are added automatically on first boot (ALTER TABLE, safe for existing databases).
Player normalisation
The browser player can now apply normalisation in real time using the Web Audio API (GainNode) — no transcoding, no server round-trip.
Settings (Playback panel):
- Enable normalisation — on/off toggle
- Mode — Track or Album
- Pre-amp — −6 to +6 dB adjustment
- Clip prevention — hard limiter to avoid clipping after gain boost
Gain priority chain (highest priority first):
- EBU R128 measured value (rsgain/ffmpeg)
- R128 tag from file (+5 dB offset applied per standard)
- ReplayGain tag from file
- Disabled (no gain correction)
A badge below the player controls shows the active gain offset in dB.
Server-side gain (transcode + MPV cast)
- Transcoded streams (
src/api/transcode.js) receive avolume=+alimiterffmpeg filter when gain is resolved from the database. - MPV Cast (
src/api/server-playback.js) applies gain via MPVaf set volume=IPC command afterfile-loaded.
Normalisation Workshop (Admin → Tools)
A new Normalisation tab in Admin shows:
- Measurement progress bar (measured / total)
- Live counters: total files, measured, queued, failed, has existing tags
- Active file being measured (monospace display)
- Measurement rate (files/min) and elapsed time
- Tool banner (rsgain or ffmpeg)
- Controls: Start, Stop, Reset Failed, Reset All
New API endpoints:
POST /api/v1/admin/rg/startPOST /api/v1/admin/rg/stopGET /api/v1/admin/rg/statusPOST /api/v1/admin/rg/reset-failedPOST /api/v1/admin/rg/reset-all
Fix — Firefox queue performance (virtual scroll)
Firefox was sluggish when scrolling or interacting with large queues because every queue item had draggable="true", causing the browser to perform hit-testing on every mouse move for every draggable node.
The queue now uses virtual scroll: only ~15 DOM nodes are rendered at a time regardless of queue size. A Float32Array of cumulative heights and a binary search keep the viewport window up to date. Drag-and-drop works unchanged via a drag-freeze pattern.
Fix — AcoustID and MB enrichment worker queue priority removed
The acoustid_priority and mb_enrich_priority database columns are no longer written or read by the workers. Queue ordering is now simply by insertion/timestamp order, which is sufficient and simpler. The Retry No Match and Re-enrich files buttons in the Tag Workshop continue to work.
Files changed (selection)
src/api/rg-analysis.js (new) · src/util/rg-analysis-worker.mjs (new) · src/util/rsgain-bootstrap.js (new) · src/db/sqlite-backend.js · src/db/manager.js · src/api/db.js · src/api/transcode.js · src/api/server-playback.js · src/server.js · src/util/acoustid-worker.mjs · src/util/mb-enrich-worker.mjs · webapp/app.js · webapp/admin/index.js · webapp/admin/index.html · webapp/index.html · webapp/style.css · webapp/locales/*.json (all 12) · Dockerfile · docs/replaygain-info.md (new) · package.json
v6.13.8-velvet — Tag Workshop, Scanner Mount Guard & AcoustID Improvements
v6.13.8-velvet — April 2026 — Tag Workshop, Scanner Mount Guard & AcoustID Improvements
Overview
This release ships the Tag Workshop — a full MusicBrainz-powered batch tag editor accessible from the file explorer — plus two scanner safety improvements and an AcoustID quality-of-life feature.
New — Tag Workshop
A new batch metadata editor is available in the file explorer for admin users. Click Tag Workshop in the filter bar on any non-root folder.
MusicBrainz comparison panel
Files that have been fingerprinted and enriched via AcoustID/MusicBrainz show their current tags side-by-side with MB-suggested values, with per-field diff highlighting (current value in strikethrough red → suggested value in green). You can:
- Accept a single file's suggestions with Accept MB
- Bulk-accept all checked files with Accept MB for N checked
Accepted tags are written to disk immediately and marked in the database.
Manual editor panel
Files without MB data have a bulk field editor (artist / album / year / genre — blank = keep) and a per-file inline editor with all fields (title / artist / album / year / genre / track / disc).
Other features
- Filename shown on every row for easy identification
- Per-file enrichment status badge — shows queue position, no AcoustID match, MB error, or no data
- Enrich N files with MusicBrainz button — queues fingerprinted files for background MB enrichment and starts the worker
- Subdirectory navigation — browse into subfolders without leaving the workshop
- Breadcrumb navigation — click any path segment to jump directly to that folder
- Live progress counter during bulk save operations
- Admin-only access — the Tag Workshop button is only visible to admins; all server endpoints enforce admin guard
New API endpoints: GET /api/v1/tagworkshop/folder, POST /api/v1/tagworkshop/enrich/files
New — Scanner mount guard (.velvet.md sentinel)
mStream Velvet now protects your database from being accidentally wiped if an NFS, SMB, or Docker volume mount disappears.
How it works:
- After every successful library scan, mStream Velvet writes a hidden
.velvet.mdfile to the root of the scanned music directory. - Before the next scan starts, mStream Velvet checks that
.velvet.mdis present. - If the file is missing — meaning the mount is likely absent — the scan is aborted and your database is left completely untouched.
- A second guard also aborts the scan if the filesystem walk finds zero audio files but the database already has records for that library.
Upgrading from an earlier version: the first scan after upgrading will not have a .velvet.md file yet. mStream Velvet detects this and allows the first scan to proceed (the zero-files guard still protects against a genuinely absent mount). The sentinel is written automatically at the end of that scan — all subsequent scans are then fully protected.
Admin → Directories shows a Reset mount guard button for each root library directory. Use it to re-write the sentinel if it was accidentally deleted and scanning is now blocked. Child vpaths (subdirectory filters of a parent library) correctly share the parent's sentinel and show no button.
New API endpoint: POST /api/v1/admin/directory/reset-sentinel
New — AcoustID: Retry No Match
The AcoustID stats panel in Admin now shows not_found and errors as separate rows. A new Retry N No Match button re-queues all files where no fingerprint match was found in the AcoustID database, and starts the worker immediately — useful when running mStream Velvet on new or obscure releases that AcoustID may have since indexed.
New API endpoint: POST /api/v1/acoustid/reset-not-found
Fix — Unknown artist placeholder image quality
The default placeholder image shown for artists without a photo has been updated to a higher-quality version.
v6.13.7-velvet — Proxy Auth Fix, Log Retention & Persistence Fixes
v6.13.7-velvet — April 2026 — Proxy Auth Fix, Log Retention & Persistence Fixes
Overview
This release fixes three bugs introduced between v6.13.3 and v6.13.5, adds configurable log file retention, and closes a long-standing 500 error on the art lookup endpoint.
Critical fix — proxy authentication broken since v6.13.5
Who is affected: everyone running mStream Velvet behind a reverse proxy (nginx, Caddy, Traefik, etc.) — especially if the proxy itself has its own password/pre-authentication step.
What happened: v6.13.5 added httpOnly: true to the auth cookie as a security hardening measure. This accidentally broke the proxy recovery mechanism that had been in place since well before v6.13.0. When a proxy strips the x-access-token request header, the client falls back to a plain fetch() (letting the browser send the cookie automatically), then reads the token back from document.cookie to restore S.token for WebSocket connections. With httpOnly: true, document.cookie no longer contains the cookie, so S.token stayed empty — resulting in persistent 401 errors on /api/v1/db/status and all subsequent API calls.
httpOnly was removed. It provided no meaningful security improvement here because the same JWT is already stored in localStorage, which has identical XSS exposure.
As a bonus, the server now also accepts Authorization: Bearer <token> for programmatic/API clients.
Fix — custom artist placeholder image lost after reinstall or Docker recreation
Who is affected: anyone who uploaded a custom artist placeholder image and then reinstalled, ran git clean, cloned fresh, or — for Docker users — recreated the container without a persistent image-cache volume.
The image was stored in image-cache/artist-placeholder.jpg. That directory is treated as an ephemeral cache on all install types and is not guaranteed to survive. It has been moved to save/conf/artist-placeholder.jpg. The save/ directory is the designated persistent data directory and is always volume-mapped in Docker setups.
Automatic migration: on first boot after upgrading, if the file exists at the old path and not the new one, it is moved automatically with no manual steps required.
Fix — art endpoint crashing with 500 on missing files
When a song in a restored queue no longer existed on disk (drive unmounted, files moved, etc.), the /api/v1/files/art endpoint threw an ENOENT error which propagated to the browser as an ugly 500. The endpoint now returns { aaFile: null } — no album art is shown, but the player continues working normally.
New — configurable log file retention
A Keep logs for dropdown is now available in Admin → Logging:
| Option | Retention |
|---|---|
| 1 day | Minimal — active debugging only |
| 3 days | Short-term |
| 14 days | Default |
| 30 days | Extended |
A Delete old logs now button performs an immediate prune without waiting for the next rotation cycle. The setting is persisted in save/conf/default.json as logRetention.
Files changed
src/api/auth.js · src/api/download.js · src/api/artists-browse.js · src/server.js · src/logger.js · src/util/admin.js · src/api/admin.js · src/state/config.js · webapp/admin/index.js · webapp/locales/en.json · webapp/locales/nl.json · all other locale files (EN placeholder sync)
v6.13.6-velvet — Artist Fan Art & Scan Scheduler
v6.13.6-velvet — April 2026 — Artist Fan Art & Scan Scheduler
Overview
v6.13.6 restores artist fan art display (broken since v6.13.4), fixes the artist profile CSS, and adds a new daily scan start time option to the scan scheduler.
What's fixed
Artist fan art not displaying
Four database columns (fanart_file, genre, country, formed_year) were silently removed from the code in v6.13.4. Fan art banners, genre/country/formed-year chips, and the hero layout were therefore never shown regardless of what was cached on disk. All four columns are now restored with automatic DB migrations, and the 400 or so previously cached fan art images have been backfilled into the database.
Artist profile CSS missing
The entire .artpro-hero / .artpro-meta-chips / .artpro-meta-chip CSS block was also deleted in v6.13.4, making the hero banner render with incorrect dimensions and chips completely unstyled. The block is fully restored.
Bio layout when fan art is present
When a fan art banner is available the square artist photo is now hidden and the biography panel expands to full width, using the new .artpro-header-no-img CSS class.
What's new
Daily scan start time
A new Daily Start Time field is available in Admin → DB Settings. Set a fixed 24-hour clock time (e.g. 02:30) and the library scanner will fire at that exact time every day instead of counting hours from boot. Leave it empty to keep the existing interval-only behaviour.
- The next scheduled scan is shown as a live countdown in the Admin panel
- The setting is persisted in the server config and survives restarts
- The POST endpoint
/api/v1/admin/db/params/scan-start-timehandles updates
Files changed
src/db/sqlite-backend.js · src/db/task-queue.js · src/state/config.js · src/util/admin.js · src/api/admin.js · webapp/app.js · webapp/style.css · webapp/admin/index.js · webapp/locales/en.json · webapp/locales/nl.json · all other locale files (EN placeholder sync)
v6.13.5-velvet — Regression Fix: Custom Artist Placeholder Image
v6.13.5-velvet — April 2026 — Regression Fix: Custom Artist Placeholder Image
Overview
v6.13.5 is a targeted regression fix. The v6.13.4 release inadvertently removed the entire custom artist placeholder image feature that was introduced in v6.13.3 — affecting all four artist image surfaces in the player, the Admin panel UI, and the backend API endpoints. This release fully restores it.
In addition, two mandatory process-safety rules have been added to the internal development instructions to prevent this class of regression from recurring.
Regression Fix: Custom Artist Placeholder Image Restored
The following components were silently lost in v6.13.4 and are now restored:
Backend — src/api/artists-browse.js
Three admin API endpoints were missing:
| Endpoint | Purpose |
|---|---|
POST /api/v1/admin/artists/placeholder |
Upload a custom placeholder image (resized to 400×400 JPEG) |
DELETE /api/v1/admin/artists/placeholder |
Remove the custom image, revert to built-in default |
GET /api/v1/admin/artists/placeholder-info |
Return { hasCustom: true/false } |
Constants ARTIST_PLACEHOLDER_FILE and ARTIST_PLACEHOLDER_DEFAULT were also restored.
Admin panel — webapp/admin/index.js
The placeholder card in Admin → Artist Images was missing entirely:
- Circular preview image showing the current placeholder
- Upload image button with multipart upload
- Delete custom button (visible only when a custom image is active)
- Status text: "Custom placeholder active" / "Showing built-in default"
- Cache-refresh hint after upload/delete
Player — webapp/app.js (5 locations)
All four artist image surfaces reverted to a CSS skeleton div or showing nothing:
| Surface | What was broken | What is restored |
|---|---|---|
| Artist Home shelf cards | CSS animated skeleton div | <img src="api/v1/artists/placeholder"> |
| Artist Library list rows | Empty — no image at all | Placeholder img + onerror fallback |
| Artist Profile page (initial load) | CSS artpro-img-ph div |
<img src="api/v1/artists/placeholder"> |
| Artist Profile page (after data loads) | Only showed image if artist had one | Always shows img; placeholder if no image found |
| Playing Now panel | No image shown until API returned one | Placeholder immediately; replaced when image loads |
Locales — all 12 locale files
Ten admin.artists.placeholder* i18n keys were missing from all locale files. Restored:
nl.json— restored from v6.13.3 git history (Dutch translations)- All other 10 locales — synced from English as placeholder text
Process Improvements
Two new mandatory rules added to the internal development checklist to prevent future regressions of this type:
-
Mandatory pre-commit changelog check — Before every
git commit, rungrep "^## v" changes-fork-velvet.md | head -3and confirm the current version has a header entry. Do not commit without it. -
Mandatory regression check on shared files — Before making any edit to
webapp/app.js,src/api/artists-browse.js, orwebapp/admin/index.js, rungit diff HEAD~1 HEAD -- <file>to see what the previous commit added. Verify those features are still present after editing.
Root Cause
The v6.13.4 commit made large edits to the same three files that v6.13.3 had just modified. The net result was 386 + 163 + 415 lines net-removed across those files — the placeholder feature was discarded without any automated or manual check catching it. The public endpoint (GET /api/v1/artists/placeholder in server.js) was not touched and continued to work, which is why the missing feature was not immediately obvious from a server health check.
v6.13.4-velvet — Audio Output & Queue Fixes
v6.13.4-velvet — April 2026 — Audio Output & Queue Fixes
Overview
This release ships the Audio Output Device selector — choose which speaker or audio device your browser sends music to without touching Windows or macOS system settings. It also fixes a PayloadTooLarge crash for users with very large queues, collapsible Admin sidebar sections, and several smaller fixes.
New: Audio Output Device Selector
A new section in Settings → Playback → Audio Output Device lets you pick any connected speaker, headphone, or Bluetooth device for playback — independently of whatever Windows has set as the system default.
How it works
- The dropdown is populated via
navigator.mediaDevices.enumerateDevices() - Your selection is stored in
localStorageand automatically restored on the next page load — even across different browsers on the same machine - The switch is seamless: no track restart, no gap in playback
First-time setup
Browsers require microphone permission before they reveal individual audio device names (a browser security requirement — mStream Velvet never records anything). If the dropdown shows only Default, click the Allow devices button:
- The browser shows a microphone permission dialog
- Click Allow — the mic stream is immediately released
- The dropdown populates with all your audio outputs
In Edge, the microphone entry only appears in the lock icon after the site has requested it — use the Allow devices button instead of trying the lock icon manually.
Technical note
mStream Velvet uses the Web Audio API for EQ, ReplayGain, and VU meters. Because the audio element is connected to a MediaElementSourceNode, HTMLMediaElement.setSinkId() has no effect — AudioContext.setSinkId() is the correct API, and that's what we use. The stored device preference is applied the moment the AudioContext is created on first play.
See docs/audio-output.md for full documentation.
New: Collapsible Admin Sidebar
All 6 sidebar sections in the Admin panel can now be collapsed and expanded by clicking the section header — matching the main player's sidebar behaviour.
- Animated chevron rotates when collapsed
- Collapse state is saved to
localStorageand restored on reload - The section containing the currently active view is always kept expanded on boot
Fix: Large Queue No Longer Crashes the Server (PayloadTooLarge)
Users with very large queues (1,000+ songs) were hitting a PayloadTooLargeError when mStream Velvet tried to sync the queue to the server. This affected all browsers equally.
Two-part fix:
- Default
maxRequestSizeraised from1 MB→10 MBin the server config (affects Docker users and new installs without an explicit setting) - Client-side queue sync capped at 2,000 songs centred around the current track — at ~400 bytes/track this keeps payloads well under 1 MB while the full queue remains in memory and
localStorage
Fix: Admin Stats — Album Count
The album count in Admin → Stats previously counted every distinct album tag across all vpaths, inflating the number. It now only counts albums within albumsOnly directories.
Fix: Sidebar Chevron Visibility
Nav-chevron arrows in the dark/Velvet sidebar theme were too faint. Opacity and size increased for readability.
Files Changed
webapp/app.js— audio output selector, queue sync cap,_sinkSwitchingflagwebapp/index.html— cache-buster bumpwebapp/style.css— chevron visibility fixwebapp/admin/index.html— collapsible sidebar structurewebapp/admin/index.css— collapsible sidebar styleswebapp/admin/index.js—toggleNavGroup(),restoreNavGroups()src/state/config.js—maxRequestSizedefault raised to10MBsrc/db/sqlite-backend.js—countAlbumsForSources()src/api/admin.js— stats route usescountAlbumsForSources()docs/audio-output.md— new documentation page