diff options
| author | Adam Soltys <[email protected]> | 2026-10-09 13:41:19 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-09 13:41:19 -0700 |
| commit | 2454b9a4c12ad1ae403a20b2793b4c03b1164651 (patch) | |
| tree | 698f1a428d4bc004473496646d7f219334742373 /src/features | |
| parent | 776a846ba431cce9da5ef18585d766af5c154565 (diff) | |
| download | coinosv3-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.js | 16 |
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); })); } |
