mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
cctv: live-video panel card — canvas painted from the active camera's video element, 16:9 wrap, gated against the still-image path
This commit is contained in:
1 parent
e02492cfc2
commit
71aa889133
6 files changed
+64
-1
No files matched your search
@@ -83,6 +83,7 @@ export function createPresentation({
|
|||||||
mountHeightM: camera.mountHeightM,
|
mountHeightM: camera.mountHeightM,
|
||||||
active: isActive,
|
active: isActive,
|
||||||
feedType: camera.feedType,
|
feedType: camera.feedType,
|
||||||
|
isVideo: parts.model.isVideoFeedType(camera.feedType),
|
||||||
sourceKind:
|
sourceKind:
|
||||||
health?.sourceKind ||
|
health?.sourceKind ||
|
||||||
camera.sourceKind ||
|
camera.sourceKind ||
|
||||||
|
|||||||
@@ -2618,6 +2618,7 @@ export class StyleManager {
|
|||||||
_cctvFocusBtn: this._cctvFocusBtn,
|
_cctvFocusBtn: this._cctvFocusBtn,
|
||||||
_cctvFrame: this._cctvFrame,
|
_cctvFrame: this._cctvFrame,
|
||||||
_cctvFrameWrap: this._cctvFrameWrap,
|
_cctvFrameWrap: this._cctvFrameWrap,
|
||||||
|
_cctvVideo: this._cctvVideo,
|
||||||
_cctvMeta: this._cctvMeta,
|
_cctvMeta: this._cctvMeta,
|
||||||
_cctvNearestBtn: this._cctvNearestBtn,
|
_cctvNearestBtn: this._cctvNearestBtn,
|
||||||
_cctvNextBtn: this._cctvNextBtn,
|
_cctvNextBtn: this._cctvNextBtn,
|
||||||
|
|||||||
@@ -164,7 +164,46 @@ export function _renderCctvState(state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._cctvFrame) {
|
// Live video: for HLS cameras, paint the active camera's decoded <video>
|
||||||
|
// element onto a canvas each frame (one decoder, two surfaces — the plane
|
||||||
|
// reads the same element). The loop rides through hls.js pipeline resets
|
||||||
|
// (a null element or unchanged currentTime is a skipped frame, not an exit)
|
||||||
|
// and self-stops when the card hides or the feed goes away. The still-image
|
||||||
|
// path below is suppressed while a live feed is showing.
|
||||||
|
const liveIntent = enabled && !!activeCamera?.isVideo;
|
||||||
|
if (this._cctvVideo) {
|
||||||
|
this._cctvVideo.hidden = !liveIntent;
|
||||||
|
if (this._cctvFrame) this._cctvFrame.hidden = liveIntent;
|
||||||
|
this._cctvVideoActive = liveIntent;
|
||||||
|
if (liveIntent && !this._cctvVideoRaf) {
|
||||||
|
const canvas = this._cctvVideo;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
let lastTime = -1;
|
||||||
|
const paint = () => {
|
||||||
|
if (this.destroyed || !this._cctvVideoActive) {
|
||||||
|
this._cctvVideoRaf = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const v = this.cctv.getActiveVideoElement();
|
||||||
|
if (v && v.currentTime !== lastTime) {
|
||||||
|
lastTime = v.currentTime;
|
||||||
|
if (
|
||||||
|
canvas.width !== v.videoWidth ||
|
||||||
|
canvas.height !== v.videoHeight
|
||||||
|
) {
|
||||||
|
canvas.width = v.videoWidth;
|
||||||
|
canvas.height = v.videoHeight;
|
||||||
|
}
|
||||||
|
ctx.drawImage(v, 0, 0);
|
||||||
|
}
|
||||||
|
this._cctvVideoRaf = requestAnimationFrame(paint);
|
||||||
|
};
|
||||||
|
this._cctvVideoRaf = requestAnimationFrame(paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._cctvFrame && !liveIntent) {
|
||||||
const nextSrc = enabled ? activeCamera?.frameUrl : null;
|
const nextSrc = enabled ? activeCamera?.frameUrl : null;
|
||||||
const nextCameraId = enabled ? activeCamera?.id || '' : '';
|
const nextCameraId = enabled ? activeCamera?.id || '' : '';
|
||||||
const cameraChanged = this._cctvFrame.dataset.cameraId !== nextCameraId;
|
const cameraChanged = this._cctvFrame.dataset.cameraId !== nextCameraId;
|
||||||
@@ -183,6 +222,8 @@ export function _renderCctvState(state) {
|
|||||||
if (!nextSrc) {
|
if (!nextSrc) {
|
||||||
this._clearCctvFrame();
|
this._clearCctvFrame();
|
||||||
}
|
}
|
||||||
|
} else if (liveIntent) {
|
||||||
|
this._clearCctvFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._syncCctvSourceBadge(activeCamera, enabled);
|
this._syncCctvSourceBadge(activeCamera, enabled);
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ export function readShellElements(document = globalThis.document) {
|
|||||||
_cctvCalibResetBtn: document.getElementById('cctv-calib-reset-btn'),
|
_cctvCalibResetBtn: document.getElementById('cctv-calib-reset-btn'),
|
||||||
_cctvFrame: document.getElementById('cctv-frame'),
|
_cctvFrame: document.getElementById('cctv-frame'),
|
||||||
_cctvFrameWrap: document.getElementById('cctv-frame-wrap'),
|
_cctvFrameWrap: document.getElementById('cctv-frame-wrap'),
|
||||||
|
_cctvVideo: document.getElementById('cctv-video'),
|
||||||
_cctvSourceBadge: document.getElementById('cctv-source-badge'),
|
_cctvSourceBadge: document.getElementById('cctv-source-badge'),
|
||||||
_cctvMeta: document.getElementById('cctv-meta'),
|
_cctvMeta: document.getElementById('cctv-meta'),
|
||||||
_cctvSummary: document.getElementById('cctv-summary'),
|
_cctvSummary: document.getElementById('cctv-summary'),
|
||||||
|
|||||||
@@ -234,6 +234,8 @@
|
|||||||
image inside is taller — and the wrap is overflow:hidden, which clipped
|
image inside is taller — and the wrap is overflow:hidden, which clipped
|
||||||
the bottom of the frame right back off. Never shrink the preview. */
|
the bottom of the frame right back off. Never shrink the preview. */
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Lightweight loading shimmer while the active frame is fetching */
|
/* Lightweight loading shimmer while the active frame is fetching */
|
||||||
@@ -298,10 +300,26 @@
|
|||||||
transition: opacity 180ms ease;
|
transition: opacity 180ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cctv-video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
#cctv-frame.active {
|
#cctv-frame.active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cctv-video:not([hidden]) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cctv-frame[hidden],
|
||||||
|
#cctv-video[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#cctv-meta {
|
#cctv-meta {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="cctv-frame-wrap" class="cctv-frame-wrap">
|
<div id="cctv-frame-wrap" class="cctv-frame-wrap">
|
||||||
<img id="cctv-frame" alt="CCTV feed frame" />
|
<img id="cctv-frame" alt="CCTV feed frame" />
|
||||||
|
<canvas id="cctv-video" class="cctv-frame" hidden></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div id="cctv-source-badge" role="status" aria-live="polite">SOURCE · UNKNOWN</div>
|
<div id="cctv-source-badge" role="status" aria-live="polite">SOURCE · UNKNOWN</div>
|
||||||
<div id="cctv-meta">Enable CCTV to load camera intersections</div>
|
<div id="cctv-meta">Enable CCTV to load camera intersections</div>
|
||||||
|
|||||||
Reference in new issue
Block a user