diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/messages.js | 45 |
1 files changed, 41 insertions, 4 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 9cba810..61cdc18 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -6467,6 +6467,8 @@ export function messagesFeature(ctx) { } catch {} return 25e6; } + // Data Saver on, or a slow link: the connection to be careful with + const frugalData = () => { try { const c = navigator.connection; return !!(c && (c.saveData || /(^|-)2g$|^3g$/.test(c.effectiveType || ''))); } catch { return false; } }; const tappedClips = new Set(); // asked for by hand this session: plays, whatever it costs function videoHeavy(url) { if (tappedClips.has(url) || largeVideosOk()) return false; @@ -12120,18 +12122,32 @@ export function messagesFeature(ctx) { const m = slide.querySelector('video.mp-media'); if (m && m.getAttribute('src')) { try { m.pause(); m.removeAttribute('src'); m.load(); } catch {} } } - // Only the clip on screen loads in full. The next one gets its opening - // (preload metadata: the index and first frames) if it's within budget; - // every other clip holds nothing. Sizes are asked for a few slides ahead, - // so the verdict is in before the reader gets there. + // Flipping through clips is asking to watch them: whatever they cost, the + // one on screen plays at once (streaming the rest as it goes), and the + // next two get a head start: the opening half megabyte of each fetched + // ahead into the browser's cache, where the player finds it — but no + // more, so a big file isn't pulled down for a clip that may be swiped + // past. (Not a player of their own yet: one given its file, even told to + // preload only the metadata, takes the whole of it on a fast link.) Unless the connection + // is one to be careful with (Data Saver, a slow link). Then only the clip on screen + // loads in full, the next gets its opening if it's within budget, and a + // clip over budget waits for a tap; sizes are asked for a few slides + // ahead, so the verdict is in before the reader gets there. Every other + // clip holds nothing. function pagerNeighbours(slide) { if (!pager || pager.kind !== 'videos') return; const slides = [...pager.scroller.children]; const i = slides.indexOf(slide); + const eager = !frugalData(); slides.forEach((s, j) => { if (Math.abs(j - i) <= 2) slidePoster(s); if (j === i) return; const v = s.querySelector('video.mp-media'); + if (eager && (j === i + 1 || j === i + 2)) { + pagerRelease(s); + headStart(playSrc(v.getAttribute('data-src'))); + return; + } if (j === i + 1) { const url = v.getAttribute('data-src'); probeVideo(url).then(() => { @@ -12143,11 +12159,32 @@ export function messagesFeature(ctx) { }); for (const s of slides.slice(i + 2, i + 4)) probeVideo(s.querySelector('video.mp-media').getAttribute('data-src')); } + // 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 + // is answered from there as far as it goes, and streams on from the host. + // A host that ignores the range sends the whole file: the opening is + // read, the rest cancelled. A host that doesn't allow the request (no + // CORS) gets none — its player still has its metadata and first frame. + const HEAD_START = 512 * 1024; + const headStarted = new Set(); + function headStart(url) { + if (!url || url.startsWith('blob:') || headStarted.has(url) || frugalData()) return; + headStarted.add(url); + if (headStarted.size > 300) headStarted.delete(headStarted.values().next().value); + fetch(url, { headers: { Range: 'bytes=0-' + (HEAD_START - 1) } }).then(async (r) => { + const reader = r.ok && r.body ? r.body.getReader() : null; + if (!reader) return; + let got = 0; + while (got < HEAD_START) { const { done, value } = await reader.read(); if (done) return; got += value.length; } + reader.cancel().catch(() => {}); + }).catch(() => {}); + } function playSlide(slide) { const v = slide.querySelector('video.mp-media'); if (!v) return; pagerNeighbours(slide); const url = v.getAttribute('data-src'); + if (!frugalData()) { startSlide(slide); return; } // no waiting on its size: it plays probeVideo(url).then(() => { if (!pager || pager.cur !== slide) return; if (videoHeavy(url)) { |
