fix: startup speed, image retry, shuffle random start, genres, search badge, playlist refresh
- Remove await on fetchRomajiIndex() at startup so navigation is not blocked by the romaji index network call (was the main cause of 30s Android startup) - Add window._imgRetry() with 3 retries (1.5s/3s/4.5s backoff) for all card/ song/cover images that fail to load; falls back to emoji only after exhausting retries - playQueue with HAS_BRIDGE + shuffle: pick a random startIdx so album/playlist playback starts at a random track, not always index 0 - Reduce mobile genre grid limit from 12 to 6 - Fix "Global Suche" badge text: was "iTunes Store + Local", now "iTunes Store" - deleteSong: always refresh loadPlaylists() unconditionally (not just when page is active) with a 3s delay so song counts are current when navigating back Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
6dc47a2c77
commit
1f1a5cd3a6
6 files changed
+26
-13
No files matched your search
+1
-1
@@ -315,7 +315,7 @@
|
||||
<div class="page" id="page-discover">
|
||||
<div class="page-header">
|
||||
<div class="page-title"><span class="blue">Global</span> Suche</div>
|
||||
<span class="badge badge-itunes" style="font-size:11px;padding:4px 10px">iTunes Store + Local</span>
|
||||
<span class="badge badge-itunes" style="font-size:11px;padding:4px 10px">iTunes Store</span>
|
||||
</div>
|
||||
<div class="search-wrap" style="padding:0 0 20px">
|
||||
<div class="search-box" style="background:var(--surface);border-color:var(--border2)">
|
||||
|
||||
+10
-2
@@ -1,5 +1,13 @@
|
||||
import { S } from './state.js';
|
||||
import { esc, formatDur } from './utils.js';
|
||||
|
||||
// Exposed on window so inline onerror= handlers can call it.
|
||||
// Retries the same src up to 3 times with increasing delays, then shows fallback.
|
||||
window._imgRetry = function(img, fallback) {
|
||||
const n = (img._r = (img._r || 0) + 1);
|
||||
if (n > 3 || !img.src) { img.parentNode.innerHTML = `<div class="no-art">${fallback}</div>`; return; }
|
||||
setTimeout(() => { const s = img.src; img.src = ''; img.src = s; }, 1500 * n);
|
||||
};
|
||||
import { coverUrl, dispTitle, dispArtistParts, dispAlbum } from './api.js';
|
||||
// Circular imports — safe (only used inside event handlers after module init)
|
||||
import { playQueue, toggleStar, playPreview, parseArtists } from './player.js';
|
||||
@@ -11,7 +19,7 @@ export function makeCard(title, sub, imgUrl, fallback='💿') {
|
||||
const c = document.createElement('div');
|
||||
c.className = 'card';
|
||||
c.innerHTML = `<div class="card-art">
|
||||
${imgUrl ? `<img src="${imgUrl}" loading="lazy" onerror="this.parentNode.innerHTML='<div class=\\'no-art\\'>${fallback}</div>'">` : `<div class="no-art">${fallback}</div>`}
|
||||
${imgUrl ? `<img src="${imgUrl}" loading="lazy" onerror="_imgRetry(this,'${fallback}')">` : `<div class="no-art">${fallback}</div>`}
|
||||
<div class="play-overlay"><button class="play-btn"><svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg></button></div>
|
||||
</div>
|
||||
<div class="card-info"><div class="card-title">${esc(title)}</div><div class="card-sub">${esc(sub||'')}</div></div>`;
|
||||
@@ -45,7 +53,7 @@ export function makeSongItem(s, idx, list, { noArtistLink = false } = {}) {
|
||||
? `<span class="song-plays">${s.playCount ? s.playCount.toLocaleString() + '<span class="song-plays-x">×</span>' : '—'}</span>`
|
||||
: '';
|
||||
el.innerHTML = `<div class="song-num">${idx+1}</div>
|
||||
<div class="song-thumb">${cv ? `<img src="${cv}" loading="lazy" onerror="this.parentNode.innerHTML='<div class=\\'no-art\\'>♪</div>'">` : '<div class="no-art">♪</div>'}</div>
|
||||
<div class="song-thumb">${cv ? `<img src="${cv}" loading="lazy" onerror="_imgRetry(this,'♪')">` : '<div class="no-art">♪</div>'}</div>
|
||||
<div class="song-info">
|
||||
<div class="song-title">${esc(dispTitle(s))}</div>
|
||||
<div class="song-artist">${artistHtml}</div>
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ async function startApp() {
|
||||
if (bnav) bnav.style.display = '';
|
||||
absLoadSettingsLibraries();
|
||||
}
|
||||
if (S.cfg.showRomaji) await fetchRomajiIndex().catch(() => {});
|
||||
if (S.cfg.showRomaji) fetchRomajiIndex().catch(() => {}); // fire-and-forget — don't block navigation
|
||||
if(_navLoad() && S._navCursor >= 0 && S._navStack[S._navCursor]) {
|
||||
const last = S._navStack[S._navCursor];
|
||||
S._navJumping = true;
|
||||
|
||||
@@ -359,6 +359,11 @@ export function playQueue(songs, startIdx) {
|
||||
_radioFilling = false; // cancel any in-flight radio fill for the old queue
|
||||
S._origQueue = null;
|
||||
_histClear();
|
||||
// On Android with shuffle on, start at a random position so we don't always
|
||||
// begin at index 0 (the first album/playlist track).
|
||||
if (HAS_BRIDGE && S.shuffle && songs.length > 1) {
|
||||
startIdx = Math.floor(Math.random() * songs.length);
|
||||
}
|
||||
if (!HAS_BRIDGE && S.shuffle && songs.length > 1) {
|
||||
const start = songs[startIdx];
|
||||
const rest = songs.filter((_, i) => i !== startIdx).sort(() => Math.random() - 0.5);
|
||||
|
||||
@@ -536,13 +536,13 @@ export async function deleteSong() {
|
||||
if (!r.ok) throw new Error(d.error || `HTTP ${r.status}`);
|
||||
toast(`🗑 "${s.title}" gelöscht`);
|
||||
closeInfoModal();
|
||||
// Refresh playlist views after Navidrome's background scan has a chance to finish
|
||||
// Refresh playlist data after Navidrome's background scan has a chance to finish.
|
||||
// Always reload playlists (even if not active) so counts are fresh when navigating back.
|
||||
setTimeout(() => {
|
||||
if (document.getElementById('page-playlists')?.classList.contains('active'))
|
||||
loadPlaylists();
|
||||
else if (document.getElementById('page-playlist-detail')?.classList.contains('active') && S._currentPlaylist)
|
||||
loadPlaylists();
|
||||
if (document.getElementById('page-playlist-detail')?.classList.contains('active') && S._currentPlaylist)
|
||||
loadPlaylistDetail(S._currentPlaylist);
|
||||
}, 2000);
|
||||
}, 3000);
|
||||
} catch (e) {
|
||||
status.textContent = '❌ Fehler: ' + e.message;
|
||||
}
|
||||
|
||||
+4
-4
@@ -180,7 +180,7 @@ function _genreColor(name) {
|
||||
|
||||
let _allGenres = [];
|
||||
let _genresExpanded = false;
|
||||
const GENRE_LIMIT = () => window.innerWidth >= 700 ? 24 : 12;
|
||||
const GENRE_LIMIT = () => window.innerWidth >= 700 ? 24 : 6;
|
||||
|
||||
async function loadGenreSection() {
|
||||
const el = document.getElementById('home-genres');
|
||||
@@ -631,7 +631,7 @@ export async function loadItunesAlbumDetail(album) {
|
||||
document.getElementById('itunes-album-detail-artist').textContent = album.artistName || '';
|
||||
const cw = document.getElementById('itunes-album-cover-wrap');
|
||||
const art = (album.artworkUrl100 || '').replace('100x100bb','600x600bb').replace('100x100','600x600');
|
||||
cw.innerHTML = art ? `<img src="${art}" onerror="this.parentNode.innerHTML='<div class=\\'no-art\\'>💿</div>'">` : '<div class="no-art">💿</div>';
|
||||
cw.innerHTML = art ? `<img src="${art}" onerror="_imgRetry(this,'💿')">` : '<div class="no-art">💿</div>';
|
||||
const dlBtn = document.getElementById('itunes-album-dl-btn');
|
||||
dlBtn.onclick = () => openDlModal({ artistName: album.artistName, trackName: album.collectionName, collectionName: album.collectionName, artworkUrl100: album.artworkUrl100||'', isAlbum: true, itunesData: album, collectionId: album.collectionId });
|
||||
const sl = document.getElementById('itunes-album-songs');
|
||||
@@ -657,7 +657,7 @@ export async function loadAlbumDetail(album) {
|
||||
.join('<span style="color:var(--text3)"> • </span>') || '';
|
||||
const cw = document.getElementById('album-cover-wrap');
|
||||
const cv = coverUrl(album.coverArt, 512);
|
||||
cw.innerHTML = cv ? `<img src="${cv}" onerror="this.parentNode.innerHTML='<div class=\\'no-art\\'>💿</div>'">` : '<div class="no-art">💿</div>';
|
||||
cw.innerHTML = cv ? `<img src="${cv}" onerror="_imgRetry(this,'💿')">` : '<div class="no-art">💿</div>';
|
||||
const sl = document.getElementById('album-songs');
|
||||
sl.innerHTML = '<div class="spinner"></div>';
|
||||
try {
|
||||
@@ -717,7 +717,7 @@ export async function loadPlaylistDetail(pl) {
|
||||
document.getElementById('playlist-detail-name').textContent = pl.name;
|
||||
const cw = document.getElementById('playlist-cover');
|
||||
const cv = coverUrl(pl.coverArt, 512);
|
||||
cw.innerHTML = cv ? `<img src="${cv}" onerror="this.parentNode.innerHTML='<div class=\\'no-art\\'>🎶</div>'">` : '<div class="no-art">🎶</div>';
|
||||
cw.innerHTML = cv ? `<img src="${cv}" onerror="_imgRetry(this,'🎶')">` : '<div class="no-art">🎶</div>';
|
||||
document.getElementById('btn-edit-playlist').onclick = () => openEditPlModal(S._currentPlaylist);
|
||||
const sl = document.getElementById('playlist-songs');
|
||||
sl.innerHTML = '<div class="spinner"></div>';
|
||||
|
||||
Reference in new issue
Block a user