diff --git a/api/api.go b/api/api.go index 07f4aab..eece652 100644 --- a/api/api.go +++ b/api/api.go @@ -690,6 +690,7 @@ type TrackInfo struct { PlayCount int `json:"play_count,omitempty"` Tags []string `json:"tags,omitempty"` ArtID uint32 `json:"art_id,omitempty"` + ArtChecked bool `json:"art_checked,omitempty"` // false with art_id 0 = never probed; the web UI requests art optimistically to trigger lazy extraction FileMissing bool `json:"file_missing,omitempty"` } @@ -744,6 +745,7 @@ func (s *Server) libTrackToInfo(t *library.Track) TrackInfo { ColorName: trackColorNames[t.ColorID], PlayCount: t.PlayCount, ArtID: t.ArtID, + ArtChecked: t.ArtChecked, FileMissing: t.FileMissing, } if !t.DateAdded.IsZero() { diff --git a/api/web/index.html b/api/web/index.html index 0291e44..28d251e 100644 --- a/api/web/index.html +++ b/api/web/index.html @@ -5586,12 +5586,32 @@ } // `sort` is the accessor used by the comparator. `sortable: false` opts a // column out of header-click sorting (e.g. the waveform thumbnail). +// artHTML renders a track's artwork . The request is made optimistically +// for tracks whose file has never been probed (art_id 0 with art_checked +// unset): GET /api/artwork/{id} is what triggers the server's lazy ffmpeg +// extraction, so the first view of a newly added track pulls the art out of +// the file instead of waiting for a CDJ load to analyze it. Tracks already +// probed and found artless (art_checked true) go straight to the placeholder, +// so there is no repeated 404 traffic; onerror degrades to the placeholder +// for the artless-probe and concurrent-probe cases. +function artHTML(t, imgClass, fbClass, fbText) { + if (t.art_id > 0 || !t.art_checked) { + return ``; + } + return `
${fbText}
`; +} +function artFallback(img) { + const d = document.createElement('div'); + d.className = img.getAttribute('data-fb-class') || 'lib-art empty'; + d.textContent = img.getAttribute('data-fb-text') || ''; + img.replaceWith(d); +} + const LIBRARY_COLUMNS = [ { key: 'art', label: 'ART', default: false, thClass: '', tdClass: 'art-cell', sortable: false, - cell: t => t.art_id > 0 - ? `` - : `
` }, + cell: t => artHTML(t, 'lib-art', 'lib-art empty', '') }, { key: 'waveform', label: 'WV', default: true, thClass: '', tdClass: 'wave-cell', sortable: false, cell: t => `
${cueMarkerOverlay(t.id, t.duration)}
` }, @@ -6191,9 +6211,7 @@ mobileMQ.addEventListener('change', placeMobileSidebar); function libCardHtml(t) { - const art = t.art_id > 0 - ? `` - : `
`; + const art = artHTML(t, 'lib-art', 'lib-art placeholder', '♪'); const facts = []; if (t.bpm) facts.push((+t.bpm).toFixed(1)); if (t.key) facts.push(t.key); @@ -7654,9 +7672,7 @@ // Tags section is re-rendered into
after mutations. const tagsHTML = '
'; - const detailArt = t.art_id > 0 - ? `` - : `
`; + const detailArt = artHTML(t, 'detail-art', 'detail-art empty', ''); document.getElementById('detail-body').innerHTML = `
${detailArt}