global player pt. 8 preload stuff
This commit is contained in:
@@ -26,6 +26,12 @@
|
||||
|
||||
let audioEl: HTMLAudioElement | null = null;
|
||||
|
||||
// Best-effort preload of the upcoming track's URL
|
||||
let nextPreloadHref = $state<string | null>(null);
|
||||
|
||||
// Dedicated preloader element to warm the connection / decode pipeline a bit
|
||||
let preloadEl = $state<HTMLAudioElement | null>(null);
|
||||
|
||||
let isPlaying = $state(false);
|
||||
let currentTime = $state(0);
|
||||
let duration = $state(0);
|
||||
@@ -95,6 +101,41 @@
|
||||
audioEl.volume = snap.volume;
|
||||
}
|
||||
|
||||
function computeNextTrackToPreload(): Track | null {
|
||||
// Prefer to preload the "linear next" item. For shuffle mode, we can still
|
||||
// best-effort preload the first item in the upcoming order.
|
||||
if (snap.queue.length === 0) return null;
|
||||
|
||||
if (snap.shuffleEnabled) {
|
||||
const nextIdx = snap.order[0];
|
||||
if (typeof nextIdx === "number") return snap.queue[nextIdx] ?? null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (snap.currentIndex == null) return snap.queue[0] ?? null;
|
||||
const nextIdx = snap.currentIndex + 1;
|
||||
return nextIdx >= 0 && nextIdx < snap.queue.length
|
||||
? snap.queue[nextIdx]
|
||||
: null;
|
||||
}
|
||||
|
||||
function updatePreloadTargets() {
|
||||
const nextTrack = computeNextTrackToPreload();
|
||||
nextPreloadHref = nextTrack?.src ?? null;
|
||||
|
||||
// Also warm via an offscreen <audio> element. This may or may not help depending
|
||||
// on browser caching policies, but it's safe and often reduces first-buffer delay.
|
||||
if (!preloadEl) return;
|
||||
|
||||
if (nextTrack?.src) {
|
||||
preloadEl.src = nextTrack.src;
|
||||
preloadEl.load();
|
||||
} else {
|
||||
preloadEl.removeAttribute("src");
|
||||
preloadEl.load();
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForEvent(el: HTMLMediaElement, eventName: string) {
|
||||
await new Promise<void>((resolve) => {
|
||||
const onEvent = () => {
|
||||
@@ -114,35 +155,25 @@
|
||||
|
||||
syncAudioToCurrentTrack();
|
||||
|
||||
// If the src changed, some browsers require a "load -> playing" sequence.
|
||||
// Strategy:
|
||||
// - if src changed: ensure load is kicked, then wait for loadedmetadata/canplay
|
||||
// - call play()
|
||||
// - if it's still paused, wait for 'playing' and retry play once
|
||||
const afterSrc = el.currentSrc;
|
||||
|
||||
if (afterSrc !== beforeSrc) {
|
||||
// Ensure metadata exists (duration available, etc.)
|
||||
if (el.readyState < 1) {
|
||||
await waitForEvent(el, "loadedmetadata");
|
||||
}
|
||||
// Ensure enough data to play.
|
||||
if (el.readyState < 3) {
|
||||
await waitForEvent(el, "canplay");
|
||||
}
|
||||
} else {
|
||||
// Even if src didn't change, allow a minimal yield to let state settle.
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
try {
|
||||
await el.play();
|
||||
} catch {
|
||||
// Autoplay may be blocked; bail out quietly.
|
||||
return;
|
||||
}
|
||||
|
||||
// If the browser didn't actually start playback, retry after 'canplay'/'playing'.
|
||||
if (el.paused) {
|
||||
if (el.readyState < 3) {
|
||||
await waitForEvent(el, "canplay");
|
||||
@@ -218,11 +249,11 @@
|
||||
|
||||
$effect(() => {
|
||||
media.setTrack(snap.currentTrack);
|
||||
|
||||
// Persist queue/settings/UI state (throttled) from within a component-scoped effect
|
||||
// to avoid orphaned module-level `$effect`.
|
||||
schedulePersistNow();
|
||||
|
||||
// Keep preload targets updated as the queue/traversal state changes
|
||||
updatePreloadTargets();
|
||||
|
||||
if (!audioEl) return;
|
||||
|
||||
audioEl.volume = snap.volume;
|
||||
@@ -235,6 +266,13 @@
|
||||
window.addEventListener("resize", updateIsMobile);
|
||||
}
|
||||
|
||||
// Create the offscreen preloader audio element in the browser
|
||||
if (browser) {
|
||||
preloadEl = new Audio();
|
||||
preloadEl.preload = "auto";
|
||||
preloadEl.muted = true;
|
||||
}
|
||||
|
||||
// Subscribe to player changes instead of polling
|
||||
unsubscribe = subscribe((s) => {
|
||||
snap = s;
|
||||
@@ -252,6 +290,13 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<!-- Preload the next track (best-effort). We set this href reactively below. -->
|
||||
{#if nextPreloadHref}
|
||||
<link rel="preload" as="audio" href={nextPreloadHref} />
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
{#if isMobile}
|
||||
<!-- Mobile: mini bar + expandable drawer -->
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user