diff --git a/public/sw.js b/public/sw.js
index 42c2ce3..feca39b 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'ns-v40';
+const CACHE = 'ns-v41';
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.
diff --git a/src/components.js b/src/components.js
index 5327080..482a14d 100644
--- a/src/components.js
+++ b/src/components.js
@@ -19,7 +19,7 @@ export function makeCard(title, sub, imgUrl, fallback='πΏ') {
}
export function makeAlbumCard(a) {
- const cv = coverUrl(a.coverArt, 200);
+ const cv = coverUrl(a.coverArt, 512);
const c = makeCard(a.name, a.artist||'', cv, 'πΏ');
c.querySelector('.card-art').onclick = (e) => { e.stopPropagation(); quickPlayAlbum(a); };
c.querySelector('.card-info').onclick = () => navigate('album-detail', a);
@@ -31,7 +31,7 @@ export function makeSongItem(s, idx, list, { noArtistLink = false, noAlbumLink =
const el = document.createElement('div');
if(s.starred && !s._starred) s._starred = true;
el.className = 'song-item' + (S.queue[S.qIdx]?.id === s.id ? ' playing' : '');
- const cv = coverUrl(s.coverArt, 60);
+ const cv = coverUrl(s.coverArt, 128);
const artistParts = (s.artist || '').split(/\s*(?:\/|β’|,)\s*/).filter(Boolean);
const artistHtml = artistParts.length
? artistParts.map((name, i) => {
diff --git a/src/main.js b/src/main.js
index 8bb63d2..efa2247 100644
--- a/src/main.js
+++ b/src/main.js
@@ -182,22 +182,25 @@ window.addEventListener('load', () => {
// pageshow fires more reliably than visibilitychange on iOS PWA and Samsung
window.addEventListener('pageshow', e => { if(e.persisted) _onForeground(); });
- // Watchdog: detect when audio silently stalled in background (ended event not fired, or frozen)
- let _wdLast = 0, _wdStalledAt = 0;
- setInterval(() => {
- if(!S.audio.src || S.audio.paused) { _wdStalledAt = 0; _wdLast = 0; return; }
- const dur = S.audio.duration;
- const cur = S.audio.currentTime;
- // ended but event didn't fire (background throttling)
- if(dur > 0 && cur >= dur - 0.5) { onEnded(); _wdStalledAt = 0; _wdLast = 0; return; }
- if(cur !== _wdLast) { _wdStalledAt = 0; _wdLast = cur; return; }
- if(!_wdStalledAt) { _wdStalledAt = Date.now(); return; }
- // frozen for >8s β try to resume
- if(Date.now() - _wdStalledAt > 8000) {
- _wdStalledAt = 0;
- S.audio.play().catch(() => {});
- }
- }, 3000);
+ // Watchdog: detect when audio silently stalled in background (ended event not fired, or frozen).
+ // Disabled with Bridge β the foreground service keeps ExoPlayer alive independently.
+ if (typeof window.NSBridge === 'undefined') {
+ let _wdLast = 0, _wdStalledAt = 0;
+ setInterval(() => {
+ if(!S.audio.src || S.audio.paused) { _wdStalledAt = 0; _wdLast = 0; return; }
+ const dur = S.audio.duration;
+ const cur = S.audio.currentTime;
+ // ended but event didn't fire (background throttling)
+ if(dur > 0 && cur >= dur - 0.5) { onEnded(); _wdStalledAt = 0; _wdLast = 0; return; }
+ if(cur !== _wdLast) { _wdStalledAt = 0; _wdLast = cur; return; }
+ if(!_wdStalledAt) { _wdStalledAt = Date.now(); return; }
+ // frozen for >8s β try to resume
+ if(Date.now() - _wdStalledAt > 8000) {
+ _wdStalledAt = 0;
+ S.audio.play().catch(() => {});
+ }
+ }, 3000);
+ }
injectPWA();
initProgressScrub();
diff --git a/src/nowplaying.js b/src/nowplaying.js
index 67e116a..831a19c 100644
--- a/src/nowplaying.js
+++ b/src/nowplaying.js
@@ -230,7 +230,7 @@ export function renderNpQueue() {
S.queue.forEach((s, i) => {
const item = document.createElement('div');
item.className = 'np-queue-item' + (i===S.qIdx ? ' nq-active' : '');
- const cv = coverUrl(s.coverArt, 60);
+ const cv = coverUrl(s.coverArt, 128);
item.innerHTML = `
${i+1}
${cv ? `

` : 'βͺ'}
${esc(s.title)}
${esc(s.artist||'')}
`;
item.onclick = () => { S.qIdx=i; playCurrent(); switchNpTab('player'); };
el.appendChild(item);
@@ -254,7 +254,7 @@ export function npGoAlbum() {
// βββ NP ACTION SHEET βββ
export function openNpMenu() {
const s = S.queue[S.qIdx]; if(!s) return;
- const cv = coverUrl(s.coverArt, 80);
+ const cv = coverUrl(s.coverArt, 256);
document.getElementById('np-sheet-song').innerHTML = `
${cv ? `

` : '
π΅
'}
${esc(s.title)}
${esc(s.artist||'')}${s.album?' Β· '+esc(s.album):''}
`;
diff --git a/src/player.js b/src/player.js
index 5680167..090d429 100644
--- a/src/player.js
+++ b/src/player.js
@@ -6,27 +6,47 @@ import { loadRecommendations } from './views.js';
import { updateLyricsHighlight, onNpSongChange } from './nowplaying.js';
// βββ NATIVE BRIDGE DETECTION βββββββββββββββββββββββββββββββββββββββββββββββ
-// Returns true when running inside the NovaSonic Android wrapper (window.NSBridge present).
-// Checked at call-time so it works even if the bridge appears after module load.
-const _native = () => !!(window.NSBridge);
+// True when running inside the NovaSonic Android wrapper (NSBridge injected before loadUrl).
+const HAS_BRIDGE = typeof window.NSBridge !== 'undefined';
// Track native playback state; driven by window.NSPlayer callbacks below.
let _nativePos = 0; // ms
-let _nativeDur = 0; // ms
let _nativePlaying = false;
+let _lastDurationMs = 0; // ms β set by onPositionUpdate, used for seek calculations
// Unified duration / position / seek helpers β use native state when available,
// fall back to HTMLAudioElement when not.
-const _dur = () => _native() ? _nativeDur / 1000 : (S.audio.duration || 0);
-const _pos = () => _native() ? _nativePos / 1000 : (S.audio.currentTime || 0);
-const _seek = ms => _native() ? _nsBridge('seekTo', ms) : (S.audio.currentTime = ms / 1000);
+const _dur = () => HAS_BRIDGE ? _lastDurationMs / 1000 : (S.audio.duration || 0);
+const _pos = () => HAS_BRIDGE ? _nativePos / 1000 : (S.audio.currentTime || 0);
+const _seek = ms => HAS_BRIDGE ? _nsBridge('seekTo', ms) : (S.audio.currentTime = ms / 1000);
// Safe NSBridge caller β never throws into JS.
function _nsBridge(method, ...args) {
try { window.NSBridge[method](...args); } catch(_) {}
}
-// Convert a Navidrome song object to the NSBridge Track JSON schema.
+// Build the NSBridge Track JSON array from S.queue.
+function buildTrackList() {
+ return S.queue.map(s => ({
+ id: s.id,
+ url: streamUrl(s.id),
+ title: s.title || '',
+ artist: s.artist || '',
+ album: s.album || null,
+ artworkUrl: s.coverArt ? coverUrl(s.coverArt, 1024) : null,
+ durationMs: Math.floor((s.duration || 0) * 1000),
+ }));
+}
+
+// Push the current JS queue to native whenever S.queue changes (radio top-ups, etc.).
+function _pushQueueIfBridge() {
+ if (!HAS_BRIDGE) return;
+ try {
+ window.NSBridge.setQueue(JSON.stringify(buildTrackList()), S.qIdx, !S.audio.paused);
+ } catch (e) { console.warn('NSBridge.setQueue failed', e); }
+}
+
+// Convert a single Navidrome song to the NSBridge Track schema (used for addToQueue).
function _trackToNative(s) {
return {
id: s.id,
@@ -34,8 +54,8 @@ function _trackToNative(s) {
title: s.title || '',
artist: s.artist || '',
album: s.album || null,
- artworkUrl: s.coverArt ? _cachedCoverUrl(s.coverArt, 512) : null,
- durationMs: Math.round((s.duration || 0) * 1000),
+ artworkUrl: s.coverArt ? coverUrl(s.coverArt, 1024) : null,
+ durationMs: Math.floor((s.duration || 0) * 1000),
};
}
@@ -46,66 +66,74 @@ window.NSPlayer = {
// Called when MediaController connects (first load or WebView reload).
onReady() {
- const raw = window.NSBridge?.getState();
- const state = raw ? (JSON.parse(raw) || {}) : {};
- const nativeLen = state.queueLength || 0;
-
- if (nativeLen > 0 && nativeLen === S.queue.length) {
- // Native still has the same queue (e.g. after a WebView reload) β re-sync UI.
- S.qIdx = state.currentIndex ?? S.qIdx;
- _nativePlaying = state.isPlaying || false;
- _nativePos = state.positionMs || 0;
- const s = S.queue[S.qIdx];
- if (s) { updatePlayerUI(s, true); onNpSongChange(s.id); }
- setPlayIcon(_nativePlaying);
- renderQueue();
- } else if (S.queue.length > 0) {
- // Native queue is empty or stale β push JS-side state (restored from localStorage).
- const tracks = S.queue.map(_trackToNative);
- _nsBridge('setQueue', JSON.stringify(tracks), S.qIdx, false);
+ if (HAS_BRIDGE && S.queue.length > 0) {
+ try {
+ const raw = window.NSBridge.getState();
+ const state = raw ? (JSON.parse(raw) || {}) : {};
+ const nativeLen = state.queueLength || 0;
+ if (nativeLen > 0 && nativeLen === S.queue.length) {
+ // Native still has the same queue β just re-sync UI state.
+ S.qIdx = state.currentIndex ?? S.qIdx;
+ _nativePlaying = state.isPlaying || false;
+ _nativePos = state.positionMs || 0;
+ const s = S.queue[S.qIdx];
+ if (s) { updatePlayerUI(s, true); onNpSongChange(s.id); }
+ setPlayIcon(_nativePlaying);
+ renderQueue();
+ } else {
+ // Native queue is empty or stale β push JS state (restored from localStorage).
+ window.NSBridge.setQueue(JSON.stringify(buildTrackList()), S.qIdx, false);
+ }
+ } catch (e) {}
}
- // Always sync settings.
+ // Always sync shuffle/repeat settings.
_nsBridge('setRepeatMode', S.repeat === false ? 'off' : S.repeat);
_nsBridge('setShuffleMode', S.shuffle);
},
// Play/pause/buffer state changed.
onPlaybackStateChanged(stateJson) {
- const state = JSON.parse(stateJson);
- const prev = _nativePlaying;
- _nativePlaying = state.isPlaying;
- if (state.isPlaying !== prev) {
- setPlayIcon(state.isPlaying);
- document.getElementById('player-art')?.classList.toggle('spinning', state.isPlaying);
- if (navigator.mediaSession)
- navigator.mediaSession.playbackState = state.isPlaying ? 'playing' : 'paused';
- }
+ try {
+ const state = JSON.parse(stateJson);
+ const prev = _nativePlaying;
+ _nativePlaying = state.isPlaying;
+ if (state.isPlaying !== prev) {
+ setPlayIcon(state.isPlaying);
+ document.getElementById('player-art')?.classList.toggle('spinning', state.isPlaying);
+ if (navigator.mediaSession)
+ navigator.mediaSession.playbackState = state.isPlaying ? 'playing' : 'paused';
+ }
+ } catch (e) {}
},
// Native moved to a different track (auto-advance, seek, or queue replace).
onTrackChanged(payloadJson) {
- const payload = JSON.parse(payloadJson);
- S.qIdx = payload.index;
- const s = S.queue[S.qIdx];
- if (!s) return;
- updatePlayerUI(s, true);
- onNpSongChange(s.id);
- renderQueue();
- // Radio mode: top up the queue when running low; push new songs to native.
- if (S.radioMode && S.queue.length - S.qIdx < 5) {
- const prevLen = S.queue.length;
- fillRadioQueue().then(() => {
- S.queue.slice(prevLen).forEach(t =>
- _nsBridge('addToQueue', JSON.stringify(_trackToNative(t)), -1)
- );
- });
- }
+ try {
+ const payload = JSON.parse(payloadJson);
+ if (typeof payload.index === 'number' && payload.index !== S.qIdx) {
+ S.qIdx = payload.index;
+ const s = S.queue[S.qIdx];
+ if (!s) return;
+ updatePlayerUI(s, true);
+ onNpSongChange(s.id);
+ renderQueue();
+ // Radio mode: top up the queue when running low; push new songs to native.
+ if (S.radioMode && S.queue.length - S.qIdx < 5) {
+ const prevLen = S.queue.length;
+ fillRadioQueue().then(() => {
+ S.queue.slice(prevLen).forEach(t =>
+ _nsBridge('addToQueue', JSON.stringify(_trackToNative(t)), -1)
+ );
+ });
+ }
+ }
+ } catch (e) {}
},
// ~250 ms position tick while playing.
onPositionUpdate(posMs, durMs) {
- _nativePos = posMs;
- _nativeDur = durMs;
+ _nativePos = posMs;
+ _lastDurationMs = durMs;
if (S._scrubbing) return;
const dur = durMs / 1000;
const cur = posMs / 1000;
@@ -156,7 +184,7 @@ function _cachedCoverUrl(id, size) {
// βββ AUDIO SESSION β set once, never again βββββββββββββββββββββββββββββββ
let _audioSessionSet = false;
function _setAudioSession() {
- if (_native() || _audioSessionSet) return;
+ if (HAS_BRIDGE || _audioSessionSet) return;
_audioSessionSet = true;
try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {}
}
@@ -175,7 +203,7 @@ function _nextIdx() {
}
function _preloadNext() {
- if (_native()) return; // ExoPlayer handles buffering natively
+ if (HAS_BRIDGE) return; // ExoPlayer handles buffering natively
const ni = _nextIdx();
if(ni < 0) return;
const next = S.queue[ni];
@@ -189,7 +217,7 @@ function _preloadNext() {
_preLink.href = _preUrl;
document.head.appendChild(_preLink);
if(next.coverArt) {
- [96, 300, 512].forEach(size => {
+ [256, 512, 1024].forEach(size => {
const img = new Image();
img.src = _cachedCoverUrl(next.coverArt, size);
});
@@ -209,9 +237,8 @@ export function playQueue(songs, startIdx) {
S.qIdx = startIdx;
_clearPreload();
- if (_native()) {
- const tracks = S.queue.map(_trackToNative);
- _nsBridge('setQueue', JSON.stringify(tracks), startIdx, true);
+ if (HAS_BRIDGE) {
+ window.NSBridge.setQueue(JSON.stringify(buildTrackList()), startIdx, true);
renderQueue();
if (S.radioMode) fillRadioQueue();
return;
@@ -226,7 +253,7 @@ export function playCurrent() {
if(S.qIdx < 0 || S.qIdx >= S.queue.length) return;
const s = S.queue[S.qIdx];
- if (_native()) {
+ if (HAS_BRIDGE) {
// Update DOM immediately for snappy feedback; onTrackChanged will confirm.
S._trackChanging = true;
updatePlayerUI(s, true);
@@ -306,7 +333,7 @@ export function updatePlayerUI(s, _skipMediaSession = false) {
document.getElementById('player-album-sep').style.display = s.album ? '' : 'none';
document.getElementById('player-album-name').textContent = s.album || '';
const art = document.getElementById('player-art');
- const cv = coverUrl(s.coverArt, 100);
+ const cv = coverUrl(s.coverArt, 256);
const existingImg = art.querySelector('img');
if (cv && existingImg) existingImg.src = cv;
else art.innerHTML = cv ? `
` : 'βͺ
';
@@ -353,7 +380,7 @@ function _crossfadeNpBg(cv) {
export function _syncNpDirect(s) {
if(!S.npOpen || !s) return;
- const cv = coverUrl(s.coverArt, 400);
+ const cv = coverUrl(s.coverArt, 1024);
const cover = document.getElementById('np-cover');
if (cover) {
const existingImg = cover.querySelector('img');
@@ -380,7 +407,7 @@ export function _syncNpDirect(s) {
// βββ TRANSPORT ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
export function togglePlay() {
- if (_native()) { _nsBridge('togglePlay'); return; }
+ if (HAS_BRIDGE) { _nsBridge('togglePlay'); return; }
if(S.audio.src) S.audio.paused ? S.audio.play() : S.audio.pause();
}
@@ -399,14 +426,14 @@ export function setPlayIcon(playing) {
export function prevSong() {
_clearPreload();
- if (_native()) { _nsBridge('prev'); return; }
+ if (HAS_BRIDGE) { _nsBridge('prev'); return; }
if(S.audio.currentTime > 3) { S.audio.currentTime = 0; return; }
if(S.qIdx > 0) { S.qIdx--; playCurrent(); }
}
export function nextSong() {
_clearPreload();
- if (_native()) { _nsBridge('next'); return; }
+ if (HAS_BRIDGE) { _nsBridge('next'); return; }
if(S.shuffle) S.qIdx = Math.floor(Math.random() * S.queue.length);
else if(S.qIdx < S.queue.length - 1) S.qIdx++;
else if(S.repeat) S.qIdx = 0;
@@ -437,14 +464,14 @@ export function toggleShuffle() {
S.shuffle = !S.shuffle;
localStorage.setItem('ns_shuffle', S.shuffle ? '1' : '0');
_applyShuffleRepeat();
- if (_native()) _nsBridge('setShuffleMode', S.shuffle);
+ if (HAS_BRIDGE) _nsBridge('setShuffleMode', S.shuffle);
}
export function toggleRepeat() {
S.repeat = S.repeat === false ? 'all' : S.repeat === 'all' ? 'one' : false;
localStorage.setItem('ns_repeat', S.repeat === false ? '0' : S.repeat);
_applyShuffleRepeat();
- if (_native()) _nsBridge('setRepeatMode', S.repeat === false ? 'off' : S.repeat);
+ if (HAS_BRIDGE) _nsBridge('setRepeatMode', S.repeat === false ? 'off' : S.repeat);
}
// βββ RADIO ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@@ -469,13 +496,14 @@ export async function fillRadioQueue() {
const r = await api('getRandomSongs', '&size=20');
S.queue.push(...(r.randomSongs?.song || []));
renderQueue();
+ _pushQueueIfBridge();
} catch {}
}
// βββ PROGRESS & SCRUBBING βββββββββββββββββββββββββββββββββββββββββββββββββ
export function updateProgress() {
- if(_native()) return; // driven by NSPlayer.onPositionUpdate instead
+ if(HAS_BRIDGE) return; // driven by NSPlayer.onPositionUpdate instead
if(!S.audio.duration || S._scrubbing) return;
syncMediaSessionPosition();
const rem = S.audio.duration - S.audio.currentTime;
@@ -504,8 +532,8 @@ export function seekTo(e) {
const fill = document.getElementById('progress-fill');
if (fill) { fill.style.transition = 'none'; fill.style.width = (pos * 100) + '%'; }
S._scrubbing = true;
- if (_native()) {
- _nsBridge('seekTo', Math.round(pos * _nativeDur));
+ if (HAS_BRIDGE) {
+ _nsBridge('seekTo', Math.round(pos * _lastDurationMs));
const done = () => { S._scrubbing = false; requestAnimationFrame(() => { if (fill) fill.style.transition = ''; }); };
setTimeout(done, 500);
} else {
@@ -537,8 +565,8 @@ function _initBarScrub(bar, fill, onMove) {
function endScrub(pos) {
fill.style.width = (pos * 100) + '%';
const dur = _dur();
- if (_native()) {
- _nsBridge('seekTo', Math.round(pos * _nativeDur));
+ if (HAS_BRIDGE) {
+ _nsBridge('seekTo', Math.round(pos * _lastDurationMs));
S._scrubbing = false;
requestAnimationFrame(() => { fill.style.transition = ''; });
} else if (dur) {
@@ -570,19 +598,19 @@ export function setVolume(v) {
const absAudio = document.getElementById('abs-audio');
if (absAudio) absAudio.volume = vol;
document.querySelectorAll('.volume-slider-sync').forEach(el => { el.value = v; });
- if (_native()) _nsBridge('setVolume', vol);
+ if (HAS_BRIDGE) _nsBridge('setVolume', vol);
}
// βββ MEDIA SESSION ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
export function updateMediaSession(s) {
// When native is active, Media3 owns the system notification β don't touch it.
- if (_native()) return;
+ if (HAS_BRIDGE) return;
if(!navigator.mediaSession) return;
const artwork = s.coverArt ? [
- { src: _cachedCoverUrl(s.coverArt, 96), sizes:'96x96', type:'image/jpeg' },
- { src: _cachedCoverUrl(s.coverArt, 300), sizes:'300x300', type:'image/jpeg' },
- { src: _cachedCoverUrl(s.coverArt, 512), sizes:'512x512', type:'image/jpeg' },
+ { src: _cachedCoverUrl(s.coverArt, 256), sizes:'256x256', type:'image/jpeg' },
+ { src: _cachedCoverUrl(s.coverArt, 512), sizes:'512x512', type:'image/jpeg' },
+ { src: _cachedCoverUrl(s.coverArt, 1024), sizes:'1024x1024', type:'image/jpeg' },
] : [];
navigator.mediaSession.metadata = new MediaMetadata({ title:s.title, artist:s.artist||'', album:s.album||'', artwork });
navigator.mediaSession.setActionHandler('play', togglePlay);
@@ -595,7 +623,7 @@ export function updateMediaSession(s) {
}
export function syncMediaSessionPosition() {
- if (_native()) return;
+ if (HAS_BRIDGE) return;
if(!navigator.mediaSession?.setPositionState || !S.audio.duration || isNaN(S.audio.duration)) return;
const now = Date.now();
if(now - S._msPosLast < 1000) return;
@@ -670,7 +698,7 @@ export function renderQueue() {
S.queue.forEach((s, i) => {
const item = document.createElement('div');
item.className = 'queue-item' + (i === S.qIdx ? ' active' : '');
- const cv = coverUrl(s.coverArt, 60);
+ const cv = coverUrl(s.coverArt, 128);
item.innerHTML = `${i+1}
${cv ? `

` : 'βͺ'}
${esc(s.title)}
${esc(s.artist||'')}
`;
item.onclick = () => { S.qIdx = i; playCurrent(); };
el.appendChild(item);
@@ -702,9 +730,9 @@ export function downloadCurrent() {
export function playPreview(track) {
if(!track.previewUrl) { toast('Kein Preview verfΓΌgbar'); return; }
- const wasPlaying = _native() ? _nativePlaying : !S.audio.paused;
+ const wasPlaying = HAS_BRIDGE ? _nativePlaying : !S.audio.paused;
if(wasPlaying) {
- if (_native()) _nsBridge('pause');
+ if (HAS_BRIDGE) _nsBridge('pause');
else S.audio.pause();
}
S.previewCurrent = track;
@@ -727,7 +755,7 @@ export function stopPreview(resume) {
document.getElementById('preview-fill').style.width = '0%';
S.previewCurrent = null;
if (shouldResume) {
- if (_native()) _nsBridge('play');
+ if (HAS_BRIDGE) _nsBridge('play');
else if (S.audio.src) S.audio.play();
}
}
@@ -758,11 +786,11 @@ export function _restoreLastPlayed() {
document.getElementById('player-album-sep').style.display = s.album ? '' : 'none';
document.getElementById('player-album-name').textContent = s.album || '';
const art = document.getElementById('player-art');
- const cv = coverUrl(s.coverArt, 100);
+ const cv = coverUrl(s.coverArt, 256);
art.innerHTML = cv ? `
` : 'βͺ
';
updateStarBtn();
document.title = `${s.title} β NovaSonic`;
- if (_native()) return; // native will sync state via NSPlayer.onReady
+ if (HAS_BRIDGE) return; // native will sync state via NSPlayer.onReady
// Web path: prime the audio element so play button works immediately.
S.audio.src = streamUrl(s.id);
S.audio.load();
diff --git a/src/views.js b/src/views.js
index 0c26373..77907fa 100644
--- a/src/views.js
+++ b/src/views.js
@@ -250,7 +250,7 @@ export async function loadArtistDetail(artist) {
tsEl.appendChild(h);
const list = document.createElement('div'); list.style.marginBottom = '8px';
topSongs.forEach((s, i) => {
- const cv = coverUrl(s.coverArt, 60);
+ const cv = coverUrl(s.coverArt, 128);
const row = document.createElement('div'); row.className = 'topsong-item';
row.innerHTML = `${i+1}
${cv ? `

` : '
π΅
'}
@@ -360,7 +360,7 @@ function _renderArtistAlbums(el, ndAlbums, iAlbums, artist) {
if(partial.length) {
const g = addGrid('UnvollstΓ€ndig', partial.length);
partial.forEach(({ nd, itunes, have, total }) => {
- const art = itunes?.artworkUrl100?.replace('100x100','300x300') || coverUrl(nd.coverArt, 300);
+ const art = itunes?.artworkUrl100?.replace('100x100','512x512') || coverUrl(nd.coverArt, 512);
const c = makeCard(nd.name, `${have} / ${total} Tracks`, art, 'πΏ');
c.querySelector('.card-art').onclick = (e) => { e.stopPropagation(); quickPlayAlbum(nd); };
c.querySelector('.card-info').onclick = () => itunes ? navigate('itunes-album-detail', itunes) : navigate('album-detail', { ...nd, _fromArtist: artist });
@@ -471,7 +471,7 @@ export async function loadAlbumDetail(album) {
return `${esc(name)}`;
}).join(' β’ ') || '';
const cw = document.getElementById('album-cover-wrap');
- const cv = coverUrl(album.coverArt, 300);
+ const cv = coverUrl(album.coverArt, 512);
cw.innerHTML = cv ? `
` : 'πΏ
';
const sl = document.getElementById('album-songs');
sl.innerHTML = '';
@@ -500,7 +500,7 @@ export async function loadPlaylists() {
g.innerHTML = '';
if(!lists.length) { g.innerHTML = 'πΆ
Keine Playlists vorhanden
'; return; }
lists.forEach(pl => {
- const cv = coverUrl(pl.coverArt, 200);
+ const cv = coverUrl(pl.coverArt, 512);
const c = makeCard(pl.name, pl.songCount+' Titel', cv, 'πΆ');
c.querySelector('.card-art').onclick = (e) => { e.stopPropagation(); quickPlayPlaylist(pl); };
c.querySelector('.card-info').onclick = () => navigate('playlist-detail', pl);
@@ -531,7 +531,7 @@ export async function loadPlaylistDetail(pl) {
S._currentPlaylist = {...pl};
document.getElementById('playlist-detail-name').textContent = pl.name;
const cw = document.getElementById('playlist-cover');
- const cv = coverUrl(pl.coverArt, 300);
+ const cv = coverUrl(pl.coverArt, 512);
cw.innerHTML = cv ? `
` : 'πΆ
';
document.getElementById('btn-edit-playlist').onclick = () => openEditPlModal(S._currentPlaylist);
const sl = document.getElementById('playlist-songs');
@@ -701,7 +701,7 @@ export async function doLocalSearch(q) {
const s = mkSection('Titel');
const g = document.createElement('div'); g.className = 'scroll-row';
songs.forEach((so,i) => {
- const cv = coverUrl(so.coverArt, 200);
+ const cv = coverUrl(so.coverArt, 256);
const c = makeCard(so.title, so.artist||'', cv, 'π΅');
c.querySelector('.play-btn').onclick = (e) => { e.stopPropagation(); playQueue(songs, i); };
c.onclick = () => playQueue(songs, i);