fix: preload=auto, artwork pre-warm, AbortError reload (matching Kima Hub)
- Add preload="auto" playsinline to <audio> element: browser starts buffering as soon as src is set, not only when play() is called — critical for background where play() has no data to work with - Fix AbortError handling: instead of silently returning (was swallowing audio element bad-state errors), reload src + load() like Kima Hub so the already-registered canplay listener retries play() once buffered - Pre-warm next song's cover art in _preloadNext() using _cachedCoverUrl so the lock screen notification never cold-starts artwork on first skip - Move _coverCache to top of file so _preloadNext and updateMediaSession share the same stable token URLs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
a0c6d09c41
commit
0344dcdcb6
2 files changed
+29
-13
No files matched your search
+1
-1
@@ -590,7 +590,7 @@
|
||||
<div class="jobs-panel" id="jobs-panel"></div>
|
||||
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
<audio id="audio-element"></audio>
|
||||
<audio id="audio-element" preload="auto" playsinline></audio>
|
||||
<audio id="preview-audio"></audio>
|
||||
<audio id="abs-audio"></audio>
|
||||
|
||||
|
||||
+28
-12
@@ -5,6 +5,17 @@ import { api, streamUrl, coverUrl, downloadUrl } from './api.js';
|
||||
import { loadRecommendations } from './views.js';
|
||||
import { updateLyricsHighlight, onNpSongChange } from './nowplaying.js';
|
||||
|
||||
// ─── COVER ART URL CACHE ───
|
||||
// Stable URLs let the OS cache artwork images — without this every skip regenerates a new
|
||||
// auth token URL, Samsung has to re-fetch the image, and the lock screen notification
|
||||
// visually reloads. Defined here (top of file) so both preload and MediaSession can use it.
|
||||
const _coverCache = new Map();
|
||||
function _cachedCoverUrl(id, size) {
|
||||
const key = `${id}:${size}`;
|
||||
if (!_coverCache.has(key)) _coverCache.set(key, coverUrl(id, size));
|
||||
return _coverCache.get(key);
|
||||
}
|
||||
|
||||
// ─── PRELOAD (resource hint keeps connection open through Doze gap) ───
|
||||
let _preUrl = '';
|
||||
let _preId = '';
|
||||
@@ -31,6 +42,14 @@ function _preloadNext() {
|
||||
_preLink.as = 'fetch';
|
||||
_preLink.href = _preUrl;
|
||||
document.head.appendChild(_preLink);
|
||||
// Pre-warm cover art so lock screen notification never cold-starts artwork on first skip.
|
||||
// Uses _cachedCoverUrl so the same token is reused when updateMediaSession() runs later.
|
||||
if(next.coverArt) {
|
||||
[96, 300, 512].forEach(size => {
|
||||
const img = new Image();
|
||||
img.src = _cachedCoverUrl(next.coverArt, size);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _clearPreload() {
|
||||
@@ -73,8 +92,15 @@ export function playCurrent() {
|
||||
try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {}
|
||||
S.audio.play().catch(err => {
|
||||
S._trackChanging = false;
|
||||
if (err?.name === 'AbortError') return; // src changed mid-flight, stop
|
||||
if (attempt < 4 && S.queue[S.qIdx]?.id === s.id) {
|
||||
if (S.queue[S.qIdx]?.id !== s.id) return; // song changed while we waited — stop
|
||||
if (err?.name === 'AbortError') {
|
||||
// Audio element in bad state (interruption or load race) — reload like Kima Hub does
|
||||
S._trackChanging = true;
|
||||
S.audio.src = src;
|
||||
S.audio.load(); // canplay listener above will call play() once buffered
|
||||
return;
|
||||
}
|
||||
if (attempt < 4) {
|
||||
setTimeout(() => {
|
||||
if (S.queue[S.qIdx]?.id !== s.id) return;
|
||||
S._trackChanging = true;
|
||||
@@ -354,16 +380,6 @@ export function setVolume(v) {
|
||||
}
|
||||
|
||||
// ─── MEDIA SESSION ───
|
||||
// Cache cover art URLs — same auth token is reused so the OS can cache the artwork image.
|
||||
// Without this, every metadata update regenerates a new token URL and Samsung re-fetches
|
||||
// the artwork on each skip, causing the notification to visually "restart".
|
||||
const _coverCache = new Map();
|
||||
function _cachedCoverUrl(id, size) {
|
||||
const key = `${id}:${size}`;
|
||||
if (!_coverCache.has(key)) _coverCache.set(key, coverUrl(id, size));
|
||||
return _coverCache.get(key);
|
||||
}
|
||||
|
||||
export function updateMediaSession(s) {
|
||||
// Android native MediaSession bridge — use 512px art for high-DPI screens
|
||||
if (window.Android) {
|
||||
|
||||
Reference in new issue
Block a user