aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js45
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)) {