Refactor video player and camera angle switching

- Removed inline JSON data for video clips; replaced with API endpoint to fetch video data.
- Simplified camera angle switching UI with a 3-column button grid around a Model Y graphic.
- Updated camera view mapping to use 'back' instead of 'rear'.
- Enhanced noise canvas handling during video loading.
- Streamlined clip playback logic and added auto-play for next clips in the same camera.
- Improved search and sorting functionality by removing unnecessary elements.
- Updated HTML structure for better accessibility and user experience.
This commit is contained in:
zwitschi committed 2026-07-25 00:43:43 +02:00
1 parent 06dc4f5f51
commit d12d5e5414
5 files changed
+508 -805

No files matched your search

+146 -90
View File
@@ -25,45 +25,27 @@
<small>Dashcam &amp; Sentry Archive</small>
</div>
</div>
<div class="search-wrap">
<span class="search-icon" aria-hidden="true">
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="11" cy="11" r="7" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
</span>
<input
type="text"
id="searchBox"
placeholder="Search by date, timestamp or filename…"
autocomplete="off"
aria-label="Search clips"
/>
<button class="clear-btn" id="clearSearch" aria-label="Clear search">
&times;
</button>
</div>
</header>
<!-- Main Layout -->
<!-- Main Layout: single column, centered -->
<div class="layout">
<!-- Left: Video Player Panel -->
<!-- Video Player -->
<section class="player-panel">
<div class="video-stage">
<video id="player" controls preload="metadata" playsinline></video>
{% set src = "src='" ~ default_url ~ "'" if default_url else '' %} {%
set hidden = 'hidden' if not default_url else '' %}
<video
id="player"
controls
preload="metadata"
playsinline
{{src}}
></video>
<canvas id="noiseCanvas" class="noise-canvas"></canvas>
<div class="video-badge" id="videoBadge">
<span class="dot"></span><span id="badgeText">Front</span>
</div>
<div class="video-placeholder" id="videoPlaceholder">
<div class="video-placeholder" id="videoPlaceholder" {{hidden}}>
<svg
viewBox="0 0 24 24"
fill="none"
@@ -73,79 +55,153 @@
<rect x="2" y="6" width="14" height="12" rx="2" />
<path d="M16 10l6-3v10l-6-3" />
</svg>
<div>Select a clip from the list to begin playback</div>
<div>No front camera footage found</div>
</div>
</div>
<!-- Synchronized Camera Switching Bar -->
<div class="cam-switch" id="camSwitch">
<span class="cs-label">Camera Angles — Same Timestamp</span>
<div
id="camSwitchButtons"
style="display: flex; flex-wrap: wrap; gap: 8px; width: 100%"
{% set phidden = 'hidden' if not default_url else '' %}
<div class="now-playing" id="nowPlaying" {{phidden}}>
<span
>Now Playing:
<strong id="npName"
>{% if default_url %}Front — oldest clip{% else %}—{% endif
%}</strong
></span
>
<span
style="color: var(--text-muted); font-size: 12.5px; padding: 4px"
>Select a video to unlock synchronized camera switching.</span
>
</div>
</div>
<div class="now-playing" id="nowPlaying" hidden>
<span>Now Playing: <strong id="npName">—</strong></span>
<span class="ts" id="npTime">—</span>
</div>
</section>
<!-- Right: Sidebar & Clip Library -->
<aside class="sidebar">
<div class="cam-tabs" id="camTabs">
<button class="tab active" data-cam="all">
All Views <span class="count" id="countAll">0</span>
</button>
{% for name, data in categorized_videos.items() %}
<button class="tab" data-cam="{{ data.raw_key }}">
{{ name }} <span class="count">{{ data.videos | length }}</span>
</button>
<!-- Camera Angle Selector: 3-column grid around Model Y graphic -->
<section class="cam-selector">
<h2 class="cs-heading">Camera Angles</h2>
<div class="cs-grid">
{% for col in camera_list %}
<div class="cs-col">
{% for cam in col %} {% set disabled = 'disabled' if not
cam.first_url else '' %}
<button
class="cs-btn{% if cam.key == 'front' %} active{% endif %}"
data-cam="{{ cam.key }}"
data-url="{{ cam.first_url or '' }}"
{{
disabled
}}
title="{{ cam.display }}{% if not cam.first_url %} — no footage{% endif %}"
>
<span class="cs-btn-label">{{ cam.display }}</span>
<span class="cs-btn-count">{{ cam.count }}</span>
</button>
{% endfor %}
</div>
{% endfor %}
</div>
<div class="list-toolbar">
<span id="resultCount">0 clips</span>
<select id="sortSelect" aria-label="Sort clips">
<option value="oldest">Oldest first</option>
<option value="name">Filename A–Z</option>
</select>
<!-- Model Y Top-Down SVG -->
<div class="car-graphic" aria-hidden="true">
<svg viewBox="0 0 200 400" class="car-svg">
<!-- Car body -->
<rect
x="55"
y="80"
width="90"
height="240"
rx="30"
ry="30"
fill="none"
stroke="var(--text-muted)"
stroke-width="2"
/>
<!-- Windshield -->
<line
x1="65"
y1="110"
x2="135"
y2="110"
stroke="var(--text-muted)"
stroke-width="1.5"
/>
<!-- Back view -->
<line
x1="65"
y1="290"
x2="135"
y2="290"
stroke="var(--text-muted)"
stroke-width="1.5"
/>
<!-- Front view camera dot -->
<circle cx="100" cy="78" r="5" fill="var(--accent)" />
<!-- Back view camera dot -->
<circle cx="100" cy="322" r="5" fill="var(--accent)" />
<!-- Left repeater dot -->
<circle cx="53" cy="130" r="4" fill="var(--accent)" />
<!-- Right repeater dot -->
<circle cx="147" cy="130" r="4" fill="var(--accent)" />
<!-- Left pillar dot -->
<circle cx="53" cy="200" r="4" fill="var(--accent)" />
<!-- Right pillar dot -->
<circle cx="147" cy="200" r="4" fill="var(--accent)" />
<!-- Labels -->
<text
x="100"
y="68"
text-anchor="middle"
fill="var(--text-muted)"
font-size="9"
>
Front
</text>
<text
x="100"
y="342"
text-anchor="middle"
fill="var(--text-muted)"
font-size="9"
>
Back
</text>
<text
x="42"
y="134"
text-anchor="end"
fill="var(--text-muted)"
font-size="8"
>
L Rep
</text>
<text
x="158"
y="134"
text-anchor="start"
fill="var(--text-muted)"
font-size="8"
>
R Rep
</text>
<text
x="42"
y="204"
text-anchor="end"
fill="var(--text-muted)"
font-size="8"
>
L Pil
</text>
<text
x="158"
y="204"
text-anchor="start"
fill="var(--text-muted)"
font-size="8"
>
R Pil
</text>
</svg>
</div>
<div class="clip-list" id="clipList">
<!-- Dynamically populated -->
</div>
</aside>
</section>
</div>
<!-- Embedded Data Payload from Flask -->
<script type="application/json" id="videoData">
{
{% for name, data in categorized_videos.items() %}
"{{ data.raw_key }}": {
"display": {{ name | tojson }},
"raw_key": {{ data.raw_key | tojson }},
"videos": [
{% for v in data.videos %}
{
"filename": {{ v.filename | tojson }},
"timestamp": {{ v.timestamp | tojson }},
"rel_path": {{ v.rel_path | tojson }},
"url": {{ v.url | tojson }}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}{% if not loop.last %},{% endif %}
{% endfor %}
}
</script>
<!-- Camera metadata loaded via /api/videos by app.js -->
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
</body>
</html>