Update to support sync captions
This commit is contained in:
230
server/php/display-polling.php
Normal file
230
server/php/display-polling.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Multi-User Transcription Display (Polling)</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: transparent;
|
||||
font-family: Arial, sans-serif;
|
||||
color: white;
|
||||
}
|
||||
#transcriptions {
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.transcription {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 5px;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
transition: opacity 1s ease-out;
|
||||
}
|
||||
.transcription.fading {
|
||||
opacity: 0;
|
||||
}
|
||||
.timestamp {
|
||||
color: #888;
|
||||
font-size: 0.9em;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.user {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
/* Color set dynamically via inline style */
|
||||
}
|
||||
.text {
|
||||
color: white;
|
||||
}
|
||||
#status {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border-radius: 5px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
#status.connected { color: #4CAF50; }
|
||||
#status.disconnected { color: #f44336; }
|
||||
#status.polling { color: #FFC107; }
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="status" class="polling">🟡 Polling...</div>
|
||||
<div id="transcriptions"></div>
|
||||
|
||||
<script>
|
||||
// Get URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const room = urlParams.get('room') || 'default';
|
||||
const fadeAfter = parseInt(urlParams.get('fade') || '10');
|
||||
const showTimestamps = urlParams.get('timestamps') !== 'false';
|
||||
const pollInterval = parseInt(urlParams.get('poll') || '1000'); // Poll every 1 second
|
||||
|
||||
const container = document.getElementById('transcriptions');
|
||||
const statusEl = document.getElementById('status');
|
||||
const userColors = new Map(); // Map user names to HSL colors
|
||||
let colorIndex = 0;
|
||||
let lastCount = 0; // Track how many transcriptions we've seen
|
||||
let consecutiveErrors = 0;
|
||||
let isPolling = false;
|
||||
|
||||
// Generate distinct color for each user using golden ratio
|
||||
function getUserColor(userName) {
|
||||
if (!userColors.has(userName)) {
|
||||
// Use golden ratio for evenly distributed hues
|
||||
const goldenRatio = 0.618033988749895;
|
||||
const hue = (colorIndex * goldenRatio * 360) % 360;
|
||||
// High saturation and medium lightness for vibrant, readable colors
|
||||
const color = `hsl(${hue}, 85%, 65%)`;
|
||||
userColors.set(userName, color);
|
||||
colorIndex++;
|
||||
}
|
||||
return userColors.get(userName);
|
||||
}
|
||||
|
||||
function addTranscription(data) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'transcription';
|
||||
|
||||
// Get user color (generates new color if first time)
|
||||
const userColor = getUserColor(data.user_name);
|
||||
|
||||
let html = '';
|
||||
if (showTimestamps && data.timestamp) {
|
||||
html += `<span class="timestamp">[${data.timestamp}]</span>`;
|
||||
}
|
||||
if (data.user_name) {
|
||||
html += `<span class="user" style="color: ${userColor}">${data.user_name}:</span>`;
|
||||
}
|
||||
html += `<span class="text">${data.text}</span>`;
|
||||
|
||||
div.innerHTML = html;
|
||||
container.appendChild(div);
|
||||
|
||||
// Auto-scroll to bottom
|
||||
container.scrollTop = container.scrollHeight;
|
||||
|
||||
// Set up fade-out if enabled
|
||||
if (fadeAfter > 0) {
|
||||
setTimeout(() => {
|
||||
div.classList.add('fading');
|
||||
setTimeout(() => {
|
||||
if (div.parentNode === container) {
|
||||
container.removeChild(div);
|
||||
}
|
||||
}, 1000);
|
||||
}, fadeAfter * 1000);
|
||||
}
|
||||
|
||||
// Limit to 100 transcriptions
|
||||
while (container.children.length > 100) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// Poll for new transcriptions
|
||||
async function poll() {
|
||||
if (isPolling) return; // Prevent concurrent polls
|
||||
isPolling = true;
|
||||
|
||||
try {
|
||||
const url = `server.php?action=list&room=${encodeURIComponent(room)}&t=${Date.now()}`;
|
||||
const response = await fetch(url, {
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.transcriptions) {
|
||||
const currentCount = data.transcriptions.length;
|
||||
|
||||
// Only show new transcriptions
|
||||
if (currentCount > lastCount) {
|
||||
const newTranscriptions = data.transcriptions.slice(lastCount);
|
||||
newTranscriptions.forEach(addTranscription);
|
||||
lastCount = currentCount;
|
||||
}
|
||||
|
||||
// Update status
|
||||
statusEl.textContent = `🟢 Connected (${currentCount})`;
|
||||
statusEl.className = 'connected';
|
||||
consecutiveErrors = 0;
|
||||
} else {
|
||||
statusEl.textContent = '🟡 Waiting for data...';
|
||||
statusEl.className = 'polling';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Polling error:', error);
|
||||
consecutiveErrors++;
|
||||
|
||||
if (consecutiveErrors < 5) {
|
||||
statusEl.textContent = `🟡 Retrying... (${consecutiveErrors})`;
|
||||
statusEl.className = 'polling';
|
||||
} else {
|
||||
statusEl.textContent = '🔴 Connection failed';
|
||||
statusEl.className = 'disconnected';
|
||||
}
|
||||
} finally {
|
||||
isPolling = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load initial transcriptions
|
||||
async function loadInitial() {
|
||||
try {
|
||||
const url = `server.php?action=list&room=${encodeURIComponent(room)}&t=${Date.now()}`;
|
||||
const response = await fetch(url, { cache: 'no-cache' });
|
||||
const data = await response.json();
|
||||
|
||||
if (data.transcriptions && data.transcriptions.length > 0) {
|
||||
// Show last 20 transcriptions
|
||||
const recent = data.transcriptions.slice(-20);
|
||||
recent.forEach(addTranscription);
|
||||
lastCount = data.transcriptions.length;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading initial transcriptions:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Start polling
|
||||
async function start() {
|
||||
statusEl.textContent = '🟡 Loading...';
|
||||
statusEl.className = 'polling';
|
||||
|
||||
await loadInitial();
|
||||
|
||||
// Start regular polling
|
||||
setInterval(poll, pollInterval);
|
||||
poll(); // First poll immediately
|
||||
}
|
||||
|
||||
// Start when page loads
|
||||
start();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user