aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-19 22:19:27 -0700
committerAdam Soltys <[email protected]>2026-10-19 22:19:27 -0700
commita944d02106e3114cd3b5bef49406a62148b17d3d (patch)
treea104bcfc968c5e5ae7434c9a7b672e64a826900f
parent1366ef7e517cbede61ac81b133bda8ab9ff74f23 (diff)
downloadcoinosv3-a944d02106e3114cd3b5bef49406a62148b17d3d.tar.gz
coinosv3-a944d02106e3114cd3b5bef49406a62148b17d3d.zip
Videos: the clip just left keeps its player, so a swipe back starts at once too (on Adam's phone it was the slow one: 0.66-0.78 s, built anew, against 0.04-0.08 s onto the warm next clip). The swipe clock records the player's readyState and how far it had buffered.
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
-rw-r--r--src/features/messages.js26
-rw-r--r--tools/pager-eager-test.js6
2 files changed, 22 insertions, 10 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index dadd084..bf6096a 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -14516,6 +14516,7 @@ export function messagesFeature(ctx) {
const prevGroup = (g) => { let n = g && g.previousElementSibling; while (n && (!n.classList.contains('mp-group') || n.classList.contains('mp-off'))) n = n.previousElementSibling; return n; };
const up = prevGroup(slide.closest('.mp-group')), back = up && rowSlide(up);
if (back) out.push(back); // (the one before: ready again for the swipe back)
+ out.back = back || null;
return out;
}
function pagerAttach(slide) {
@@ -14568,16 +14569,20 @@ export function messagesFeature(ctx) {
if (j === i) return;
const v = s.querySelector('video.mp-media');
if (eager && k >= 0) {
- // the next clip: a player of its own, warm — once its opening is in
- // the cache, given the file to read up to its first frame (preload
- // 'metadata': Chrome reads ~2 s of it and stops) and paused there,
- // so the swipe onto it is only play and sound (TikTok's trick). The
- // others ahead get their opening only.
- if (k === 0 && v.getAttribute('src')) return; // warm already
+ // the next clip and the one before: a player of its own, warm — once
+ // its opening is in the cache, given the file to read up to its
+ // first frame (preload 'metadata': Chrome reads ~2 s of it and
+ // stops) and paused there, so the swipe onto it is only play and
+ // sound (TikTok's trick). The one just left keeps the player it
+ // played in (a swipe back to it took 0.7 s on a phone, built anew).
+ // The others ahead get their opening only.
+ const warm = k === 0 || s === ahead.back;
+ if (warm && v.getAttribute('src')) return; // warm already
pagerRelease(s);
const head = headStart(playSrc(v.getAttribute('data-src')), k === 0 ? 1 : 0.5);
- if (k === 0) head.then(() => {
- if (!pager || !s.isConnected || pager.cur === s || v.getAttribute('src') || pagerAhead(pager.cur)[0] !== s) return;
+ if (warm) head.then(() => {
+ const now = pager && pager.cur && pagerAhead(pager.cur);
+ if (!now || !s.isConnected || pager.cur === s || v.getAttribute('src') || (now[0] !== s && now.back !== s)) return;
v.preload = 'metadata';
v.muted = true;
pagerAttach(s);
@@ -14798,12 +14803,13 @@ export function messagesFeature(ctx) {
const t0 = slide._arriveAt;
slide._arriveAt = 0;
if (!t0) return;
- const url = v.getAttribute('data-src') || '', warm = v.readyState >= 2;
+ const url = v.getAttribute('data-src') || '', warm = v.readyState >= 2, rs = v.readyState;
+ const buf = v.buffered && v.buffered.length ? +v.buffered.end(v.buffered.length - 1).toFixed(1) : 0;
const d = videoInfo.get(url) || {};
let host = ''; try { host = new URL(url).hostname; } catch {}
const done = () => {
const ms = Math.round(performance.now() - t0);
- pagerStarts.push({ ms, warm, host, mb: d.size ? +(d.size / 1e6).toFixed(1) : null, faststart: d.faststart ?? null, src: (v.getAttribute('src') || '').startsWith(VMEDIA) ? 'copy' : 'original', url });
+ pagerStarts.push({ ms, warm, rs, buf, host, mb: d.size ? +(d.size / 1e6).toFixed(1) : null, faststart: d.faststart ?? null, src: (v.getAttribute('src') || '').startsWith(VMEDIA) ? 'copy' : 'original', url });
if (pagerStarts.length > 100) pagerStarts.shift();
dlog('pager start', ms + ' ms', warm ? 'warm' : 'cold', host);
};
diff --git a/tools/pager-eager-test.js b/tools/pager-eager-test.js
index 01c8dd6..3b5c5c3 100644
--- a/tools/pager-eager-test.js
+++ b/tools/pager-eager-test.js
@@ -151,6 +151,12 @@ async function run(saveData, slow = false) {
// the link measured now: a fast one, still a couple of seconds' worth; one slower than the clip, the cap
if (!slow) check('…a fast link measured: still two seconds\' worth (half, as the second ahead)', near(head3, RATE), kb(head3));
else check('…the link measured slower than the clip: it grows to the cap (half, as the second ahead)', head3 === HEAD_MAX / 2, kb(head3));
+ // the clip just left keeps its player: the swipe back is warm too
+ check('the clip just left keeps its player', !!(await slide('c0'))?.src);
+ await page.keyboard.press('ArrowUp');
+ await waitFor(() => coinosPagerStarts().starts.filter((x) => /c0\.webm$/.test(x.url)).length >= 2, null, 5000);
+ const back = await page.evaluate(() => coinosPagerStarts().starts.filter((x) => /c0\.webm$/.test(x.url)).at(-1));
+ check('…so swiping back to it starts warm', back && back.warm && back.ms < 1000, JSON.stringify(back));
} else {
await sleep(3000);
const s = await slide('c1');