fix(android): instant cold-start resume + play button icon flicker
WebBridge: skip setMediaItems/prepare when the track is already buffered from the cold-start pre-warm, so resuming the last song is instant (~0s) instead of re-buffering from scratch (~10s). Player: show pause icon during buffering (isPlaying=false, isBuffering=true) so the button doesn't flash back to "play" between tracks. Also optimistically set _nativePlaying=true and call setPlayIcon immediately on playCurrent so the icon updates before the native callback round-trip. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
76efa393ea
commit
60e89fe3b6
2 files changed
+16
-2
No files matched your search
@@ -28,11 +28,20 @@ class WebBridge(private val controllerProvider: () -> MediaController?) {
|
|||||||
runOnMain {
|
runOnMain {
|
||||||
val ctrl = controllerProvider() ?: return@runOnMain
|
val ctrl = controllerProvider() ?: return@runOnMain
|
||||||
val idx = startIndex.coerceAtLeast(0)
|
val idx = startIndex.coerceAtLeast(0)
|
||||||
|
// If ExoPlayer already has this exact single track buffered from the cold-start
|
||||||
|
// pre-warm, skip setMediaItems+prepare so we don't throw away the buffered data.
|
||||||
|
// Just call play() — this makes the "resume last track" instant instead of ~10s.
|
||||||
|
val alreadyReady = items.size == 1 &&
|
||||||
|
ctrl.mediaItemCount == 1 &&
|
||||||
|
ctrl.currentMediaItem?.mediaId == items[0].mediaId &&
|
||||||
|
ctrl.playbackState != Player.STATE_IDLE
|
||||||
|
if (!alreadyReady) {
|
||||||
ctrl.setMediaItems(items, idx, 0L)
|
ctrl.setMediaItems(items, idx, 0L)
|
||||||
// Pre-warm artwork before prepare()/play() so the notification bitmap
|
// Pre-warm artwork before prepare()/play() so the notification bitmap
|
||||||
// cache is populated before Media3 renders the first notification.
|
// cache is populated before Media3 renders the first notification.
|
||||||
MusicService.instance?.publicWarmArtwork(idx)
|
MusicService.instance?.publicWarmArtwork(idx)
|
||||||
ctrl.prepare()
|
ctrl.prepare()
|
||||||
|
}
|
||||||
if (autoPlay) ctrl.play()
|
if (autoPlay) ctrl.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,9 +189,12 @@ window.NSPlayer = {
|
|||||||
_nativePlaying = state.isPlaying;
|
_nativePlaying = state.isPlaying;
|
||||||
if (!state.isPlaying && !state.isBuffering) _cancelScrobble();
|
if (!state.isPlaying && !state.isBuffering) _cancelScrobble();
|
||||||
if (state.isPlaying && S.previewCurrent) stopPreview(false);
|
if (state.isPlaying && S.previewCurrent) stopPreview(false);
|
||||||
|
// Show pause icon while buffering (isPlaying=false but isBuffering=true) so the
|
||||||
|
// button doesn't flash "play" during the brief re-buffer between tracks.
|
||||||
|
const showPlaying = state.isPlaying || state.isBuffering;
|
||||||
if (state.isPlaying !== prev) {
|
if (state.isPlaying !== prev) {
|
||||||
setPlayIcon(state.isPlaying);
|
setPlayIcon(showPlaying);
|
||||||
document.getElementById('player-art')?.classList.toggle('spinning', state.isPlaying);
|
document.getElementById('player-art')?.classList.toggle('spinning', showPlaying);
|
||||||
if (navigator.mediaSession)
|
if (navigator.mediaSession)
|
||||||
navigator.mediaSession.playbackState = state.isPlaying ? 'playing' : 'paused';
|
navigator.mediaSession.playbackState = state.isPlaying ? 'playing' : 'paused';
|
||||||
}
|
}
|
||||||
@@ -374,8 +377,10 @@ export function playCurrent() {
|
|||||||
|
|
||||||
if (HAS_BRIDGE) {
|
if (HAS_BRIDGE) {
|
||||||
S._trackChanging = true;
|
S._trackChanging = true;
|
||||||
|
_nativePlaying = true;
|
||||||
_resetProgressUI();
|
_resetProgressUI();
|
||||||
updatePlayerUI(s, true);
|
updatePlayerUI(s, true);
|
||||||
|
setPlayIcon(true);
|
||||||
onNpSongChange(s.id);
|
onNpSongChange(s.id);
|
||||||
renderQueue();
|
renderQueue();
|
||||||
try { window.NSBridge.setQueue(_nativeSingle(s), 0, true); } catch(_) {}
|
try { window.NSBridge.setQueue(_nativeSingle(s), 0, true); } catch(_) {}
|
||||||
|
|||||||
Reference in new issue
Block a user