add noise canvas for loading indicator and improve video loading experience
This commit is contained in:
1 parent
b562b57bb4
commit
443ed74093
3 files changed
+89
-5
No files matched your search
+20
-3
@@ -12,8 +12,9 @@
|
|||||||
--radius: 12px;
|
--radius: 12px;
|
||||||
--radius-sm: 8px;
|
--radius-sm: 8px;
|
||||||
--shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
|
--shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
|
||||||
--font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
--font:
|
||||||
Helvetica, Arial, sans-serif;
|
"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
||||||
|
Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -95,7 +96,9 @@ header {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color 0.2s, box-shadow 0.2s;
|
transition:
|
||||||
|
border-color 0.2s,
|
||||||
|
box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
.search-wrap input:focus {
|
.search-wrap input:focus {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
@@ -166,6 +169,20 @@ header {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background: #000;
|
background: #000;
|
||||||
}
|
}
|
||||||
|
.noise-canvas {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 1;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
.noise-canvas.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
.video-placeholder {
|
.video-placeholder {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|||||||
+64
-1
@@ -30,6 +30,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const player = document.getElementById("player");
|
const player = document.getElementById("player");
|
||||||
|
const noiseCanvas = document.getElementById("noiseCanvas");
|
||||||
const placeholder = document.getElementById("videoPlaceholder");
|
const placeholder = document.getElementById("videoPlaceholder");
|
||||||
const badge = document.getElementById("videoBadge");
|
const badge = document.getElementById("videoBadge");
|
||||||
const badgeText = document.getElementById("badgeText");
|
const badgeText = document.getElementById("badgeText");
|
||||||
@@ -50,6 +51,65 @@
|
|||||||
let sortMode = "oldest";
|
let sortMode = "oldest";
|
||||||
let current = null;
|
let current = null;
|
||||||
|
|
||||||
|
// ---- Static noise canvas for loading indicator ----
|
||||||
|
|
||||||
|
var noiseCtx = noiseCanvas.getContext("2d");
|
||||||
|
var noiseAnimId = null;
|
||||||
|
var noiseRunning = false;
|
||||||
|
|
||||||
|
function noiseStart() {
|
||||||
|
if (noiseRunning) return;
|
||||||
|
noiseRunning = true;
|
||||||
|
noiseCanvas.classList.remove("hidden");
|
||||||
|
noiseAnimId = null;
|
||||||
|
noiseTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
function noiseStop() {
|
||||||
|
noiseRunning = false;
|
||||||
|
noiseCanvas.classList.add("hidden");
|
||||||
|
if (noiseAnimId) {
|
||||||
|
cancelAnimationFrame(noiseAnimId);
|
||||||
|
noiseAnimId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function noiseTick() {
|
||||||
|
if (!noiseRunning) return;
|
||||||
|
var w = noiseCanvas.width;
|
||||||
|
var h = noiseCanvas.height;
|
||||||
|
var rect = noiseCanvas.getBoundingClientRect();
|
||||||
|
// Render at reduced resolution (160px wide) for performance,
|
||||||
|
// CSS image-rendering: pixelated handles the upscale.
|
||||||
|
var targetW = Math.max(1, Math.min(160, (rect.width * 0.25) | 0));
|
||||||
|
var ratio = rect.height / Math.max(1, rect.width);
|
||||||
|
var targetH = Math.max(1, (targetW * ratio) | 0);
|
||||||
|
if (w !== targetW || h !== targetH) {
|
||||||
|
noiseCanvas.width = targetW;
|
||||||
|
noiseCanvas.height = targetH;
|
||||||
|
w = targetW;
|
||||||
|
h = targetH;
|
||||||
|
}
|
||||||
|
if (w === 0 || h === 0) {
|
||||||
|
noiseAnimId = requestAnimationFrame(noiseTick);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var imageData = noiseCtx.createImageData(w, h);
|
||||||
|
var data = imageData.data;
|
||||||
|
for (var i = 0; i < data.length; i += 4) {
|
||||||
|
var v = (Math.random() * 255) | 0;
|
||||||
|
data[i] = v;
|
||||||
|
data[i + 1] = v;
|
||||||
|
data[i + 2] = v;
|
||||||
|
data[i + 3] = 255;
|
||||||
|
}
|
||||||
|
noiseCtx.putImageData(imageData, 0, 0);
|
||||||
|
noiseAnimId = requestAnimationFrame(noiseTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start noise immediately on page load (no video selected yet)
|
||||||
|
noiseStart();
|
||||||
|
|
||||||
// ---- Prefetch helpers ----
|
// ---- Prefetch helpers ----
|
||||||
|
|
||||||
// Track already-prefetched URLs so we don't re-fetch
|
// Track already-prefetched URLs so we don't re-fetch
|
||||||
@@ -262,9 +322,11 @@
|
|||||||
player.src = clip.url;
|
player.src = clip.url;
|
||||||
player.load();
|
player.load();
|
||||||
|
|
||||||
// Start playback first, prefetch only after the video is actually playing
|
// Show noise while loading, stop when video starts playing
|
||||||
|
noiseStart();
|
||||||
var prefetchDone = false;
|
var prefetchDone = false;
|
||||||
function onPlaying() {
|
function onPlaying() {
|
||||||
|
noiseStop();
|
||||||
if (prefetchDone) return;
|
if (prefetchDone) return;
|
||||||
prefetchDone = true;
|
prefetchDone = true;
|
||||||
player.removeEventListener("playing", onPlaying);
|
player.removeEventListener("playing", onPlaying);
|
||||||
@@ -273,6 +335,7 @@
|
|||||||
player.addEventListener("playing", onPlaying);
|
player.addEventListener("playing", onPlaying);
|
||||||
player.play().catch(function () {
|
player.play().catch(function () {
|
||||||
// Even if autoplay is blocked, still prefetch for when the user clicks play
|
// Even if autoplay is blocked, still prefetch for when the user clicks play
|
||||||
|
noiseStop();
|
||||||
if (!prefetchDone) {
|
if (!prefetchDone) {
|
||||||
prefetchDone = true;
|
prefetchDone = true;
|
||||||
player.removeEventListener("playing", onPlaying);
|
player.removeEventListener("playing", onPlaying);
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>TeslaCam Video Player</title>
|
<title>TeslaCam Video Player</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="{{ url_for('static', filename='css/style.css') }}"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
@@ -56,6 +59,7 @@
|
|||||||
<section class="player-panel">
|
<section class="player-panel">
|
||||||
<div class="video-stage">
|
<div class="video-stage">
|
||||||
<video id="player" controls preload="metadata" playsinline></video>
|
<video id="player" controls preload="metadata" playsinline></video>
|
||||||
|
<canvas id="noiseCanvas" class="noise-canvas"></canvas>
|
||||||
<div class="video-badge" id="videoBadge">
|
<div class="video-badge" id="videoBadge">
|
||||||
<span class="dot"></span><span id="badgeText">Front</span>
|
<span class="dot"></span><span id="badgeText">Front</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user