
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.
379 lines
15 KiB
HTML
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> |