Skip to content

Releases: aroundmyroom/Velvet

v0.3.22 — Auto-DJ scoring engine + Samsung TV parity

Choose a tag to compare

@aroundmyroom aroundmyroom released this 11 Aug 16:03

Velvet v0.3.22 — Auto-DJ scoring engine + TV parity

A smarter Auto-DJ brain for the web player, now also running on the Samsung TV app.

Auto-DJ — weighted scoring engine, year/era continuity, smarter fallback

Auto-DJ used to accept whatever song the server handed back first. It now asks
for up to 5 candidates per pick and scores each one before queuing the best:

Signal Weight What it checks
Harmonic (Camelot) fit 25% Same key, adjacent key, or two steps away
Genre compatibility 15% A matrix of transition scores (e.g. electronic↔disco 0.7, electronic↔hard 0.05) rather than a strict same/different check
Last.fm similarity rank 20% How close the artist is to the seed artist on Last.fm's similar-artist list
BPM proximity 20% Blended against a rolling tempo average
Year/era proximity 10% New — a rolling average of the last 8 picked years, so a run of similar-artist hops can't quietly drift from 2005 to 2023
Artist diversity 10% Penalises artists played recently in the session

Fixes bundled with the new engine

  • BPM/harmonic anchors no longer seed from a manually-played song. Previously, switching Auto-DJ on while listening to an unrelated track (e.g. a slow ballad) could lock the whole session to that tempo, exhausting the similar-artist pool almost immediately. The very first Auto-DJ pick is now unconstrained and lets artist/genre/harmonic signals guide it — BPM continuity kicks in naturally once the first real DJ pick plays.
  • Tier-3 "free pick" fallback (used when nothing matches BPM/key constraints) now retries up to 3 times and still enforces the keyword filter and hard genre-group jump guard, instead of accepting anything unconditionally.
  • Genre-escape fallback (used to break out of a stuck genre run) now validates its candidate the same way as every other pick; if the escape candidate is itself blocked, the original pick is kept.
  • Artist-cooldown list capped at 15 entries sent to the server (was up to 500). On smaller libraries the full history could exhaust the whole catalog, silently disabling the cooldown constraint server-side.
  • Compound artist names ("A & B", "A vs. B", "A pres. B", "A x B") are now split and tried individually against Last.fm until one returns results, and each match now carries its similarity rank for the new scoring engine.

Velvet TV — Auto-DJ parity

The Samsung TV app's Auto-DJ was a simple "pick a random unplayed song" loop.
It now runs the same scoring/continuity engine as the web player: similar-artist
matching, BPM continuity, harmonic mixing, year/era continuity, artist diversity,
and the same 3-tier fallback (similar+BPM+key → library-wide BPM+key → free pick).

The TV has no settings screen, so there's nothing to configure — these all run
with fixed defaults tuned to match the web app's behaviour. The keyword and
genre whitelist/blacklist filters remain web/desktop-only, since editing a list
of words or genres needs a UI the remote doesn't have.


Downloads

Samsung TV app (.wgt)

Docker image

docker pull ghcr.io/aroundmyroom/velvet:v0.3.22
docker pull ghcr.io/aroundmyroom/velvet:latest

v0.3.21 — Maintenance

Choose a tag to compare

@aroundmyroom aroundmyroom released this 10 Aug 16:36

Velvet v0.3.21 — Maintenance

Waveform reliability improvements, visualizer graceful fallback, Subsonic large-queue position fix, and deterministic album sort order.

Waveform — reliability and performance

  • New cache format. Waveform cache files are regenerated on first boot with an improved internal format. Old files are cleaned up automatically — no manual action needed.
  • Eliminated redundant redraws. The waveform animation loop now skips frames where the playhead pixel hasn't moved, reducing GPU wake-ups during normal playback. Rendering resumes immediately on any seek.
  • Seek-while-paused now repaints. Seeking on a paused track previously left the waveform showing the old playhead position until play resumed. The progress bar updates immediately.
  • Fixed empty bars at the tail of short files — the downsampling window now always covers at least one sample.

Visualizer — graceful error recovery

If WebGL2 is unavailable (unsupported GPU, browser flag, or driver restriction), the visualizer now shuts down cleanly instead of throwing an unhandled exception. A toast message informs you — "Visualizer unavailable — your browser or GPU may not support WebGL2" — and playback is never affected.

Subsonic — large-queue position loss fixed

Fixed: resuming a large queue (1000+ tracks) from a Subsonic client always restarted from track 1. The HTTP query string parser silently drops parameters past a certain limit; with a long id list the current track and position parameters were discarded before we could read them. These scalars are now read directly from the raw URL, bypassing the limit entirely. The same fix applies to createPlaylist and updatePlaylist with large song lists.

Albums — deterministic sort order

Fixed: same-year albums for an artist could appear in a different order on each page load. The SQL query used ORDER BY year DESC with no tiebreaker, leaving within-year ordering to the database engine — which can vary by query plan. Albums sharing a year are now sorted consistently A→Z (case-insensitive).

v0.3.20 — Samsung TV app: album parity + shareable build

Choose a tag to compare

@aroundmyroom aroundmyroom released this 09 Aug 11:45

Velvet v0.3.20 — Samsung TV app: album parity + shareable build

This release brings the Velvet TV Samsung Tizen app up to feature parity with
the web player for albums, and adds a clean, shareable widget build you can attach
to a GitHub release.

Velvet TV (Samsung Tizen 5.5+)

Albums now match the web UI:

  • CUE-sheet albums (single-file FLAC+CUE) are split into their individual
    tracks — selecting a track seeks to its start, stops cleanly at the boundary,
    and auto-advances to the next.
  • Multi-disc sets are grouped under disc headers ("CD 1", "Disc 2", …).
  • Each album shows year • track count • total duration; the currently
    playing track is highlighted in the list.

A–Z quick-jump strip:

  • A vertical alphabet on the Albums view — press Right from the grid, move
    Up/Down to a letter, OK to jump to the first album starting with it.
    Only letters that have albums are shown (digits/symbols under #).
  • Fixed the D-pad visibility check so position: fixed controls are reachable by
    the remote.

Shareable, credential-free build:

  • npm run build:tizen:dist produces dist/velvet-tv-<version>.wgt with no
    server URL or login baked in
    — safe to publish so users configure their own
    server on the TV.
  • npm run release now builds and uploads this clean widget automatically, and
    aborts if the package ever appears to contain credentials.
  • New guide: docs/tizen-tv.md — install via
    Apps2Samsung, remote/media-key
    reference, album features, build modes and troubleshooting.

Compatibility

The TV app targets Tizen 5.5 / Chromium 69 and avoids the Web Audio API
(which silences audio on Samsung panels); visuals use Canvas 2D driven by the
server's pre-computed waveform envelope.


Downloads

Samsung TV app (.wgt)

Docker image

docker pull ghcr.io/aroundmyroom/velvet:v0.3.20
docker pull ghcr.io/aroundmyroom/velvet:latest

v0.3.19 — Maintenance

Choose a tag to compare

@aroundmyroom aroundmyroom released this 05 Aug 05:58

v0.3.19 — Maintenance

Maintenance release with Auto-DJ improvements and admin fixes.

  • Auto-DJ: fixed a session-pinning issue where the DJ could get stuck repeating untagged tracks indefinitely when the artist cooldown covered every artist in scope. Untagged songs now correctly fall through to the cooldown-relaxed fallback path so real variety is restored.
  • Admin: API routes now return the correct HTTP 403 status when accessed by a non-admin user (previously returned 405, which is reserved for the admin-lock setting).
  • Admin: loaders no longer hang on a permanent spinner when a network request fails on startup.

v0.3.18 — Maintenance

Choose a tag to compare

@aroundmyroom aroundmyroom released this 01 Aug 16:39

v0.3.18 — Maintenance

Maintenance release with performance improvements.

v0.3.17 — Maintenance

Choose a tag to compare

@aroundmyroom aroundmyroom released this 01 Aug 09:41

v0.3.17 — Maintenance

Maintenance release with internal optimisations.

  • Similar-songs queries now use a BPM-range SQL index, bounding the candidate
    set to 2000 rows instead of scanning the full library.
  • BPM analysis decodes a configurable window (default 60 s) from mid-track
    instead of the entire file, speeding up analysis roughly 4×.
  • Fixed a startup crash on fresh databases introduced in v0.3.16 where
    CREATE INDEX for the BPM columns ran before the corresponding
    ALTER TABLE ADD COLUMN migrations.

v0.3.16 — OpenSubsonic scrobble fix

Choose a tag to compare

@aroundmyroom aroundmyroom released this 01 Aug 09:05

Velvet v0.3.16 — OpenSubsonic scrobble fix

What changed

reportPlayback scrobbling now works correctly

Scrobbles sent by OpenSubsonic-compatible clients (such as Naviamp) were
silently dropped. Here is why, and what was fixed.

Background — two scrobble flows

The original Subsonic API has a scrobble endpoint: the client sends it
once when it decides the user has "finished" a track. Every client picks
its own threshold (often 50% played, or a hard-coded timer).

OpenSubsonic introduced a different, server-owned flow via the
reportPlayback extension (added to the spec March 2026). Instead of a
single "scrobble now" call, the client sends periodic state updates:

state=starting  → user pressed play
state=playing   → every ~15–30 s while playing
state=paused    → user paused
state=stopped   → user moved to the next track / stopped playback

The key design intent, as described in the OpenSubsonic spec:

"Servers should not assume a media is played after the calculated end
of content, but needs to wait for a stopped state."

The server decides when a stopped event should count as a scrobble,
keeping threshold logic in one place rather than every client re-implementing it.

What was wrong

Velvet's reportPlayback handler recognised started, playing,
paused, and completed — but completed is not a valid state in the
spec
. The real terminal state is stopped. So every time Naviamp (or
any other compliant client) sent state=stopped when skipping or
finishing a track, the server did nothing and the scrobble was lost.

What is fixed

  • state=stopped now triggers the scrobble path.
  • A listen-threshold check is applied before scrobbling on stopped:
    the user must have heard ≥ 50 % of the track or ≥ 4 minutes,
    whichever is the shorter threshold (mirrors the Last.fm / ListenBrainz
    minimum). Position is read from the positionMs parameter; if that is
    zero, elapsed time is estimated from when starting/playing was last
    received.
  • Tracks where positionMs indicates less than 30 seconds of listening
    are never scrobbled (handles quick skips).
  • state=starting is accepted for now-playing tracking (the spec-correct
    name; the old started alias still works for older clients).
  • state=completed is still accepted and scrobbles unconditionally, so
    any client already using that value keeps working.
  • ignoreScrobble=true suppresses side effects on stopped the same way
    it does on every other state.

Admin panel setting

The per-user toggles "Scrobble to Last.fm via Subsonic" and
"Scrobble to ListenBrainz via Subsonic" control whether stopped
events are forwarded. Users who have not enabled these remain unaffected.

v0.3.15 — Playlist reorder, marquee fix, security patches

Choose a tag to compare

@aroundmyroom aroundmyroom released this 01 Aug 08:58

Velvet v0.3.15 — Playlist reorder, marquee fix, security patches

What's new

Drag-to-reorder in playlists

Songs in a saved playlist can now be dragged into any order. Hover a
track to reveal the grip handle (⠿) on its right side, then drag it up
or down. The new order is saved to the server the moment you drop.

A sort_order column is silently added to the playlist table on first
start — no data loss, existing playlists keep their insertion order until
you manually reorder them.

Scrolling title fix

Long song titles that triggered the marquee animation were displaying a
truncated ellipsis ("…") while scrolling. The full title now animates
through cleanly.

Security

brace-expansion was upgraded from 5.0.7 to 5.0.9, fixing a
high-severity DoS where a crafted pattern could cause unbounded memory
expansion and crash the process. body-parser was also updated to
address a low-severity DoS where an invalid limit value silently
disabled request-size enforcement. Both are dependency-only fixes with no
API changes.

Dependencies

All 12 pending Dependabot updates merged: helmet 8.3.0, nanoid 6.0.0,
ws 8.21.1, eslint-plugin-sonarjs 4.2.0, fast-xml-parser 5.10.1, axios
1.19.0, music-metadata 11.14.0, eslint 10.8.0, globals 17.8.0,
actions/setup-node v7, trivy-action (latest hash).

v0.3.14 — - **Fixed: returning to a paused device after playing on another machine kept the stale queue** instead of pulling the updated one from the server. The queue sync relied entirely on the event (tab hidden → visible), but if the Velvet tab was always visible (desktop that was never minimised or locked), that event never fired — leaving the old queue in place indefinitely

@aroundmyroom aroundmyroom released this 10 Jul 20:24

Velvet v0.3.14 — Cross-device queue sync fix

What changed

When you played music on a second device while your main machine's
Velvet tab was paused but always visible (desktop, no sleep, no tab
switch), the main machine would keep its old queue forever. Switching
tabs or locking the screen would have fixed it — but who does that?

Root cause: queue synchronisation between devices relied entirely on
the browser's visibilitychange event (fired when a tab goes hidden and
returns visible). On a desktop with Velvet permanently open in a visible
tab, that event never fired, so the stale localStorage queue was never
replaced with the one saved by the other device.

Fix: a 2-minute background interval now runs while audio is paused.
It fetches your server-side settings and applies the same cross-device
logic as the existing tab-focus sync — if the DB record was saved by a
different browser/device and the local queue differs, the DB wins and
the queue is restored silently. The interval shares the same throttle
as the tab-focus handler so both paths co-exist without double-syncing.

v0.3.13 — - **Fixed: any authenticated user could load the admin panel HTML page** (, ) even without in their config. The route guard in only verified the JWT was valid (user logged in) but never checked the admin flag. Non-admin users are now redirected to immediately — they never see the admin UI

@aroundmyroom aroundmyroom released this 10 Jul 15:05

Velvet v0.3.13 — Security fix + child vpath access

Security: admin panel now requires admin role

Any authenticated user could previously load the admin panel HTML
(/admin, /admin/index.html) even without admin: true in their
config. The route guard only verified the JWT was valid — it never
checked the admin flag. Non-admin users are now redirected to /
immediately and never see the admin UI.

Admin API endpoints already rejected non-admin requests (403/405);
this closes the HTML-access gap.

Bug fix: users with restricted vpath access can now play music

This release also fixes a long-standing issue where users whose vpaths
list contained only child vpaths (sub-folders of a root vpath) could not
browse or play music at all — every menu was empty and every playback
attempt failed with 403/500 errors.

What was broken

Velvet indexes all music files under the root vpath (e.g. Music). A user
configured with only "vpaths": ["12-inches"] — where 12-inches is a sub-folder
of Music — had queries like WHERE vpath IN ('12-inches') which returned zero
rows, because no files are stored under that name in the database.

What is fixed

  • Playback: files stream correctly through the parent root's media mount,
    restricted to the user's allowed filesystem prefix.
  • Artists menu: shows only artists that have files in the user's sub-folder.
  • Albums menu: empty for users with no albumsOnly vpath (correct behaviour).
  • Genres and Decades menus: filtered to only the user's allowed content.
  • All other menus (search, recently played, home stats, random, starred, etc.)
    are scoped correctly to the user's accessible files.
  • Access control is preserved: users cannot access files outside their
    configured vpath prefix — the fix is purely additive.

How it works

Auth middleware now computes a dbVpaths set for child-only users by adding the
parent root vpaths needed for database lookups. All ~25 DB query endpoints use
this expanded set. Access-control checks (media streaming, metadata, waveforms,
playback logging) continue to use the original strict vpaths list with an
added prefix check.