@xysfe/memento-core
Version:
record and replay the web
252 lines (249 loc) • 10.7 kB
JavaScript
import { __values, __assign } from '../../node_modules/tslib/tslib.es6.js';
import { createMachine as s, interpret as v, assign as o } from '../../node_modules/@xstate/fsm/es/index.js';
import { ReplayerEvents, EventType } from '../types.js';
import { getDelay } from './timer.js';
import { needCastInSyncMode } from '../utils.js';
function discardPriorSnapshots(events, baselineTime) {
for (var idx = events.length - 1; idx >= 0; idx--) {
var event = events[idx];
if (event.type === EventType.Meta) {
if (event.timestamp <= baselineTime) {
return events.slice(idx);
}
}
}
return events;
}
function createPlayerService(context, _a) {
var getCastFn = _a.getCastFn, emitter = _a.emitter;
var playerMachine = s({
id: 'player',
context: context,
initial: 'inited',
states: {
inited: {
on: {
PLAY: {
target: 'playing',
actions: ['recordTimeOffset', 'play'],
},
TO_LIVE: {
target: 'live',
actions: ['startLive'],
},
},
},
playing: {
on: {
PAUSE: {
target: 'paused',
actions: ['pause'],
},
PLAY: {
target: 'playing',
actions: ['recordTimeOffset', 'play'],
},
FAST_FORWARD: 'skipping',
CAST_EVENT: {
target: 'playing',
actions: 'castEvent',
},
ADD_EVENTS: {
target: 'playing',
actions: ['addEvents', 'recordTimeOffset'],
},
},
},
paused: {
on: {
RESUME: {
target: 'playing',
actions: ['recordTimeOffset', 'play'],
},
CAST_EVENT: {
target: 'paused',
actions: 'castEvent',
},
ADD_EVENTS: {
target: 'paused',
actions: ['addEvents'],
},
},
},
skipping: {
on: {
BACK_TO_NORMAL: 'playing',
},
},
ended: {
on: {
REPLAY: {
target: 'playing',
actions: ['recordTimeOffset', 'play'],
},
ADD_EVENTS: {
target: 'playing',
actions: ['addEvents', 'recordTimeOffset', 'play'],
},
},
},
live: {
on: {
ADD_EVENT: {
target: 'live',
actions: ['addEvent'],
},
},
},
},
}, {
actions: {
castEvent: o({
lastPlayedEvent: function (ctx, event) {
if (event.type === 'CAST_EVENT') {
return event.payload.event;
}
return context.lastPlayedEvent;
},
}),
recordTimeOffset: o(function (ctx, event) {
var timeOffset = ctx.timeOffset;
if ('payload' in event && 'timeOffset' in event.payload) {
timeOffset = event.payload.timeOffset;
}
return __assign(__assign({}, ctx), { timeOffset: timeOffset, baselineTime: ctx.events[0].timestamp + timeOffset });
}),
play: function (ctx) {
var e_1, _a;
var timer = ctx.timer, events = ctx.events, baselineTime = ctx.baselineTime; ctx.lastPlayedEvent;
timer.clear();
var neededEvents = discardPriorSnapshots(events, baselineTime);
var actions = new Array();
var _loop_1 = function (event) {
var isSync = event.timestamp < baselineTime;
if (isSync && !needCastInSyncMode(event)) {
return "continue";
}
var castFn = getCastFn(event, isSync);
if (isSync) {
castFn();
}
else {
actions.push({
doAction: function () {
castFn();
emitter.emit(ReplayerEvents.EventCast, event);
},
delay: getDelay(event, baselineTime),
});
}
};
try {
for (var neededEvents_1 = __values(neededEvents), neededEvents_1_1 = neededEvents_1.next(); !neededEvents_1_1.done; neededEvents_1_1 = neededEvents_1.next()) {
var event = neededEvents_1_1.value;
_loop_1(event);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (neededEvents_1_1 && !neededEvents_1_1.done && (_a = neededEvents_1.return)) _a.call(neededEvents_1);
}
finally { if (e_1) throw e_1.error; }
}
emitter.emit(ReplayerEvents.Flush);
timer.addActions(actions);
timer.start();
},
pause: function (ctx) {
ctx.timer.clear();
},
startLive: o({
baselineTime: function (ctx, event) {
ctx.timer.start();
if (event.type === 'TO_LIVE' && event.payload.baselineTime) {
return event.payload.baselineTime;
}
return Date.now();
},
}),
addEvent: o(function (ctx, machineEvent) {
var baselineTime = ctx.baselineTime, timer = ctx.timer, events = ctx.events;
if (machineEvent.type === 'ADD_EVENT') {
var event_1 = machineEvent.payload.event;
events.push(event_1);
var isSync = event_1.timestamp < baselineTime;
var castFn_1 = getCastFn(event_1, isSync);
if (isSync) {
castFn_1();
}
else {
timer.addAction({
doAction: function () {
castFn_1();
emitter.emit(ReplayerEvents.EventCast, event_1);
},
delay: getDelay(event_1, baselineTime),
});
}
}
return __assign(__assign({}, ctx), { events: events });
}),
addEvents: o(function (ctx, machineEvent) {
var e_2, _a;
var timer = ctx.timer, events = ctx.events, baselineTime = ctx.baselineTime, lastPlayedEvent = ctx.lastPlayedEvent;
if (machineEvent.type === 'ADD_EVENTS') {
var eventList = machineEvent.payload.events;
if (Array.isArray(eventList) && eventList.length) {
events = events.concat(eventList);
var neededEvents = discardPriorSnapshots(eventList, baselineTime);
var actions = new Array();
var _loop_2 = function (event) {
if (lastPlayedEvent &&
lastPlayedEvent.timestamp > baselineTime &&
(event.timestamp <= lastPlayedEvent.timestamp ||
event === lastPlayedEvent)) {
return "continue";
}
var isSync = event.timestamp < baselineTime;
if (isSync && !needCastInSyncMode(event)) {
return "continue";
}
var castFn = getCastFn(event, isSync);
if (isSync) {
castFn();
}
else {
actions.push({
doAction: function () {
castFn();
emitter.emit(ReplayerEvents.EventCast, event);
},
delay: getDelay(event, baselineTime),
});
}
};
try {
for (var neededEvents_2 = __values(neededEvents), neededEvents_2_1 = neededEvents_2.next(); !neededEvents_2_1.done; neededEvents_2_1 = neededEvents_2.next()) {
var event = neededEvents_2_1.value;
_loop_2(event);
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (neededEvents_2_1 && !neededEvents_2_1.done && (_a = neededEvents_2.return)) _a.call(neededEvents_2);
}
finally { if (e_2) throw e_2.error; }
}
emitter.emit(ReplayerEvents.AddEvents);
timer.addActions(actions);
}
}
return __assign(__assign({}, ctx), { events: events });
}),
},
});
return v(playerMachine);
}
export { createPlayerService, discardPriorSnapshots };