UNPKG

@xysfe/memento-core

Version:

record and replay the web

252 lines (249 loc) 10.7 kB
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 };