Commit Graph
364 Commits
Author SHA1 Message Date
HoneyandClaude Sonnet 4.6 377a6954e3 fix: radio mode uses getSimilarSongs2 for artist/genre-aware playback
- Dedup now checks only the last 50 queued songs instead of the entire
  queue, so the radio doesn't stall on small libraries once every similar
  song has been seen
- If every candidate is a recent duplicate (tiny library), allow up to 5
  repeats rather than returning empty and letting the queue run dry
- Add second _queueGen guard after the getRandomSongs fallback await

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 08:45:16 +02:00
HoneyandClaude Sonnet 4.6 9fe8656a21 fix: resolve queue desync causing player bar / NP to show wrong song
Root cause: addToQueue was sent to native twice for every radio top-up.
fillRadioQueue() already sends addToQueue internally, but onTrackChanged
and onQueueEnded also re-sent the same songs in their .then() callbacks,
causing native to accumulate duplicates. With native having double the
songs JS has, native indices no longer map to the correct JS queue entries.

Also fixed: playQueue called fillRadioQueue before setQueue was dispatched,
so addToQueue could land in the old native queue before setQueue wiped it.

Changes:
- fillRadioQueue: add _queueGen guard — bail if playQueue() replaced the
  queue while the network request was in flight
- playQueue: increment _queueGen; move fillRadioQueue() inside the rAF
  after setQueue so addToQueue always lands in the correct native queue
- onTrackChanged: remove .then() that re-sent addToQueue (double-send fix)
- onQueueEnded: remove addToQueue re-send, keep only _nsBridge('play')
- onPlaybackStateChanged: add safety-net sync when isPlaying transitions
  to true — catches any remaining edge cases where onTrackChanged missed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 00:02:42 +02:00
HoneyandClaude Sonnet 4.6 52079536b6 fix: eliminate notification/playerbar song desync on Android
Two-pronged fix for the state drift between the system notification
(ground truth) and the WebView player bar:

JS (player.js): onTrackChanged now reconciles the incoming index with a
synchronous NSBridge.getState() call before applying it. Stale callbacks
from earlier rapid skips immediately correct themselves to the live
settled index instead of overwriting the correct state.

Kotlin (MusicService.kt): debounce JS-initiated transitions (seek /
setQueue) by 120 ms so rapid skip sequences produce one onTrackChanged
notification at the final index rather than one per skip step. Auto-advance
transitions are still fired immediately since JS doesn't initiate those.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 23:38:25 +02:00
HoneyandClaude Sonnet 4.6 0874fd4f9f feat: radio mode uses getSimilarSongs2 for artist/genre-aware playback
Replaces getRandomSongs with getSimilarSongs2 seeded from the current track.
Falls back to random if Navidrome returns nothing (no Last.fm config or unknown song).
Deduplicates against songs already in the queue.
Compatible with AudioMuse-AI plugin if added later — same endpoint, better results.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 23:06:17 +02:00
HoneyandClaude Sonnet 4.6 e4cc93d362 fix: restore LRCLIB fallback for synced lyrics; fix np-drag-handle null crash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 19:03:42 +02:00
HoneyandClaude Sonnet 4.6 654cde6796 refactor: remove LRCLIB fallback — handled by navidrome-lyrics-plugin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 18:54:45 +02:00
HoneyandClaude Sonnet 4.6 4b2f277ff7 feat: persist volume level in localStorage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:39:56 +02:00
HoneyandClaude Sonnet 4.6 4f152028f4 feat: auto-close preview when main playback starts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:10:08 +02:00
HoneyandClaude Sonnet 4.6 2553534cc8 tweak: keep player bar static during swipe-up; improve title/album/artist spacing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:03:54 +02:00
HoneyandClaude Sonnet 4.6 ac4f09c8b8 fix: never rebuild native queue in onReady while ExoPlayer is playing
When onReady fires during MediaController reconnect (fragment navigation,
activity resume), calling setQueue restarts the current song from pos 0.
Guard: if native isPlaying=true, only re-sync JS UI — never touch the queue.
Only push a new queue when native is idle/empty (cold start).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:22:27 +02:00
HoneyandClaude Sonnet 4.6 0e05740df7 feat: show title/album/artist order in bottom bar and now playing UI
- Bottom bar: player-info is now a centered flex column so text block
  aligns properly against the art circle
- Now playing: split artist·album line into album (above) + artist (below)
  matching the bottom bar layout; removed np-album-sep reference
- Removed stale np-album-sep style.display call from player.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:20:04 +02:00
HoneyandClaude Sonnet 4.6 4c4dee4e55 fix: remove player-album-sep references after HTML restructure
Removing the separator span from the HTML broke updatePlayerUI which still
called getElementById('player-album-sep').style — null crash on every
song play. Removed both occurrences since album now has its own line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:03:11 +02:00
HoneyandClaude Sonnet 4.6 aa8d58d01f fix: null guard in hideCtxMenu and scroll/touchmove listeners
hideCtxMenu now guards against null #ctx-menu element. scroll/touchmove
listeners only fire when menu is actually visible, preventing spurious
calls on every scroll event.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:00:23 +02:00
HoneyandClaude Sonnet 4.6 96e3cb0451 fix: prevent stacked reorder mode from multiple 'Neu anordnen' clicks
Added _reorderCtx.active flag — plReorderEnter bails early if already
active, preventing duplicate handles and event listeners from stacking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 19:54:36 +02:00
HoneyandClaude Sonnet 4.6 92edaa9da4 feat: show album above artist in bottom bar; close ctx menu on scroll
- Player bar sub-line now shows album (clickable) on first row, artist below
- Context menu now closes on scroll and touchmove (was staying open on mobile)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 00:03:20 +02:00
HoneyandClaude Sonnet 4.6 47cf3d13e5 fix: use pointerdown+pointerType to block touch seeks on bottom bar
mousedown does not expose the input source — replaced with pointerdown
which carries pointerType='touch', allowing noTouch=true to reliably
skip touch input without affecting mouse/stylus on PC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:39:02 +02:00
HoneyandClaude Sonnet 4.6 162bf530a2 fix: block synthetic mousedown on bottom progress bar from touch taps
noTouch=true now also adds a touchstart preventDefault listener so the
browser does not fire synthetic mousedown/click after a tap, which was
still triggering seeks in the PWA despite touch events being disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:35:43 +02:00
HoneyandClaude Sonnet 4.6 e47450b20d tweak: reduce swipe-up overlay speed from 2.5x to 1.5x for smoother feel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:32:00 +02:00
HoneyandClaude Sonnet 4.6 43312be65d fix: remove overlay cap during swipe-up so NP slides smoothly without stopping
The Math.min(160) cap on 'lift' meant the overlay froze after 160px of drag
(at ~70% down the screen) while the finger kept moving — the felt 'stop'.

Now: player bar caps at its own height (once it slides off, no more movement
needed), overlay tracks the finger with no cap at 2.5× speed so ~360px of
upward drag fully reveals the now-playing screen. Release animation (from
the previous fix) then carries it smoothly to position 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:16:32 +02:00
HoneyandClaude Sonnet 4.6 b0fb1b200d fix: smooth NP swipe-open animation; live playing highlight in song lists
Swipe animation:
- _resetDrag(true) now reads the overlay's current drag position and starts
  the CSS transition from there instead of resetting to translateY(100%) first.
  That reset was causing the visible stutter (overlay jumped off-screen then
  slid back up). Now the overlay continues seamlessly from wherever the finger
  released to translateY(0).

Playing highlight:
- makeSongItem: adds data-song-id attribute to every .song-item element.
- updatePlayerUI: calls _updatePlayingHighlights() after every track change.
  This toggles .playing on all visible song items in any open list/playlist/
  favorites view so the highlighted row always matches what's actually playing,
  without requiring the user to navigate away and back.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:10:03 +02:00
HoneyandClaude Sonnet 4.6 4856f0ca57 fix: bottom bar no touch seek; lyrics seek on Android; smoother NP animation
- initProgressScrub: pass noTouch=true for the bottom mini-bar so touch
  events are never registered on it; only mouse events remain (desktop-only).
  The now-playing scrubber is unchanged and keeps full touch support.
- Export seekToMs(ms) from player.js; use it in the lyrics click handler
  instead of S.audio.currentTime which has no effect when HAS_BRIDGE
  (native ExoPlayer handles playback, not the HTML audio element).
- styles.css: add will-change:transform to .player and .np-overlay to keep
  them on GPU compositor layers during the swipe gesture; tweak NP open
  transition to cubic-bezier(.4,0,.2,1) for a snappier feel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:42:58 +02:00
HoneyandClaude Sonnet 4.6 9d8a5a22bb fix: button focus ring, playlist reorder mode, edit modal mobile, backdrop-filter
- button:focus{outline:none} — removes the blue tap-highlight box in WebView
- Playlist reorder: replaced always-on drag handles with explicit 'Neu anordnen'
  mode. Handles only appear after tapping the button; a sticky Speichern/Abbrechen
  bar appears above the list. Touch drag is now fully supported. Context-menu
  (long-press) is blocked while reorder mode is active to prevent conflicts.
- styles.css: .pl-drag-handle always visible when pl-draggable (no hover needed)
- Edit modal footer: wrap buttons on small screens so Save doesn't overflow
- Edit modal overlay: remove backdrop-filter:blur — causes WebView compositor
  glitch where 3/4 of the modal disappears after returning from background

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:40:11 +02:00
HoneyandClaude Sonnet 4.6 68bec67880 fix: sync queue insert/append to native Android player
Both 'Als Nächstes einfügen' and 'Am Ende der Queue' updated S.queue in JS
but never called NSBridge, so the native ExoPlayer ignored the change and
kept playing the original next item.

- Export nativeInsertAt(song, position) from player.js
- Call it in showCtxMenu with the exact splice index for insert-next
  and -1 (append) for add-to-end

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:27:25 +02:00
HoneyandClaude Sonnet 4.6 e893b7cc12 fix: radio mode appends songs without restarting; favorites/song row mobile fixes
- fillRadioQueue: use addToQueue instead of setQueue so toggling radio mode
  no longer resets position to 0 on the current song
- loadStarred: pass noArtistLink/noAlbumLink to prevent accidental navigation
  taps on mobile (matches playlist behavior)
- styles.css: add pointer:coarse alongside hover:none so song-actions are
  visible on Android WebView which may report hover:hover
- components.js: remove inline download button from song row; keep only star
  and 3-dots (download stays accessible via context menu)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:25:26 +02:00
HoneyandClaude Sonnet 4.6 1b7c05f66d feat: notification 'Öffnen' routes to album for track/album downloads
- Single track and album downloads now navigate to the album page
- Artist downloads continue to navigate to the artist page
- Button label changes to 'Zum Album →' or 'Zum Künstler →' accordingly
- album name stored in job metadata so it survives until poll completes
- Search uses exact album+artist match, falls back to album-name-only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:16:15 +02:00
HoneyandClaude Sonnet 4.6 ba382aa6d9 fix: cover art caching, skip desync, notification tap opens now playing
- api.js: cache coverUrl() per id+size so WebView HTTP cache can work;
  previously each call generated a new random salt/token → unique URL →
  no cache hits → images re-fetched on every render

- player.js: replace _nsBridge('next')/'prev' with explicit seekToIndex
  in nextSong/prevSong so native always jumps to the exact same index JS
  computed; 'next'/'prev' relied on native's internal pointer being in sync
  which caused 'UI shows new song but audio plays old one' when they drifted

- MusicService: set setSessionActivity PendingIntent on MediaSession so
  tapping the playback notification opens the app

- MainActivity: handle open_now_playing extra in onCreate (cold start)
  and onNewIntent (app already running) to show the Now Playing screen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:12:29 +02:00
HoneyandClaude Sonnet 4.6 aa239f8e6a feat: romaji index for Japanese kanji song search
Backend:
- Add /api/romaji-index endpoint using kuromoji morphological analyzer
- Fetches all Navidrome songs, converts kanji → hiragana readings → romaji
- Only indexes songs with Japanese characters; caches for 20 min
- /api/romaji-index DELETE clears cache (force rebuild)

Frontend:
- fetchRomajiIndex() in api.js: lazy-fetches and caches the index
- doLocalSearch: when query is romaji (ASCII), queries romaji index in
  parallel with Navidrome search3 and merges+deduplicates results

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:15:07 +02:00
HoneyandClaude Sonnet 4.6 bd8919fb39 fix: prevent accidental seek on tap in mobile progress bar
Touch scrubbing now requires ≥8px horizontal drag before committing.
A plain tap on the progress bar is ignored — no unintentional position jump.
Also removed redundant onclick="seekTo()" since _initBarScrub already
handles mouse seeking via mousedown/mousemove/mouseup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:05:55 +02:00
HoneyandClaude Sonnet 4.6 a47b2be177 feat: romaji search for Japanese songs via wanakana
When search input is pure ASCII (romaji), converts it to hiragana in
parallel using wanakana and merges both result sets. Deduplicates by ID
so no double entries. Helps find artists/songs tagged with hiragana in
sort fields (e.g. 'inabakumori' → 'いなばくもり').

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:03:42 +02:00
HoneyandClaude Sonnet 4.6 27eb209945 feat: three-dot context menu on song rows + search shows songs as rows
- Add ⋮ button to every song-item triggering the context menu (same as right-click)
- Show song-actions always visible on touch devices via @media (hover:none)
- Search results: songs now render as makeSongItem rows instead of cards
  (artists/albums remain as horizontal card rows)
- Fix Android app opening system settings on every launch after notification
  permission denial — now only redirects to settings once using notif_settings_shown flag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:58:59 +02:00
HoneyandClaude Sonnet 4.6 c037ee623d fix: preload hi-res NP cover on track change so overlay opens instantly
_crossfadeNpBg only preloaded the 1024px cover when the NP overlay was
already open. Opening it fresh showed the old cover for ~0.5s while the
new image downloaded. Preloading in updatePlayerUI caches it immediately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:49:57 +02:00
HoneyandClaude Sonnet 4.6 30ef5300cf fix: sync native track index before opening Now Playing overlay
On Android, onTrackChanged fires asynchronously so opening the overlay
quickly after a track change could show the previous song. Query
NSBridge.getState() synchronously at open time to get the real index.
Same flash can also occur on web — openNowPlaying now always renders
from current S.qIdx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:49:05 +02:00
HoneyandClaude Sonnet 4.6 ee6df1e1fe fix: prevent music canplay callback from interrupting ABS playback start
When absPlayItem() paused S.audio, the stale canplay listener from the
last playCurrent() call still had a matching _playGen value and would
call S.audio.play() — triggering absInterrupt() which immediately paused
the ABS audio. Fix: export cancelMusicAutoplay() that bumps _playGen
before pausing, invalidating any pending canplay/guard callbacks.

Also shrink OK button and widen time input in sleep timer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:21:29 +02:00
Honey 55370ad760 feat: show iTunes enrichment status indicator in Spotify importer 2026-05-14 18:01:34 +02:00
Honey 11dec04fea fix: improve Spotify importer metadata — copy iTunes durationMs, skip redundant backend lookup when frontend already enriched track 2026-05-14 17:58:36 +02:00
Honey e78df7bdb9 feat: add search filter bar to playlist detail and favorites views 2026-05-13 13:58:08 +02:00
Honey 47f1605965 feat: add sort filter chips to albums and favorites views 2026-05-13 12:53:34 +02:00
Honey 751cc05b89 fix: reset progress bar to 0:00 immediately on track change on web too 2026-05-13 12:46:58 +02:00
Honey 10e3e0eb1d fix: reset progress bar to 0:00 immediately on track skip/play in bridge mode 2026-05-13 12:45:55 +02:00
Honey a1a4416bb6 fix: restart skip grace period on isPlaying:true to suppress multi-fire buffering during seek 2026-05-13 12:44:27 +02:00
HoneyandClaude Sonnet 4.6 d87abd2e9e fix: suppress transient pause during seek in bridge mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:46:12 +02:00
HoneyandClaude Sonnet 4.6 ee05998840 fix: suppress transient isPlaying:false during native skip transitions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:42:07 +02:00
HoneyandClaude Sonnet 4.6 1e61d35018 fix: use isPlaying() in syncNowPlaying to handle bridge/native mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 02:00:26 +02:00
HoneyandClaude Sonnet 4.6 0c5fe53f13 fix: inject iTunes previewUrl into Spotify importer track items
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:56:14 +02:00
HoneyandClaude Sonnet 4.6 bbe4412f42 chore: remove unused showSetupError from ui.js (duplicate of _showSetupError in main.js)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:07:49 +02:00
HoneyandClaude Sonnet 4.6 6bedd3ed28 fix: proxy iTunes API through backend to resolve CORS errors
Frontend was calling itunes.apple.com directly; browsers block it (no CORS headers).
Added /api/itunes/:endpoint proxy on the backend and updated all itunesSearch/Lookup
calls in api.js to route through it when backendUrl is configured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:23:40 +02:00
HoneyandClaude Sonnet 4.6 d4c41a9f92 fix: enforce Spotify rate limit cooldown server- and client-side
Server tracks _spRateLimitUntil and rejects retries instantly without
hitting Spotify, returning the remaining wait time. Retrying before the
window expired was resetting Spotify's timer (19s → 7s → 48s).

Frontend now shows a live countdown with a note not to retry early.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:22:10 +02:00
HoneyandClaude Sonnet 4.6 eacd68aea7 fix: remove Spotify credential requirement — no API keys needed
spotdl (SpotAPI) works without developer credentials. Remove the
health-check warning, credential error UI, and Client Credentials
fallback that were blocking the importer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:01:35 +02:00
HoneyandClaude Sonnet 4.6 859f4a458a fix: fail fast on Spotify 429 instead of retrying into timeout
Retrying with wait times (23s + 60s + 60s) exceeded the 120s global
timeout and showed a generic timeout error. Now returns immediately
with a RATE_LIMITED error and shows the user how long to wait.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 00:05:41 +02:00
HoneyandClaude Sonnet 4.6 76271c1e54 fix: 25s backend timeout + 30s frontend timeout on Spotify resolve
Extracts resolve logic into _resolveSpotify() and wraps it in
Promise.race with a 25s timer so the backend always sends a response
even if undici's AbortSignal.timeout silently fails on blocked sockets.
Also reduces frontend timeout from 90s to 30s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 23:39:07 +02:00