Skip to content

Releases: aroundmyroom/mStream

v6.14.6-velvet — Syncthing & Sonos Stability Fixes

01 May 05:59

Choose a tag to compare

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 via bootFailCount; 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' from localStorage and start polling GET /api/v1/sonos/transport-status every 2 seconds indefinitely — even though Sonos was explicitly disabled. initOutput() now checks d.enabled === false and silently falls back to Web output, clears the stale localStorage entry, and calls webInitAudio()
  • Main player cast session: S.castingToSonos = true was being restored from casting_sonos localStorage on every page load regardless of S.sonosEnabled. When Sonos was disabled, the player still routed seek/play/pause/next calls to Sonos API endpoints. The restore block is now gated on S.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.md index with all 11 endpoints listed

v6.14.5-velvet — Server Remote Output Picker & Sonos Fixes

30 Apr 12:53

Choose a tag to compare

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-cast permission; 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_VOL raised 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 localStorage on page reload now also disables crossfade at that point (previously missed)
  • Added !castingToMpv && !castingToSonos guard to the timeupdate crossfade 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 because indexOf('/') === 0 < 1
  • Fixed at three layers: addToQueue() strips leading slashes before storing relPath; the /cast endpoint strips them on receipt; sonosCastCurrent() strips them client-side as a defensive measure

v6.14.3-velvet — MPV Full Audio Sync & CUE Artist Images

29 Apr 15:04

Choose a tag to compare

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|…] (the lavfi= wrapper is required; bare pan= 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 _currentBalance and _currentVolumePct between tracks so settings survive without the browser re-sending them
  • On every MPV file-loaded event 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 @keyframes animation 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(), and resetRgAll() 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.js and exported from manager.js
  • getRgStatus() now also returns has_tags, measured_rsgain, measured_ffmpeg breakdowns 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 -d

v6.14.1-velvet — Tag Workshop Fixes & Docker User Mapping

28 Apr 19:01

Choose a tag to compare

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 / renderTagWorkshop functions (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 group

How 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 -d

The 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 -d

v6.14.0-velvet — The Normalize Version

28 Apr 17:54

Choose a tag to compare

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):

  1. EBU R128 measured value (rsgain/ffmpeg)
  2. R128 tag from file (+5 dB offset applied per standard)
  3. ReplayGain tag from file
  4. 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 a volume= + alimiter ffmpeg filter when gain is resolved from the database.
  • MPV Cast (src/api/server-playback.js) applies gain via MPV af set volume= IPC command after file-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/start
  • POST /api/v1/admin/rg/stop
  • GET /api/v1/admin/rg/status
  • POST /api/v1/admin/rg/reset-failed
  • POST /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

27 Apr 20:36

Choose a tag to compare

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:

  1. After every successful library scan, mStream Velvet writes a hidden .velvet.md file to the root of the scanned music directory.
  2. Before the next scan starts, mStream Velvet checks that .velvet.md is present.
  3. If the file is missing — meaning the mount is likely absent — the scan is aborted and your database is left completely untouched.
  4. 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

27 Apr 16:16

Choose a tag to compare

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

27 Apr 13:19

Choose a tag to compare

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-time handles 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

27 Apr 11:30

Choose a tag to compare

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:

  1. Mandatory pre-commit changelog check — Before every git commit, run grep "^## v" changes-fork-velvet.md | head -3 and confirm the current version has a header entry. Do not commit without it.

  2. Mandatory regression check on shared files — Before making any edit to webapp/app.js, src/api/artists-browse.js, or webapp/admin/index.js, run git 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

27 Apr 11:02

Choose a tag to compare

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 localStorage and 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:

  1. The browser shows a microphone permission dialog
  2. Click Allow — the mic stream is immediately released
  3. 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 localStorage and 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 maxRequestSize raised from 1 MB10 MB in 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, _sinkSwitching flag
  • webapp/index.html — cache-buster bump
  • webapp/style.css — chevron visibility fix
  • webapp/admin/index.html — collapsible sidebar structure
  • webapp/admin/index.css — collapsible sidebar styles
  • webapp/admin/index.jstoggleNavGroup(), restoreNavGroups()
  • src/state/config.jsmaxRequestSize default raised to 10MB
  • src/db/sqlite-backend.jscountAlbumsForSources()
  • src/api/admin.js — stats route uses countAlbumsForSources()
  • docs/audio-output.md — new documentation page