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:
1 parent
06dc4f5f51
commit
d12d5e5414
5 files changed
+508
-805
No files matched your search
+146
-90
@@ -25,45 +25,27 @@
|
||||
<small>Dashcam & 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">
|
||||
×
|
||||
</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>
|
||||
Reference in new issue
Block a user