diff --git a/src/mediaPreload.ts b/src/mediaPreload.ts new file mode 100644 index 0000000..a95ab7a --- /dev/null +++ b/src/mediaPreload.ts @@ -0,0 +1,53 @@ +import type { MediaConfig } from './mediaConfig'; +import { preloadAudioAssets } from './mediaSounds'; +import { ARTIFACTS_BASE_URL } from './puzzle2Config'; + +const preloadedUrls = new Set(); + +function preloadOnce(url: string, load: () => void): void { + if (preloadedUrls.has(url)) return; + preloadedUrls.add(url); + load(); +} + +export function preloadImage(url: string): void { + preloadOnce(url, () => { + const img = new Image(); + img.src = url; + }); +} + +export function preloadVideo(url: string): void { + preloadOnce(url, () => { + const video = document.createElement('video'); + video.preload = 'auto'; + video.src = url; + video.load(); + }); +} + +/** Warm cache for config-driven audio, images, and video. */ +export function preloadMediaAssets(config: MediaConfig): void { + preloadAudioAssets(config); + preloadImage(config.mapImageUrl); + preloadImage(config.tedCelebrateImageUrl); + preloadVideo(config.unlockVideoUrl); +} + +export function preloadArtifactImages(entries: readonly { file: string }[]): void { + for (const entry of entries) { + preloadImage(`${ARTIFACTS_BASE_URL}/${encodeURIComponent(entry.file)}`); + } +} + +/** Fetch artifact manifest and preload all puzzle-2 thumbnails. */ +export async function preloadArtifactManifest(): Promise { + try { + const res = await fetch(`${ARTIFACTS_BASE_URL}/manifest.json`); + if (!res.ok) return; + const data = (await res.json()) as { file: string }[]; + preloadArtifactImages(data); + } catch { + /* manifest unavailable until setup script runs */ + } +} diff --git a/src/useArtifactManifest.ts b/src/useArtifactManifest.ts index fe0ae70..9108f45 100644 --- a/src/useArtifactManifest.ts +++ b/src/useArtifactManifest.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { preloadArtifactImages } from './mediaPreload'; import { ARTIFACTS_BASE_URL } from './puzzle2Config'; export interface ArtifactEntry { @@ -19,6 +20,7 @@ export function useArtifactManifest(): ArtifactEntry[] | null { .then((res) => (res.ok ? res.json() : null)) .then((data: ArtifactEntry[] | null) => { if (cancelled || !data) return; + preloadArtifactImages(data); setManifest(data); }) .catch(() => { diff --git a/src/useMediaConfig.ts b/src/useMediaConfig.ts index 6abf766..d603032 100644 --- a/src/useMediaConfig.ts +++ b/src/useMediaConfig.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { DEFAULT_MEDIA_CONFIG, mergeMediaConfig, type MediaConfig } from './mediaConfig'; -import { preloadAudioAssets } from './mediaSounds'; +import { preloadArtifactManifest, preloadMediaAssets } from './mediaPreload'; export function useMediaConfig(): MediaConfig { const [config, setConfig] = useState(DEFAULT_MEDIA_CONFIG); @@ -24,7 +24,8 @@ export function useMediaConfig(): MediaConfig { }, []); useEffect(() => { - preloadAudioAssets(config); + preloadMediaAssets(config); + void preloadArtifactManifest(); }, [config]); return config;