"use client"; import { useEffect, useRef, useState } from "react"; /* Frutiger Aero soundtrack: a random track from the "Frutiger Aero" playlist on SoundCloud. A hidden SoundCloud iframe is driven through their Widget API so the playlist autoplays, jumps to a random track and re-randomises whenever playback moves on; a small glass chip shows what's playing and gives the visitor a play/pause control. The iframe is only mounted by `WebLayout` while aero mode is active, so unmounting it stops the music. */ const PLAYLIST_URL = "https://soundcloud.com/rustie-gamer/sets/frutiger-aero"; const API_SRC = "https://w.soundcloud.com/player/api.js"; const VOLUME = 35; /* Ignore PLAY events for this long after we pick a track: a `skip()` can still report the previous index for a moment, which would otherwise bounce us into an endless re-pick. */ const PICK_GUARD_MS = 3000; type SoundCloudSound = { title?: string; user?: { username?: string }; }; type SoundCloudWidget = { play: () => void; pause: () => void; skip: (index: number) => void; setVolume: (volume: number) => void; getSounds: (callback: (sounds: SoundCloudSound[]) => void) => void; getCurrentSound: (callback: (sound: SoundCloudSound | null) => void) => void; getCurrentSoundIndex: (callback: (index: number) => void) => void; bind: (event: string, listener: () => void) => void; }; type SoundCloudGlobal = { Widget: ((element: HTMLIFrameElement) => SoundCloudWidget) & { Events: { READY: string; PLAY: string; FINISH: string }; }; }; type Track = { title: string; artist: string }; const PLACEHOLDER: Track = { title: "Frutiger Aero", artist: "SoundCloud playlist" }; declare global { interface Window { SC?: SoundCloudGlobal; } } const WIDGET_SRC = "https://w.soundcloud.com/player/?url=" + encodeURIComponent(PLAYLIST_URL) + "&color=%2364e3ff&auto_play=true&hide_related=true&show_comments=false" + "&show_user=false&show_reposts=false&show_teaser=false&visual=false"; let apiPromise: Promise | null = null; /** Loads SoundCloud's Widget API once; later mounts reuse the same promise. */ function loadWidgetApi(): Promise { if (window.SC) return Promise.resolve(window.SC); apiPromise ??= new Promise((resolve, reject) => { const script = document.createElement("script"); script.dataset.soundcloudApi = ""; script.src = API_SRC; script.async = true; script.addEventListener("load", () => { if (window.SC) resolve(window.SC); else reject(new Error("SoundCloud widget API did not initialise")); }); script.addEventListener("error", () => reject(new Error("SoundCloud widget API failed to load")), ); document.head.appendChild(script); }); return apiPromise; } function toTrack(sound: SoundCloudSound | null | undefined): Track { if (!sound) return PLACEHOLDER; return { title: sound.title || PLACEHOLDER.title, artist: sound.user?.username || "SoundCloud", }; } export default function AeroMusic() { const frameRef = useRef(null); const widgetRef = useRef(null); const [track, setTrack] = useState(PLACEHOLDER); const [playing, setPlaying] = useState(true); useEffect(() => { let cancelled = false; /* The index we deliberately jumped to, and when — see PICK_GUARD_MS. */ let chosen = -1; let pickedAt = 0; let syncTimer = 0; loadWidgetApi() .then((SC) => { const frame = frameRef.current; if (cancelled || !frame) return; const widget = SC.Widget(frame); widgetRef.current = widget; /* The chip mirrors what the player is actually on, so a skip that gets ignored can't leave it lying. Partial reads are ignored rather than blanking the label. */ const syncTrack = () => { widget.getCurrentSound((sound) => { if (cancelled || !sound || !sound.title) return; setTrack({ title: sound.title, artist: sound.user?.username || "SoundCloud" }); }); }; const playRandomTrack = () => { widget.getSounds((sounds) => { if (cancelled || !sounds || sounds.length === 0) return; let index = Math.floor(Math.random() * sounds.length); /* Don't just replay whatever is already on. */ if (sounds.length > 1 && index === chosen) index = (index + 1) % sounds.length; chosen = index; pickedAt = Date.now(); widget.skip(index); widget.play(); /* Show it straight away, then settle on the truth once the player has caught up. */ setTrack(toTrack(sounds[index])); window.clearTimeout(syncTimer); syncTimer = window.setTimeout(syncTrack, 900); }); }; widget.bind(SC.Widget.Events.READY, () => { widget.setVolume(VOLUME); playRandomTrack(); }); /* The set plays on in its own order — grab every chance to re-randomise instead of following the playlist track by track. */ widget.bind(SC.Widget.Events.PLAY, () => { setPlaying(true); syncTrack(); if (Date.now() - pickedAt < PICK_GUARD_MS) return; widget.getCurrentSoundIndex((index) => { if (cancelled || index === chosen) return; playRandomTrack(); }); }); /* Fires at the end of the set (or of the single selected track). */ widget.bind(SC.Widget.Events.FINISH, playRandomTrack); /* Cover the case where READY already fired before we bound it. */ widget.setVolume(VOLUME); playRandomTrack(); }) .catch(() => { /* Offline, blocked or SoundCloud is down — the theme just stays silent. */ }); return () => { cancelled = true; window.clearTimeout(syncTimer); widgetRef.current?.pause(); widgetRef.current = null; }; }, []); const toggle = () => { const widget = widgetRef.current; if (!widget) return; if (playing) { widget.pause(); setPlaying(false); } else { widget.play(); setPlaying(true); } }; return ( <>