Download static/app.js from Marnu25/x-tracker: direct link, hf CLI and curl.
- Browser
- Download file 37.8 kB
-
https://huggingface.co/spaces/Marnu25/x-tracker/resolve/main/static/app.js
- Command line
-
hf download hf://spaces/Marnu25/x-tracker/static/app.js
-
curl -L -o app.js https://huggingface.co/spaces/Marnu25/x-tracker/resolve/main/static/app.js
37.8 kB
let cameraRunning = false;
let isRecording = false;
let currentFilter = 'normal';
let activeCameraId = 'laptop_cam';
let galleryFilter = 'all';
let currentZoom = 1.0;
let lastUserZoomTime = 0;
let aiDetectionActive = true;
let hudOverlayActive = true;
let captionsActive = false;
let speechRecognizer = null;
let activeTargetId = null;
let activeTrackingMode = 'off';
let browserWebcamActive = false;
let browserWebcamStream = null;
let browserWebcamWs = null;
// DOM Elements
const streamFeed = document.getElementById('streamFeed');
const streamBox = document.getElementById('streamBox');
const standbyOverlay = document.getElementById('standbyOverlay');
const flashEffect = document.getElementById('flashEffect');
const overlayStartBtn = document.getElementById('overlayStartBtn');
const camOrb = document.getElementById('camOrb');
const camStateBadge = document.getElementById('camStateBadge');
const camResBadge = document.getElementById('camResBadge');
const camFpsBadge = document.getElementById('camFpsBadge');
const activeFilterBadge = document.getElementById('activeFilterBadge');
const gpuProviderBadge = document.getElementById('gpuProviderBadge');
const recordingTag = document.getElementById('recordingTag');
const recTimerText = document.getElementById('recTimerText');
const clientCount = document.getElementById('clientCount');
const camTabsContainer = document.getElementById('camTabsContainer');
const addCamBtn = document.getElementById('addCamBtn');
const browserWebcamBtn = document.getElementById('browserWebcamBtn');
const filterPills = document.querySelectorAll('.filter-pill');
// Zoom, PTZ Focus & AI Detection Elements
const zoomSlider = document.getElementById('zoomSlider');
const zoomValText = document.getElementById('zoomValText');
const zoomInBtn = document.getElementById('zoomInBtn');
const zoomOutBtn = document.getElementById('zoomOutBtn');
const zoomPresetBtns = document.querySelectorAll('.zoom-preset-btn');
const trackPills = document.querySelectorAll('.track-pill');
const resetFocusBtn = document.getElementById('resetFocusBtn');
const aiToggleBtn = document.getElementById('aiToggleBtn');
const aiBtnText = document.getElementById('aiBtnText');
const hudOverlayBtn = document.getElementById('hudOverlayBtn');
const hudOverlayText = document.getElementById('hudOverlayText');
const captionsToggleBtn = document.getElementById('captionsToggleBtn');
const captionsBtnText = document.getElementById('captionsBtnText');
const liveCaptionsBar = document.getElementById('liveCaptionsBar');
const liveCaptionsText = document.getElementById('liveCaptionsText');
const detectionsStrip = document.getElementById('detectionsStrip');
const sceneTitleText = document.getElementById('sceneTitleText');
const sceneDescText = document.getElementById('sceneDescText');
const sceneStatusPill = document.getElementById('sceneStatusPill');
const trackingScoreSummary = document.getElementById('trackingScoreSummary');
const trackingClassBars = document.getElementById('trackingClassBars');
const trackingPartsRow = document.getElementById('trackingPartsRow');
const powerBtn = document.getElementById('powerBtn');
const powerBtnText = document.getElementById('powerBtnText');
const snapBtn = document.getElementById('snapBtn');
const recordBtn = document.getElementById('recordBtn');
const recordBtnText = document.getElementById('recordBtnText');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const lanUrlCode = document.getElementById('lanUrlCode');
const tsUrlCode = document.getElementById('tsUrlCode');
const tsUrlContainer = document.getElementById('tsUrlContainer');
const mediaGrid = document.getElementById('mediaGrid');
const mediaCountBadge = document.getElementById('mediaCountBadge');
const filterBtns = document.querySelectorAll('.filter-btn');
const mediaModal = document.getElementById('mediaModal');
const modalTitle = document.getElementById('modalTitle');
const modalBody = document.getElementById('modalBody');
const modalCloseBtn = document.getElementById('modalCloseBtn');
const addCamModal = document.getElementById('addCamModal');
const addCamForm = document.getElementById('addCamForm');
const addCamCloseBtn = document.getElementById('addCamCloseBtn');
const cancelAddCamBtn = document.getElementById('cancelAddCamBtn');
const toastEl = document.getElementById('toast');
function showToast(msg, duration = 3000) {
toastEl.textContent = msg;
toastEl.classList.remove('hidden');
setTimeout(() => {
toastEl.classList.add('hidden');
}, duration);
}
function formatTimer(seconds) {
const mins = Math.floor(seconds / 60).toString().padStart(2, '0');
const secs = (seconds % 60).toString().padStart(2, '0');
return `${mins}:${secs}`;
}
function copyUrl(elementId) {
const text = document.getElementById(elementId).textContent;
navigator.clipboard.writeText(text).then(() => {
showToast(`Copied: ${text}`);
}).catch(() => {
showToast(`Selected: ${text}`);
});
}
// Camera Tabs
function renderCameraTabs(cameras, activeId) {
camTabsContainer.innerHTML = cameras.map(cam => {
const isActive = (cam.id === activeId);
const isDefault = cam.is_default;
return `
<div class="cam-tab ${isActive ? 'active' : ''}" onclick="switchCamera('${cam.id}')">
<span>${cam.name}</span>
${!isDefault ? `<span class="cam-tab-del" onclick="deleteCamera(event, '${cam.id}')" title="Delete camera">×</span>` : ''}
</div>
`;
}).join('');
}
async function switchCamera(camId) {
if (camId === activeCameraId) return;
try {
const res = await fetch(`/api/cameras/active/${camId}`, { method: 'POST' });
const data = await res.json();
if (res.ok) {
activeCameraId = camId;
showToast(`Switched to: ${data.active_camera.name}`);
streamFeed.src = `/api/stream?t=${Date.now()}`;
await updateStatus();
} else {
showToast(`Error: ${data.detail || 'Failed to switch camera'}`);
}
} catch (e) {
showToast('Camera switch request failed');
}
}
async function deleteCamera(e, camId) {
e.stopPropagation();
if (!confirm('Are you sure you want to remove this IP camera?')) return;
try {
const res = await fetch(`/api/cameras/${camId}`, { method: 'DELETE' });
const data = await res.json();
if (res.ok) {
showToast('Camera removed');
await updateStatus();
} else {
showToast(`Error: ${data.detail || 'Delete failed'}`);
}
} catch (e) {
showToast('Delete request failed');
}
}
// Filter Control (Instant Real-Time)
async function setFilter(filterName) {
try {
const res = await fetch(`/api/filter/${filterName}`, { method: 'POST' });
const data = await res.json();
if (res.ok) {
currentFilter = filterName;
activeFilterBadge.textContent = filterName.toUpperCase();
filterPills.forEach(pill => {
pill.classList.toggle('active', pill.dataset.filter === filterName);
});
showToast(`Active Filter: ${filterName.toUpperCase()}`);
} else {
showToast(`Error: ${data.detail || 'Filter failed'}`);
}
} catch (e) {
showToast('Filter request failed');
}
}
// Digital Zoom & PTZ Focus
let zoomServerDebounce = null;
async function applyZoom(val) {
const num = Math.max(1.0, Math.min(4.0, Math.round(parseFloat(val) * 10) / 10));
currentZoom = num;
lastUserZoomTime = Date.now();
if (zoomSlider) zoomSlider.value = num;
if (zoomValText) zoomValText.textContent = `${num.toFixed(1)}x`;
zoomPresetBtns.forEach(btn => {
const pZoom = parseFloat(btn.dataset.zoom);
btn.classList.toggle('active', Math.abs(pZoom - num) < 0.05);
});
if (streamFeed) {
streamFeed.style.transform = (num > 1.0) ? `scale(${1.0 + (num - 1.0) * 0.15})` : 'scale(1)';
}
clearTimeout(zoomServerDebounce);
zoomServerDebounce = setTimeout(async () => {
try {
await fetch(`/api/zoom/${num}`, { method: 'POST' });
} catch (e) {
console.error("Zoom server update error", e);
}
}, 80);
}
// Targeted Focus on Point (Click on Stream)
async function focusOnPoint(cx, cy) {
const targetZoom = Math.max(2.0, currentZoom);
currentZoom = targetZoom;
lastUserZoomTime = Date.now();
if (zoomSlider) zoomSlider.value = targetZoom;
if (zoomValText) zoomValText.textContent = `${targetZoom.toFixed(1)}x`;
try {
const res = await fetch('/api/zoom/focus', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ x: cx, y: cy, zoom: targetZoom })
});
if (res.ok) {
showToast(`🎯 Centered Focus on (${(cx*100).toFixed(0)}%, ${(cy*100).toFixed(0)}%)`);
}
} catch (e) {
console.error("Focus error", e);
}
}
// Targeted Focus on Specific Object / Body Part
async function focusOnObject(objId, label) {
lastUserZoomTime = Date.now();
activeTargetId = objId;
try {
const res = await fetch(`/api/zoom/target/${objId}`, { method: 'POST' });
if (res.ok) {
showToast(`🎯 Zoom Locked on ${label.toUpperCase()} (${objId})`);
activeTrackingMode = 'selected';
trackPills.forEach(pill => pill.classList.remove('active'));
}
} catch (e) {
showToast('Failed to lock target object');
}
}
// Set Auto-Tracking Mode
async function setAutoTracking(mode) {
try {
const res = await fetch(`/api/zoom/track/${mode}`, { method: 'POST' });
if (res.ok) {
activeTrackingMode = mode;
trackPills.forEach(pill => {
pill.classList.toggle('active', pill.dataset.mode === mode);
});
showToast(`Auto-Tracking: ${mode.toUpperCase()}`);
if (mode !== 'off' && currentZoom <= 1.1) {
applyZoom(2.0);
}
}
} catch (e) {
showToast('Tracking mode request failed');
}
}
// Reset Zoom & PTZ Centering
async function resetZoomAndFocus() {
lastUserZoomTime = Date.now();
currentZoom = 1.0;
activeTargetId = null;
activeTrackingMode = 'off';
if (zoomSlider) zoomSlider.value = 1.0;
if (zoomValText) zoomValText.textContent = '1.0x';
if (streamFeed) streamFeed.style.transform = 'scale(1)';
trackPills.forEach(pill => {
pill.classList.toggle('active', pill.dataset.mode === 'off');
});
zoomPresetBtns.forEach(btn => {
btn.classList.toggle('active', parseFloat(btn.dataset.zoom) === 1.0);
});
try {
await fetch('/api/zoom/reset', { method: 'POST' });
showToast('⟲ Zoom & Focus Reset to Normal');
} catch (e) {
console.error("Reset error", e);
}
}
// AI Detection Toggle (YOLOv8 + Body-Parts Pose Engine)
async function toggleAIDetection() {
try {
const res = await fetch('/api/detection/toggle', { method: 'POST' });
const data = await res.json();
if (res.ok) {
aiDetectionActive = data.ai_detection_enabled;
aiToggleBtn.classList.toggle('active', aiDetectionActive);
aiBtnText.textContent = `AI Engine: ${aiDetectionActive ? 'ON' : 'OFF'}`;
showToast(data.message);
}
} catch (e) {
showToast('Detection toggle failed');
}
}
// Toggle HUD Boxes & Wireframe Visibility (Tracking stays active without overlay)
async function toggleHudOverlay() {
const nextState = !hudOverlayActive;
try {
const res = await fetch(`/api/overlay/${nextState}`, { method: 'POST' });
const data = await res.json();
if (res.ok) {
hudOverlayActive = data.show_hud_overlay;
if (hudOverlayBtn && hudOverlayText) {
hudOverlayBtn.classList.toggle('active', hudOverlayActive);
hudOverlayText.textContent = `👁️ HUD Boxes: ${hudOverlayActive ? 'ON' : 'OFF'}`;
}
showToast(data.message);
}
} catch (e) {
showToast('Failed to toggle HUD overlay');
}
}
// Toggle Real-Time Spoken Dialogue Captions (Web Speech API)
function toggleLiveCaptions() {
const SpeechRec = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRec) {
showToast('Browser Speech Recognition API not supported in this browser');
return;
}
captionsActive = !captionsActive;
if (captionsToggleBtn && captionsBtnText) {
captionsToggleBtn.classList.toggle('active', captionsActive);
captionsBtnText.textContent = `💬 Captions: ${captionsActive ? 'ON' : 'OFF'}`;
}
if (captionsActive) {
if (liveCaptionsBar) liveCaptionsBar.classList.remove('hidden');
if (!speechRecognizer) {
speechRecognizer = new SpeechRec();
speechRecognizer.continuous = true;
speechRecognizer.interimResults = true;
speechRecognizer.lang = navigator.language || 'en-US';
speechRecognizer.onresult = (event) => {
let transcript = '';
for (let i = Math.max(0, event.results.length - 2); i < event.results.length; i++) {
transcript += event.results[i][0].transcript + ' ';
}
if (liveCaptionsText && transcript.trim()) {
liveCaptionsText.textContent = transcript.trim();
}
};
speechRecognizer.onerror = () => {};
speechRecognizer.onend = () => {
if (captionsActive) {
try { speechRecognizer.start(); } catch (_) {}
}
};
}
try {
speechRecognizer.start();
showToast('Live Dialogue Captions enabled');
} catch (_) {}
} else {
if (liveCaptionsBar) liveCaptionsBar.classList.add('hidden');
if (speechRecognizer) {
try { speechRecognizer.stop(); } catch (_) {}
}
showToast('Live Dialogue Captions hidden');
}
}
// Render Dedicated Scene Intelligence & Real-Time Pose/Object Telemetry Panel
function renderSceneIntelligence(scene) {
if (!scene) return;
if (sceneTitleText && scene.title) {
sceneTitleText.textContent = scene.title;
}
if (sceneDescText && scene.description) {
sceneDescText.textContent = scene.description;
}
const details = scene.tracking_details || {};
if (sceneStatusPill) {
const statusStr = details.status || 'ACTIVE TRACKING';
sceneStatusPill.textContent = statusStr;
sceneStatusPill.className = 'badge badge-sfw';
}
if (trackingScoreSummary) {
const prov = details.provider || 'ONNX Runtime';
const total = details.total_targets ?? 0;
trackingScoreSummary.textContent = `${prov} | ${total} Targets`;
}
if (trackingClassBars && details.category_breakdown) {
const entries = Object.entries(details.category_breakdown);
trackingClassBars.innerHTML = entries.map(([cls, count]) => {
const pct = Math.min(100, Math.max(4, count * 18));
const barColor = cls === 'Body Parts' ? '#10b981' : (cls === 'People' ? '#3b82f6' : '#8b5cf6');
return `
<div class="tracking-bar-item">
<div class="tracking-bar-label">
<span>${cls}</span>
<span>${count}</span>
</div>
<div class="tracking-bar-track">
<div class="tracking-bar-fill" style="width: ${pct}%; background: ${barColor};"></div>
</div>
</div>
`;
}).join('');
}
if (trackingPartsRow) {
const parts = details.visible_body_parts || [];
if (parts.length === 0) {
trackingPartsRow.innerHTML = `<span class="det-placeholder">No body segments currently in frame</span>`;
} else {
trackingPartsRow.innerHTML = parts.map(p => {
return `<span class="badge badge-secondary">${p.toUpperCase()}</span>`;
}).join('');
}
}
}
// Render Detected Objects Chips (Expanded 80 Objects + Body Parts with Click-to-Focus)
function renderDetections(detections) {
if (!aiDetectionActive || !detections || detections.length === 0) {
detectionsStrip.innerHTML = `<span class="det-placeholder">${aiDetectionActive ? 'Scanning with YOLOv8 & Body-Parts Pose...' : 'AI Engine is OFF (Click to Enable)'}</span>`;
return;
}
const icons = {
person: '👤',
'head / face': '🧑',
'left hand': '✋',
'right hand': '✋',
'left arm': '💪',
'right arm': '💪',
'torso / chest': '👕',
'chest / torso': '👕',
'pelvic / hips': '🩳',
'left leg': '🦵',
'right leg': '🦵',
'left foot': '🦶',
'right foot': '🦶',
'cell phone': '📱',
laptop: '💻',
mouse: '🖱️',
keyboard: '⌨️',
tv: '🖥️',
bottle: '🍾',
cup: '☕',
chair: '🪑',
couch: '🛋️',
book: '📖',
clock: '⏰',
car: '🚗',
dog: '🐕',
cat: '🐈'
};
detectionsStrip.innerHTML = detections.map(d => {
const baseKey = d.base_label || d.label;
const icon = icons[baseKey] || '🎯';
const isLocked = (activeTargetId && d.id === activeTargetId);
return `
<div class="det-chip clickable ${isLocked ? 'target-locked' : ''}" onclick="focusOnObject('${d.id}', '${d.label}')" title="Click to zoom and lock focus onto this ${d.label}">
<span>${icon}</span>
<span>${d.label.toUpperCase()} ${Math.round(d.confidence)}%</span>
</div>
`;
}).join('');
}
// Status Poller
async function updateStatus() {
try {
const res = await fetch('/api/status');
if (!res.ok) return;
const data = await res.json();
// Endpoints
lanUrlCode.textContent = data.network.lan_url;
if (data.network.tailscale_url) {
tsUrlCode.textContent = data.network.tailscale_url;
tsUrlContainer.style.display = 'flex';
} else {
tsUrlContainer.style.display = 'none';
}
// Camera List
if (data.camera.cameras) {
renderCameraTabs(data.camera.cameras, data.camera.active_camera_id);
activeCameraId = data.camera.active_camera_id;
}
// Filter
if (data.camera.active_filter) {
currentFilter = data.camera.active_filter;
activeFilterBadge.textContent = currentFilter.toUpperCase();
filterPills.forEach(pill => {
pill.classList.toggle('active', pill.dataset.filter === currentFilter);
});
}
// Zoom Level Sync (only if user has not interacted recently)
if (data.camera.zoom_level !== undefined && (Date.now() - lastUserZoomTime > 3500)) {
currentZoom = data.camera.zoom_level;
if (zoomSlider && document.activeElement !== zoomSlider) {
zoomSlider.value = currentZoom;
}
if (zoomValText) {
zoomValText.textContent = `${currentZoom.toFixed(1)}x`;
}
zoomPresetBtns.forEach(btn => {
const pZoom = parseFloat(btn.dataset.zoom);
btn.classList.toggle('active', Math.abs(pZoom - currentZoom) < 0.05);
});
}
// Tracking Mode & Target ID Sync
if (data.camera.auto_tracking_mode) {
activeTrackingMode = data.camera.auto_tracking_mode;
trackPills.forEach(pill => {
pill.classList.toggle('active', pill.dataset.mode === activeTrackingMode);
});
}
activeTargetId = data.camera.target_object_id;
// AI Detection & Detections Strip
if (data.camera.ai_detection_enabled !== undefined) {
aiDetectionActive = data.camera.ai_detection_enabled;
aiToggleBtn.classList.toggle('active', aiDetectionActive);
aiBtnText.textContent = `AI Engine: ${aiDetectionActive ? 'ON' : 'OFF'}`;
renderDetections(data.camera.detections);
}
// HUD Overlay Visibility Sync
if (data.camera.show_hud_overlay !== undefined && hudOverlayBtn && hudOverlayText) {
hudOverlayActive = data.camera.show_hud_overlay;
hudOverlayBtn.classList.toggle('active', hudOverlayActive);
hudOverlayText.textContent = `👁️ HUD Boxes: ${hudOverlayActive ? 'ON' : 'OFF'}`;
}
// Scene Intelligence & Tracking Telemetry Panel
if (data.camera.scene) {
renderSceneIntelligence(data.camera.scene);
}
// GPU / AI Tracker Provider Badge
if (gpuProviderBadge) {
const telemetry = data.camera.telemetry || {};
const isGpu = telemetry.gpu_active || (telemetry.provider && telemetry.provider.includes('CUDA'));
const count = (data.camera.detections || []).length;
gpuProviderBadge.textContent = isGpu ? `⚡ T4 GPU (${count} OBJ)` : `🟢 AI TRACKER (${count} OBJ)`;
gpuProviderBadge.className = 'badge badge-sfw';
}
// Camera Sensor State
cameraRunning = data.camera.running;
if (cameraRunning) {
camOrb.classList.add('live');
camStateBadge.textContent = 'ONLINE';
camStateBadge.className = 'badge badge-success';
camResBadge.textContent = `${data.camera.width}x${data.camera.height}`;
camFpsBadge.textContent = `${data.camera.fps} FPS`;
clientCount.textContent = data.camera.client_count;
powerBtnText.textContent = 'Standby';
powerBtn.classList.remove('active-standby');
snapBtn.disabled = false;
recordBtn.disabled = false;
standbyOverlay.classList.add('hidden');
if (!streamFeed.src || streamFeed.src === window.location.href || streamFeed.classList.contains('hidden') || streamFeed.getAttribute('src') === '') {
streamFeed.src = `/api/stream?t=${Date.now()}`;
streamFeed.classList.remove('hidden');
}
} else {
camOrb.classList.remove('live');
camStateBadge.textContent = 'STANDBY';
camStateBadge.className = 'badge badge-secondary';
camResBadge.textContent = 'IDLE';
camFpsBadge.textContent = '0.0 FPS';
clientCount.textContent = '0';
powerBtnText.textContent = 'Power On';
powerBtn.classList.add('active-standby');
snapBtn.disabled = true;
recordBtn.disabled = true;
streamFeed.classList.add('hidden');
standbyOverlay.classList.remove('hidden');
}
// Recording State
isRecording = data.recorder.is_recording;
if (isRecording) {
recordingTag.classList.remove('hidden');
recTimerText.textContent = formatTimer(data.recorder.elapsed_seconds);
recordBtn.classList.add('recording');
recordBtnText.textContent = 'Stop Recording';
} else {
recordingTag.classList.add('hidden');
recordBtn.classList.remove('recording');
recordBtnText.textContent = 'Record Video';
}
} catch (e) {
camStateBadge.textContent = 'OFFLINE';
camStateBadge.className = 'badge badge-danger';
}
}
// Power Toggle
async function toggleCameraPower() {
powerBtn.disabled = true;
try {
const endpoint = cameraRunning ? '/api/camera/stop' : '/api/camera/start';
const res = await fetch(endpoint, { method: 'POST' });
const data = await res.json();
if (res.ok) {
showToast(data.message);
await updateStatus();
} else {
showToast(`Error: ${data.detail || 'Action failed'}`);
}
} catch (e) {
showToast('Failed to contact camera station');
} finally {
powerBtn.disabled = false;
}
}
// Snapshot
async function takeSnapshot() {
snapBtn.disabled = true;
flashEffect.classList.add('trigger');
setTimeout(() => flashEffect.classList.remove('trigger'), 150);
try {
const res = await fetch('/api/snapshot', { method: 'POST' });
const data = await res.json();
if (res.ok) {
showToast(`📸 Snapshot saved: ${data.filename}`);
loadGallery();
} else {
showToast(`Snapshot error: ${data.detail || 'Failed'}`);
}
} catch (e) {
showToast('Snapshot request failed');
} finally {
snapBtn.disabled = false;
}
}
// Recording Toggle
async function toggleRecording() {
recordBtn.disabled = true;
try {
if (!isRecording) {
const res = await fetch('/api/record/start', { method: 'POST' });
const data = await res.json();
if (res.ok) {
showToast(`🎥 Recording started: ${data.filename}`);
} else {
showToast(`Error: ${data.detail || 'Failed'}`);
}
} else {
const res = await fetch('/api/record/stop', { method: 'POST' });
const data = await res.json();
if (res.ok) {
showToast(`💾 Saved: ${data.info.filename} (${data.info.duration}s)`);
loadGallery();
} else {
showToast('Error stopping recording');
}
}
await updateStatus();
} catch (e) {
showToast('Recording request failed');
} finally {
recordBtn.disabled = false;
}
}
// Gallery Loader
async function loadGallery() {
try {
const res = await fetch('/api/media');
if (!res.ok) return;
const data = await res.json();
let items = [];
if (galleryFilter === 'all' || galleryFilter === 'image') {
items = items.concat(data.snapshots);
}
if (galleryFilter === 'all' || galleryFilter === 'video') {
items = items.concat(data.recordings);
}
items.sort((a, b) => (b.name > a.name ? 1 : -1));
mediaCountBadge.textContent = `${items.length} items`;
if (items.length === 0) {
mediaGrid.innerHTML = `
<div class="empty-state">
<p>No media captured yet.</p>
<small>Snap a photo or start recording to populate the gallery.</small>
</div>
`;
return;
}
mediaGrid.innerHTML = items.map(item => {
if (item.type === 'image') {
return `
<div class="media-item">
<div class="media-thumb-wrap" onclick="openMedia('image', '${item.url}', '${item.name}')">
<img src="${item.url}" class="media-thumb" loading="lazy">
</div>
<div class="media-info">
<div class="media-meta-left">
<span class="media-name">${item.name}</span>
<span class="media-sub">${item.size_formatted} • ${item.modified.split(' ')[1]}</span>
</div>
<div class="media-actions">
<a href="${item.url}" download class="media-btn" title="Download">⬇</a>
</div>
</div>
</div>
`;
} else {
return `
<div class="media-item">
<div class="media-thumb-wrap" onclick="openMedia('video', '${item.url}', '${item.name}')">
<video src="${item.url}" class="media-thumb" preload="metadata"></video>
<div class="play-badge">►</div>
</div>
<div class="media-info">
<div class="media-meta-left">
<span class="media-name">${item.name}</span>
<span class="media-sub">${item.size_formatted} • ${item.modified.split(' ')[1]}</span>
</div>
<div class="media-actions">
<a href="${item.url}" download class="media-btn" title="Download">⬇</a>
</div>
</div>
</div>
`;
}
}).join('');
} catch (e) {
console.error("Gallery load error:", e);
}
}
// Media Modal
function openMedia(type, url, title) {
modalTitle.textContent = title;
if (type === 'image') {
modalBody.innerHTML = `<img src="${url}">`;
} else {
modalBody.innerHTML = `<video src="${url}" controls autoplay style="width:100%; height:auto;"></video>`;
}
mediaModal.classList.remove('hidden');
}
function closeModal() {
modalBody.innerHTML = '';
mediaModal.classList.add('hidden');
}
modalCloseBtn.addEventListener('click', closeModal);
document.querySelector('#mediaModal .modal-backdrop').addEventListener('click', closeModal);
// Add Camera Modal
addCamBtn.addEventListener('click', () => {
addCamModal.classList.remove('hidden');
});
function closeAddCamModal() {
addCamModal.classList.add('hidden');
addCamForm.reset();
}
addCamCloseBtn.addEventListener('click', closeAddCamModal);
cancelAddCamBtn.addEventListener('click', closeAddCamModal);
document.querySelector('#addCamModal .modal-backdrop').addEventListener('click', closeAddCamModal);
addCamForm.addEventListener('submit', async (e) => {
e.preventDefault();
const name = document.getElementById('camNameInput').value.trim();
const type = document.getElementById('camTypeSelect').value;
const source = document.getElementById('camSourceInput').value.trim();
try {
const res = await fetch('/api/cameras', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, type, source })
});
const data = await res.json();
if (res.ok) {
showToast(`Camera '${name}' linked successfully!`);
closeAddCamModal();
await updateStatus();
} else {
showToast(`Error: ${data.detail || 'Failed to add camera'}`);
}
} catch (err) {
showToast('Failed to contact server');
}
});
// Fullscreen
fullscreenBtn.addEventListener('click', () => {
if (!document.fullscreenElement) {
streamBox.requestFullscreen().catch(err => alert(err.message));
} else {
document.exitFullscreen();
}
});
// Real-Time Visual Filter Listeners
filterPills.forEach(pill => {
pill.addEventListener('click', () => {
const f = pill.dataset.filter;
setFilter(f);
});
});
// Digital Zoom Slider & Preset Buttons
zoomSlider.addEventListener('input', (e) => {
applyZoom(e.target.value);
});
zoomInBtn.addEventListener('click', () => {
applyZoom(currentZoom + 0.2);
});
zoomOutBtn.addEventListener('click', () => {
applyZoom(currentZoom - 0.2);
});
zoomPresetBtns.forEach(btn => {
btn.addEventListener('click', () => {
applyZoom(parseFloat(btn.dataset.zoom));
});
});
// Auto-Tracking Target Listeners
trackPills.forEach(pill => {
pill.addEventListener('click', () => {
const mode = pill.dataset.mode;
setAutoTracking(mode);
});
});
resetFocusBtn.addEventListener('click', resetZoomAndFocus);
// Interactive Viewport Click-to-Focus, Wheel Zoom & Double-Click
streamBox.addEventListener('click', (e) => {
if (e.target.closest('#standbyOverlay')) return;
const rect = streamBox.getBoundingClientRect();
const clickX = (e.clientX - rect.left) / rect.width;
const clickY = (e.clientY - rect.top) / rect.height;
focusOnPoint(clickX, clickY);
});
streamBox.addEventListener('wheel', (e) => {
e.preventDefault();
const step = e.deltaY < 0 ? 0.2 : -0.2;
applyZoom(currentZoom + step);
}, { passive: false });
streamBox.addEventListener('dblclick', (e) => {
e.preventDefault();
const targetZoom = (currentZoom > 1.2) ? 1.0 : 2.0;
applyZoom(targetZoom);
});
// AI Detection, HUD Overlay & Captions Toggles
aiToggleBtn.addEventListener('click', toggleAIDetection);
if (hudOverlayBtn) {
hudOverlayBtn.addEventListener('click', toggleHudOverlay);
}
if (captionsToggleBtn) {
captionsToggleBtn.addEventListener('click', toggleLiveCaptions);
}
// Action Buttons
powerBtn.addEventListener('click', toggleCameraPower);
overlayStartBtn.addEventListener('click', toggleCameraPower);
snapBtn.addEventListener('click', takeSnapshot);
recordBtn.addEventListener('click', toggleRecording);
// Gallery Filter Tabs
filterBtns.forEach(btn => {
btn.addEventListener('click', () => {
filterBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
galleryFilter = btn.dataset.filter;
loadGallery();
});
});
// High-FPS Browser Webcam Bridge (WebSocket + HTTP Fallback)
const hiddenVideo = document.createElement('video');
hiddenVideo.autoplay = true;
hiddenVideo.playsInline = true;
hiddenVideo.muted = true;
const hiddenCanvas = document.createElement('canvas');
hiddenCanvas.width = 640;
hiddenCanvas.height = 360;
const hiddenCtx = hiddenCanvas.getContext('2d');
let pushingFrame = false;
function startBrowserWebcamBridge(silent = false) {
if (browserWebcamActive) return;
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
if (!silent) showToast('Browser Webcam API requires HTTPS or localhost');
return;
}
navigator.mediaDevices.getUserMedia({
video: { width: { ideal: 640 }, height: { ideal: 360 }, frameRate: { ideal: 30 } },
audio: false
}).then(stream => {
browserWebcamStream = stream;
hiddenVideo.srcObject = stream;
browserWebcamActive = true;
if (browserWebcamBtn) {
browserWebcamBtn.classList.add('active');
browserWebcamBtn.innerHTML = '<span>🟢 Webcam Active</span>';
}
if (!silent) showToast('📷 Browser Webcam linked at high FPS');
// Open binary WebSocket for ultra-low-latency frame streaming
try {
const wsProto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
browserWebcamWs = new WebSocket(`${wsProto}//${window.location.host}/ws/frame`);
} catch (_) {
browserWebcamWs = null;
}
function pumpNextFrame() {
if (!browserWebcamActive) return;
if (pushingFrame || hiddenVideo.readyState < 2) {
setTimeout(pumpNextFrame, 25);
return;
}
pushingFrame = true;
hiddenCtx.drawImage(hiddenVideo, 0, 0, 640, 360);
hiddenCanvas.toBlob(blob => {
if (!blob || !browserWebcamActive) {
pushingFrame = false;
return;
}
if (browserWebcamWs && browserWebcamWs.readyState === WebSocket.OPEN) {
blob.arrayBuffer().then(buf => {
browserWebcamWs.send(buf);
pushingFrame = false;
setTimeout(pumpNextFrame, 35);
}).catch(() => {
pushingFrame = false;
setTimeout(pumpNextFrame, 45);
});
} else {
fetch('/api/frame/push', { method: 'POST', body: blob })
.catch(() => {})
.finally(() => {
pushingFrame = false;
setTimeout(pumpNextFrame, 40);
});
}
}, 'image/jpeg', 0.80);
}
pumpNextFrame();
}).catch(() => {
if (!silent) showToast('Camera permission denied or no webcam found');
});
}
function stopBrowserWebcamBridge() {
browserWebcamActive = false;
if (browserWebcamWs) {
try { browserWebcamWs.close(); } catch (_) {}
browserWebcamWs = null;
}
if (browserWebcamStream) {
browserWebcamStream.getTracks().forEach(t => t.stop());
browserWebcamStream = null;
}
if (browserWebcamBtn) {
browserWebcamBtn.classList.remove('active');
browserWebcamBtn.innerHTML = '<span>📷 Browser Webcam</span>';
}
showToast('Browser Webcam disconnected');
}
if (browserWebcamBtn) {
browserWebcamBtn.addEventListener('click', () => {
if (browserWebcamActive) {
stopBrowserWebcamBridge();
} else {
startBrowserWebcamBridge(false);
}
});
}
// Lifecycle Boot
setInterval(updateStatus, 1000);
updateStatus();
loadGallery();
// Auto-start Browser Webcam Bridge on Hugging Face Spaces
if (window.location.hostname.includes('hf.space') || window.location.search.includes('browser_cam=1')) {
startBrowserWebcamBridge(true);
}
let cameraRunning = false;
let isRecording = false;
let currentFilter = 'normal';
let activeCameraId = 'laptop_cam';
let galleryFilter = 'all';
let currentZoom = 1.0;
let lastUserZoomTime = 0;
let aiDetectionActive = true;
let hudOverlayActive = true;
let captionsActive = false;
let speechRecognizer = null;
let activeTargetId = null;
let activeTrackingMode = 'off';
let browserWebcamActive = false;
let browserWebcamStream = null;
let browserWebcamWs = null;
// DOM Elements
const streamFeed = document.getElementById('streamFeed');
const streamBox = document.getElementById('streamBox');
const standbyOverlay = document.getElementById('standbyOverlay');
const flashEffect = document.getElementById('flashEffect');
const overlayStartBtn = document.getElementById('overlayStartBtn');
const camOrb = document.getElementById('camOrb');
const camStateBadge = document.getElementById('camStateBadge');
const camResBadge = document.getElementById('camResBadge');
const camFpsBadge = document.getElementById('camFpsBadge');
const activeFilterBadge = document.getElementById('activeFilterBadge');
const gpuProviderBadge = document.getElementById('gpuProviderBadge');
const recordingTag = document.getElementById('recordingTag');
const recTimerText = document.getElementById('recTimerText');
const clientCount = document.getElementById('clientCount');
const camTabsContainer = document.getElementById('camTabsContainer');
const addCamBtn = document.getElementById('addCamBtn');
const browserWebcamBtn = document.getElementById('browserWebcamBtn');
const filterPills = document.querySelectorAll('.filter-pill');
// Zoom, PTZ Focus & AI Detection Elements
const zoomSlider = document.getElementById('zoomSlider');
const zoomValText = document.getElementById('zoomValText');
const zoomInBtn = document.getElementById('zoomInBtn');
const zoomOutBtn = document.getElementById('zoomOutBtn');
const zoomPresetBtns = document.querySelectorAll('.zoom-preset-btn');
const trackPills = document.querySelectorAll('.track-pill');
const resetFocusBtn = document.getElementById('resetFocusBtn');
const aiToggleBtn = document.getElementById('aiToggleBtn');
const aiBtnText = document.getElementById('aiBtnText');
const hudOverlayBtn = document.getElementById('hudOverlayBtn');
const hudOverlayText = document.getElementById('hudOverlayText');
const captionsToggleBtn = document.getElementById('captionsToggleBtn');
const captionsBtnText = document.getElementById('captionsBtnText');
const liveCaptionsBar = document.getElementById('liveCaptionsBar');
const liveCaptionsText = document.getElementById('liveCaptionsText');
const detectionsStrip = document.getElementById('detectionsStrip');
const sceneTitleText = document.getElementById('sceneTitleText');
const sceneDescText = document.getElementById('sceneDescText');
const sceneStatusPill = document.getElementById('sceneStatusPill');
const trackingScoreSummary = document.getElementById('trackingScoreSummary');
const trackingClassBars = document.getElementById('trackingClassBars');
const trackingPartsRow = document.getElementById('trackingPartsRow');
const powerBtn = document.getElementById('powerBtn');
const powerBtnText = document.getElementById('powerBtnText');
const snapBtn = document.getElementById('snapBtn');
const recordBtn = document.getElementById('recordBtn');
const recordBtnText = document.getElementById('recordBtnText');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const lanUrlCode = document.getElementById('lanUrlCode');
const tsUrlCode = document.getElementById('tsUrlCode');
const tsUrlContainer = document.getElementById('tsUrlContainer');
const mediaGrid = document.getElementById('mediaGrid');
const mediaCountBadge = document.getElementById('mediaCountBadge');
const filterBtns = document.querySelectorAll('.filter-btn');
const mediaModal = document.getElementById('mediaModal');
const modalTitle = document.getElementById('modalTitle');
const modalBody = document.getElementById('modalBody');
const modalCloseBtn = document.getElementById('modalCloseBtn');
const addCamModal = document.getElementById('addCamModal');
const addCamForm = document.getElementById('addCamForm');
const addCamCloseBtn = document.getElementById('addCamCloseBtn');
const cancelAddCamBtn = document.getElementById('cancelAddCamBtn');
const toastEl = document.getElementById('toast');
function showToast(msg, duration = 3000) {
toastEl.textContent = msg;
toastEl.classList.remove('hidden');
setTimeout(() => {
toastEl.classList.add('hidden');
}, duration);
}