aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-09 13:41:19 -0700
committerAdam Soltys <[email protected]>2026-10-09 13:41:19 -0700
commit2454b9a4c12ad1ae403a20b2793b4c03b1164651 (patch)
tree698f1a428d4bc004473496646d7f219334742373 /src/features
parent776a846ba431cce9da5ef18585d766af5c154565 (diff)
downloadcoinosv3-2454b9a4c12ad1ae403a20b2793b4c03b1164651.tar.gz
coinosv3-2454b9a4c12ad1ae403a20b2793b4c03b1164651.zip
Videos grid: a tile without a still of its own shows the server's (vmedia thumb.jpg) at once — about a second the first time anyone asks for that clip, a CDN hit after — instead of waiting on the browser's probe (64 KB) and frame grab (≈1 MB of the clip, decoded) per tile, which on a phone left most of the grid as empty boxes. The frame grab still runs for a clip the server had no picture for. A known-heavy clip wears its size badge over the still. vmedia makes 8 stills at once (was 4). tools/video-tile-poster-test.js
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js16
1 files changed, 14 insertions, 2 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index ae976a6..ed4136a 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -7181,6 +7181,7 @@ export function messagesFeature(ctx) {
const serverThumb = (url) => (vmediaOk(url) ? VMEDIA + 'thumb.jpg?u=' + encodeURIComponent(url) : undefined);
const h264Of = (url) => VMEDIA + 'h264.mp4?u=' + encodeURIComponent(url);
const noPicture = new Set(), h264Failed = new Set();
+ const serverThumbFailed = new Set(); // clips vmedia had no picture for (this session)
let hevcPlays;
const canHevc = () => hevcPlays ??= (() => {
try { return !!document.createElement('video').canPlayType('video/mp4; codecs="hvc1.1.6.L93.B0"'); } catch { return true; }
@@ -11121,7 +11122,14 @@ export function messagesFeature(ctx) {
return h('div', { class: 'media-grid', 'data-key': 'media-grid:' + mediaMode },
...items.map((it) => {
const k = mediaKey(it);
- const poster = mediaMode === 'videos' ? videoThumbs.get(it.url) || posterOf(it.ev, it.url) || (videoInfo.get(it.url) || {}).image : null;
+ // a video tile shows a still at once: kept, given, or the server's
+ // (vmedia, cached at the CDN — about a second the first time anyone
+ // asks, instant after). The browser's own frame grab — a probe, then
+ // a megabyte of the clip, decoded — is left for clips the server
+ // couldn't picture.
+ const poster = mediaMode === 'videos' ? videoThumbs.get(it.url) || posterOf(it.ev, it.url) || (videoInfo.get(it.url) || {}).image
+ || (serverThumbFailed.has(it.url) ? null : serverThumb(it.url)) : null;
+ const fromServer = mediaMode === 'videos' && poster && poster === serverThumb(it.url);
return h('button', {
class: 'media-tile' + (mediaMode === 'videos' ? ' video' : ''), type: 'button', 'data-key': 'tile:' + k,
'aria-label': displayName(it.ev.pubkey), onClick: () => openMediaPager(mediaMode, k),
@@ -11129,9 +11137,13 @@ export function messagesFeature(ctx) {
mediaMode === 'images' || poster
? h('img', { src: poster || tileThumbs.get(it.url) || mediaSrc(it.url), alt: '', draggable: 'false',
loading: poster || tileThumbs.has(it.url) ? 'eager' : 'lazy', decoding: 'async',
- onError: (e) => { const b = e.target.closest('.media-tile'); if (b) b.classList.add('broken'); } })
+ onError: (e) => {
+ if (fromServer) { serverThumbFailed.add(it.url); render(); return; } // the frame grab's turn
+ const b = e.target.closest('.media-tile'); if (b) b.classList.add('broken');
+ } })
// no still to show: the clip's first frame, fetched once the tile is near
: h('video', { 'data-src': it.url + '#t=0.1', muted: true, playsinline: true, preload: 'metadata' }),
+ mediaMode === 'videos' && poster && videoInfo.get(it.url)?.size && videoHeavy(it.url) ? h('span', { class: 'media-tile-badge' }, heavyLabel(it.url)) : null,
mediaMode === 'videos' ? h('span', { class: 'media-tile-play', 'aria-hidden': 'true' }, '▶') : null);
}));
}