A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: queue improvements

+67 -40
+45 -14
src/components/engine/queue/worker.js
··· 197 197 if (p.length === 0) return; 198 198 199 199 const n = $now.value; 200 - const [last] = p.splice(p.length - 1, 1); 200 + const last = p[p.length - 1]; 201 201 202 + $past.value = p.slice(0, p.length - 1); 202 203 $now.value = last ?? null; 203 204 if (n) $future.value = [n, ...$future.value]; 204 205 } ··· 392 393 * 393 394 * if (result.length !== 4) throw new Error("expected 4 total items (2 existing + 2 new)"); 394 395 * ``` 396 + * 397 + * @example Does not add tracks that have already played or are now playing 398 + * ```js 399 + * import { fillShuffle, $lake, $past, $now } from "~/components/engine/queue/worker.js"; 400 + * 401 + * $lake.value = ["a", "b", "c", "d"]; 402 + * $past.value = [{ id: "a", manualEntry: false }]; 403 + * $now.value = { id: "b", manualEntry: false }; 404 + * 405 + * const result = fillShuffle(4, [], 0); 406 + * 407 + * if (result.some((i) => i.id === "a" || i.id === "b")) throw new Error("past and now tracks should be excluded"); 408 + * ``` 409 + * 410 + * @example Falls back to full lake when everything has been played 411 + * ```js 412 + * import { fillShuffle, $lake, $past } from "~/components/engine/queue/worker.js"; 413 + * 414 + * $lake.value = ["a", "b"]; 415 + * $past.value = [{ id: "a", manualEntry: false }, { id: "b", manualEntry: false }]; 416 + * 417 + * const result = fillShuffle(2, [], 0); 418 + * 419 + * if (result.length !== 2) throw new Error("expected 2 items from full lake fallback"); 420 + * ``` 395 421 */ 396 422 export function fillShuffle(fillAmount, future, autoFutureCount) { 397 - // Determine pool of available queue items 398 - /** @type {Item[]} */ 399 - const pool = []; 423 + const excludeIds = new Set($past.value.map((i) => i.id)); 424 + if ($now.value) excludeIds.add($now.value.id); 425 + future.forEach((i) => excludeIds.add(i.id)); 400 426 401 - const pastSet = new Set($past.value.map((i) => i.id)); 402 - let reducedPool = pool; 427 + let pool = $lake.value 428 + .filter((id) => !excludeIds.has(id)) 429 + .map((id) => ({ id, manualEntry: false })); 403 430 404 - $lake.value.forEach((id) => { 405 - if (pastSet.delete(id) === false) { 406 - pool.push({ id, manualEntry: false }); 407 - } 408 - }); 431 + // Fallback: if everything has been played/is playing/is queued, use tracks not in past or now 432 + if (pool.length === 0) { 433 + const pastAndNowIds = new Set($past.value.map((i) => i.id)); 434 + if ($now.value) pastAndNowIds.add($now.value.id); 435 + pool = $lake.value 436 + .filter((id) => !pastAndNowIds.has(id)) 437 + .map((id) => ({ id, manualEntry: false })); 438 + } 409 439 410 - if (reducedPool.length === 0) { 411 - reducedPool = $lake.value.map((id) => ({ id, manualEntry: false })); 440 + // Final fallback: everything has been played, use the full lake 441 + if (pool.length === 0) { 442 + pool = $lake.value.map((id) => ({ id, manualEntry: false })); 412 443 } 413 444 414 - const poolSelection = arrayShuffle(reducedPool).slice( 445 + const poolSelection = arrayShuffle(pool).slice( 415 446 0, 416 447 Math.max(0, fillAmount - autoFutureCount), 417 448 );
+22 -26
src/themes/winamp/winamp/element.js
··· 195 195 /** @type {ReturnType<typeof setInterval> | undefined} */ 196 196 #marqueeStepInterval = undefined; 197 197 #marqueeText = signal(""); 198 - #selectedTrackId = signal(/** @type {string | null} */ (null)); 198 + #selectedIndex = signal(/** @type {number | null} */ (null)); 199 199 #mainOpen = signal(true); 200 200 #eqOpen = signal(true); 201 201 #mainShade = signal(false); ··· 1360 1360 ); 1361 1361 }; 1362 1362 1363 - /** @param {string} id */ 1364 - #selectTrack = (id) => { 1365 - this.#selectedTrackId.value = id; 1363 + /** @param {number} idx */ 1364 + #selectTrack = (idx) => { 1365 + this.#selectedIndex.value = idx; 1366 1366 }; 1367 1367 1368 - /** @param {string} id */ 1369 - #playTrack = (id) => { 1370 - this.#selectedTrackId.value = id; 1368 + /** @param {number} idx */ 1369 + #playTrack = (idx) => { 1370 + this.#selectedIndex.value = idx; 1371 1371 const queue = this.$queue.value; 1372 1372 if (!queue) return; 1373 1373 const past = queue.past(); 1374 - const now = queue.now(); 1375 - const future = queue.future(); 1376 - if (now?.id === id) return; 1377 - const pastIdx = past.findIndex((i) => i.id === id); 1378 - if (pastIdx !== -1) { 1379 - const stepsBack = past.length - pastIdx; 1374 + const pastLen = past.length; 1375 + if (idx === pastLen) return; 1376 + if (idx < pastLen) { 1377 + const stepsBack = pastLen - idx; 1380 1378 for (let i = 0; i < stepsBack; i++) queue.unshift(); 1381 1379 return; 1382 1380 } 1383 - const futureIdx = future.findIndex((i) => i.id === id); 1384 - if (futureIdx !== -1) { 1385 - const stepsForward = futureIdx + 1; 1386 - for (let i = 0; i < stepsForward; i++) queue.shift(); 1387 - } 1381 + const stepsForward = idx - pastLen; 1382 + for (let i = 0; i < stepsForward; i++) queue.shift(); 1388 1383 }; 1389 1384 1390 1385 // RENDER ··· 1472 1467 const trackMap = col?.state === "loaded" 1473 1468 ? new Map(col.data.map((t) => [t.id, t])) 1474 1469 : new Map(); 1475 - const selectedId = this.#selectedTrackId.value; 1470 + const selectedIdx = this.#selectedIndex.value; 1471 + const nowIdx = nowItem ? (queueEl?.past().length ?? 0) : -1; 1476 1472 const playlistRows = allItems.map((item, i) => { 1477 1473 const track = trackMap.get(item.id); 1478 - const isCurrent = nowItem?.id === item.id; 1479 - const isSelected = selectedId === item.id; 1474 + const isCurrent = i === nowIdx; 1475 + const isSelected = selectedIdx === i; 1480 1476 const artist = track?.tags?.artist ?? ""; 1481 1477 const title = track?.tags?.title ?? ""; 1482 1478 const label = artist ? `${artist} - ${title}` : title; ··· 1488 1484 : ""; 1489 1485 const color = isCurrent ? "#FFFFFF" : "#00FF00"; 1490 1486 const bg = isSelected && !isCurrent ? "#0000FF" : "transparent"; 1491 - return { id: item.id, n: i + 1, label, dur, color, bg }; 1487 + return { idx: i, n: i + 1, label, dur, color, bg }; 1492 1488 }); 1493 1489 1494 1490 // Playlist running time display: currentTrackDuration/totalPlaylistDuration ··· 1776 1772 <div 1777 1773 class="track-cell" 1778 1774 style="color: ${r.color}; background-color: ${r.bg};" 1779 - @click="${() => this.#selectTrack(r.id)}" 1780 - @dblclick="${() => this.#playTrack(r.id)}" 1775 + @click="${() => this.#selectTrack(r.idx)}" 1776 + @dblclick="${() => this.#playTrack(r.idx)}" 1781 1777 > 1782 1778 ${r.n}. ${r.label} 1783 1779 </div> ··· 1790 1786 <div 1791 1787 class="track-cell" 1792 1788 style="color: ${r.color}; background-color: ${r.bg};" 1793 - @click="${() => this.#selectTrack(r.id)}" 1794 - @dblclick="${() => this.#playTrack(r.id)}" 1789 + @click="${() => this.#selectTrack(r.idx)}" 1790 + @dblclick="${() => this.#playTrack(r.idx)}" 1795 1791 > 1796 1792 ${r.dur} 1797 1793 </div>