diff options
| author | Adam Soltys <[email protected]> | 2026-10-19 22:30:46 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-19 22:30:46 -0700 |
| commit | 4f767c3d0e1d1eb98126396e6acea633bb5c32d3 (patch) | |
| tree | 67de92bbc074e0e212e6bbc211a25ac9829362f9 /src/features/messages.js | |
| parent | a944d02106e3114cd3b5bef49406a62148b17d3d (diff) | |
| download | coinosv3-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.js | 40 |
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); }; |
