diff options
Diffstat (limited to 'src/features')
| -rw-r--r-- | src/features/messages.js | 74 |
1 files changed, 47 insertions, 27 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index c273d32..59dc966 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -11512,7 +11512,7 @@ export function messagesFeature(ctx) { // time, no waiting on avatars, pictures and link cards (the grid shows // none of that), and the feed's own page of posts left as it was. let mediaCrawling = null; - function crawlMedia(c = feedNow()) { + function crawlMedia(c = feedNow(), kind = mediaMode === 'videos' ? 'videos' : 'images') { const def = feedDef(c.id); if (!def || !feedHasQuery(def)) return Promise.resolve(); if (def.curated) return loadOlderFeed(); // a curated feed pages its own way @@ -11522,7 +11522,6 @@ export function messagesFeature(ctx) { scheduleRepaint(); // Clips are rare (one post in a hundred, on a busy relay), so a crawl // keeps reading pages until it has found a few, or read several. - const kind = mediaMode === 'videos' ? 'videos' : 'images'; const had = r[kind].size; for (let pass = 0; pass < 5 && r[kind].size - had < 6; pass++) { const from = [r.until, c.notes.at(-1)?.created_at].filter(Boolean); @@ -12824,9 +12823,7 @@ export function messagesFeature(ctx) { else if (pager.profile) markSeen(slide._ev); playSlide(slide); pagerCounts(slide); - // near the end: ask the feed for more, the pager picks them up - if (pagerSource()) pagerFeedAhead(); - else { const gs = pagerGroups(); if (gs.indexOf(slide.closest('.mp-group')) >= gs.length - 3) crawlMedia().catch(() => {}); } + pagerLoadAhead(); }; pager.io = new IntersectionObserver((entries) => { for (const e of entries) { @@ -12844,6 +12841,7 @@ export function messagesFeature(ctx) { if (first) pagerShow(first); pager.anchor = first; // the slide meant to be on screen, from the first moment pager.screenH = scroller.clientHeight; + pagerLoadAhead(); // the clip on screen's seek bar, every frame (timeupdate comes a few times a second) if (video) { const p = pager; @@ -12866,33 +12864,55 @@ export function messagesFeature(ctx) { }; window.addEventListener('resize', pager.onResize); // new posts land in the feed while the pager is open; counts move - pager.tick = setInterval(() => { if (pager) { growPager(); pagerFeedAhead(); if (el.classList.contains('show')) pagerCounts(pager.cur); } }, 1500); + pager.tick = setInterval(() => { if (pager) { growPager(); pagerLoadAhead(); if (el.classList.contains('show')) pagerCounts(pager.cur); } }, 1500); } - // A pager opened from the feed walks the feed's own posts, which page in - // ten at a time as the feed is scrolled. Swiping is the scrolling here: - // with only a few posts left below the one on screen (PAGER_AHEAD), the feed pages on - // (the posts it holds, then older ones from the relays) until there are - // more — several pages if those posts carry none. A page that brings - // nothing (the feed busy, or at its end) stops it; the tick tries again. - // how many posts ahead to keep: clips are fewer and further between - // (more pages to find the next) and each is watched a while, so further - const PAGER_AHEAD = { images: 4, videos: 6 }; - let pagerFeeding = false; - async function pagerFeedAhead() { - const src = pagerSource(); - if (pagerFeeding || !src) return; - pagerFeeding = true; + // All media pagers build the same buffer, retaining the source's ordering + // and filters. The furthest post reached never moves back when we wrap, + // so pending refills continue while the reader circles through older items. + const PAGER_INITIAL = 30, PAGER_AHEAD = 20; + function pagerLoadSource(p) { + const src = pagerSource(p); + if (src) return { + more: src.more, done: src.done, + progress: () => src.c.shown + ':' + src.c.notes.length, + }; + // A profile cache can be replaced while refreshing; never fall back + // to another source in that gap. The next timer tick will try again. + if (p.profile || p.fromFeed) return { more: async () => {}, done: () => true, progress: () => '' }; + const c = feedNow(), r = mediaIndex(c.id); + const curated = feedDef(c.id)?.curated; + return { + more: () => crawlMedia(c, p.kind), + done: () => c.stopped || (curated ? c.end && c.shown >= c.notes.length : !!r.end), + progress: () => [r.until, r[p.kind].size, c.shown, c.notes.length].join(':'), + }; + } + async function pagerLoadAhead() { + const p = pager; + if (!p) return; + const groups = pagerGroups(); + const slide = p.cur || p.anchor; + p.furthest = Math.max(p.furthest ?? -1, groups.indexOf(slide && slide.closest('.mp-group'))); + if (p.loadingAhead || Date.now() < (p.retryAheadAt || 0)) return; + p.loadingAhead = true; + let stalled = false; try { - const c = src.c; - for (let pages = 0; pages < 8 && pager && pagerSource(); pages++) { - const gs = pagerGroups(); - if (gs.length - 1 - gs.indexOf(pager.cur && pager.cur.closest('.mp-group')) > (PAGER_AHEAD[pager.kind] || 4)) break; - const shown = c.shown, held = c.notes.length; + // Bound each run; the timer resumes if sparse media needs more pages. + for (let pages = 0; pages < 8 && pager === p; pages++) { + if (pagerGroups().length >= Math.max(PAGER_INITIAL, p.furthest + 1 + PAGER_AHEAD)) break; + const src = pagerLoadSource(p); + if (src.done()) break; + const before = src.progress(); await src.more(); + if (pager !== p) return; growPager(); - if (c.shown === shown && c.notes.length === held) break; + if (src.progress() === before) { stalled = true; break; } } - } catch {} finally { pagerFeeding = false; } + } catch { stalled = true; } finally { + p.loadingAhead = false; + // An empty or busy source gets a breather, never concurrent requests. + p.retryAheadAt = Date.now() + (stalled ? 10000 : 1500); + } } // Slides for feed items the pager doesn't have yet: into their post's row // (a quote fetched late joins its post), a new post appended below (never |
