church-api/chunk_streaming_test.html
Benjamin Slingo 0c06e159bb Initial commit: Church API Rust implementation
Complete church management system with bulletin management, media processing, live streaming integration, and web interface. Includes authentication, email notifications, database migrations, and comprehensive test suite.
2025-08-19 20:56:41 -04:00

235 lines
8.2 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Netflix-Style Chunk Streaming Test</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background: #111;
color: #fff;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.status {
background: #333;
padding: 15px;
border-radius: 5px;
margin: 10px 0;
font-family: monospace;
}
.chunk-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 5px;
margin: 20px 0;
}
.chunk {
padding: 10px;
text-align: center;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.chunk.not-started { background: #666; }
.chunk.transcoding { background: #f39c12; animation: pulse 1s infinite; }
.chunk.cached { background: #27ae60; }
.chunk.failed { background: #e74c3c; }
.chunk.current { border: 3px solid #fff; }
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
button {
background: #e50914;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
}
button:hover { background: #f40612; }
button:disabled { background: #666; cursor: not-allowed; }
.controls {
margin: 20px 0;
text-align: center;
}
.log {
background: #222;
padding: 10px;
border-radius: 5px;
height: 200px;
overflow-y: auto;
font-family: monospace;
font-size: 12px;
}
.success { color: #27ae60; }
.error { color: #e74c3c; }
.info { color: #3498db; }
</style>
</head>
<body>
<div class="container">
<h1>🍿 Netflix-Style Chunk Streaming Test</h1>
<div class="status" id="status">
Click "Load Video Info" to start
</div>
<div class="controls">
<button onclick="loadVideoInfo()">Load Video Info</button>
<button onclick="testChunk()" id="testBtn" disabled>Test Random Chunk</button>
<button onclick="testSequential()" id="seqBtn" disabled>Test Sequential</button>
<button onclick="clearLog()">Clear Log</button>
</div>
<div class="chunk-grid" id="chunkGrid"></div>
<div class="log" id="log"></div>
</div>
<script>
const API_BASE = window.location.origin;
const MEDIA_ID = '123e4567-e89b-12d3-a456-426614174000'; // Test UUID
let videoInfo = null;
let chunkStatus = {};
function log(message, type = 'info') {
const logDiv = document.getElementById('log');
const timestamp = new Date().toLocaleTimeString();
const className = type;
logDiv.innerHTML += `<div class="${className}">[${timestamp}] ${message}</div>`;
logDiv.scrollTop = logDiv.scrollHeight;
}
function clearLog() {
document.getElementById('log').innerHTML = '';
}
function updateStatus(message) {
document.getElementById('status').textContent = message;
}
async function loadVideoInfo() {
try {
log('Loading video info...', 'info');
const response = await fetch(`${API_BASE}/api/media/stream/${MEDIA_ID}/info`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
videoInfo = await response.json();
log(`✅ Video loaded: ${videoInfo.total_duration}s, ${videoInfo.num_segments} chunks`, 'success');
updateStatus(`Video: ${Math.round(videoInfo.total_duration)}s (${videoInfo.num_segments} chunks of ${videoInfo.segment_duration}s each)`);
// Enable buttons
document.getElementById('testBtn').disabled = false;
document.getElementById('seqBtn').disabled = false;
// Create chunk grid
createChunkGrid();
} catch (error) {
log(`❌ Failed to load video info: ${error.message}`, 'error');
updateStatus('Failed to load video info');
}
}
function createChunkGrid() {
const grid = document.getElementById('chunkGrid');
grid.innerHTML = '';
for (let i = 0; i < videoInfo.num_segments; i++) {
const chunk = document.createElement('div');
chunk.className = 'chunk not-started';
chunk.textContent = `${i}`;
chunk.title = `Chunk ${i} (${i * videoInfo.segment_duration}-${(i + 1) * videoInfo.segment_duration}s)`;
chunk.onclick = () => testSpecificChunk(i);
grid.appendChild(chunk);
chunkStatus[i] = 'not-started';
}
}
function updateChunkStatus(index, status) {
chunkStatus[index] = status;
const chunk = document.querySelector(`#chunkGrid .chunk:nth-child(${index + 1})`);
if (chunk) {
chunk.className = `chunk ${status}`;
}
}
async function testSpecificChunk(index) {
try {
updateChunkStatus(index, 'transcoding');
log(`🎬 Testing chunk ${index}...`, 'info');
const startTime = performance.now();
const response = await fetch(`${API_BASE}/api/media/stream/${MEDIA_ID}/chunk/${index}`);
const endTime = performance.now();
if (response.status === 202) {
// Still transcoding
const info = await response.json();
log(`⏳ Chunk ${index} transcoding (ETA: ${info.estimated_seconds}s)`, 'info');
// Poll for completion
setTimeout(() => testSpecificChunk(index), 2000);
return;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const contentLength = response.headers.get('content-length');
const contentType = response.headers.get('content-type');
const isCached = response.headers.get('x-chunk-cached') === 'true';
updateChunkStatus(index, 'cached');
log(`✅ Chunk ${index}: ${Math.round(endTime - startTime)}ms, ${contentLength} bytes, ${contentType} ${isCached ? '(cached)' : '(transcoded)'}`, 'success');
} catch (error) {
updateChunkStatus(index, 'failed');
log(`❌ Chunk ${index} failed: ${error.message}`, 'error');
}
}
async function testChunk() {
if (!videoInfo) return;
const randomIndex = Math.floor(Math.random() * videoInfo.num_segments);
await testSpecificChunk(randomIndex);
}
let sequentialIndex = 0;
async function testSequential() {
if (!videoInfo) return;
if (sequentialIndex >= videoInfo.num_segments) {
log('🎉 Sequential test completed!', 'success');
sequentialIndex = 0;
return;
}
await testSpecificChunk(sequentialIndex);
sequentialIndex++;
// Continue after a short delay
setTimeout(testSequential, 1000);
}
// Load video info on page load
window.addEventListener('load', loadVideoInfo);
</script>
</body>
</html>