aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/features/messages.js16
-rw-r--r--tools/pager-first-frame-test.js10
2 files changed, 14 insertions, 12 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 326c420..0ea7520 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -12893,21 +12893,23 @@ export function messagesFeature(ctx) {
// it starts playing"). The server's first frame (vmedia first.jpg: frame
// 0 at full size, any host, any layout, cached at the CDN) is the first
// choice, fetched and decoded aside and put on the slide when it is
- // ready; the frame drawn here is the fallback for the next clip; a clip
- // with neither keeps the still there is (a kept frame, the post's
- // picture), soft but not black, replaced by a first frame when one comes.
+ // ready; the frame drawn here is the fallback for the next clip. A clip
+ // with neither gets no picture at all — and so stays out of the carousel
+ // (pagerReveal): the post's own still, put up meanwhile, was often not
+ // the clip's first frame, and the swap from one to the other jarred
+ // (Adam, 2026-10-17: "i don't want to show posters and then switch to
+ // the first frame … could we just always wait until we have a non-black
+ // frame before including the video in the pager?").
function slideFrame(s, k) {
const v = s.querySelector('video.mp-media');
if (!v || v._frame === 'first' || v._frameBusy) return;
const url = v.getAttribute('data-src');
const put = (p, kind) => {
if (!p || !pager || !s.isConnected || v._frame === 'first') return false;
- if (kind === 'soft' && (v._frame || pager.cur === s)) return false; // never a soft still under the clip on screen
v.setAttribute('poster', p); v._frame = kind;
pagerReveal();
return true;
};
- const soft = () => put(videoThumbs.get(url) || posterOf(s._ev, url), 'soft');
const first = serverFirst(url);
if (first && !serverFirstFailed.has(url)) {
v._frameBusy = true;
@@ -12926,10 +12928,8 @@ export function messagesFeature(ctx) {
if (k === 0 && !v._grabbed) { // the next clip: its opening is in the cache, the frame comes from there
v._grabbed = true;
v._frameBusy = true;
- pagerPoster(playSrc(url)).then((p) => { v._frameBusy = false; if (!put(p, 'first')) soft(); });
- return;
+ pagerPoster(playSrc(url)).then((p) => { v._frameBusy = false; put(p, 'first'); });
}
- if (first) soft(); // (the server had none: the still, if any, rather than black)
}
// The opening of a clip about to be reached, fetched into the browser's
// HTTP cache with a range request: the player's own request for the file
diff --git a/tools/pager-first-frame-test.js b/tools/pager-first-frame-test.js
index f88a074..6507607 100644
--- a/tools/pager-first-frame-test.js
+++ b/tools/pager-first-frame-test.js
@@ -2,8 +2,8 @@
// before the reader gets there: the server's (vmedia first.jpg — frame 0 at
// full size, any host), put on the slide once fetched and decoded; where the
// server has none, the next clip's frame is drawn here from its cached
-// opening, and a clip beyond keeps the post's still rather than black, until
-// a first frame comes. The slide left behind keeps its frame for the swipe
+// opening, and a clip beyond gets no picture (and stays out of the
+// carousel) until a first frame comes — never the post's still first. The slide left behind keeps its frame for the swipe
// back. Run: bun tools/pager-first-frame-test.js
import puppeteer from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
@@ -106,13 +106,15 @@ try {
console.log('\n[the slides ahead hold their first frames]');
check('the next clip holds the server\'s first frame', await waitPoster('tall', 'server'), JSON.stringify(await slides()));
check('…the server was asked for it once', asked.filter((x) => x === 'first:tall').length === 1, asked.join(' '));
- check('the clip after, which the server has no frame for, keeps the post\'s still', await waitPoster('last', 'still'), JSON.stringify(await slides()));
+ await sleep(1500);
+ check('the clip after, which the server has no frame for, gets no stand-in still', (await posterOf('last')) === 'none', JSON.stringify(await slides()));
+ check('…and waits out of the carousel', await page.evaluate(() => [...document.querySelectorAll('.mp-group')].find((g) => /last\.webm$/.test(g.querySelector('.mp-slide').getAttribute('data-k'))).classList.contains('mp-wait')));
check('…no thumbnail route was tried for any of them', !asked.some((x) => /thumb\.jpg/.test(x)), asked.join(' '));
await page.keyboard.press('ArrowDown');
check('on to the next: it plays', await waitFor(() => { const s = document.querySelector('.mp-slide.mp-cur'); return !!s && s.getAttribute('data-k').endsWith('/tall.webm') && !s.querySelector('video').paused; }, null, 6000), JSON.stringify(await slides()));
check('…the server\'s frame still under it', (await posterOf('tall')) === 'server', String(await posterOf('tall')));
check('the one left behind holds its first frame for the swipe back', await waitPoster('wide', 'server'), JSON.stringify(await slides()));
- check('the next, now the clip ahead, has its frame drawn here in place of the still', await waitPoster('last', 'drawn', 12000), JSON.stringify(await slides()));
+ check('the next, now the clip ahead, has its frame drawn here ', await waitPoster('last', 'drawn', 12000), JSON.stringify(await slides()));
check('…the server asked only once more for it, not again and again', asked.filter((x) => x === 'first:last').length === 1, asked.join(' '));
await page.keyboard.press('ArrowUp');
check('back on the first: it plays again, its frame under it', await waitFor(() => { const s = document.querySelector('.mp-slide.mp-cur'); return !!s && s.getAttribute('data-k').endsWith('/wide.webm') && !s.querySelector('video').paused && /first\.jpg/.test(s.querySelector('video').getAttribute('poster') || ''); }, null, 6000), JSON.stringify(await slides()));