Add opt-in phone gameplay recording and JSON export
This commit is contained in:
1 parent
ede1f48152
commit
fe64242090
8 files changed
+394
-12
No files matched your search
+49
@@ -15,6 +15,7 @@ import { cv } from './dom.js';
|
||||
import { haptic } from './haptics.js';
|
||||
import * as stats from './stats.js';
|
||||
import * as profile from './profile.js';
|
||||
import * as recorder from './recorder.js';
|
||||
import './input.js';
|
||||
|
||||
let last = performance.now();
|
||||
@@ -89,6 +90,7 @@ let pickedTour = remembered('tour') || TOURS[0].id;
|
||||
// menus share, so its offset outlives the stage that earned it unless it is reset here — the one
|
||||
// place every stage change goes through.
|
||||
const go = stage => {
|
||||
if (stage === 'stats') refreshRecorder();
|
||||
if (stage === 'setup') syncSetup();
|
||||
if (stage === 'train') buildTrain();
|
||||
intro.dataset.stage = stage;
|
||||
@@ -792,6 +794,53 @@ function start(modeId = pickedMode) {
|
||||
const openStats = () => {
|
||||
stats.drawStats('all', SPRV); go('stats');
|
||||
};
|
||||
|
||||
// A phone can record without a console. Opt in from Stats or ?record=1; export is local/share
|
||||
// sheet only. Keep this off the normal splash and give active recordings one small live marker.
|
||||
const recordingMeta = () => ({
|
||||
build: document.querySelector('script[src*="main.js"]').getAttribute('src'),
|
||||
viewport: [innerWidth, innerHeight], pixelRatio: devicePixelRatio,
|
||||
touch: matchMedia('(pointer: coarse)').matches,
|
||||
});
|
||||
function refreshRecorder() {
|
||||
const r = recorder.recordingInfo();
|
||||
document.getElementById('recStatus').textContent = r.warning || (r.full
|
||||
? 'Recording full. Export it, then start a new recording.'
|
||||
: (r.active ? 'Recording' : r.exists ? 'Paused' : 'Off') + ' · ' + r.points + ' points saved on this phone.');
|
||||
document.getElementById('recToggle').textContent = r.active ? 'STOP RECORDING' : r.exists ? 'RESUME RECORDING' : 'RECORD PLAYTEST';
|
||||
document.getElementById('recToggle').disabled = r.full;
|
||||
document.getElementById('recExport').disabled = !r.events;
|
||||
document.getElementById('recNew').hidden = !r.exists || r.active;
|
||||
const lamp = document.getElementById('recLamp');
|
||||
lamp.hidden = !r.active && !r.full;
|
||||
lamp.textContent = r.full ? 'REC FULL' : 'REC';
|
||||
}
|
||||
recorder.onRecorderChange(refreshRecorder);
|
||||
document.getElementById('recToggle').addEventListener('click', () => {
|
||||
const r = recorder.recordingInfo();
|
||||
if (!r.exists) recorder.beginRecording(recordingMeta());
|
||||
else recorder.setRecording(!r.active, recordingMeta());
|
||||
});
|
||||
document.getElementById('recNew').addEventListener('click', () => recorder.beginRecording(recordingMeta()));
|
||||
document.getElementById('recExport').addEventListener('click', async () => {
|
||||
try {
|
||||
const result = await recorder.exportRecording();
|
||||
if (result === 'download') document.getElementById('recStatus').textContent = 'Download requested. Your recording is also kept on this phone.';
|
||||
} catch {
|
||||
document.getElementById('recStatus').textContent = 'Export could not open. Your recording is kept; please try again.';
|
||||
}
|
||||
});
|
||||
if (new URLSearchParams(location.search).get('record') === '1') {
|
||||
if (recorder.recordingInfo().exists) recorder.setRecording(true, recordingMeta());
|
||||
else recorder.beginRecording(recordingMeta());
|
||||
}
|
||||
refreshRecorder();
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
recorder.recordEvent('run_end', { outcome: 'backgrounded' }); recorder.saveRecording();
|
||||
}
|
||||
});
|
||||
addEventListener('pagehide', () => recorder.saveRecording());
|
||||
// entering a question screen rebuilds it, so the LAST tag always names the run actually played —
|
||||
// and the FIRST visit ever detours through the identity screen (the leagues): the club wants a
|
||||
// name before it deals a match. ENTER THE CLUB lands on the mode screen, so the detour costs the
|
||||
|
||||
Reference in new issue
Block a user