const eventTypeFilter = document.getElementById("event-type-filter"); const dateFilter = document.getElementById("date-filter"); const eventList = document.getElementById("event-list"); const player = document.getElementById("player"); const playerInfo = document.getElementById("player-info"); const statsEl = document.getElementById("stats"); const cacheInfoEl = document.getElementById("cache-info"); const clearCacheBtn = document.getElementById("clear-cache-btn"); const EVENT_TYPE_LABELS = { person_presence: "人物出现", fall: "摔倒", activity: "活动", }; function formatEventLabel(event) { const typeName = EVENT_TYPE_LABELS[event.event_type] || event.event_type; return event.label ? `${typeName} · ${event.label}` : typeName; } async function loadStats() { const res = await fetch("/api/stats"); const stats = await res.json(); const parts = Object.entries(stats).map( ([type, count]) => `${EVENT_TYPE_LABELS[type] || type}: ${count}` ); statsEl.textContent = parts.join(" | "); } function formatBytes(bytes) { if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`; return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`; } async function loadCacheStats() { const res = await fetch("/api/cache/stats"); const stats = await res.json(); cacheInfoEl.textContent = `转码缓存:${stats.file_count} 个文件,共占用 ${formatBytes(stats.total_size_bytes)}`; } clearCacheBtn.addEventListener("click", async () => { if (!confirm("确认清空所有转码缓存吗?清空后再次播放这些视频需要重新转码。")) return; const res = await fetch("/api/cache/clear", { method: "POST" }); const result = await res.json(); alert(`已清理 ${result.deleted} 个文件,释放 ${formatBytes(result.freed_bytes)} 空间`); loadCacheStats(); }); async function loadEvents() { const params = new URLSearchParams(); if (eventTypeFilter.value) params.set("event_type", eventTypeFilter.value); if (dateFilter.value) params.set("date", dateFilter.value.replaceAll("-", "")); const res = await fetch(`/api/events?${params.toString()}`); const events = await res.json(); renderEventList(events); } function renderEventList(events) { eventList.innerHTML = ""; for (const event of events) { const item = document.createElement("div"); item.className = "event-item"; const thumb = document.createElement("img"); thumb.className = "event-thumb"; thumb.alt = formatEventLabel(event); if (event.thumbnail_url) thumb.src = event.thumbnail_url; const info = document.createElement("div"); info.className = "event-info"; info.innerHTML = `
${formatEventLabel(event)}
${event.timestamp_abs || event.capture_date}
`; item.appendChild(thumb); item.appendChild(info); item.addEventListener("click", () => playEvent(event)); eventList.appendChild(item); } } function playEvent(event) { const isSameVideo = player.dataset.videoId === String(event.video_id); player.dataset.videoId = event.video_id; const seekAndPlay = () => { player.currentTime = event.start_offset_sec || 0; player.play(); }; if (isSameVideo) { seekAndPlay(); } else { player.src = `/media/${event.video_id}/stream`; player.addEventListener("loadedmetadata", () => { seekAndPlay(); loadCacheStats(); }, { once: true }); } playerInfo.textContent = `${event.file_name} · ${formatEventLabel(event)}`; } eventTypeFilter.addEventListener("change", loadEvents); dateFilter.addEventListener("change", loadEvents); loadStats(); loadCacheStats(); loadEvents();