aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-03 15:36:58 -0700
committerAdam Soltys <[email protected]>2026-10-03 15:36:58 -0700
commit7adb8cdf7b1a405c58ad9c51c901d852c29dd2ad (patch)
tree227462cad69a3c9608affa6c887ba1a4b12cd3dd
parentd39a1b87358965bb0118cb88e4c8a86c89992a4c (diff)
downloadcoinosv3-7adb8cdf7b1a405c58ad9c51c901d852c29dd2ad.tar.gz
coinosv3-7adb8cdf7b1a405c58ad9c51c901d852c29dd2ad.zip
Pager: a large clip shows its frame under the tap-to-play cover
The slide's poster was fixed when the pager was built, and a clip over budget held no source, so a clip whose frame was kept a moment later (or never) was a black screen with a button. Now the kept frame is looked up as a slide comes near; without one, the clip's first frame is loaded under the cover (only the start of the file, when it leads with its index or is served in ranges) and kept for next time. A tap plays it from the start. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
-rw-r--r--src/features/messages.js20
-rw-r--r--tools/feed-media-views-test.js5
2 files changed, 23 insertions, 2 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index a0e9f96..e0e1d05 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -9939,6 +9939,7 @@ export function messagesFeature(ctx) {
const slides = [...pager.scroller.children];
const i = slides.indexOf(slide);
slides.forEach((s, j) => {
+ if (Math.abs(j - i) <= 2) slidePoster(s);
if (j === i) return;
const v = s.querySelector('video.mp-media');
if (j === i + 1) {
@@ -9961,7 +9962,16 @@ export function messagesFeature(ctx) {
if (!pager || pager.cur !== slide) return;
if (videoHeavy(url)) {
if (slide.querySelector('.vid-heavy')) return;
- pagerRelease(slide);
+ // 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)
+ 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');
+ } else pagerRelease(slide);
slide.append(h('button', { class: 'vid-heavy', type: 'button', 'aria-label': t('videoTapToPlay'), onClick: (e) => {
e.stopPropagation();
tappedClips.add(url);
@@ -9973,8 +9983,16 @@ export function messagesFeature(ctx) {
startSlide(slide);
});
}
+ // the kept frame as the poster, looked up as the slide comes near (it may
+ // have been kept after the slide was built)
+ function slidePoster(slide) {
+ const v = slide.querySelector('video.mp-media');
+ const thumb = v && videoThumbs.get(v.getAttribute('data-src'));
+ if (thumb && v.getAttribute('poster') !== thumb) v.setAttribute('poster', thumb);
+ }
function startSlide(slide) {
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';
pagerAttach(slide);
v.muted = !pagerLoud;
diff --git a/tools/feed-media-views-test.js b/tools/feed-media-views-test.js
index 0811520..9181904 100644
--- a/tools/feed-media-views-test.js
+++ b/tools/feed-media-views-test.js
@@ -198,7 +198,10 @@ try {
st = await pagerState();
check('the 50 MB clip waits for a tap, saying what it costs', st.src === 'c.mp4' && await page.evaluate(() => {
const s = document.querySelectorAll('.mp-slide')[2]; const b = s.querySelector('.vid-heavy');
- return !!b && /50 MB/.test(b.textContent) && !s.querySelector('video').getAttribute('src'); }), JSON.stringify(st));
+ return !!b && /50 MB/.test(b.textContent) && !/^[^#]*$/.test(s.querySelector('video').getAttribute('src') || '#'); }), JSON.stringify(st));
+ check('...over a picture of it (the kept frame, or the first frame itself)', 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 shot('heavy');
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));