diff options
| author | Adam Soltys <[email protected]> | 2026-10-03 15:41:42 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-03 15:41:42 -0700 |
| commit | 6488806b840c009278f4ae16de9e079ea4b892bc (patch) | |
| tree | 2c7a721c110a5525eaaca3d88c33b9d99c73ca82 | |
| parent | 7adb8cdf7b1a405c58ad9c51c901d852c29dd2ad (diff) | |
| download | coinosv3-6488806b840c009278f4ae16de9e079ea4b892bc.tar.gz coinosv3-6488806b840c009278f4ae16de9e079ea4b892bc.zip | |
Pager: a large clip keeps its picture when you come back to it
Leaving a slide lets go of its file, and coming back skipped the still
because the cover was already there — black again. The still is now
restored on every return. Pager clips also load CORS-readable (a plain
retry for hosts without CORS), so their frame is kept the first time —
the still under a cover and any clip that plays — and from then on the
poster alone covers it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
| -rw-r--r-- | src/features/messages.js | 25 | ||||
| -rw-r--r-- | tools/feed-media-views-test.js | 4 |
2 files changed, 24 insertions, 5 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index e0e1d05..4c1e5b5 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -9917,6 +9917,16 @@ export function messagesFeature(ctx) { }) }, h('span', { class: 'i', html: I_ZAP }), h('span', { class: 'n' }))))); slide._ev = ev; if (video) { + // readable pixels, so its frame can be kept; a host without CORS gets + // the same source again, plainly + media.crossOrigin = 'anonymous'; + media.addEventListener('error', () => { + if (!media.hasAttribute('crossorigin') || !media.getAttribute('src')) return; + const src = media.getAttribute('src'); + media.removeAttribute('crossorigin'); + media.setAttribute('src', src); + if (pager && pager.cur === slide && !slide.querySelector('.vid-heavy')) media.play().catch(() => {}); + }); media.muted = !pagerLoud; media.addEventListener('click', () => {}); // a tap is the pager's (overlay), not the player's } @@ -9961,17 +9971,20 @@ export function messagesFeature(ctx) { probeVideo(url).then(() => { if (!pager || pager.cur !== slide) return; if (videoHeavy(url)) { - if (slide.querySelector('.vid-heavy')) return; // under the cover, a picture of what it is: the kept frame, else the // first frame itself (only the start of the file, when it leads with - // its index or is served in ranges) + // its index or is served in ranges) — again on every return, since + // a slide left behind lets go of its file slidePoster(slide); const d = videoInfo.get(url) || {}; if (!videoThumbs.has(url) && (d.faststart || d.ranges || !d.probed)) { - v.preload = 'metadata'; - v.addEventListener('loadeddata', () => setTimeout(() => keepVideoThumb(v, url), 150), { once: true }); - v.setAttribute('src', url + '#t=0.1'); + if (!v.getAttribute('src')) { + v.preload = 'metadata'; + v.addEventListener('loadeddata', () => setTimeout(() => keepVideoThumb(v, url), 150), { once: true }); + v.setAttribute('src', url + '#t=0.1'); + } } else pagerRelease(slide); + if (slide.querySelector('.vid-heavy')) return; slide.append(h('button', { class: 'vid-heavy', type: 'button', 'aria-label': t('videoTapToPlay'), onClick: (e) => { e.stopPropagation(); tappedClips.add(url); @@ -9994,6 +10007,8 @@ export function messagesFeature(ctx) { const v = slide.querySelector('video.mp-media'); if (/#t=[\d.]+$/.test(v.getAttribute('src') || '')) v.setAttribute('src', v.getAttribute('data-src')); // the still's src: from the start now v.preload = 'auto'; + const url = v.getAttribute('data-src'); + if (!videoThumbs.has(url)) v.addEventListener('loadeddata', () => setTimeout(() => keepVideoThumb(v, url), 150), { once: true }); pagerAttach(slide); v.muted = !pagerLoud; v.play().catch((err) => { diff --git a/tools/feed-media-views-test.js b/tools/feed-media-views-test.js index 9181904..dee807c 100644 --- a/tools/feed-media-views-test.js +++ b/tools/feed-media-views-test.js @@ -203,6 +203,10 @@ try { const v = document.querySelectorAll('.mp-slide')[2].querySelector('video'); return (v.getAttribute('poster') || '').startsWith('blob:') || (/#t=/.test(v.getAttribute('src') || '') && v.readyState >= 2); }, 5000)); await shot('heavy'); + await page.keyboard.press('ArrowUp'); await sleep(1200); await page.keyboard.press('ArrowDown'); await sleep(1500); + check('away and back, it still shows its picture', await waitFor(() => { + const v = document.querySelectorAll('.mp-slide')[2].querySelector('video'); + return (v.getAttribute('poster') || '').startsWith('blob:') || (/#t=/.test(v.getAttribute('src') || '') && v.readyState >= 2); }, 5000)); await page.evaluate(() => document.querySelectorAll('.mp-slide')[2].querySelector('.vid-heavy').click()); check('a tap plays it', await waitFor(() => { const v = document.querySelectorAll('.media-pager video')[2]; return !v.paused && v.currentTime > 0; }, 6000)); check('...without showing the overlay', !(await pagerState()).show); |
