Commit Graph
78 Commits
Author SHA1 Message Date
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 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 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 1aff19e319 feat: keep Japanese in main tags, romaji in sort tags + frontend toggle
- Downloads now store original Japanese in TITLE/ARTIST/ALBUM tags
  so Apple Music, iTunes, YTM etc. all see the authentic original text
- Sort tags (TITLESORT/ARTISTSORT/ALBUMSORT) written with romaji for
  apps that use them for sorting/display
- iTunes-resolved English artist name used for ARTISTSORT and folder names
  when available (more accurate than kuromoji for known artists)
- rawArtist parameter added to downloadTrack so YTM search query uses
  the original Japanese channel name (fixes wrong song downloads)
- spotify.js: artistName preserved as original; itunesArtistName stored
  separately so the Spotify importer shows Japanese titles
- romaji index now includes titleRomaji/artistRomaji/albumRomaji fields
- Frontend: showRomaji setting (Settings > Bibliothek) — when on, player
  bar, now-playing, queue, and song lists all show romaji via index lookup
- refreshDisplayMode() updates all live UI when the toggle is saved

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 14:58:53 +02:00
HoneyandClaude Sonnet 4.6 467ee8a0fe fix: consistent artist parsing across player, NP overlay, and song list
Extract parseArtists() helper — uses Navidrome's resolved artists array
when it has multiple entries, otherwise splits artist string on ';' only.
Removes old split on / that broke Au/Ra, HUNTR/X etc everywhere.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 13:08:33 +02:00
HoneyandClaude Sonnet 4.6 6894e8a94a feat: Spotify-style shuffle — pre-shuffle queue, each song plays once
Shuffle now shuffles the queue array once (current song first, rest
randomised behind it) instead of picking a random index on every skip.
Toggling shuffle off restores the original queue order. Removed the
'once' repeat state added in the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 00:31:50 +02:00
HoneyandClaude Sonnet 4.6 89aeeb7421 fix: radio fill lock prevents concurrent fills; random fallback when similar songs exhausted
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 13:15:55 +02:00
HoneyandClaude Sonnet 4.6 b9a5c6468f refactor: dedup buildTrackList, extract topsong row helper, split romaji to lib
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:36:06 +02:00
HoneyandClaude Sonnet 4.6 3128b06b24 fix: 15s scrobble, play count clarity, star always visible, all-songs actions
- Scrobble timer: 12s → 15s
- Play count moved after action buttons, shown as "23×" (distinct from duration)
- Song star/menu buttons always visible (removed opacity:0 hover-only rule)
- Artist all-songs page: added star + 3-dots to every row (same as top songs)
- Hero card: picks from full recent+frequent pool (was capped at first 5 = all frequent)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:47:06 +02:00
HoneyandClaude Sonnet 4.6 7e07fef7ba feat: timer scrobble (12s), artist topsong star+menu, play count toggle
Scrobbling:
- Replace end-of-track scrobble with 12s timer approach
- Android: schedule on onTrackChanged, cancel on pause
- Web: schedule in playCurrent, cancel in togglePlay pause + onEnded
- Skip/new track resets the timer automatically (new song ID)

Artist top songs:
- Add star button and 3-dots context menu to every top song row
- Topsong grid expanded to 5 columns to fit action buttons

Settings — play count toggle:
- New "Bibliothek" section with toggle switch
- When on: shows play count in album/playlist song rows
- Artist page always shows play count regardless of toggle
- Persisted in ns_cfg localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:35:26 +02:00
HoneyandClaude Sonnet 4.6 f79817410b feat: scrobbling, genre grid redesign with detail view + play button
- Scrobble to Navidrome on auto-advance (Android) and track end (web)
  so type=recent and type=frequent are accurate for the hero card
- Genre section: pill chips → grid cards with colored left bars
- Top 15 genres shown by default (sorted by song count), expand toggle
- Genre card: click → genre detail page with full song list
- Genre card: hover play button → shuffle-play without navigating
- Genre detail page reuses album-detail layout (back btn, stats, song list)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:18:56 +02:00
HoneyandClaude Sonnet 4.6 d3a1bf6a82 feat: home hero+genres, fix Android queue desync via song ID verification
Home view:
- Add hero card (random pick from recent+frequent combined pool)
- Add genre browser chips (sorted by song count, tap = shuffle-play)

Android sync fix:
- Add currentId to getState() snapshot so JS can verify song identity
- onReady: reset stale localStorage queue when native song ID mismatches
- onTrackChanged: reject stale old-queue callbacks by ID verification
- onPlaybackStateChanged safety net: ID-guard prevents wrong-queue index corruption
- connectToService: call updateStateSnapshot() before onReady so getState() is never empty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:06:47 +02:00
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 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 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 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 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 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 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 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 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 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 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
Honey 7dd22d9639 fix: apply square curve to volume slider so low positions are genuinely quiet 2026-05-09 17:12:37 +02:00
HoneyandClaude Sonnet 4.6 8e752fa892 fix: player bar instant update + stop music on app close
player.js: move NSBridge.setQueue() into requestAnimationFrame so the
updatePlayerUI DOM changes are committed and painted before the blocking
@JavascriptInterface call. setQueue serializes a large JSON payload and
blocks the JS/Blink main thread while Java parses it — deferring it one
frame ensures the player bar visually updates immediately on tap.

MusicService: onTaskRemoved now calls player.stop() + stopSelf() so
swiping the app from recents stops playback instead of leaving it running
in the background.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 15:00:18 +02:00
HoneyandClaude Sonnet 4.6 7084fc4118 fix: player bar freezes on manual song start in bridge mode
- playQueue: update UI immediately before NSBridge.setQueue so the bar
  reflects the new song without waiting for onTrackChanged
- onTrackChanged: remove index guard that blocked UI refresh when
  S.qIdx was already set; always call updatePlayerUI/onNpSongChange
- nextSong/prevSong: optimistic UI update before calling native next/prev
- _computeNextIdx: extracted helper for shuffle/repeat/linear logic
- _lastPositionMs: new variable for prev() 3s threshold in bridge mode
- updatePlayerUI/_syncNpDirect: guard img.src assignment to prevent
  redundant reloads and flicker on double update

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 13:04:33 +02:00
HoneyandClaude Sonnet 4.6 cf2921c56f fix: cover art on auto-advance + native back-button overlay routing
Issue 1 (MusicService): wrap DefaultHttpDataSource in CacheBitmapLoader so
Media3 notification artwork is fetched once and served from memory on every
subsequent auto-advance. warmArtworkCache() pre-loads current+next 3 tracks
on both onMediaItemTransition and onTimelineChanged (queue replace via setQueue).
Adds explicit media3-datasource dependency.

Issue 2 (back button): replace deprecated onKeyDown with OnBackPressedCallback.
WebBridge.setOverlayState(bool) tracks whether JS has an overlay open;
consumeBackPress() is called first — if true, MainActivity delegates to
window.NSPlayer.handleBack() which closes the topmost layer. Falls back to
webView.goBack() then moveTaskToBack(true) so the MusicService binding survives.
JS side: _syncOverlayState() + MutationObserver keep the native flag current;
handleBack covers NP overlay, queue panel, action sheet, all modals, sidebar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 11:11:09 +02:00
HoneyandClaude Sonnet 4.6 76ecfe2268 feat: wire NSBridge fully + HD artwork + disable watchdog in wrapper mode
player.js
- Replace `_native()` function with `const HAS_BRIDGE` (evaluated once at
  module load so bridge detection is rock-solid)
- Add `buildTrackList()` — maps S.queue to NSBridge Track array with 1024px
  artworkUrl so lock-screen cover is crisp on high-DPR displays
- Add `_pushQueueIfBridge()` — called by `fillRadioQueue()` so radio top-ups
  are pushed to ExoPlayer in real time (fixes autoplay gap after radio refill)
- `playQueue`: route through `NSBridge.setQueue` when bridge present; old
  `S.audio` path retained for browser/PWA fallback
- `playCurrent`: `NSBridge.seekToIndex + play` in bridge mode
- `nextSong / prevSong / togglePlay / onEnded`: delegate to bridge, skip local
  S.qIdx manipulation (native sends onTrackChanged callback instead)
- `seekTo / _initBarScrub`: use `_lastDurationMs` for ms-accurate seeks
- `onTrackChanged`: guard with `p.index !== S.qIdx` to avoid redundant redraws
- `updateMediaSession`: artwork sizes 96/300/512 → 256/512/1024
- Player-bar cover: 100 → 256px; NP cover/bg: 400 → 1024px;
  queue items: 60 → 128px; preload hint sizes: 96/300/512 → 256/512/1024

main.js
- Wrap watchdog setInterval in `typeof window.NSBridge === 'undefined'` guard;
  the foreground service keeps ExoPlayer alive — JS watchdog not needed there

views.js
- Album detail / playlist detail covers: 300 → 512px
- Playlist grid cards: 200 → 512px
- Search result song cards: 200 → 256px
- Artist top-songs / all-songs thumbnails: 60 → 128px
- Partial album art in _renderArtistAlbums: 300 → 512px

components.js
- makeAlbumCard: 200 → 512px
- makeSongItem: 60 → 128px

nowplaying.js
- NP queue items: 60 → 128px
- NP action sheet thumb: 80 → 256px

sw.js
- Bump CACHE to ns-v41 so stale small-cover entries are evicted on next load

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 10:33:43 +02:00
HoneyandClaude Sonnet 4.6 0e17d2e5db feat: native Android audio via Media3 ExoPlayer + full NSBridge JS adapter
- Replace legacy MediaBrowserServiceCompat/MediaSessionCompat with
  Media3 MediaSessionService (MusicService.kt) + ExoPlayer set to
  WAKE_MODE_NETWORK — holds WakeLock + WifiLock through Doze so audio
  plays uninterrupted on locked screen
- Media3 manages the notification; no manual NotificationCompat.Builder
  → no disappear/reappear flicker when skipping tracks
- Full bidirectional bridge:
    JS → native: window.NSBridge.* (WebBridge.kt, 13 methods)
    Native → JS: window.NSPlayer.* callbacks (onReady, onTrackChanged,
      onPositionUpdate, onPlaybackStateChanged, onQueueEnded, onError)
- player.js: progressive enhancement — all playback routes through
  NSBridge when running inside the wrapper, falls back to S.audio on
  plain PWA/browser; removes all window.Android.* calls
- main.js: guard _onForeground mediaSession playbackState setter behind
  !window.NSBridge so native Media3 session is never stomped
- ProGuard rules updated to keep WebBridge @JavascriptInterface methods
  and Media3 classes
- bridge-test.html: manual integration test page for every bridge method

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 01:14:28 +02:00
HoneyandClaude Sonnet 4.6 1ede8727a2 fix: arm canplay before play(), reset on any error, remove stale MediaSession on restore
- playCurrent(): arm canplay listener BEFORE calling play() so it's never missed
  when play() hangs (common on locked Android — promise neither resolves nor rejects)
- playCurrent(): on ANY play() failure reset audio element (src + load()) so the
  audio engine starts fresh and canplay fires reliably; previously only AbortError
  triggered the reset — NotAllowedError (audio focus lost on lock screen) did not
- playCurrent(): single _armCanplay() helper replaces two separate guard/listener
  setups; re-arms after failure in case canplay already fired before the error
- _restoreLastPlayed(): remove updateMediaSession() call — pre-setting stale song
  metadata caused Samsung to treat the first actual playCurrent() as a new session
  (old song metadata → new song metadata while playing = notification close/reopen)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 18:55:10 +02:00