260 lines
11 KiB
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"> |