mobile: the first tap anywhere unlocks the player (silent clip inside the gesture) so the first track autoplays when it lands; on iOS the cover pulses to the beat instead of the spectrum, so music keeps playing with the screen off and ignores the mute switch
shots / shots (push) Canceled after 28s
shots / shots (push) Canceled after 28s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6
This commit is contained in:
1 parent
d372da5950
commit
0ffeefe94d
1 file changed
+28
-1
+28
-1
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src https://api.venice.ai https://inference.yolopreneur.lol; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src blob:; font-src 'self'">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src https://api.venice.ai https://inference.yolopreneur.lol; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src blob: data:; font-src 'self'">
|
||||
<title>gandora</title>
|
||||
<meta name="description" content="Type a seed. Get a station. Every song is made for you as it plays, and every cover is drawn from the song's own settings.">
|
||||
<link rel="icon" href="favicon.svg">
|
||||
@@ -595,8 +595,35 @@ function drawRings(canvas, spec, { lit = 1, pulse = null } = {}) {
|
||||
}
|
||||
|
||||
/* ---------- the visualizer: an analyser on the player, driving the cover while music plays ---------- */
|
||||
/* the first tap unlocks the player: a silent clip played inside the gesture makes the <audio> element "user-started",
|
||||
so the first real track can start itself when the render lands, however long that takes */
|
||||
const SILENCE = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA=";
|
||||
function prime() {
|
||||
if (state.primed || (state.audio.src && !state.audio.paused)) return;
|
||||
try { state.audio.src = SILENCE; const p = state.audio.play(); if (p && p.then) p.then(() => { state.primed = true; }).catch(() => {}); } catch {}
|
||||
}
|
||||
for (const ev of ["pointerdown", "touchend", "keydown"]) document.addEventListener(ev, prime, { capture: true, passive: true });
|
||||
/* iOS routes sound through Web Audio the moment the element is wired to an analyser, and suspends Web Audio when
|
||||
the screen locks (and obeys the mute switch). So on iOS the cover pulses to the beat from the sheet instead,
|
||||
and the music stays a plain <audio> element that keeps playing in the background. */
|
||||
const IOS = /iP(hone|ad|od)/.test(navigator.userAgent) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
||||
const viz = { ctx: null, an: null, data: null, raf: 0, smooth: { bass: 0, mid: 0, high: 0 } };
|
||||
function vizBeat() {
|
||||
cancelAnimationFrame(viz.raf);
|
||||
const canvas = $("#cover canvas"), t0 = performance.now(), rnd = prng(hash32(state.current.spec.title || ""));
|
||||
const frame = () => {
|
||||
if (!state.playing || !state.current || !state.current.blob) { viz.raf = 0; return; }
|
||||
const spec = state.current.spec, t = (performance.now() - t0) / 1000, bps = (+spec.bpm || 100) / 60, en = +spec.energy || 0.5;
|
||||
const beat = Math.pow(Math.max(0, Math.cos(2 * Math.PI * t * bps)), 6); // a kick on every beat
|
||||
const bar = Math.pow(Math.max(0, Math.cos(2 * Math.PI * t * bps / 4)), 2); // and a swell on the bar
|
||||
const bass = Math.min(1, (0.25 + 0.75 * beat) * (0.5 + 0.5 * en)), mid = 0.3 + 0.3 * bar * en, high = 0.2 + 0.25 * Math.pow(Math.max(0, Math.sin(2 * Math.PI * t * bps * 2)), 4) * en;
|
||||
drawCover(canvas, spec, { pulse: { bass, mid, high, t } });
|
||||
viz.raf = requestAnimationFrame(frame);
|
||||
};
|
||||
viz.raf = requestAnimationFrame(frame);
|
||||
}
|
||||
function vizStart() {
|
||||
if (IOS || state.beatViz) return vizBeat();
|
||||
// the player is only wired through the analyser once the context is actually running: a suspended context
|
||||
// (iOS before a tap) would mute the music, and the cover can wait for the next gesture
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user