implement pause which has a better performance than play at some time offset

This commit is contained in:
Yanzhen Yu
2026-04-01 12:00:00 +08:00
parent 47467ff692
commit 97ed5eb229

View File

@@ -31,11 +31,14 @@ export class Replayer {
private iframe: HTMLIFrameElement;
private mouse: HTMLDivElement;
private startTime: number = 0;
private baselineTime: number = 0;
private timerIds: number[] = [];
private emitter: mitt.Emitter = mitt();
// record last played event timestamp when paused
private lastPlayedEvent: eventWithTime;
constructor(events: eventWithTime[], config?: Partial<playerConfig>) {
if (events.length < 2) {
throw new Error('Replayer need at least 2 events.');
@@ -76,37 +79,10 @@ export class Replayer {
* @param timeOffset number
*/
public play(timeOffset = 0) {
this.startTime = this.events[0].timestamp + timeOffset;
this.baselineTime = this.events[0].timestamp + timeOffset;
for (const event of this.events) {
const isSync = event.timestamp < this.startTime;
let castFn: undefined | (() => void);
switch (event.type) {
case EventType.DomContentLoaded:
case EventType.Load:
break;
case EventType.Meta:
castFn = () =>
this.emitter.emit('resize', {
width: event.data.width,
height: event.data.height,
});
break;
case EventType.FullSnapshot:
castFn = () => {
this.rebuildFullSnapshot(event);
this.iframe.contentWindow!.scrollTo(event.data.initialOffset);
};
break;
case EventType.IncrementalSnapshot:
castFn = () => {
this.applyIncremental(event.data, isSync);
};
break;
default:
}
if (!castFn) {
continue;
}
const isSync = event.timestamp < this.baselineTime;
const castFn = this.getCastFn(event, isSync);
if (isSync) {
castFn();
} else {
@@ -119,6 +95,20 @@ export class Replayer {
this.timerIds.forEach(clear);
}
public resume(timeOffset = 0) {
for (const event of this.events) {
if (
event.timestamp < this.lastPlayedEvent.timestamp ||
event === this.lastPlayedEvent
) {
continue;
}
const delayToBaseline = this.getDelay(event);
const castFn = this.getCastFn(event);
this.later(castFn, delayToBaseline - timeOffset);
}
}
private setupDom() {
this.wrapper = document.createElement('div');
this.wrapper.classList.add('replayer-wrapper');
@@ -151,7 +141,7 @@ export class Replayer {
event.data.source === IncrementalSource.MouseMove
) {
const firstOffset = event.data.positions[0].timeOffset;
// timeoffset is a negative offset to event.timestamp
// timeOffset is a negative offset to event.timestamp
const firstTimestamp = event.timestamp + firstOffset;
event.data.positions = event.data.positions.map(p => {
return {
@@ -159,12 +149,49 @@ export class Replayer {
timeOffset: p.timeOffset - firstOffset,
};
});
return firstTimestamp - this.startTime;
return firstTimestamp - this.baselineTime;
}
return event.timestamp - this.startTime;
return event.timestamp - this.baselineTime;
}
private rebuildFullSnapshot(event: fullSnapshotEvent) {
private getCastFn(event: eventWithTime, isSync = false) {
let castFn: undefined | (() => void);
switch (event.type) {
case EventType.DomContentLoaded:
case EventType.Load:
break;
case EventType.Meta:
castFn = () =>
this.emitter.emit('resize', {
width: event.data.width,
height: event.data.height,
});
break;
case EventType.FullSnapshot:
castFn = () => {
this.rebuildFullSnapshot(event);
this.iframe.contentWindow!.scrollTo(event.data.initialOffset);
};
break;
case EventType.IncrementalSnapshot:
castFn = () => {
this.applyIncremental(event.data, isSync);
};
break;
default:
}
const wrappedCastFn = () => {
if (castFn) {
castFn();
}
this.lastPlayedEvent = event;
};
return wrappedCastFn;
}
private rebuildFullSnapshot(
event: fullSnapshotEvent & { timestamp: number },
) {
mirror.map = rebuild(event.data.node, this.iframe.contentDocument!)[1];
// avoid form submit to refresh the iframe
this.iframe.contentDocument!.querySelectorAll('form').forEach(form => {