168 lines
6.3 KiB
JavaScript
168 lines
6.3 KiB
JavaScript
/**
|
|
* Dev-only regression test: feed-window video play/pause/replay.
|
|
*
|
|
* Symptom under test (Build/Research/Map/Quests windows): open the
|
|
* window, close it, open it again — the feed stays PAUSED.
|
|
*
|
|
* Root cause lives in the vendored Phaser 4 Video object: play() only
|
|
* issues the native <video>.play() while its internal _playCalled flag
|
|
* is false; pause() leaves the flag true, so a later play() call is a
|
|
* silent no-op. resume() is the code path that re-issues native
|
|
* playback for a previously-played, now-paused clip.
|
|
*
|
|
* This page exercises exactly those call sequences on real Phaser
|
|
* Video objects (assets/videos/build.mp4) and reports:
|
|
* A play → pause → play (old open() path — EXPECTED to stay paused)
|
|
* B play → pause → resume (new open() path — EXPECTED to play)
|
|
* C resume (first open) (new open() path on a fresh clip)
|
|
*
|
|
* node dev/server.mjs 8123 # any static server that also answers /sleep?ms=N
|
|
* firefox --headless --screenshot VIDEO_REPLAY.png \
|
|
* --window-size=960,520 "http://127.0.0.1:8123/dev/video-replay.html"
|
|
*/
|
|
import Phaser from '../js/vendor/phaser.js';
|
|
|
|
const errors = [];
|
|
const origErr = console.error.bind(console);
|
|
console.error = (...a) => { errors.push(a.map(String).join(' ')); origErr(...a); };
|
|
window.addEventListener('error', (e) => errors.push(String(e.message)));
|
|
window.addEventListener('unhandledrejection', (e) => errors.push(`rejection: ${e.reason}`));
|
|
|
|
const report = document.createElement('pre');
|
|
report.id = 'report';
|
|
report.style.cssText =
|
|
'position:fixed;left:10px;top:10px;z-index:9999;margin:0;padding:10px 14px;font:13px/1.55 monospace;color:#eaf6ff;background:rgba(6,20,16,0.94);border:1px solid #1b3a5a;white-space:pre-wrap;';
|
|
document.body.appendChild(report);
|
|
const setReport = (lines) => { report.textContent = (Array.isArray(lines) ? lines : [lines]).join('\n'); };
|
|
setReport('booting…');
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
class VideoTestScene extends Phaser.Scene {
|
|
constructor() {
|
|
super({ key: 'VideoTestScene' }); // v4: scenes carry their key explicitly
|
|
}
|
|
preload() {
|
|
this.load.video('feedA', 'assets/videos/build.mp4');
|
|
this.load.video('feedB', 'assets/videos/build.mp4');
|
|
this.load.video('feedC', 'assets/videos/build.mp4');
|
|
}
|
|
create() {
|
|
this.__ready = true; // create() runs after the loader completes
|
|
}
|
|
}
|
|
|
|
const game = new Phaser.Game({
|
|
type: Phaser.AUTO,
|
|
width: 300,
|
|
height: 200,
|
|
parent: 'game',
|
|
backgroundColor: '#04060d',
|
|
scene: VideoTestScene,
|
|
});
|
|
window.game = game;
|
|
|
|
const results = [];
|
|
const line = (ok, tag, detail) => results.push(`${ok ? 'PASS' : 'FAIL'} ${tag} — ${detail}`);
|
|
|
|
function snapshot(v, label) {
|
|
const el = v && v.video;
|
|
const s = el
|
|
? `paused=${el.paused} t=${(el.currentTime || 0).toFixed(2)}s ended=${el.ended}`
|
|
: 'no <video> element';
|
|
return `${label}: ${s}`;
|
|
}
|
|
|
|
async function run() {
|
|
// the scene manager resolves by name only after boot — poll (same as
|
|
// the other dev harnesses, e.g. research-shot.mjs)
|
|
let scene = null;
|
|
for (let i = 0; i < 200; i++) {
|
|
scene = game.scene.getScene('VideoTestScene');
|
|
if (scene) break;
|
|
await sleep(100);
|
|
}
|
|
if (!scene) {
|
|
const dump = JSON.stringify({
|
|
booted: game.isBooted,
|
|
renderer: game.renderer && game.renderer.type,
|
|
scenes: (game.scene && game.scene.scenes || []).map((sc) => (sc.sys && (sc.sys.settings && sc.sys.settings.key || sc.sys.key)) || sc.key),
|
|
}, null, 0);
|
|
throw new Error('scene never registered — ' + dump);
|
|
}
|
|
// wait for the scene's create() (loader done)
|
|
for (let i = 0; i < 200 && !scene.__ready; i++) await sleep(100);
|
|
if (!scene.__ready) throw new Error('scene never reached create()');
|
|
await sleep(300);
|
|
|
|
const mk = (key) => {
|
|
const v = scene.add.video(0, 0, key);
|
|
v.setLoop(true);
|
|
if (v.video) v.video.muted = true;
|
|
return v;
|
|
};
|
|
|
|
// ── A: the OLD open() path — play() after pause() ─────────────────
|
|
const a = mk('feedA');
|
|
a.play(); // open #1
|
|
await sleep(1200);
|
|
const a1 = a.video;
|
|
line(!a1.paused, 'A-open#1 play()', snapshot(a, 'after play()'));
|
|
a.pause(); // close
|
|
await sleep(400);
|
|
line(a1.paused, 'A-close pause()', snapshot(a, 'after pause()'));
|
|
a.play(); // open #2 — the old code path
|
|
await sleep(1200);
|
|
line(
|
|
a1.paused,
|
|
'A-open#2 play() (old path — reproduces the bug: stays paused)',
|
|
snapshot(a, 'after 2nd play()')
|
|
);
|
|
|
|
// ── B: the NEW open() path — resume() after pause() ────────────────
|
|
const b = mk('feedB');
|
|
b.resume(); // open #1 (resume() delegates to play() on a fresh clip)
|
|
await sleep(1200);
|
|
const b1 = b.video;
|
|
line(!b1.paused, 'B-open#1 resume()', snapshot(b, 'after first resume()'));
|
|
b.pause(); // close
|
|
await sleep(400);
|
|
const tBefore = b1.currentTime;
|
|
b.resume(); // open #2 — the new code path
|
|
await sleep(1200);
|
|
line(
|
|
!b1.paused && b1.currentTime > tBefore,
|
|
'B-open#2 resume() (new path — fixed)',
|
|
snapshot(b, `after resume() (was t=${tBefore.toFixed(2)}s)`)
|
|
);
|
|
|
|
// ── C: first-ever open uses resume() (never played before) ────────
|
|
const c = mk('feedC');
|
|
c.resume(); // open — never played
|
|
await sleep(1200);
|
|
const c1 = c.video;
|
|
line(!c1.paused && c1.currentTime > 0, 'C-open#1 resume() (fresh clip)', snapshot(c, 'after resume()'));
|
|
|
|
// ── verdict ───────────────────────────────────────────────────────
|
|
const allPass = results.every((r) => r.startsWith('PASS'));
|
|
const lines = [
|
|
`Phaser ${Phaser.VERSION}`,
|
|
allPass ? 'VIDEO REPLAY TEST — ALL PASS' : 'VIDEO REPLAY TEST — FAILURES',
|
|
'─'.repeat(64),
|
|
...results,
|
|
'',
|
|
'A-open#2 staying paused is EXPECTED: it is the Phaser 4 play()-after-',
|
|
'pause() no-op that the window fix (resume()) works around.',
|
|
errors.length ? `errors:\n${errors.slice(0, 5).join('\n')}` : 'no console errors',
|
|
];
|
|
setReport(lines);
|
|
window.__done = true; // dev/shot-firefox.mjs polls this
|
|
a.pause(); // stop the repro clip; B/C keep looping (ambience, muted)
|
|
setReport(lines);
|
|
}
|
|
|
|
run().catch((e) => {
|
|
window.__done = true;
|
|
setReport(['VIDEO REPLAY TEST — ERROR', String((e && e.message) || e), ...errors.slice(0, 5)]);
|
|
});
|