diff --git a/src/replay/index.ts b/src/replay/index.ts index 316f525d..2a10aa18 100644 --- a/src/replay/index.ts +++ b/src/replay/index.ts @@ -21,6 +21,9 @@ import { mirror } from '../utils'; import injectStyleRules from './styles/inject-style'; import './styles/style.css'; +const SKIP_TIME_THRESHOLD = 10 * 1000; +const SKIP_TIME_INTERVAL = 5 * 1000; + smoothscroll.polyfill(); // https://github.com/rollup/rollup/issues/1267#issuecomment-296395734 @@ -31,6 +34,7 @@ const defaultConfig: playerConfig = { speed: 1, root: document.body, loadTimeout: 0, + skipInactive: false, }; export class Replayer { @@ -48,6 +52,9 @@ export class Replayer { // record last played event timestamp when paused private lastPlayedEvent: eventWithTime; + private nextUserInteractionEvent: eventWithTime | null; + private noramlSpeed: number; + private timer: Timer; private missingNodeRetryMap: missingNodeMap = {}; @@ -167,8 +174,10 @@ export class Replayer { const firstOffset = event.data.positions[0].timeOffset; // timeOffset is a negative offset to event.timestamp const firstTimestamp = event.timestamp + firstOffset; + event.delay = firstTimestamp - this.baselineTime; return firstTimestamp - this.baselineTime; } + event.delay = event.timestamp - this.baselineTime; return event.timestamp - this.baselineTime; } @@ -194,6 +203,36 @@ export class Replayer { case EventType.IncrementalSnapshot: castFn = () => { this.applyIncremental(event, isSync); + if (event === this.nextUserInteractionEvent) { + this.nextUserInteractionEvent = null; + this.setConfig({ speed: this.noramlSpeed }); + this.emitter.emit('skip-end'); + } + if (this.config.skipInactive && !this.nextUserInteractionEvent) { + for (const _event of this.events) { + if (_event.delay! <= event.delay!) { + continue; + } + if (this.isUserInteraction(_event)) { + if ( + _event.delay! - event.delay! > + SKIP_TIME_THRESHOLD * this.config.speed + ) { + this.nextUserInteractionEvent = _event; + } + break; + } + } + if (this.nextUserInteractionEvent) { + this.noramlSpeed = this.config.speed; + const skipTime = + this.nextUserInteractionEvent.delay! - event.delay!; + this.setConfig({ + speed: Math.round(skipTime / SKIP_TIME_INTERVAL), + }); + this.emitter.emit('skip-start'); + } + } }; break; default: @@ -457,8 +496,12 @@ export class Replayer { const target: HTMLInputElement = (mirror.getNode( d.id, ) as Node) as HTMLInputElement; - target.checked = d.isChecked; - target.value = d.text; + try { + target.checked = d.isChecked; + target.value = d.text; + } catch (error) { + // for safe + } break; } default: @@ -506,4 +549,14 @@ export class Replayer { currentEl = currentEl.parentElement; } } + + private isUserInteraction(event: eventWithTime): boolean { + if (event.type !== EventType.IncrementalSnapshot) { + return false; + } + return ( + event.data.source > IncrementalSource.Mutation && + event.data.source <= IncrementalSource.Input + ); + } } diff --git a/src/replay/timer.ts b/src/replay/timer.ts index 2255bb4d..be4434d0 100644 --- a/src/replay/timer.ts +++ b/src/replay/timer.ts @@ -28,15 +28,15 @@ export default class Timer { public start() { this.actions.sort((a1, a2) => a1.delay - a2.delay); let delayed = 0; - const start = performance.now(); + let lastTimestamp = performance.now(); const { actions, config } = this; const self = this; function check(time: number) { - delayed = time - start; + delayed += (time - lastTimestamp) * config.speed; + lastTimestamp = time; while (actions.length) { const action = actions[0]; - const delayNeeded = action.delay / config.speed; - if (delayed >= delayNeeded) { + if (delayed >= action.delay) { actions.shift(); action.doAction(); } else { diff --git a/src/types.ts b/src/types.ts index ea28e2fe..86ffb8f0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -95,6 +95,7 @@ export type event = export type eventWithTime = event & { timestamp: number; + delay?: number; }; export type recordOptions = { @@ -227,6 +228,7 @@ export type playerConfig = { speed: number; root: Element; loadTimeout: number; + skipInactive: Boolean; }; export type playerMetaData = {