const box = document.getElementById('box'); const fromEl = document.getElementById('from'); const textEl = document.getElementById('text'); const token = location.pathname.split('/').filter(Boolean).pop(); const socket = io({ query: { role: 'overlay', token } }); let hideTimer = null; let audioCtx = null; function getAudioCtx() { audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)(); return audioCtx; } // Browsers suspend AudioContext until a user gesture happens on the page. // OBS's Browser Source doesn't enforce this, but for testing in a normal // tab, unlock it on the first click/keypress anywhere on the page. ['click', 'keydown'].forEach((eventName) => { document.addEventListener(eventName, () => { const ctx = getAudioCtx(); if (ctx.state === 'suspended') ctx.resume(); }, { once: true }); }); function playNotificationSound() { try { const ctx = getAudioCtx(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(880, ctx.currentTime); osc.frequency.setValueAtTime(1320, ctx.currentTime + 0.1); gain.gain.setValueAtTime(0.2, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.35); osc.connect(gain); gain.connect(ctx.destination); osc.start(); osc.stop(ctx.currentTime + 0.35); } catch (err) { console.error('Could not play notification sound:', err); } } function showMessage(text, from) { clearTimeout(hideTimer); fromEl.textContent = from || ''; fromEl.style.display = from ? 'block' : 'none'; textEl.textContent = text; // force reflow so the transition re-triggers even if a message is already visible void box.offsetWidth; box.classList.add('visible'); playNotificationSound(); const durationMs = 10000; hideTimer = setTimeout(() => { box.classList.remove('visible'); }, durationMs); } function playSpeech(base64Audio, mimeType) { const audio = new Audio(`data:${mimeType};base64,${base64Audio}`); audio.play().catch((err) => console.error('Could not play TTS audio:', err)); } socket.on('display_message', (payload) => { showMessage(payload.text, payload.from); }); socket.on('display_audio', (payload) => { playSpeech(payload.audio, payload.mimeType); }); socket.on('invalid_link', () => { console.error('Invalid overlay link — check the URL from your dashboard.'); });