Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/thatstupidcomputer / apps/web/src/animation/rive/playback-gate.mjs |
| Date | Not specified in source |
| Repository | Janady13/thatstupidcomputer |
| Revision | 57555cde46910102dd25d6c1c1fb2c359e9655bd |
| Branch | main |
| Path or archive member | apps/web/src/animation/rive/playback-gate.mjs |
| Original SHA-256 | 561699d2b5c41a9ddd2389db0791cf1ea1a83a99ea237a8ce224dae11da3f0ee |
| Git blob SHA | ac583642ceddde3cc2d5ba237f4b5a77c0b4dfd6 |
| Reading-copy SHA-256 | 561699d2b5c41a9ddd2389db0791cf1ea1a83a99ea237a8ce224dae11da3f0ee |
Recovered source transcript
Download complete source text ↓68 lines · 2.5 KB
import { MOBILE_ACTIONS } from '../core/contracts.mjs';
/** Pure command gate for the pinned Rive timeline API. Permission to select an
* action belongs to the caller's safeAction contract; this gate never chooses one.
* Keeping conditions outside React effect closures prevents replay/visibility races.
*/
export function createPlaybackGate(player, initial) {
let state;
let restartPending = true;
let playing = false;
let completed = false;
let disposed = false;
let issuingStop = false;
let activeAction;
function validate(next) {
if (!MOBILE_ACTIONS.includes(next.action)) throw new RangeError('Unknown native action');
if (!Number.isSafeInteger(next.replay) || next.replay < 0) throw new RangeError('Invalid replay counter');
for (const key of ['paused', 'visible', 'hidden', 'reduced']) if (typeof next[key] !== 'boolean') throw new TypeError(`Expected boolean ${key}`);
return next;
}
function sync() {
if (disposed) return;
if (state.paused || !state.visible || state.hidden || state.reduced || completed) {
if (playing) { playing = false; player.pause(); }
return;
}
if (restartPending) {
// stop() synchronously emits Stop in the pinned runtime. It is our restart,
// not a natural completion of the just-selected action.
issuingStop = true;
try { player.stop(); } finally { issuingStop = false; }
restartPending = false;
activeAction = state.action;
playing = true;
player.play(state.action);
} else if (!playing) {
// play(existing paused name) resumes its instance; stop() would lose phase.
playing = true;
player.play(state.action);
}
}
const gate = Object.freeze({
update(patch) {
if (disposed) return;
const next = validate({ ...state, ...patch });
if (next.action !== state.action || next.replay !== state.replay) { restartPending = true; completed = false; }
state = next;
sync();
},
stopped(data) {
if (disposed || issuingStop) return;
const names = typeof data === 'string' ? [data] : Array.isArray(data) ? data : [];
if (!names.includes(activeAction)) return;
playing = false;
// A newer explicit replay/action queued while hidden remains pending.
if (!restartPending && activeAction === state.action) completed = true;
},
dispose() {
if (disposed) return;
disposed = true;
if (playing) { playing = false; player.pause(); }
},
});
state = validate({ ...initial });
sync();
return gate;
}