aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/messages.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-19 22:30:46 -0700
committerAdam Soltys <[email protected]>2026-10-19 22:30:46 -0700
commit4f767c3d0e1d1eb98126396e6acea633bb5c32d3 (patch)
tree67de92bbc074e0e212e6bbc211a25ac9829362f9 /src/features/messages.js
parenta944d02106e3114cd3b5bef49406a62148b17d3d (diff)
downloadcoinosv3-4f767c3d0e1d1eb98126396e6acea633bb5c32d3.tar.gz
coinosv3-4f767c3d0e1d1eb98126396e6acea633bb5c32d3.zip
Videos: a warm player gets its sound going before the swipe lands. On an Android phone a player's first play with sound held a still picture ~250 ms while the audio output started (muted: 60 ms; a player that had played resumes in 20-80 ms). So the next clip (and the one behind) plays at a volume nobody hears until its picture moves, a frame or two in, and pauses there (a seek back to 0 undoes it). Only with the pager's sound on, and for clips up to 25 MB: once played, a player reads the whole file.
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
Diffstat (limited to 'src/features/messages.js')
-rw-r--r--src/features/messages.js40
1 files changed, 38 insertions, 2 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index bf6096a..b218235 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -14539,9 +14539,41 @@ export function messagesFeature(ctx) {
im.src = full;
}, 300);
}
+ // A player's first play with sound waits on the phone's audio output to
+ // start: on Adam's Android ~250 ms of a still picture, every clip, warm
+ // or not (muted, 60 ms; a muted start then unmuted freezes the picture
+ // for those 250 ms instead). A player that has played with sound resumes
+ // in 20-80 ms, even many seconds later. So a warm player plays at a
+ // volume nobody hears until its picture moves (a frame or two in: the
+ // audio's start is the wait) and pauses there — not back to 0: a seek
+ // undoes it. Only while the pager has sound on, and the browser lets it.
+ // Having played, the player no longer stops reading at its first frame —
+ // it fetches the rest of the file — so only a clip known to be small
+ // enough to be worth that is primed (most are 3-25 MB).
+ const PRIME_MAX = 25e6;
+ function primeSound(s, v) {
+ if (!pagerLoud || v._primed || (navigator.userActivation && !navigator.userActivation.hasBeenActive)) return;
+ const d = videoInfo.get(v.getAttribute('data-src')) || {};
+ if (!(d.size > 0 && d.size <= PRIME_MAX)) return;
+ const go = () => {
+ if (!pager || pager.cur === s || !s.isConnected || !v.getAttribute('src') || !v.paused || v._primed) return;
+ v._primed = true;
+ v.muted = false; v.volume = 0.001;
+ const done = () => {
+ clearTimeout(v._primeTimer);
+ if (pager && pager.cur !== s) { try { v.pause(); } catch {} }
+ v.volume = 1;
+ if (pager && pager.cur !== s) v.muted = true;
+ };
+ v._primeTimer = setTimeout(done, 2000);
+ const f = () => { if (v.currentTime >= 0.03 || v.paused) done(); else v.requestVideoFrameCallback(f); };
+ v.play().then(() => (v.requestVideoFrameCallback ? v.requestVideoFrameCallback(f) : setTimeout(done, 400)), () => { v._primed = false; done(); });
+ };
+ if (v.readyState >= 3) go(); else v.addEventListener('canplay', go, { once: true });
+ }
function pagerRelease(slide) {
const m = slide.querySelector('video.mp-media');
- if (m && m.getAttribute('src')) { try { m.pause(); m.removeAttribute('src'); m.load(); } catch {} }
+ if (m && m.getAttribute('src')) { try { m.pause(); m.removeAttribute('src'); m.load(); m._primed = false; } catch {} }
}
// 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
@@ -14586,6 +14618,7 @@ export function messagesFeature(ctx) {
v.preload = 'metadata';
v.muted = true;
pagerAttach(s);
+ primeSound(s, v);
});
return;
}
@@ -14785,6 +14818,8 @@ export function messagesFeature(ctx) {
else v.addEventListener('loadedmetadata', () => { try { v.currentTime = at; } catch {} }, { once: true });
}
v.muted = !pagerLoud;
+ v.volume = 1;
+ clearTimeout(v._primeTimer);
clockStart(slide, v);
v.play().catch((err) => {
// sound needs a tap the browser counted: play quietly, the button says so
@@ -14804,12 +14839,13 @@ export function messagesFeature(ctx) {
slide._arriveAt = 0;
if (!t0) return;
const url = v.getAttribute('data-src') || '', warm = v.readyState >= 2, rs = v.readyState;
+ const primed = !!v._primed;
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, 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 });
+ pagerStarts.push({ ms, warm, primed, 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);
};