Files
usda-vision/camera-management-api/test_video_streaming.html

275 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>USDA Vision Camera - Video Streaming Test</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #2c3e50;
text-align: center;
}
.video-container {
margin: 20px 0;
text-align: center;
}
video {
width: 100%;
max-width: 800px;
height: auto;
border: 2px solid #3498db;
border-radius: 4px;
}
.video-info {
background: #ecf0f1;
padding: 10px;
margin: 10px 0;
border-radius: 4px;
font-family: monospace;
font-size: 14px;
}
.test-results {
margin: 20px 0;
padding: 15px;
background: #e8f5e8;
border-left: 4px solid #27ae60;
border-radius: 4px;
}
.error {
background: #fdf2f2;
border-left-color: #e74c3c;
}
button {
background: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background: #2980b9;
}
.video-list {
margin: 20px 0;
}
.video-item {
background: #f8f9fa;
padding: 10px;
margin: 5px 0;
border-radius: 4px;
cursor: pointer;
border: 1px solid #dee2e6;
}
.video-item:hover {
background: #e9ecef;
}
.video-item.selected {
background: #d1ecf1;
border-color: #3498db;
}
</style>
</head>
<body>
<div class="container">
<h1>🎥 USDA Vision Camera System - Video Streaming Test</h1>
<div class="test-results" id="testResults">
<strong>✅ Streaming Implementation Updated!</strong><br>
• Fixed progressive streaming with chunked responses<br>
• Added HTTP range request support (206 Partial Content)<br>
• Videos should now play in web browsers instead of downloading<br>
</div>
<div class="video-container">
<video id="videoPlayer" controls preload="metadata">
<source id="videoSource" src="" type="video/mp4">
Your browser does not support the video tag.
</video>
<div class="video-info" id="videoInfo">
<strong>Current Video:</strong> None selected<br>
<strong>Streaming URL:</strong> -<br>
<strong>Status:</strong> Ready to test
</div>
</div>
<div>
<button onclick="loadVideoList()">🔄 Load Available Videos</button>
<button onclick="testCurrentVideo()">🧪 Test Current Video</button>
<button onclick="checkStreamingHeaders()">📊 Check Streaming Headers</button>
</div>
<div class="video-list" id="videoList">
<p>Click "Load Available Videos" to see available MP4 files...</p>
</div>
<div id="debugInfo" class="video-info" style="display: none;">
<strong>Debug Information:</strong><br>
<div id="debugContent"></div>
</div>
</div>
<script>
const API_BASE = 'http://localhost:8000';
let currentVideoId = null;
let availableVideos = [];
async function loadVideoList() {
try {
const response = await fetch(`${API_BASE}/videos/?limit=20`);
const data = await response.json();
availableVideos = data.videos.filter(v => v.format === 'mp4' && v.is_streamable);
const videoListDiv = document.getElementById('videoList');
if (availableVideos.length === 0) {
videoListDiv.innerHTML = '<p>❌ No streamable MP4 videos found</p>';
return;
}
videoListDiv.innerHTML = `
<h3>📹 Available MP4 Videos (${availableVideos.length})</h3>
${availableVideos.map(video => `
<div class="video-item" onclick="selectVideo('${video.file_id}')">
<strong>${video.file_id}</strong><br>
<small>Camera: ${video.camera_name} | Size: ${(video.file_size_bytes / 1024 / 1024).toFixed(1)} MB | Status: ${video.status}</small>
</div>
`).join('')}
`;
// Auto-select first video
if (availableVideos.length > 0) {
selectVideo(availableVideos[0].file_id);
}
} catch (error) {
document.getElementById('videoList').innerHTML = `<p class="error">❌ Error loading videos: ${error.message}</p>`;
}
}
function selectVideo(fileId) {
currentVideoId = fileId;
const video = availableVideos.find(v => v.file_id === fileId);
// Update UI
document.querySelectorAll('.video-item').forEach(item => item.classList.remove('selected'));
event.target.closest('.video-item').classList.add('selected');
// Update video player
const streamingUrl = `${API_BASE}/videos/${fileId}/stream`;
document.getElementById('videoSource').src = streamingUrl;
document.getElementById('videoPlayer').load();
// Update info
document.getElementById('videoInfo').innerHTML = `
<strong>Current Video:</strong> ${fileId}<br>
<strong>Camera:</strong> ${video.camera_name}<br>
<strong>Size:</strong> ${(video.file_size_bytes / 1024 / 1024).toFixed(1)} MB<br>
<strong>Streaming URL:</strong> ${streamingUrl}<br>
<strong>Status:</strong> Ready to play
`;
}
async function testCurrentVideo() {
if (!currentVideoId) {
alert('Please select a video first');
return;
}
const streamingUrl = `${API_BASE}/videos/${currentVideoId}/stream`;
const debugDiv = document.getElementById('debugInfo');
const debugContent = document.getElementById('debugContent');
try {
// Test range request
const rangeResponse = await fetch(streamingUrl, {
headers: { 'Range': 'bytes=0-1023' }
});
// Test full request (with timeout)
const controller = new AbortController();
setTimeout(() => controller.abort(), 2000);
const fullResponse = await fetch(streamingUrl, {
signal: controller.signal
}).catch(e => ({ status: 'timeout', headers: new Map() }));
debugContent.innerHTML = `
<strong>Range Request Test (bytes=0-1023):</strong><br>
Status: ${rangeResponse.status} ${rangeResponse.statusText}<br>
Content-Type: ${rangeResponse.headers.get('content-type')}<br>
Content-Length: ${rangeResponse.headers.get('content-length')}<br>
Content-Range: ${rangeResponse.headers.get('content-range')}<br>
Accept-Ranges: ${rangeResponse.headers.get('accept-ranges')}<br><br>
<strong>Full Request Test:</strong><br>
Status: ${fullResponse.status || 'timeout (expected)'}<br>
Content-Type: ${fullResponse.headers?.get('content-type') || 'N/A'}<br>
<br><strong>Expected Results:</strong><br>
✅ Range request: 206 Partial Content<br>
✅ Content-Length: 1024<br>
✅ Content-Range: bytes 0-1023/[file_size]<br>
✅ Accept-Ranges: bytes<br>
✅ Full request: 200 OK (or timeout)
`;
debugDiv.style.display = 'block';
} catch (error) {
debugContent.innerHTML = `❌ Error testing video: ${error.message}`;
debugDiv.style.display = 'block';
}
}
async function checkStreamingHeaders() {
if (!currentVideoId) {
alert('Please select a video first');
return;
}
const streamingUrl = `${API_BASE}/videos/${currentVideoId}/stream`;
try {
const response = await fetch(streamingUrl, {
method: 'HEAD'
}).catch(async () => {
// If HEAD fails, try GET with range
return await fetch(streamingUrl, {
headers: { 'Range': 'bytes=0-0' }
});
});
const headers = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
alert(`Streaming Headers:\n${JSON.stringify(headers, null, 2)}`);
} catch (error) {
alert(`Error checking headers: ${error.message}`);
}
}
// Auto-load videos when page loads
window.addEventListener('load', loadVideoList);
</script>
</body>
</html>