Files
instant-replay/renderer.js
Space-Banane 5d6f678e77 first commit
2026-01-16 21:50:47 +01:00

248 lines
8.7 KiB
JavaScript

let ws;
const videoPlayer = document.getElementById('videoPlayer');
const videoOverlay = document.getElementById('videoOverlay');
const progressContainer = document.getElementById('progressContainer');
const progressFill = document.getElementById('progressFill');
const timer = document.getElementById('timer');
const loadingSpinner = document.getElementById('loadingSpinner');
const loadingStatusText = document.getElementById('loadingStatusText'); // new element
let backendDuration = null; // Duration provided by backend
console.log('Renderer loaded. DOM elements initialized.');
// Console log passthrough to Electron main
if (window.require) {
const { ipcRenderer } = window.require('electron');
['log', 'error', 'warn', 'info'].forEach(level => {
const orig = console[level];
console[level] = function(...args) {
ipcRenderer.send('ui-log', { level, args });
orig.apply(console, args);
};
});
console.log('Electron ipcRenderer logging enabled.');
}
// Always muted for security
videoPlayer.muted = true;
console.log('Video player muted.');
function connectWebSocket() {
console.log('Connecting to WebSocket...');
ws = new WebSocket('ws://localhost:8001');
ws.onmessage = (event) => {
console.log('WebSocket message received:', event.data);
try {
const message = JSON.parse(event.data);
console.log('Parsed message:', message);
if (message.type === 'play_video') {
console.log('play_video command received:', message);
playVideo(
message.url,
message.location || "center",
message.size || 5,
message.duration ?? null
);
} else if (message.type === 'stop_video') {
console.log('stop_video command received.');
stopVideo();
} else if (message.type === 'status_update') {
console.log('status_update received:', message.status);
updateLoadingStatus(message.status);
}
} catch (error) {
showError('Error parsing message from server.');
console.error('Error parsing message:', error);
}
};
ws.onclose = () => {
console.warn('WebSocket closed. Retrying...');
showError('Lost connection to WebSocket server. Retrying...');
setTimeout(connectWebSocket, 3000);
};
ws.onerror = (err) => {
console.error('WebSocket error:', err);
showError('WebSocket connection error. Is the server running?');
};
}
function updateLoadingStatus(status) {
console.log('Updating loading status:', status);
if (loadingStatusText) {
loadingStatusText.textContent = status;
loadingSpinner.style.display = 'flex';
videoPlayer.style.display = 'none';
progressContainer.style.display = 'none';
videoOverlay.classList.remove('hidden');
}
}
function playVideo(url, location = "center", size = 5, duration = null) {
console.log('[UI] playVideo called:', { url, location, size, duration });
backendDuration = typeof duration === 'number' && duration > 0 ? duration : null;
videoPlayer.src = url;
videoOverlay.classList.remove('hidden');
setOverlayPosition(location, size);
// Show loading spinner, hide video and progress until ready
loadingSpinner.style.display = 'flex';
videoPlayer.style.display = 'none';
progressContainer.style.display = backendDuration ? 'block' : 'none';
// Start loading and wait for canplay to avoid black frames
videoPlayer.load();
console.log('[UI] Video loading started.');
const cleanup = () => {
videoPlayer.removeEventListener('canplay', onCanPlay);
videoPlayer.removeEventListener('error', onError);
};
const onCanPlay = () => {
console.log('[UI] canplay: starting playback');
loadingSpinner.style.display = 'none';
videoPlayer.style.display = '';
videoPlayer.play().then(() => {
console.log('[UI] Video playback started.');
}).catch((err) => {
console.warn('[UI] Video playback failed:', err);
});
updateProgress();
cleanup();
};
const onError = (e) => {
console.error('[UI] Video error:', e);
showError(e.message ? e.message.toString() : 'Unknown video error');
cleanup();
};
videoPlayer.addEventListener('canplay', onCanPlay, { once: true });
videoPlayer.addEventListener('error', onError, { once: true });
// Safety fallback in case canplay doesn't fire quickly
setTimeout(() => {
if (loadingSpinner.style.display !== 'none') {
console.warn('[UI] Fallback: forcing playback after timeout.');
onCanPlay();
}
}, 2000);
}
function stopVideo() {
console.log('stopVideo called.');
hideVideo();
}
function sendStoppedMessage() {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "stopped" }));
console.log('Sent "stopped" message to backend.');
}
}
function hideVideo() {
console.log('hideVideo called.');
backendDuration = null; // Clear backend-provided duration
videoOverlay.classList.add('hidden');
videoPlayer.pause();
videoPlayer.src = '';
videoPlayer.style.width = '100vw';
videoPlayer.style.height = '100vh';
progressContainer.style.display = 'none';
progressFill.style.width = '0%';
timer.textContent = '';
loadingSpinner.style.display = 'none';
videoPlayer.style.display = '';
sendStoppedMessage(); // Notify backend when video stops
}
// Start WebSocket connection when page loads
console.log('Starting WebSocket connection...');
connectWebSocket();
videoPlayer.addEventListener('ended', () => {
console.log('Video ended event.');
hideVideo();
sendStoppedMessage(); // Notify backend when video ends
});
videoPlayer.addEventListener('timeupdate', () => {
// This can be noisy, so only log occasionally
if (Math.floor(videoPlayer.currentTime) % 5 === 0) {
console.log('Video timeupdate:', videoPlayer.currentTime);
}
updateProgress();
});
function updateProgress() {
let current = 0;
try {
current = Number(videoPlayer.currentTime) || 0;
if (typeof backendDuration === 'number' && backendDuration > 0) {
const total = backendDuration;
const percent = Math.min((current / total) * 100, 100);
progressFill.style.width = percent + '%';
timer.textContent = formatTime(current) + ' / ' + formatTime(total);
// Optional: reduce noisy logs
// console.log(`[UI] Progress: ${percent.toFixed(2)}%`);
} else {
// No duration provided; hide progress UI
progressFill.style.width = '0%';
timer.textContent = formatTime(current) + ' / --:--';
progressContainer.style.display = 'none';
}
} catch (err) {
progressFill.style.width = '0%';
timer.textContent = formatTime(current) + ' / --:--';
console.error('[UI] Error updating progress:', err);
}
}
function formatTime(seconds) {
seconds = Math.floor(seconds);
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m}:${s.toString().padStart(2, '0')}`;
}
function setOverlayPosition(location, size) {
console.log('setOverlayPosition called:', { location, size });
// Reset styles
videoOverlay.style.justifyContent = '';
videoOverlay.style.alignItems = '';
videoOverlay.style.top = '';
videoOverlay.style.left = '';
videoOverlay.style.right = '';
videoOverlay.style.bottom = '';
videoOverlay.style.width = '100vw';
videoOverlay.style.height = '100vh';
// Calculate video size as percentage of viewport (10% to 100%)
const percent = Math.min(Math.max(size, 1), 10) * 10;
videoPlayer.style.width = percent + 'vw';
videoPlayer.style.height = percent + 'vh';
// Position overlay and video
switch (location) {
case 'top_left':
videoOverlay.style.justifyContent = 'flex-start';
videoOverlay.style.alignItems = 'flex-start';
break;
case 'bottom_left':
videoOverlay.style.justifyContent = 'flex-start';
videoOverlay.style.alignItems = 'flex-end';
break;
case 'top_right':
videoOverlay.style.justifyContent = 'flex-end';
videoOverlay.style.alignItems = 'flex-start';
break;
case 'center':
default:
videoOverlay.style.justifyContent = 'center';
videoOverlay.style.alignItems = 'center';
break;
}
console.log('Overlay position set.');
}