VU wave: pravi audio spekter prek Web Audio analizatorja ob vklopu zvoka (fallback CSS pri mute)

This commit is contained in:
OpenClaw Agent 2026-07-17 08:56:24 +00:00
parent b7918e41c7
commit a0c935af7a

View File

@ -45,6 +45,7 @@
.ctrl { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; background: #0A0A0A; border: 1px solid rgba(255,255,255,0.14); border-top: 0; padding: 12px 16px; margin-top: -1px; }
.vu { display: flex; gap: 3px; align-items: flex-end; height: 30px; flex: none; }
.vu span { width: 3px; height: 100%; background: var(--akzent); transform-origin: bottom; animation: fx-vu 0.7s infinite ease-in-out alternate; }
.vu.live span { animation: none; transform: scaleY(.15); transition: transform 60ms linear; }
.now { flex: 1; min-width: 180px; }
.now .lbl { font-size: 10px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase; color: #6B6B6B; }
.now .t { font-weight: 700; font-size: 15px; text-transform: uppercase; letter-spacing: -0.005em; margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@ -147,7 +148,50 @@
// Audio toggle
var btnA = document.getElementById('btnAudio');
btnA.addEventListener('click', function(){ video.muted = !video.muted; if(!video.muted && video.volume===0) video.volume=0.8; btnA.textContent = video.muted ? '🔇' : '🔊'; });
btnA.addEventListener('click', function(){ video.muted = !video.muted; if(!video.muted && video.volume===0) video.volume=0.8; btnA.textContent = video.muted ? '🔇' : '🔊'; syncVu(); });
// --- pravi VU: Web Audio analizator na dejanskem zvoku streama ---
var vuEl = document.getElementById('vu');
var vuBars = vuEl ? vuEl.querySelectorAll('span') : [];
var audioCtx = null, analyser = null, vuRaf = null, freqData = null;
function initAnalyser(){
if (audioCtx || !window.AudioContext && !window.webkitAudioContext) return;
try {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var src = audioCtx.createMediaElementSource(video);
analyser = audioCtx.createAnalyser();
analyser.fftSize = 64;
analyser.smoothingTimeConstant = 0.75;
src.connect(analyser);
analyser.connect(audioCtx.destination);
freqData = new Uint8Array(analyser.frequencyBinCount);
} catch(e) { audioCtx = null; analyser = null; }
}
function vuLoop(){
if (!analyser) return;
analyser.getByteFrequencyData(freqData);
var n = vuBars.length; // 7 stolpcev, razporejenih cez spekter (bas -> visine)
for (var i = 0; i < n; i++) {
var a = Math.floor(i * freqData.length / (n + 4));
var b = Math.floor((i + 1) * freqData.length / (n + 4));
var sum = 0; for (var j = a; j < b; j++) sum += freqData[j];
var v = (sum / Math.max(1, b - a)) / 255;
var sc = Math.max(0.12, Math.min(1, Math.pow(v, 0.8) * 1.25));
vuBars[i].style.transform = 'scaleY(' + sc.toFixed(3) + ')';
}
vuRaf = requestAnimationFrame(vuLoop);
}
function syncVu(){
if (!video.muted) {
initAnalyser();
if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
if (analyser) { vuEl.classList.add('live'); if (!vuRaf) vuRaf = requestAnimationFrame(vuLoop); return; }
}
// mutirano ali analizator ni na voljo -> nazaj na organsko animacijo
vuEl.classList.remove('live');
if (vuRaf) { cancelAnimationFrame(vuRaf); vuRaf = null; }
for (var i = 0; i < vuBars.length; i++) vuBars[i].style.transform = '';
}
// Fullscreen
document.getElementById('btnFs').addEventListener('click', function(){
var f = document.getElementById('playerFrame');