fix: Android lock screen notification + radio mode + SW auto-update reliability
- Set mediaSession.playbackState='playing'/'paused' so Android keeps the notification alive when the screen sleeps - Provide 96x96/300x300/512x512 artwork sizes for Android notification views - playQueue() now calls fillRadioQueue() immediately when radio mode is on, so the infinite queue works without toggling radio off/on - SW: add GET_VERSION/VERSION message handler for version-query fallback - SW registration: updateViaCache:'none' prevents browser HTTP-caching sw.js - Bump cache to ns-v30 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
0b9493c147
commit
dad2b0b730
2 files changed
+31
-20
No files matched your search
@@ -116,8 +116,8 @@ window.addEventListener('load',()=>{
|
|||||||
document.getElementById('srv-pass').addEventListener('keydown',e=>{if(e.key==='Enter')doConnect()});
|
document.getElementById('srv-pass').addEventListener('keydown',e=>{if(e.key==='Enter')doConnect()});
|
||||||
audio.addEventListener('timeupdate',updateProgress);
|
audio.addEventListener('timeupdate',updateProgress);
|
||||||
audio.addEventListener('ended',onEnded);
|
audio.addEventListener('ended',onEnded);
|
||||||
audio.addEventListener('play',()=>{setPlayIcon(true);document.getElementById('player-art').classList.add('spinning')});
|
audio.addEventListener('play',()=>{setPlayIcon(true);document.getElementById('player-art').classList.add('spinning');if(navigator.mediaSession)navigator.mediaSession.playbackState='playing';});
|
||||||
audio.addEventListener('pause',()=>{setPlayIcon(false);document.getElementById('player-art').classList.remove('spinning')});
|
audio.addEventListener('pause',()=>{setPlayIcon(false);document.getElementById('player-art').classList.remove('spinning');if(navigator.mediaSession)navigator.mediaSession.playbackState='paused';});
|
||||||
previewAudio.addEventListener('timeupdate',updatePreviewProgress);
|
previewAudio.addEventListener('timeupdate',updatePreviewProgress);
|
||||||
previewAudio.addEventListener('ended',stopPreview);
|
previewAudio.addEventListener('ended',stopPreview);
|
||||||
audio.volume=0.8;
|
audio.volume=0.8;
|
||||||
@@ -975,7 +975,7 @@ function dlSongLocal(s){
|
|||||||
function downloadCurrent(){if(qIdx>=0&&queue[qIdx])dlSongLocal(queue[qIdx])}
|
function downloadCurrent(){if(qIdx>=0&&queue[qIdx])dlSongLocal(queue[qIdx])}
|
||||||
|
|
||||||
// ─── PLAYER ───
|
// ─── PLAYER ───
|
||||||
function playQueue(songs,startIdx){queue=songs.map(s=>({...s}));qIdx=startIdx;playCurrent();renderQueue()}
|
function playQueue(songs,startIdx){queue=songs.map(s=>({...s}));qIdx=startIdx;playCurrent();renderQueue();if(radioMode)fillRadioQueue();}
|
||||||
function playCurrent(){
|
function playCurrent(){
|
||||||
if(qIdx<0||qIdx>=queue.length)return;
|
if(qIdx<0||qIdx>=queue.length)return;
|
||||||
const s=queue[qIdx];
|
const s=queue[qIdx];
|
||||||
@@ -1025,7 +1025,8 @@ function _syncNpDirect(s){
|
|||||||
}
|
}
|
||||||
function updateMediaSession(s){
|
function updateMediaSession(s){
|
||||||
if(!navigator.mediaSession)return;
|
if(!navigator.mediaSession)return;
|
||||||
navigator.mediaSession.metadata=new MediaMetadata({title:s.title,artist:s.artist||'',album:s.album||'',artwork:s.coverArt?[{src:coverUrl(s.coverArt,300),sizes:'300x300'}]:[]});
|
const artwork=s.coverArt?[{src:coverUrl(s.coverArt,96),sizes:'96x96',type:'image/jpeg'},{src:coverUrl(s.coverArt,300),sizes:'300x300',type:'image/jpeg'},{src:coverUrl(s.coverArt,512),sizes:'512x512',type:'image/jpeg'}]:[];
|
||||||
|
navigator.mediaSession.metadata=new MediaMetadata({title:s.title,artist:s.artist||'',album:s.album||'',artwork});
|
||||||
navigator.mediaSession.setActionHandler('play',togglePlay);
|
navigator.mediaSession.setActionHandler('play',togglePlay);
|
||||||
navigator.mediaSession.setActionHandler('pause',togglePlay);
|
navigator.mediaSession.setActionHandler('pause',togglePlay);
|
||||||
navigator.mediaSession.setActionHandler('previoustrack',prevSong);
|
navigator.mediaSession.setActionHandler('previoustrack',prevSong);
|
||||||
@@ -1330,24 +1331,28 @@ async function loadArtists(){
|
|||||||
// ─── PWA ───
|
// ─── PWA ───
|
||||||
function injectPWA(){
|
function injectPWA(){
|
||||||
if (!('serviceWorker' in navigator)) return;
|
if (!('serviceWorker' in navigator)) return;
|
||||||
navigator.serviceWorker.register('sw.js').then(reg => {
|
|
||||||
// Force-check for SW update every time the app opens
|
|
||||||
reg.update().catch(() => {});
|
|
||||||
// New SW found while page is open (e.g. user left tab open overnight)
|
|
||||||
reg.addEventListener('updatefound', () => {
|
|
||||||
const sw = reg.installing;
|
|
||||||
if (!sw) return;
|
|
||||||
sw.addEventListener('statechange', () => {
|
|
||||||
// 'installed' + existing controller = update ready but waiting for skipWaiting
|
|
||||||
// With skipWaiting() this fires briefly; the SW_UPDATED message handles the banner
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}).catch(() => {});
|
|
||||||
|
|
||||||
// SW posts SW_UPDATED after it activates and claims this client
|
// Listen before registering so we never miss a message
|
||||||
navigator.serviceWorker.addEventListener('message', e => {
|
navigator.serviceWorker.addEventListener('message', e => {
|
||||||
if (e.data?.type === 'SW_UPDATED') showUpdateBanner(e.data.version);
|
if (e.data?.type === 'SW_UPDATED') {
|
||||||
|
localStorage.setItem('sw-version', e.data.version);
|
||||||
|
showUpdateBanner(e.data.version);
|
||||||
|
}
|
||||||
|
// Version query response — covers the race where SW activated before this listener
|
||||||
|
if (e.data?.type === 'VERSION') {
|
||||||
|
const prev = localStorage.getItem('sw-version');
|
||||||
|
if (prev && prev !== e.data.version) showUpdateBanner(e.data.version);
|
||||||
|
localStorage.setItem('sw-version', e.data.version);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// updateViaCache:'none' prevents the browser HTTP cache from hiding SW file changes
|
||||||
|
navigator.serviceWorker.register('sw.js', { updateViaCache: 'none' }).then(reg => {
|
||||||
|
reg.update().catch(() => {});
|
||||||
|
// Query active SW version right now to catch updates that happened before page JS ran
|
||||||
|
const ctrl = navigator.serviceWorker.controller;
|
||||||
|
if (ctrl) ctrl.postMessage({ type: 'GET_VERSION' });
|
||||||
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showUpdateBanner(version) {
|
function showUpdateBanner(version) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'ns-v29';
|
const CACHE = 'ns-v30';
|
||||||
const PRECACHE = ['/', 'index.html', 'styles.css', 'app.js', 'manifest.json', 'icon.svg'];
|
const PRECACHE = ['/', 'index.html', 'styles.css', 'app.js', 'manifest.json', 'icon.svg'];
|
||||||
const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js'];
|
const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js'];
|
||||||
|
|
||||||
@@ -17,6 +17,12 @@ self.addEventListener('activate', e => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.addEventListener('message', e => {
|
||||||
|
if (e.data?.type === 'GET_VERSION') {
|
||||||
|
e.source.postMessage({ type: 'VERSION', version: CACHE });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
self.addEventListener('fetch', e => {
|
self.addEventListener('fetch', e => {
|
||||||
if (!e.request.url.startsWith(self.location.origin)) return;
|
if (!e.request.url.startsWith(self.location.origin)) return;
|
||||||
if (SKIP_PATHS.some(p => e.request.url.includes(p))) return;
|
if (SKIP_PATHS.some(p => e.request.url.includes(p))) return;
|
||||||
|
|||||||
Reference in new issue
Block a user