1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
// Repaint before autoplay used to copy a handler closing over a detached video.
// Run: bun tools/video-unmute-test.js
// Phone: adb reverse tcp:5302 tcp:5302; BROWSER_URL=http://localhost:9223 bun tools/video-unmute-test.js
import assert from 'node:assert/strict';
import puppeteer from './puppeteer.js';
const app = await Bun.file('src/app.js').text();
const messages = await Bun.file('src/features/messages.js').text();
const dom = app.slice(app.indexOf('function h(tag,'), app.indexOf("const root = document.getElementById('app');"));
const video = messages.slice(messages.indexOf(' const autoplayOk ='), messages.indexOf(' // The still, with a play button'));
const clip = '/tmp/coinos-unmute-test.webm';
const ffmpeg = Bun.spawnSync(['ffmpeg', '-y', '-loglevel', 'error', '-f', 'lavfi', '-i', 'color=size=160x90:rate=10', '-f', 'lavfi', '-i', 'sine=frequency=440', '-t', '10', '-c:v', 'libvpx', '-c:a', 'libvorbis', clip]);
assert.equal(ffmpeg.exitCode, 0, 'generate a video with audio');
const server = Bun.serve({ port: 5302, fetch(req) {
if (new URL(req.url).pathname === '/clip.webm') return new Response(Bun.file(clip), { headers: { 'content-type': 'video/webm' } });
return new Response(`<style>video{width:280px}.note-video-box{position:relative}.vid-sound{position:absolute;top:8px;left:8px}</style><div id="app"></div><script>
// Hold autoplay until after repaint to make the race deterministic.
window.IntersectionObserver = undefined;
const t = key => key;
// what videoNode reaches for outside the slice: no cached stills or sizes,
// and no clip over the data budget
const videoThumbs = new Map(), videoInfo = new Map(), tappedClips = new Set();
const feedMedia = () => null, rememberVideo = () => {}, probeVideo = async () => {}, videoHeavy = () => false, heavyLabel = () => '';
const APP_BASE = 'https://v3.coinos.io';
const ui = {}; // (not the feed: which page is showing decides the full-screen button)
${dom}
${video}
const url = '/clip.webm';
const live = videoNode(url);
document.getElementById('app').append(live);
const originalButton = live.querySelector('button');
const fresh = videoNode(url);
morph(live, fresh);
// (a clip loads nothing until it comes into view; this one is in view)
live.querySelector('video').preload = 'auto';
window.test = { live, fresh, originalButton, rebuild() {
const next = videoNode(url); live.replaceWith(next); return next.querySelector('video').muted;
} };
</script>`, { headers: { 'content-type': 'text/html' } });
} });
const remote = !!process.env.BROWSER_URL;
const browser = remote
? await puppeteer.connect({ browserURL: process.env.BROWSER_URL })
: await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] });
const page = await browser.newPage();
try {
if (!remote) await page.setViewport({ width: 390, height: 844, isMobile: true, hasTouch: true });
await page.goto('http://localhost:5302/');
await page.waitForFunction(() => test.live.querySelector('video').readyState >= 2);
assert.equal(await page.evaluate(() => test.originalButton === test.live.querySelector('button') && !test.fresh.isConnected && test.live.querySelector('video').muted), true, 'repaint retains the live muted player and button');
await page.tap('.vid-sound');
await page.waitForFunction(() => !test.live.querySelector('video').paused);
assert.deepEqual(await page.evaluate(() => ({ muted: test.live.querySelector('video').muted, button: !!test.live.querySelector('button'), detachedMuted: test.fresh.querySelector('video').muted, frozen: test.live._skipMorph })), { muted: false, button: false, detachedMuted: true, frozen: true });
assert.equal(await page.evaluate(() => test.rebuild()), false, 'remounted clip remembers sound');
console.log('✓ Touch after repaint unmutes the live player, starts playback, removes the button, and survives remount');
} finally {
await page.close();
if (remote) await browser.disconnect(); else await browser.close();
server.stop(true);
}
|