From f6d7442c050a16c1cc93e87998647e31ef3ee65d Mon Sep 17 00:00:00 2001
From: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Date: Thu, 27 Aug 2026 15:32:06 -0700
Subject: [PATCH] Make Low to High Smoother
---
.../components/recordings/dashcam_routes.css | 16 ++++++
.../components/recordings/dashcam_routes.js | 52 +++++++++++++++++--
2 files changed, 64 insertions(+), 4 deletions(-)
diff --git a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.css b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.css
index 5f0c27a36..750df7112 100644
--- a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.css
+++ b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.css
@@ -1223,6 +1223,21 @@
width: 100%;
}
+.dashcam-transition-frame {
+ background: #07080b;
+ height: 100%;
+ inset: 0;
+ object-fit: contain;
+ pointer-events: none;
+ position: absolute;
+ width: 100%;
+ z-index: 1;
+}
+
+.dashcam-transition-frame[hidden] {
+ display: none;
+}
+
.dashcam-player-state {
align-items: center;
background: rgba(7, 8, 11, 0.78);
@@ -1232,6 +1247,7 @@
justify-content: center;
position: absolute;
text-align: center;
+ z-index: 2;
}
.dashcam-player-state.error {
diff --git a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js
index 7ef1048f1..353c4df43 100644
--- a/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js
+++ b/starpilot/system/the_galaxy/assets/components/recordings/dashcam_routes.js
@@ -301,6 +301,7 @@ async function openOverlay(route) {
+
Loading route metadata…
@@ -324,6 +325,7 @@ async function openOverlay(route) {
document.body.appendChild(overlay)
const video = overlay.querySelector("video")
+ const transitionFrame = overlay.querySelector(".dashcam-transition-frame")
const playerState = overlay.querySelector(".dashcam-player-state")
const segmentBar = overlay.querySelector(".dashcam-segment-bar")
const segmentSelect = overlay.querySelector(".segment-select")
@@ -341,6 +343,7 @@ async function openOverlay(route) {
let qualityToken = 0
let upgradeController = null
let upgradeTimer = null
+ let transitionActive = false
const setPlayerMessage = (message, isError = false) => {
playerState.textContent = message
@@ -359,9 +362,35 @@ async function openOverlay(route) {
.join("")
segmentBar.hidden = !segments.length
}
- const swapSource = (url, { message } = {}) => {
+
+ const finishTransition = () => {
+ transitionActive = false
+ transitionFrame.hidden = true
+ }
+ const holdCurrentFrame = () => {
+ if (video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA || !video.videoWidth || !video.videoHeight) return false
+ const context = transitionFrame.getContext("2d")
+ if (!context) return false
+ transitionFrame.width = video.videoWidth
+ transitionFrame.height = video.videoHeight
+ try {
+ context.drawImage(video, 0, 0, transitionFrame.width, transitionFrame.height)
+ } catch (_) {
+ return false
+ }
+ transitionActive = true
+ transitionFrame.hidden = false
+ setPlayerMessage("")
+ return true
+ }
+ const swapSource = (url, { message, seamless = false } = {}) => {
const playbackTime = Number.isFinite(video.currentTime) ? video.currentTime : 0
const shouldResume = !video.paused && !video.ended
+ const heldFrame = seamless && holdCurrentFrame()
+ // Never trade a working preview for a black frame. The prepared full stream can
+ // be attempted again later, while the low-resolution video keeps playing.
+ if (seamless && !heldFrame) return false
+ if (heldFrame) video.addEventListener("canplay", finishTransition, { once: true })
video.addEventListener("loadedmetadata", () => {
if (playbackTime > 0) {
try {
@@ -370,9 +399,11 @@ async function openOverlay(route) {
}
if (shouldResume) video.play().catch(() => {})
}, { once: true })
+ if (!heldFrame) finishTransition()
if (message) setPlayerMessage(message)
video.src = url
video.load()
+ return true
}
const cancelUpgrade = () => {
@@ -393,14 +424,23 @@ async function openOverlay(route) {
const fullUrl = cameraVideoUrl(segmentUrl, camera)
const stillCurrent = () =>
token === qualityToken && showingPreview && segments[current] === segmentUrl && selectedCamera === camera && !!overlay
+ const swapPreparedStream = (attempt = 0) => {
+ if (!stillCurrent()) return
+ if (swapSource(fullUrl, { seamless: true })) {
+ showingPreview = false
+ return
+ }
+ if (attempt < 5) {
+ upgradeTimer = setTimeout(() => swapPreparedStream(attempt + 1), 100)
+ }
+ }
fetch(fullUrl, { method: "HEAD", signal: controller.signal })
.then(response => {
if (!stillCurrent()) return
if (upgradeController === controller) upgradeController = null
if (response.ok) {
- showingPreview = false
- swapSource(fullUrl)
+ swapPreparedStream()
return
}
if (response.status === 503 && attempt < FULL_QUALITY_RETRIES) {
@@ -428,6 +468,7 @@ async function openOverlay(route) {
const camera = selectedCamera
if (!segmentUrl || !camera) return
cancelUpgrade()
+ finishTransition()
wantsPlayback = autoplay
showingPreview = preview === undefined ? supportsLowQuality(camera) : preview
setPlayerMessage(message || "Loading video…")
@@ -498,8 +539,11 @@ async function openOverlay(route) {
})
video.addEventListener("loadeddata", () => setPlayerMessage(""))
video.addEventListener("playing", () => setPlayerMessage(""))
- video.addEventListener("waiting", () => setPlayerMessage("Loading video…"))
+ video.addEventListener("waiting", () => {
+ if (!transitionActive) setPlayerMessage("Loading video…")
+ })
video.addEventListener("error", () => {
+ finishTransition()
// A dead preview drops through to the real stream rather than showing an error.
if (showingPreview) {
showingPreview = false