diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index fd8dd7d..4b067df 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -4605,10 +4605,14 @@ window.cancelAnimFrame = (function () { lastTimeUpdate: 0, currentColor: [0, 0, 0], targetColor: [0, 0, 0], - cleanup: null + cleanup: null, + // Bumped on every stop: async work (thumb load, color fetch, player API, loop) started for an + // earlier session sees a different number and does nothing, so it can't paint over the next item + gen: 0 }; const stopYoutubeAmbient = () => { + ytAmbientState.gen++; if (ytAmbientState.rafId) { window.cancelAnimFrame(ytAmbientState.rafId); ytAmbientState.rafId = null; @@ -4630,6 +4634,8 @@ window.cancelAnimFrame = (function () { if (!videoId) return; ytAmbientState.videoId = videoId; + const gen = ytAmbientState.gen; + const isCurrent = () => ytAmbientState.gen === gen && ytEmbed.isConnected; const context = canvas.getContext('2d'); const _SCALE = 0.5; const updateCanvasSize = () => { @@ -4650,6 +4656,7 @@ window.cancelAnimFrame = (function () { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { + if (!isCurrent()) return; try { context.filter = `blur(${blurPx}px) brightness(1.2)`; const sw = img.naturalWidth || img.width; @@ -4668,6 +4675,7 @@ window.cancelAnimFrame = (function () { canvas.classList.add('fader-in'); }; img.onerror = () => { + if (!isCurrent()) return; if (fallbackUrl && url !== fallbackUrl) { tryDrawThumb(fallbackUrl, null); } else { @@ -4685,7 +4693,7 @@ window.cancelAnimFrame = (function () { fetch(`/api/v2/ambient/yt/${videoId}`) .then(res => res.ok ? res.json() : null) .then(data => { - if (!data || !Array.isArray(data.colors) || !data.colors.length || ytAmbientState.videoId !== videoId) return; + if (!data || !Array.isArray(data.colors) || !data.colors.length || !isCurrent()) return; ytAmbientState.colors = data.colors; ytAmbientState.duration = Number(data.duration) || 0; if (ytAmbientState.colors.length > 0) { @@ -4704,6 +4712,7 @@ window.cancelAnimFrame = (function () { // 3. Render function for ambient lighting const renderAmbientFrame = () => { + if (!isCurrent()) return; if (!ytAmbientState.colors || !ytAmbientState.colors.length || !ytAmbientState.duration) return; const { cw: curW, ch: curH } = updateCanvasSize(); @@ -4757,9 +4766,9 @@ window.cancelAnimFrame = (function () { }; const startAmbientLoop = () => { - if (ytAmbientState.rafId) return; + if (ytAmbientState.rafId || !isCurrent()) return; const loop = () => { - if (!ytAmbientState.isPlaying || ytAmbientState.videoId !== videoId) { + if (!ytAmbientState.isPlaying || !isCurrent()) { ytAmbientState.rafId = null; return; } @@ -4790,11 +4799,13 @@ window.cancelAnimFrame = (function () { // Also attach YouTube IFrame API if available const tryAttachYTPlayer = () => { + if (!isCurrent()) return; if (window.YT && window.YT.Player) { try { new window.YT.Player(ytEmbed, { events: { onStateChange: (event) => { + if (!isCurrent()) return; if (event.data === 1) { // PLAYING ytAmbientState.isPlaying = true; startAmbientLoop(); @@ -5304,6 +5315,9 @@ window.cancelAnimFrame = (function () { const canvas = document.getElementById('bg'); if (elem) { + // This item has its own media: a YouTube ambient session from the previous item must end here, + // or its loop keeps painting its colour wash over the new background (flicker) + stopYoutubeAmbient(); if (canvas) { // Restore visual state on re-init const bgCfg = (typeof window.getEffectiveBackgroundTuning === 'function' ? window.getEffectiveBackgroundTuning() : null) || window.audioVisualizerTuning || DEFAULT_AUDIO_TUNING;