Initial commit: overlay + sender pages for StreamLabs browser source
Express + Socket.IO backend with Google login on the sender page, a transparent overlay page for OBS/StreamLabs Browser Source, and TTS voice-over via the edge-tts Python CLI (same engine as t2sTelegramBot). Dockerized for deployment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
commit
7882db1f37
12 changed files with 1930 additions and 0 deletions
72
public/overlay.js
Normal file
72
public/overlay.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
const box = document.getElementById('box');
|
||||
const fromEl = document.getElementById('from');
|
||||
const textEl = document.getElementById('text');
|
||||
const socket = io();
|
||||
|
||||
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);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue