Commit Graph
364 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 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
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 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 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 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 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
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 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 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 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
HoneyandClaude Sonnet 4.6 050aff6dbe feat: prev-in-shuffle retraces actual play order via history stack
Added a play history stack (_playHist) that records S.qIdx before each
forward advance (manual next, auto-advance via onTrackChanged). prevSong
now pops from this stack instead of blindly doing S.qIdx--, so pressing
back during shuffle always returns to the song that was actually playing
before, regardless of where shuffle jumped. History clears on new queue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 01:10:06 +02:00
HoneyandClaude Sonnet 4.6 2d303083a0 fix: NP overlay still shows stale song on auto-advance
Two fixes:
1. Remove snapshot read from openNowPlaying() — the MediaController snapshot
   can show the old song at the old index (both still valid entries in the
   queue), so the ID check does not detect staleness; after the onTrackChanged
   fix S.qIdx is always authoritative so there is no need to read the snapshot
2. Call syncNowPlaying() explicitly in onTrackChanged so the NP overlay is
   refreshed immediately when the song changes while it is already open

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 01:04:16 +02:00
HoneyandClaude Sonnet 4.6 ba28518d8a fix: NP overlay shows stale song on Android auto-advance
openNowPlaying() was blindly trusting the MediaController snapshot index,
which can lag one track behind ExoPlayer on auto-advance. This overwrote
the already-correct S.qIdx that onTrackChanged had just set, causing the
NP overlay to render the previous song while the mini-player showed the
right one. Now only accept the snapshot index when the song ID at that
position matches — stale snapshots are silently ignored.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:41:01 +02:00
HoneyandClaude Sonnet 4.6 9ab3dab508 fix: solid cover-color NP background via canvas extraction
Replace saturated blur filter with actual dominant-color extraction:
fetch cover as blob (avoids canvas CORS taint), draw on 24x24 canvas,
find most vivid pixel, darken to ~28% for a moody solid background.
Blurred bg layers hidden when mode is active. Transitions smoothly
between songs via CSS transition on background.
Renamed setting label to "Einfarbiger Player-Hintergrund".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:36:32 +02:00
HoneyandClaude Sonnet 4.6 9d81cdde31 feat: dynamic NP cover color, taller settings modal, fix Android song sync
- Now Playing: add "Dynamische Hintergrundfarbe" toggle in Theme settings;
  when on, np-bg uses higher saturation/brightness so the cover color really
  shows through the blurred background
- Settings modal: extend to bottom nav on mobile (was leaving ~74px dead space
  for the player bar which the overlay already covers at z-index 200)
- Android: fix onTrackChanged always staying on the first song — the snapshot
  from MediaController can lag behind ExoPlayer on auto-advance, causing the
  stale currentIndex to overwrite the correct p.index; now only advance to
  a higher live index (for rapid-skip catch-up) and use p.trackId directly
  instead of the potentially-stale snapshot id for the queue-mismatch guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:28:06 +02:00
HoneyandClaude Sonnet 4.6 2df5a54985 fix: prevent 9-second downloads caused by short iTunes duration match
When iTunes enrichment returns a jingle/preview as the first result (e.g.
trackTimeMillis=9000), the yt-dlp duration filter becomes impossible
(duration > 30 & duration < 24), all filtered steps fail, and the final
unfiltered fallback downloads whatever YouTube returns first — often a
short clip.

Fix 1: spotify.js — only copy iTunes trackTimeMillis to durationMs when
it is > 30 seconds (genuine song, not a jingle or preview).

Fix 2: ytdlp.js — only enter the duration-filtered search branch when
durationMs > 30 000ms, so a corrupted short value silently falls back to
the unfiltered search path which still finds the correct full song.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:06:33 +02:00
HoneyandClaude Sonnet 4.6 67f4be272a fix: ie-totaltracks element id + JP-strict/US-fallback iTunes matching
Prevent false Japanese matches by never using JP title-only fallback.
Strategy: JP strict → US strict → US title-only so niche Western tracks
still resolve but same-English-title Japanese songs don't pollute results.

Also fix JS error in autofillItunes: ie-total → ie-totaltracks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:15:04 +02:00
Honey 21159ad2c5 fix: iTunes route order (lookup before proxy); stable cover art URLs for browser caching 2026-05-27 18:01:09 +02:00
Honey 3837783225 fix: iTunes JP-first with US fallback; romaji filter in playlist/starred search 2026-05-27 17:16:57 +02:00
Honey a9463917e3 fix: use iTunes country=jp globally — returns Japanese original names, identical for non-Japanese music 2026-05-27 15:21:11 +02:00
Honey 8f14d013b3 fix: skip iTunes album/duration enrichment for Japanese Spotify tracks to prevent wrong-song downloads 2026-05-27 15:15:45 +02:00
Honey b5ffb1e73d fix: non-blocking romaji fetch in artist detail — prevent blank page on slow index load 2026-05-27 15:08:11 +02:00
Honey 27acede41d fix: fetch romaji index in loadArtistDetail before dispArtistById 2026-05-27 14:02:42 +02:00
HoneyandClaude Sonnet 4.6 c8ccb80f2f fix: header search overlay clipping and mobile fit
- Change overlay from position:absolute to position:fixed so it isn't
  clipped by main-content's overflow-y:auto
- Set overlay top dynamically from header.offsetHeight so it always
  anchors exactly below the sticky header
- Responsive max-height: accounts for player bar + bottom nav on mobile
- Hide logo text on ≤359px screens to keep search pill wide enough

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 13:04:34 +02:00
HoneyandClaude Sonnet 4.6 36f968881b feat: persistent inline header search with overlay results
Replaces the hidden toggle-based mobile search bar with a permanently
visible pill-shaped search input in the header row, next to the logo.

- Search is always visible — no button click required
- Typing shows a floating overlay with library results (artists, albums,
  songs) without navigating away from the current page
- Clearing the input or navigating to any page closes the overlay silently
- The ✕ clear button appears only while text is present
- navigate() now calls closeHeaderSearch() so results disappear cleanly
  when tapping any result or bottom nav tab
- Old toggleMobileSearch / closeMobileSearch kept as no-op stubs for safety

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 12:55:26 +02:00
HoneyandClaude Sonnet 4.6 b7afa19f70 feat: romaji artist names, Apple Music autofill, romaji index invalidation
- Artist grid and detail header now show romaji names when toggle is on.
  Builds a Map<artistId, artistRomaji> from the existing song romaji index
  so no extra API call is needed.

- Apple Music autofill button added to edit modal next to MusicBrainz.
  Reuses the existing lookupItunesTrackMeta backend function via a new
  GET /api/itunes/lookup endpoint — fills album, year, genre, track#,
  and cover art.

- Romaji index is invalidated after every successful download so newly
  added songs appear with correct sort tags without requiring a page reload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 12:26:51 +02:00
HoneyandClaude Sonnet 4.6 212491aa89 fix: convert katakana proper nouns (e.g. ヨルシカ) to romaji in sort tags
When kuromoji marks a katakana token as reading '*' (unknown word — common
for band names like ヨルシカ), the code fell back to the surface_form which
is still katakana. _hiraToRomaji can't handle katakana so it passed through
unchanged, causing sort tags to show ヨルシカ instead of Yorushika.

Fix: if the surface_form contains katakana and reading is *, convert it
mechanically via _kataToHira + _hiraToRomaji — katakana-to-romaji is always
unambiguous and needs no dictionary.

Also add a Japanese-char guard in the frontend when applying romaji index
values, so stale cache entries can never overwrite correct values with Japanese.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 02:52:35 +02:00