Commit Graph
814 Commits
Author SHA1 Message Date
HoneyandClaude Sonnet 4.6 d8908c69e9 feat: add session-level API cache — skip spinners on home/albums/artists/playlists revisit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 11:45:22 +02:00
HoneyandClaude Sonnet 4.6 08ca8303f3 chore: remove unused _syncNpDirect import; add express to backend/package.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 01:21:41 +02:00
HoneyandClaude Sonnet 4.6 45cb62198e fix: album cards use romaji index for album name display via _albumRomajiMap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 01:10:38 +02:00
Honey 52f5385988 fix: import dispAlbum in views.js 2026-06-06 01:03:56 +02:00
HoneyandClaude Sonnet 4.6 eb47a42f15 fix: album detail header re-renders with sort tags after songs load; search artist uses dispArtistById
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:56:33 +02:00
HoneyandClaude Sonnet 4.6 25d80eb559 feat: use file sort tags (sortAlbum/sortArtist/sortName) as display override when romaji is on
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:51:22 +02:00
HoneyandClaude Sonnet 4.6 961511b3d0 fix: apply romaji disp functions in _restoreLastPlayed and album detail header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:44:28 +02:00
HoneyandClaude Sonnet 4.6 8704a17b4f fix: romaji display race on reload; iTunes search uses original Japanese name
- Await romaji overrides before restoring player bar so disp* functions
  have the cache ready on first render after reload
- dispArtistParts now carries originalName so artist clicks pass the
  Japanese name to Navidrome/iTunes search while displaying romaji

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:09:13 +02:00
HoneyandClaude Sonnet 4.6 ccdcda74ec fix: apply romaji overrides to album cards on home/albums/artist views
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 23:43:27 +02:00
HoneyandClaude Sonnet 4.6 01407ef3f1 feat: update sort-tag labels to reflect Romaji / English support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 14:08:50 +02:00
HoneyandClaude Sonnet 4.6 bb927e8120 fix: continue lrclib attempts if first response has no lyrics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 12:01:33 +02:00
Honey 570eb803c2 Kanji → korrektes Romaji für Sort-Tags -> Deleted 2026-06-05 11:50:28 +02:00
HoneyandClaude Sonnet 4.6 89241a4fe6 fix: update romaji overrides hint text — accurate description with line break
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 10:48:35 +02:00
HoneyandClaude Sonnet 4.6 36a0b38eca feat: server-side romaji overrides (shared list); preload ExoPlayer from SharedPreferences on cold start
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 10:47:25 +02:00
HoneyandClaude Sonnet 4.6 b924f01b2a fix: ignore onQueueEnded before onReady fires — prevents stale end-of-song event from advancing queue on app reopen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 01:33:16 +02:00
HoneyandClaude Sonnet 4.6 21d55d8ab0 fix: suppress spurious onQueueEnded after reconnect; handle STATE_ENDED on reopen; add isEnded to snapshot
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 01:03:19 +02:00
HoneyandClaude Sonnet 4.6 1277913ac1 fix: apply romaji overrides in dispArtist/dispAlbum/dispTitle display functions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:50:06 +02:00
HoneyandClaude Sonnet 4.6 a8e54b3a41 feat: add --copy mode to reorganize-music.sh for safe migration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:40:52 +02:00
HoneyandClaude Sonnet 4.6 59120fd7b6 feat: rename song file artist segment when applying romaji override fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:29:21 +02:00
HoneyandClaude Sonnet 4.6 ff3bb782d3 fix: romaji override delete button (JSON.stringify breaks HTML attr); fix file stem search for en-dash track prefixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:26:39 +02:00
HoneyandClaude Sonnet 4.6 7496c28663 fix: surface first error detail in fix-artist-sort job message for debugging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:17:48 +02:00
HoneyandClaude Sonnet 4.6 ac474b1760 fix: correct onReady sync logic for single-track model; remove stale _setSkipping call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:15:39 +02:00
HoneyandClaude Sonnet 4.6 3252706dc2 fix: replace non-existent MediaSession.Callback skip overrides with onPlayerCommandRequest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:07:14 +02:00
HoneyandClaude Sonnet 4.6 d539a43307 refactor: single-track ExoPlayer model — JS owns queue entirely
Root cause of all Android player bugs: two competing queue managers.
ExoPlayer held all tracks and auto-advanced with its own index while JS
tracked S.qIdx separately. seekToIndex() is async via runOnMain, so
skips would interleave with ExoPlayer's own advances — causing desync,
wrong-song display, and skips that silently did nothing.

Fix: ExoPlayer always holds exactly ONE MediaItem. JS is the single
source of truth for queue order, shuffle, and repeat.

JS changes (player.js):
- playQueue/playCurrent/nextSong/prevSong/toggleShuffle: replace
  setQueue(allTracks, idx) + seekToIndex with setQueue([single], 0)
- _pushQueueIfBridge: send only current track
- nativeInsertAt: no-op (radio songs land in S.queue; JS picks them up)
- fillRadioQueue: remove addToQueue native call
- onTrackChanged: simplified to just schedule scrobble + radio top-up
- onPlaybackStateChanged: remove index-reconciliation block
- onPositionUpdate: remove reconciliation tick (no queue drift possible)
- onQueueEnded: call onEnded() — JS drives what plays next
- toggleRepeat: setRepeatMode('one') only for repeat=one, else 'off'
- Add onNotificationNext / onNotificationPrev to NSPlayer
- Remove _skipping/_skipTimer/_reconTick/_setSkipping (all dead code)

Android changes:
- MusicService: add onSkipToNext/onSkipToPrevious to StateListener;
  add MediaSession.Callback to intercept notification skip buttons and
  delegate them to JS instead of letting ExoPlayer handle them
- MainActivity: implement new StateListener methods → callJs()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 23:57:57 +02:00
Honey 85d8e8eae7 fix: soften cover box-shadow in dynamic-color mode so it doesn't bleed on colorful bg 2026-06-04 23:15:52 +02:00
Honey 2012251e58 fix: hide np-bg layers with display:none in dynamic-color mode to remove GPU black boxes 2026-06-04 23:14:14 +02:00
Honey 491d58875c fix: ::before was covering dynamic cover color — use background:inherit 2026-06-04 23:10:52 +02:00
Honey 47d530a11d fix: prevent horizontal scroll in romaji overrides modal 2026-06-04 23:09:03 +02:00
Honey e5076f47d9 fix: clicking a specific song always starts at that position, bypasses shuffle 2026-06-04 23:06:58 +02:00
Honey 4e6f8782ca fix: artist page song clicks bypass shuffle — play exact song in order 2026-06-04 23:06:01 +02:00
Honey 64c107da0b refactor: redesign ABS settings row to match backend status style 2026-06-04 22:56:45 +02:00
Honey d94dadf309 refactor: move romaji overrides into dedicated modal, fix placeholders 2026-06-04 22:50:17 +02:00
Honey 963ef4cbb0 chore: move fix-artist-separators.sh into scripts/ 2026-06-04 22:38:53 +02:00
HoneyandClaude Sonnet 4.6 28428aa362 feat: add reorganize-music.sh to migrate genre folders to Artist/Album structure
Reads ffprobe metadata from every audio file and places it into:
  {dest}/{AlbumArtist}/{Album}/{NN - Artist - Title.ext}
Exactly mirrors the sanitize(), folder and filename rules in
backend/lib/ytdlp.js. Dry-run by default; --move to execute, --clean
to remove empty source dirs afterwards.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 22:37:00 +02:00
HoneyandClaude Sonnet 4.6 d777301c20 feat: batch fix artist sort tags + rename folder when override is added
- edit-metadata: use albumArtistSort (when provided) for artist folder naming
  instead of auto-converted romajiAlbumArtist — ensures manual overrides sent
  by the download pipeline also place files in the correct folder
- Add POST /api/fix-artist-sort endpoint: searches Navidrome for all songs
  where artist or albumArtist matches the kanji, re-writes ARTISTSORT +
  ALBUMARTISTSORT via ffmpeg (-map_metadata 0 preserves all other existing
  tags), moves each file to the correct artist folder, cleans up empty old
  dirs, triggers Navidrome rescan — returns a job ID for progress tracking
- addRomajiOverrideUI: after saving a new override, calls fix-artist-sort and
  shows a job card with live progress

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 22:33:12 +02:00
HoneyandClaude Sonnet 4.6 0a2736e893 fix: build error in nextSong + apply romaji overrides to downloads
- Fix missing closing brace for `if (ni >= 0...)` in nextSong — the removed
  `else { _nsBridge('next') }` left the inner if-block unclosed, causing
  vite/rollup to reject player.js as invalid JS
- Apply romaji override map to iTunes (executeDownload) and Spotify
  (executeSpotifyDownload) download payloads — sends artistSort/albumArtistSort/
  titleSort/albumSort fields so the backend writes correct sort tags to the
  downloaded file and can organize into the right artist folder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:33:49 +02:00
HoneyandClaude Sonnet 4.6 8c3a23ad14 feat: romaji override list — manual kanji→romaji corrections for sort tags
Adds a maintained list in Settings where you can map kanji artist/album/title
strings to their correct romaji reading (e.g. 照井順政 → Terui Yoshimasa).
Overrides are applied last (highest priority) when the edit-metadata modal
opens, so saving the song writes the correct sort tags permanently to the file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:17:13 +02:00
HoneyandClaude Sonnet 4.6 db7a515ee4 fix: stop ExoPlayer shuffle conflict causing end-of-queue desync
When reaching the last shuffled song, the UI changed but audio kept playing
the same song. Root cause: ExoPlayer still had its own shuffle mode on (set
in onReady), so its auto-advance and _nsBridge('next') picked random indices
that conflicted with the pre-shuffled JS array.

- setShuffleMode always false: we own the playback order via pre-shuffled
  JS array, ExoPlayer must advance sequentially through it
- Remove _nsBridge('next') fallback: never let ExoPlayer decide the next
  song autonomously. End of queue with no repeat = let ExoPlayer stop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 11:13:45 +02:00
HoneyandClaude Sonnet 4.6 ef40795a9c fix: ::before backdrop was intercepting all clicks in NP overlay
The pseudo-element added for the flicker fix had position:absolute;inset:0
with no pointer-events:none, so it sat over the entire overlay and consumed
every click — queue items, lyrics lines, buttons. Add pointer-events:none.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 11:11:52 +02:00
HoneyandClaude Sonnet 4.6 c21f3be696 fix: fetch real playlist song counts from getPlaylist in background
getPlaylists returns stale/wrong songCount from Navidrome's index.
After rendering the grid, fire one getPlaylist request per playlist
in parallel and update each card's subtitle as responses arrive.
Counts are visually corrected within a second of the page loading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 11:06:34 +02:00
HoneyandClaude Sonnet 4.6 1d5f4a620d fix: NP flicker, fully-random shuffle, playlist count backpatch
NP overlay flicker (stronger fix):
- Remove contain:layout style paint (causes layer issues on some Android WebView)
- Add ::before pseudo-element with solid background so no frame can be transparent
- Add -webkit-transform prefixes for both open and closed states

Shuffle fully random:
- playQueue no longer pins startIdx when shuffle is on
- All songs shuffled into a completely random order, S.qIdx=0 always
- toggleShuffle mid-queue still pins current song first (Spotify toggle behavior)

Playlist count wrong in overview:
- Navidrome's getPlaylists returns stale/wrong songCount in its index
- Add data-pl-id to each playlist card in loadPlaylists
- After loadPlaylistDetail gets the accurate entry count, backpatch the
  matching overview card's subtitle so the grid shows the real song count

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 11:03:54 +02:00
HoneyandClaude Sonnet 4.6 3c9a8e892a fix: prevent NP overlay from flickering through to content below on Android WebView
Replace will-change:transform with translate3d(0,100%,0) / translate3d(0,0,0).
will-change triggers a layer-creation/destruction cycle on WebView — when the
player bar DOM updates during a skip, the compositor can briefly expose the
layer's clear back buffer, flashing the content below through the overlay.
translate3d pins the element to a stable GPU layer from the start.
Also add contain:layout style paint to isolate the overlay's paint context
from external repaints, and -webkit-backface-visibility for older WebView.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:56:28 +02:00
HoneyandClaude Sonnet 4.6 0552b3f5d0 fix: Spotify-style shuffle — fixed queue order, not random-each-skip
- playQueue: unified shuffle for both web and Android — pre-shuffles the
  songs array with the tapped song pinned first, saves _origQueue for restore.
  Passes S.qIdx (0 when shuffled) to NSBridge.setQueue instead of startIdx.
- _computeNextIdx: removed the random-pick shuffle branch entirely.
  Since the queue is already shuffled, sequential traversal IS the shuffle order.
- toggleShuffle: removed !HAS_BRIDGE guard so queue reorder runs on Android too.
  Re-sends reordered queue to ExoPlayer so native order stays in sync with JS.
  Removed setShuffleMode call (we manage order ourselves via pre-shuffled array).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:51:42 +02:00
HoneyandClaude Sonnet 4.6 1f1a5cd3a6 fix: startup speed, image retry, shuffle random start, genres, search badge, playlist refresh
- Remove await on fetchRomajiIndex() at startup so navigation is not blocked
  by the romaji index network call (was the main cause of 30s Android startup)
- Add window._imgRetry() with 3 retries (1.5s/3s/4.5s backoff) for all card/
  song/cover images that fail to load; falls back to emoji only after exhausting retries
- playQueue with HAS_BRIDGE + shuffle: pick a random startIdx so album/playlist
  playback starts at a random track, not always index 0
- Reduce mobile genre grid limit from 12 to 6
- Fix "Global Suche" badge text: was "iTunes Store + Local", now "iTunes Store"
- deleteSong: always refresh loadPlaylists() unconditionally (not just when
  page is active) with a 3s delay so song counts are current when navigating back

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:38:15 +02:00
HoneyandClaude Sonnet 4.6 6dc47a2c77 fix: suppress stale-snapshot NP flicker during skip window on Android
Guard both snapshot-reconciliation paths (_skipping window can return the
old index before ExoPlayer commits the new one):
- onPlaybackStateChanged: skip the safety-net snapshot read while _skipping
- onPositionUpdate: reset _reconTick (defer recon) while _skipping
- _setSkipping: always reset _reconTick on every new skip/seek

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:46:23 +02:00
HoneyandClaude Sonnet 4.6 63ff8920bf fix: repair Japanese song download pipeline
Five separate root causes fixed:

1. buildTitleKeywords (ytdlp.js): For Japanese titles \w strips all
   kana/kanji leaving no keywords. Now uses the first whitespace-
   delimited segment as a literal regex keyword so --match-filter
   title ~= requires the YTM video to contain the Japanese text.

2. Tokenizer wait (ytdlp.js): If kuromoji hasn't finished loading when
   a Japanese download starts, romajiForStorage returns the original
   Japanese text unchanged — producing Japanese folder/filenames and
   skipping sort tags. Now waits up to 30 s for the tokenizer.

3. titleOnlyMatch guard (metadata.js): iTunes titleOnlyMatch (no artist
   check) was used as a fallback for all searches. For Japanese
   titles/artists this matched wrong US songs with identical titles
   (common in anime OSTs). Now titleOnlyMatch is skipped when the
   search title or artist contains Japanese characters.

4. squid.js rawArtist: rawArtist was not in squid's signature and
   never forwarded to embedMetadata, so Qobuz-downloaded J-Pop tracks
   stored the iTunes-resolved English name as artist instead of the
   Japanese original. Fixed by accepting and forwarding rawArtist.

5. squid.js romaji paths: Folder and filename used raw artist/title
   with no romaji conversion, creating Japanese-named paths
   inconsistent with the yt-dlp path. Now uses romajiForStorage same
   as ytdlp.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:40:46 +02:00
HoneyandClaude Sonnet 4.6 52451f5086 fix: resolve Japanese-path songs stored as romaji files
When Navidrome has a Japanese path in its DB but the file was saved with
a romaji filename by the download pipeline, the existing fallback chain
compared the Japanese title stem against romaji filenames and always
failed. Added _findFileByStemOrRomaji which converts the stem via
romajiForStorage before the final recursive search, covering all three
endpoints: edit-metadata, delete-song, song-sorttags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:30:04 +02:00
HoneyandClaude Sonnet 4.6 07b8114cae fix: reconcile native state on app foreground; refresh playlist after delete
- Call reconcileNativeState() on visibilitychange→visible so the player UI
  updates immediately when the user opens the app after a background auto-advance,
  before deferred evaluateJavascript callbacks have had a chance to fire
- After deleteSong() succeeds, refresh the active playlist view (grid or detail)
  with a 2s delay to let Navidrome's background scan complete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:01:56 +02:00
HoneyandClaude Sonnet 4.6 a2714dddaa feat: delete song from disk via edit-metadata modal
- /api/delete-song endpoint: resolves path via same fallback chain as
  edit-metadata, validates file is inside music root + is an audio file,
  deletes it, then prunes the album folder if empty and the artist folder
  if that too becomes empty — no recursive rm, depth strictly limited to
  two levels above the file so no accidental mass-deletes
- Safety: path.resolve() guard ensures no path-traversal outside music root;
  rmdirSync (non-recursive) fails if dir is non-empty as extra safety net
- Frontend: delete button (red outline, trash icon) at bottom of edit mode
  with confirmation dialog showing title + folder-cleanup warning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 00:36:31 +02:00
HoneyandClaude Sonnet 4.6 0788c801fa fix: eliminate NP overlay stale-song on autoplay and cover-load delay
Three improvements:
- openNowPlaying(): reads NSBridge.getState() (always-fresh @Volatile snapshot)
  via ID search to reconcile S.qIdx before render; fixes NP showing old song
  when deferred evaluateJavascript callbacks haven't fired yet after backgrounding
- onPositionUpdate(): periodic check (~every 2s) detects track mismatch via
  snapshot ID; catches missed/delayed onTrackChanged when NP is open
- updatePlayerUI(): pre-warms next song's 1024px cover so auto-advance NP
  swap is instant from browser cache instead of a 1-second load delay
- styles: add backface-visibility:hidden + isolation:isolate to .np-overlay
  to reduce GPU composite flicker on Android WebView on manual skip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 01:30:55 +02:00