fix: lock screen audio + notification — audioSession once, deferred metadata, SW artwork cache
- Set navigator.audioSession.type='playback' only once (guarded flag) — repeated reassignment was resetting the OS audio session and triggering Doze network cuts - Defer updateMediaSession() until after play() resolves, not before audio.src swap — new MediaMetadata during HAVE_NOTHING state caused Samsung to close/reopen notification - Fix silent bug: updatePlayerUI(s, true) now actually skips MediaSession (added param) - 10s safety guards now attempt play() instead of just resetting _trackChanging flag - SW v40: cache-first for getCoverArt with token-stripped keys — artwork served from cache even when Doze blocks network, survives session restarts with new auth tokens - Pre-warm next track artwork 2s after song starts (earlier than 30s-remaining trigger) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec13124d99
commit
d355129134
3 files changed
+93
-20
No files matched your search
+38
-1
@@ -1,6 +1,9 @@
|
||||
const CACHE = 'ns-v39';
|
||||
const CACHE = 'ns-v40';
|
||||
const PRECACHE = ['/', 'index.html', 'manifest.json', 'icon.svg'];
|
||||
const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js'];
|
||||
// Auth token params stripped from cache keys so artwork survives across sessions/token refreshes.
|
||||
// The actual fetch still uses the full URL (with token); only the stored key is normalized.
|
||||
const STRIP_PARAMS = ['t', 's', 'u', 'v', 'c', 'f'];
|
||||
|
||||
self.addEventListener('install', e => {
|
||||
e.waitUntil(caches.open(CACHE).then(c => c.addAll(PRECACHE)));
|
||||
@@ -23,9 +26,43 @@ self.addEventListener('message', e => {
|
||||
}
|
||||
});
|
||||
|
||||
// Build a stable cache key by stripping Navidrome auth params
|
||||
function _cacheKey(url) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
STRIP_PARAMS.forEach(p => u.searchParams.delete(p));
|
||||
return u.toString();
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
self.addEventListener('fetch', e => {
|
||||
if (!e.request.url.startsWith(self.location.origin)) return;
|
||||
if (SKIP_PATHS.some(p => e.request.url.includes(p))) return;
|
||||
|
||||
const isCoverArt = e.request.url.includes('/rest/getCoverArt');
|
||||
|
||||
if (isCoverArt) {
|
||||
// Cache-first for artwork — serves instantly from SW cache even when Doze blocks network.
|
||||
// Key is token-stripped so it survives session restarts with new auth tokens.
|
||||
const keyUrl = _cacheKey(e.request.url);
|
||||
const keyReq = new Request(keyUrl);
|
||||
e.respondWith(
|
||||
caches.open(CACHE).then(cache =>
|
||||
cache.match(keyReq).then(cached => {
|
||||
if (cached) return cached;
|
||||
return fetch(e.request).then(res => {
|
||||
if (res.status === 200) cache.put(keyReq, res.clone());
|
||||
return res;
|
||||
}).catch(() => Response.error());
|
||||
})
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// All other requests: network-first, fall back to cache
|
||||
e.respondWith(
|
||||
fetch(e.request)
|
||||
.then(res => {
|
||||
|
||||
Reference in new issue
Block a user