Experiment to rebuild Diffuse using web applets.

feat: various artwork improvements

+61 -26
+4 -16
src/pages/constituent/blur/artwork-controller/_applet.astro
··· 63 63 left: 0; 64 64 object-fit: cover; 65 65 opacity: 0; 66 - pointer-events: none; 67 66 position: absolute; 68 67 top: 0; 69 68 transition-duration: var(--transition-durition); ··· 150 149 cite { 151 150 display: block; 152 151 font-style: normal; 153 - line-height: var(--leading-snug); 154 152 text-shadow: var(--text-shadow-sm); 155 153 } 156 154 ··· 482 480 483 481 reactive( 484 482 engine.queue, 485 - (data) => comparable(data.future), 486 - () => { 487 - const track = engine.queue.data.now; 488 - 489 - if (!track) { 490 - setActiveTrack(undefined); 491 - return; 492 - } 493 - 494 - setActiveTrack(track); 495 - }, 483 + (data) => data.now, 484 + (track) => setActiveTrack(track || undefined), 496 485 ); 497 486 498 487 // Changed artwork based on active queue item. 499 488 // (debounced) 500 489 501 - reactive(engine.queue, (data) => comparable(data.future), debounce(2000, changeArtwork)); 490 + reactive(engine.queue, (data) => data.now, debounce(2000, changeArtwork)); 502 491 503 492 async function changeArtwork() { 504 493 const track = engine.queue.data.now; ··· 595 584 const color = fac.getColor(img as HTMLImageElement); 596 585 const rgb = color.value; 597 586 const o = Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000); 598 - console.log(o); 599 587 600 588 setArtworkColor(color.rgba); 601 589 setArtworkLightMode(o > 165); ··· 657 645 computed( 658 646 () => 659 647 activeTrack()?.tags?.artist || 660 - (isMainGroup() && !activeTrack() ? "Waiting for tracks to be queued ..." : ""), 648 + (isMainGroup() && !activeTrack() ? "Waiting on queue ..." : ""), 661 649 ), 662 650 ), 663 651 ),
+10 -2
src/pages/orchestrator/queue-audio/_applet.astro
··· 60 60 const activeTrack = engine.queue.data.now; 61 61 const isPlaying = engine.audio.data.isPlaying; 62 62 63 + // Resolve URIs 64 + const url = activeTrack 65 + ? await inputUrl(configurator.input, activeTrack.uri).then((a) => a?.url) 66 + : undefined; 67 + 68 + // Check if we still need to render 69 + if (engine.queue.data.now?.id !== activeTrack?.id) return; 70 + 63 71 // Play new active queue item 64 72 // TODO: Take URL expiration timestamp into account 65 73 // TODO: Preload next queue item ··· 71 79 { 72 80 id: activeTrack.id, 73 81 isPreload: false, 74 - url: await inputUrl(configurator.input, activeTrack.uri).then((a) => a?.url), 82 + url, 75 83 }, 76 84 ] 77 - : // TODO: This probably isn't correct, keep preloads? 85 + : // TODO: Keep preloads 78 86 [], 79 87 play: activeTrack && isPlaying ? { audioId: activeTrack.id } : undefined, 80 88 },
+1
src/scripts/processor/artwork/types.d.ts
··· 11 11 stream?: ReadableStream; 12 12 tags?: Tags; 13 13 urls?: Urls; 14 + variousArtists?: boolean; 14 15 }; 15 16 16 17 // export type State = {
+46 -8
src/scripts/processor/artwork/worker.ts
··· 35 35 //////////////////////////////////////////// 36 36 // 🛠️ 37 37 //////////////////////////////////////////// 38 + function escapeLucene(str: string) { 39 + return [].map 40 + .call(str, (char) => { 41 + if ( 42 + char === "+" || 43 + char === "-" || 44 + char === "&" || 45 + char === "|" || 46 + char === "!" || 47 + char === "(" || 48 + char === ")" || 49 + char === "{" || 50 + char === "}" || 51 + char === "[" || 52 + char === "]" || 53 + char === "^" || 54 + char === '"' || 55 + char === "~" || 56 + char === "*" || 57 + char === "?" || 58 + char === ":" || 59 + char === "\\" || 60 + char === "/" 61 + ) 62 + return "\\" + char; 63 + else return char; 64 + }) 65 + .join(""); 66 + } 67 + 38 68 async function lastFm(req: ArtworkRequest): Promise<Artwork[]> { 39 69 if (!navigator.onLine) return []; 40 70 ··· 77 107 if (!navigator.onLine) return []; 78 108 if (!album && !artist) return []; 79 109 80 - // TODO 81 - const variousArtists = false; 82 - 83 - const query = `release:"${album}"` + (variousArtists ? `` : ` AND artist:"${artist}"`); 110 + const query = 111 + `release:"${escapeLucene(album || "")}"` + 112 + (req.variousArtists ? `` : ` AND artistname:"${escapeLucene(artist || "")}"`); 84 113 const encodedQuery = encodeURIComponent(query); 85 114 86 115 return await fetch(`https://musicbrainz.org/ws/2/release/?query=${encodedQuery}&fmt=json`) 87 116 .then((r) => r.json()) 88 - .then((r) => musicBrainzCover(r.releases)) 117 + .then((r) => { 118 + if (r.releases.length === 0 && !req.variousArtists) { 119 + return musicBrainz({ ...req, variousArtists: true }); 120 + } else { 121 + return musicBrainzCover(r.releases, req); 122 + } 123 + }) 89 124 .catch((err) => { 90 125 console.error(err); 91 126 return []; 92 127 }); 93 128 } 94 129 95 - async function musicBrainzCover(remainingReleases: any[]): Promise<Artwork[]> { 130 + async function musicBrainzCover(remainingReleases: any[], req: ArtworkRequest): Promise<Artwork[]> { 96 131 const release = remainingReleases[0]; 97 132 if (!release) return []; 98 133 134 + const credit = release?.["artist-credit"]?.[0]?.name; 135 + if (req.variousArtists && credit !== "Various Artists" && credit !== req.tags?.artist) return []; 136 + 99 137 return await fetch(`https://coverartarchive.org/release/${release.id}/front-500`) 100 138 .then((r) => r.blob()) 101 139 .then(async (b) => { 102 140 if (b.type.startsWith("image/")) { 103 141 return [{ bytes: await b.arrayBuffer().then((buf) => new Uint8Array(buf)), mime: b.type }]; 104 142 } else { 105 - return musicBrainzCover(remainingReleases.slice(1)); 143 + return musicBrainzCover(remainingReleases.slice(1), req); 106 144 } 107 145 }) 108 146 .catch((err) => { 109 147 console.error(err); 110 - return musicBrainzCover(remainingReleases.slice(1)); 148 + return musicBrainzCover(remainingReleases.slice(1), req); 111 149 }); 112 150 } 113 151