rrweb
Version:
record and replay the web
233 lines (230 loc) • 8.9 kB
JavaScript
import { __assign, __values } from '../../node_modules/tslib/tslib.es6.js';
import { ReplayerEvents, EventType } from '../types.js';
import { needCastInSyncMode } from '../utils.js';
import { addDelay } from './timer.js';
import { interpret as f, createMachine as c, assign as r } from '../../node_modules/@xstate/fsm/es/index.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 = c({
id: 'player',
context: context,
initial: 'paused',
states: {
playing: {
on: {
PAUSE: {
target: 'paused',
actions: ['pause'],
},
CAST_EVENT: {
target: 'playing',
actions: 'castEvent',
},
END: {
target: 'paused',
actions: ['resetLastPlayedEvent', 'pause'],
},
},
},
paused: {
on: {
PLAY: {
target: 'playing',
actions: ['recordTimeOffset', 'play'],
},
CAST_EVENT: {
target: 'paused',
actions: 'castEvent',
},
},
},
live: {
on: {
ADD_EVENT: {
target: 'live',
actions: ['addEvent'],
},
},
},
},
}, {
actions: {
castEvent: r({
lastPlayedEvent: function (ctx, event) {
if (event.type === 'CAST_EVENT') {
return event.payload.event;
}
return ctx.lastPlayedEvent;
},
}),
recordTimeOffset: r(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, e_2, _b;
var timer = ctx.timer, events = ctx.events, baselineTime = ctx.baselineTime, lastPlayedEvent = ctx.lastPlayedEvent;
timer.clear();
try {
for (var events_1 = __values(events), events_1_1 = events_1.next(); !events_1_1.done; events_1_1 = events_1.next()) {
var event = events_1_1.value;
addDelay(event, baselineTime);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (events_1_1 && !events_1_1.done && (_a = events_1.return)) _a.call(events_1);
}
finally { if (e_1) throw e_1.error; }
}
var neededEvents = discardPriorSnapshots(events, baselineTime);
var actions = new Array();
var _loop_1 = 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: event.delay,
});
}
};
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_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (neededEvents_1_1 && !neededEvents_1_1.done && (_b = neededEvents_1.return)) _b.call(neededEvents_1);
}
finally { if (e_2) throw e_2.error; }
}
emitter.emit(ReplayerEvents.Flush);
timer.addActions(actions);
timer.start();
},
pause: function (ctx) {
ctx.timer.clear();
},
resetLastPlayedEvent: r(function (ctx) {
return __assign(__assign({}, ctx), { lastPlayedEvent: null });
}),
startLive: r({
baselineTime: function (ctx, event) {
ctx.timer.toggleLiveMode(true);
ctx.timer.start();
if (event.type === 'TO_LIVE' && event.payload.baselineTime) {
return event.payload.baselineTime;
}
return Date.now();
},
}),
addEvent: r(function (ctx, machineEvent) {
var baselineTime = ctx.baselineTime, timer = ctx.timer, events = ctx.events;
if (machineEvent.type === 'ADD_EVENT') {
var event_1 = machineEvent.payload.event;
addDelay(event_1, baselineTime);
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: event_1.delay,
});
}
}
return __assign(__assign({}, ctx), { events: events });
}),
},
});
return f(playerMachine);
}
function createSpeedService(context) {
var speedMachine = c({
id: 'speed',
context: context,
initial: 'normal',
states: {
normal: {
on: {
FAST_FORWARD: {
target: 'skipping',
actions: ['recordSpeed', 'setSpeed'],
},
SET_SPEED: {
target: 'normal',
actions: ['setSpeed'],
},
},
},
skipping: {
on: {
BACK_TO_NORMAL: {
target: 'normal',
actions: ['restoreSpeed'],
},
SET_SPEED: {
target: 'normal',
actions: ['setSpeed'],
},
},
},
},
}, {
actions: {
setSpeed: function (ctx, event) {
if ('payload' in event) {
ctx.timer.setSpeed(event.payload.speed);
}
},
recordSpeed: r({
normalSpeed: function (ctx) { return ctx.timer.speed; },
}),
restoreSpeed: function (ctx) {
ctx.timer.setSpeed(ctx.normalSpeed);
},
},
});
return f(speedMachine);
}
export { createPlayerService, createSpeedService, discardPriorSnapshots };