nextav/tests/player/test-hls.html

260 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS Streaming Test</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.test-section { margin: 20px 0; padding: 15px; border: 1px solid #ccc; border-radius: 5px; }
.success { color: green; font-weight: bold; }
.error { color: red; font-weight: bold; }
.info { color: blue; }
.debug-info { background: #f5f5f5; padding: 10px; border-radius: 3px; font-family: monospace; font-size: 12px; }
button { padding: 8px 16px; margin: 5px; cursor: pointer; }
#video-container { width: 640px; height: 360px; margin: 20px 0; }
.loading { color: orange; }
</style>
</head>
<body>
<h1>🎬 HLS Streaming Test Interface</h1>
<div class="test-section">
<h2>🔧 Test HLS Endpoints</h2>
<p>Click the buttons below to test HLS streaming endpoints:</p>
<div>
<button onclick="testHLSPlaylist(109)">Test HLS Playlist (ID: 109)</button>
<button onclick="testHLSPlaylist(103)">Test HLS Playlist (ID: 103)</button>
<button onclick="testSegment(109, 0)">Test Segment 0 (ID: 109)</button>
<button onclick="testVideoPlayer()">Test Video Player</button>
</div>
<div id="test-results"></div>
</div>
<div class="test-section">
<h2>📺 Video Player Test</h2>
<div id="video-container"></div>
<div id="player-status"></div>
</div>
<div class="test-section">
<h2>📊 Debug Information</h2>
<div id="debug-info" class="debug-info"></div>
</div>
<script>
let currentPlayer = null;
let hls = null;
function log(message, type = 'info') {
const results = document.getElementById('test-results');
const timestamp = new Date().toLocaleTimeString();
const color = type === 'success' ? 'green' : type === 'error' ? 'red' : 'blue';
results.innerHTML += `<div style="color: ${color}">[${timestamp}] ${message}</div>`;
console.log(`[HLS Test] ${message}`);
}
function debug(message) {
const debugInfo = document.getElementById('debug-info');
debugInfo.innerHTML += `<div>${new Date().toLocaleTimeString()}: ${message}</div>`;
debugInfo.scrollTop = debugInfo.scrollHeight;
}
async function testHLSPlaylist(videoId) {
log(`Testing HLS playlist for video ID: ${videoId}`, 'info');
debug(`Fetching playlist for video ${videoId}`);
try {
const response = await fetch(`/api/stream/hls/${videoId}/playlist.m3u8`);
debug(`Response status: ${response.status} ${response.statusText}`);
debug(`Response headers: ${JSON.stringify([...response.headers])}`);
if (response.ok) {
const playlist = await response.text();
log(`✅ HLS playlist loaded successfully (ID: ${videoId})`, 'success');
debug(`Playlist content:\n${playlist}`);
// Parse playlist to get basic info
const lines = playlist.split('\n');
const targetDuration = lines.find(line => line.startsWith('#EXT-X-TARGETDURATION:'));
const mediaSequence = lines.find(line => line.startsWith('#EXT-X-MEDIA-SEQUENCE:'));
if (targetDuration) log(`Target duration: ${targetDuration.split(':')[1]}s`);
if (mediaSequence) log(`Media sequence: ${mediaSequence.split(':')[1]}`);
// Count segments
const segments = lines.filter(line => line.endsWith('.ts'));
log(`Found ${segments.length} segments in playlist`);
} else {
const errorText = await response.text();
log(`❌ Failed to load HLS playlist (ID: ${videoId}): ${response.status} ${response.statusText}`, 'error');
debug(`Error response: ${errorText}`);
}
} catch (error) {
log(`❌ Network error testing HLS playlist (ID: ${videoId}): ${error.message}`, 'error');
debug(`Network error: ${error.stack}`);
}
}
async function testSegment(videoId, segmentIndex) {
log(`Testing HLS segment ${segmentIndex} for video ID: ${videoId}`, 'info');
debug(`Fetching segment ${segmentIndex} for video ${videoId}`);
try {
const response = await fetch(`/api/stream/hls/${videoId}/segment/${segmentIndex}.ts`);
debug(`Response status: ${response.status} ${response.statusText}`);
debug(`Response headers: ${JSON.stringify([...response.headers])}`);
if (response.ok) {
const blob = await response.blob();
log(`✅ HLS segment ${segmentIndex} loaded successfully (ID: ${videoId}) - Size: ${(blob.size / 1024).toFixed(2)}KB`, 'success');
debug(`Segment size: ${blob.size} bytes`);
debug(`Content-Type: ${response.headers.get('content-type')}`);
} else {
const errorText = await response.text();
log(`❌ Failed to load HLS segment ${segmentIndex} (ID: ${videoId}): ${response.status} ${response.statusText}`, 'error');
debug(`Error response: ${errorText}`);
}
} catch (error) {
log(`❌ Network error testing segment (ID: ${videoId}): ${error.message}`, 'error');
debug(`Network error: ${error.stack}`);
}
}
function testVideoPlayer() {
log('Testing video player with HLS support', 'info');
debug('Initializing video player test');
const container = document.getElementById('video-container');
const status = document.getElementById('player-status');
// Clear previous player
container.innerHTML = '';
status.innerHTML = '';
// Create video element
const video = document.createElement('video');
video.controls = true;
video.style.width = '100%';
video.style.height = '100%';
video.style.backgroundColor = '#000';
container.appendChild(video);
// Test with HLS.js if available
if (Hls.isSupported()) {
debug('Hls.js is supported, creating HLS instance');
hls = new Hls({
debug: true,
enableWorker: true,
lowLatencyMode: true
});
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
debug('Media attached to HLS');
log('✅ HLS media attached', 'success');
});
hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
debug(`Manifest parsed, ${data.levels.length} quality levels available`);
log(`✅ HLS manifest parsed - ${data.levels.length} quality levels`, 'success');
status.innerHTML = `Ready: ${data.levels.length} quality levels available`;
});
hls.on(Hls.Events.ERROR, (event, data) => {
debug(`HLS Error: ${data.type} - ${data.details} (fatal: ${data.fatal})`);
log(`❌ HLS Error: ${data.type} - ${data.details}`, 'error');
status.innerHTML = `Error: ${data.type}`;
});
hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
debug(`Quality level switched to: ${data.level}`);
status.innerHTML = `Quality: Level ${data.level}`;
});
hls.loadSource('/api/stream/hls/109/playlist.m3u8');
hls.attachMedia(video);
log('Loading HLS stream for video ID 109', 'info');
status.innerHTML = 'Loading HLS stream...';
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
debug('Native HLS support detected (Safari)');
video.src = '/api/stream/hls/109/playlist.m3u8';
log('Using native HLS support', 'info');
status.innerHTML = 'Using native HLS support';
} else {
log('❌ HLS is not supported in this browser', 'error');
status.innerHTML = 'HLS not supported';
}
// Add video event listeners
video.addEventListener('loadedmetadata', () => {
log('✅ Video metadata loaded', 'success');
debug(`Video duration: ${video.duration}s`);
});
video.addEventListener('error', (e) => {
log(`❌ Video error: ${video.error?.message || 'Unknown error'}`, 'error');
debug(`Video error code: ${video.error?.code}`);
});
video.addEventListener('play', () => {
log('▶️ Video playback started', 'success');
});
}
// Load HLS.js
function loadHlsJs() {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';
script.onload = () => {
log('✅ HLS.js loaded successfully', 'success');
debug('HLS.js version: ' + Hls.version);
};
script.onerror = () => {
log('❌ Failed to load HLS.js', 'error');
};
document.head.appendChild(script);
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
log('HLS Test Interface loaded', 'info');
debug('Initializing HLS testing interface');
loadHlsJs();
});
</script>
</body>
</html>
<!--
USAGE INSTRUCTIONS:
1. Save this as test-hls.html in your project root
2. Navigate to http://localhost:3000/test-hls.html
3. Click the test buttons to verify HLS functionality
4. Check browser console for detailed debug information
-->
<!--
DEBUG CHECKLIST:
- Check browser console for HLS.js debug output
- Verify playlist loads correctly (should show M3U8 content)
- Test segment loading (should return binary data)
- Check network tab for any CORS or loading issues
- Verify error handling works by testing with invalid IDs
-->
<!--
COMMON ISSUES:
1. 404 errors: Check that routes are properly registered
2. CORS issues: Verify CORS headers in API responses
3. HLS.js errors: Check browser compatibility and version
4. Segment loading failures: Verify file paths and permissions
5. Playlist parsing errors: Check M3U8 format validity
-->"content_type":"text/html"}
</parameter>
</invoke>" file_path="/root/workspace/nextav/test-hls.html">