church-api/smart_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

379 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎯 Smart Video Streaming Test</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background: #111;
color: #fff;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 { color: #e50914; }
.video-container {
background: #222;
padding: 20px;
border-radius: 10px;
margin: 20px 0;
}
video {
width: 100%;
max-width: 800px;
height: auto;
border-radius: 5px;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 20px 0;
}
.info-card {
background: #333;
padding: 15px;
border-radius: 8px;
border: 2px solid transparent;
}
.info-card.active { border-color: #e50914; }
.info-card h3 {
margin: 0 0 10px 0;
color: #e50914;
}
.info-card p { margin: 5px 0; }
.status {
background: #444;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
font-family: monospace;
}
.controls {
text-align: center;
margin: 20px 0;
}
button {
background: #e50914;
color: white;
border: none;
padding: 12px 24px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
font-size: 16px;
}
button:hover { background: #f40612; }
.codec-support {
background: #2a2a2a;
padding: 15px;
border-radius: 8px;
margin: 15px 0;
}
.codec-support h3 { color: #4CAF50; }
.codec {
display: inline-block;
padding: 5px 10px;
margin: 5px;
border-radius: 3px;
font-family: monospace;
}
.codec.supported { background: #27ae60; color: white; }
.codec.unsupported { background: #e74c3c; color: white; }
.log {
background: #000;
color: #0f0;
padding: 15px;
border-radius: 8px;
font-family: monospace;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
margin: 20px 0;
}
.streaming-method {
font-weight: bold;
padding: 5px 10px;
border-radius: 3px;
display: inline-block;
margin: 5px 0;
}
.direct { background: #27ae60; color: white; }
.hls { background: #f39c12; color: white; }
.transcoding { background: #e74c3c; color: white; }
</style>
</head>
<body>
<div class="container">
<h1>🎯 Smart Video Streaming Test</h1>
<p>Like Jellyfin but not garbage C# - serves AV1 directly to modern browsers, HLS to legacy clients</p>
<div class="codec-support">
<h3>Your Browser Codec Support</h3>
<div id="codecSupport"></div>
</div>
<div class="video-container">
<video id="videoPlayer" controls>
Your browser does not support the video tag.
</video>
<div id="videoStatus" class="status">Click "Start Smart Stream" to begin</div>
</div>
<div class="controls">
<button onclick="startSmartStream()">🚀 Start Smart Stream</button>
<button onclick="testHLS()">📺 Test HLS Fallback</button>
<button onclick="clearLog()">🧹 Clear Log</button>
</div>
<div class="info-grid">
<div class="info-card" id="streamingCard">
<h3>🎬 Streaming Method</h3>
<p>Method: <span id="streamingMethod">Unknown</span></p>
<p>Codec: <span id="streamingCodec">Unknown</span></p>
<p>Source: <span id="streamingSource">Unknown</span></p>
</div>
<div class="info-card" id="performanceCard">
<h3>⚡ Performance</h3>
<p>Load Time: <span id="loadTime">-</span></p>
<p>File Size: <span id="fileSize">-</span></p>
<p>Bitrate: <span id="bitrate">-</span></p>
</div>
<div class="info-card" id="networkCard">
<h3>🌐 Network</h3>
<p>Response: <span id="responseCode">-</span></p>
<p>Headers: <span id="responseHeaders">-</span></p>
<p>Cached: <span id="cached">-</span></p>
</div>
</div>
<div class="log" id="log"></div>
</div>
<script>
const API_BASE = window.location.origin;
const MEDIA_ID = '123e4567-e89b-12d3-a456-426614174000';
const video = document.getElementById('videoPlayer');
// Detect codec support
function detectCodecSupport() {
const codecs = [
{ name: 'AV1', mime: 'video/mp4; codecs="av01.0.08M.08"' },
{ name: 'HEVC/H.265', mime: 'video/mp4; codecs="hvc1.1.6.L93.B0"' },
{ name: 'H.264', mime: 'video/mp4; codecs="avc1.42E01E"' },
{ name: 'VP9', mime: 'video/webm; codecs="vp9"' },
{ name: 'VP8', mime: 'video/webm; codecs="vp8"' }
];
// Try multiple HEVC variants for Safari compatibility
const hevcVariants = [
'video/mp4; codecs="hvc1.1.6.L93.B0"',
'video/mp4; codecs="hev1.1.6.L93.B0"',
'video/mp4; codecs="hvc1.1.60.L93.B0"',
'video/mp4; codecs="hvc1"'
];
let hevcSupported = false;
for (const variant of hevcVariants) {
if (video.canPlayType(variant) !== '') {
hevcSupported = true;
break;
}
}
const supportDiv = document.getElementById('codecSupport');
supportDiv.innerHTML = codecs.map(codec => {
let supported;
if (codec.name === 'HEVC/H.265') {
supported = hevcSupported; // Use our enhanced HEVC detection
} else {
supported = video.canPlayType(codec.mime) !== '';
}
return `<span class="codec ${supported ? 'supported' : 'unsupported'}">${codec.name}</span>`;
}).join('');
// Log user agent for debugging
log(`🔍 User Agent: ${navigator.userAgent}`, 'info');
return {
av1: video.canPlayType('video/mp4; codecs="av01.0.08M.08"') !== '',
hevc: hevcSupported, // Use enhanced HEVC detection
h264: video.canPlayType('video/mp4; codecs="avc1.42E01E"') !== ''
};
}
function log(message, type = 'info') {
const logDiv = document.getElementById('log');
const timestamp = new Date().toLocaleTimeString();
const className = type;
logDiv.innerHTML += `<div>[${timestamp}] ${message}</div>`;
logDiv.scrollTop = logDiv.scrollHeight;
}
function clearLog() {
document.getElementById('log').innerHTML = '';
}
function updateStatus(message) {
document.getElementById('videoStatus').textContent = message;
}
function updateStreamingInfo(method, codec, source) {
document.getElementById('streamingMethod').innerHTML =
`<span class="streaming-method ${method.toLowerCase()}">${method}</span>`;
document.getElementById('streamingCodec').textContent = codec;
document.getElementById('streamingSource').textContent = source;
document.getElementById('streamingCard').classList.add('active');
}
function updatePerformanceInfo(loadTime, fileSize, bitrate) {
document.getElementById('loadTime').textContent = `${loadTime}ms`;
document.getElementById('fileSize').textContent = formatBytes(fileSize);
document.getElementById('bitrate').textContent = bitrate ? `${(bitrate / 1000).toFixed(1)} Mbps` : 'Unknown';
document.getElementById('performanceCard').classList.add('active');
}
function updateNetworkInfo(status, headers, cached) {
document.getElementById('responseCode').textContent = status;
document.getElementById('responseHeaders').textContent = headers;
document.getElementById('cached').textContent = cached ? '✅ Yes' : '❌ No';
document.getElementById('networkCard').classList.add('active');
}
function formatBytes(bytes) {
if (!bytes) return 'Unknown';
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
}
async function startSmartStream() {
try {
log('🎯 Starting smart stream...', 'info');
updateStatus('Initiating smart stream...');
const startTime = performance.now();
const streamUrl = `${API_BASE}/api/media/stream/${MEDIA_ID}`;
log(`📡 Requesting: ${streamUrl}`, 'info');
// First, check headers with HEAD request (won't trigger transcoding)
const headResponse = await fetch(streamUrl, {
method: 'HEAD'
});
log(`📋 Headers received (status: ${headResponse.status})`, 'info');
// Check what type of streaming will be used
const streamingMethod = headResponse.headers.get('x-streaming-method');
const codec = headResponse.headers.get('x-codec');
log(`🎬 Streaming method: ${streamingMethod}, codec: ${codec}`, 'info');
const endTime = performance.now();
const loadTime = Math.round(endTime - startTime);
// Now make the actual video request based on the streaming method
if (streamingMethod === 'hls-arc-a770') {
// HLS with Intel Arc A770 on-demand segment generation
log(`🎯 Using Intel Arc A770 HLS on-demand transcoding`, 'info');
updateStreamingInfo('HLS', 'H.264 (Arc A770)', 'On-Demand Segments');
updateNetworkInfo('302 → HLS', 'Redirect to Playlist', false);
updatePerformanceInfo(loadTime, 0, null);
// Load video with HLS - Safari will follow redirect automatically
video.src = streamUrl;
updateStatus('Loading HLS with Intel Arc A770 on-demand segments...');
} else if (streamingMethod === 'direct-av1') {
// Direct AV1 streaming
log(`🚀 Direct AV1 streaming`, 'info');
updateStreamingInfo('DIRECT', 'AV1', 'Original File');
updateNetworkInfo('200 OK', 'Range Support', true);
updatePerformanceInfo(loadTime, 0, null);
// Load video directly
video.src = streamUrl;
updateStatus('Streaming AV1 directly (zero transcoding)!');
} else if (streamingMethod === 'hls-arc-a770-playlist' || streamingMethod === 'hls-playlist') {
// Direct HLS playlist response (probably from playlist endpoint)
log(`📺 Direct HLS playlist detected`, 'info');
updateStreamingInfo('HLS', 'H.264 (Arc A770)', 'Direct Playlist');
updateNetworkInfo('200 OK', 'HLS Playlist', true);
updatePerformanceInfo(loadTime, 0, null);
// This is already an HLS playlist, so Safari should handle it
video.src = streamUrl;
updateStatus('Playing HLS with Intel Arc A770 segments...');
} else {
// Unknown method - try direct loading
log(`❓ Unknown streaming method: ${streamingMethod}`, 'warn');
video.src = streamUrl;
updateStatus('Loading video...');
}
// Monitor video loading
video.addEventListener('loadstart', () => {
log('📺 Video loading started...', 'info');
});
video.addEventListener('loadedmetadata', () => {
const duration = Math.round(video.duration);
log(`✅ Video loaded: ${duration}s duration`, 'success');
updateStatus(`Ready to play (${duration}s duration)`);
});
video.addEventListener('error', (e) => {
log(`❌ Video error: ${e.message}`, 'error');
updateStatus('Video playback error');
});
} catch (error) {
log(`❌ Smart stream failed: ${error.message}`, 'error');
updateStatus('Smart streaming failed');
}
}
async function testHLS() {
try {
log('📺 Testing HLS fallback...', 'info');
const hlsUrl = `${API_BASE}/api/media/stream/${MEDIA_ID}/playlist.m3u8`;
log(`📡 Loading HLS: ${hlsUrl}`, 'info');
updateStreamingInfo('HLS', 'H.264 (transcoded)', 'HLS Segments');
updateStatus('Loading HLS playlist...');
video.src = hlsUrl;
video.addEventListener('loadedmetadata', () => {
log('✅ HLS playlist loaded successfully', 'success');
updateStatus('HLS ready (transcoding on-demand)');
});
} catch (error) {
log(`❌ HLS test failed: ${error.message}`, 'error');
updateStatus('HLS test failed');
}
}
// Initialize on page load
window.addEventListener('load', () => {
detectCodecSupport();
log('🎯 Smart streaming test initialized', 'info');
log('💡 Modern browsers get AV1 directly, legacy browsers get HLS with transcoding', 'info');
});
</script>
</body>
</html>