Don't have requestAnimationFrame looping in background for Live Mode (#1098)

This commit is contained in:
Eoghan Murray
2026-04-01 12:00:00 +08:00
committed by GitHub
parent d5ff6efc9a
commit bc2b945436
3 changed files with 38 additions and 39 deletions

View File

@@ -300,7 +300,6 @@ export class Replayer {
const timer = new Timer([], { const timer = new Timer([], {
speed: this.config.speed, speed: this.config.speed,
liveMode: this.config.liveMode,
}); });
this.service = createPlayerService( this.service = createPlayerService(
{ {
@@ -722,18 +721,16 @@ export class Replayer {
this.service.send('END'); this.service.send('END');
this.emitter.emit(ReplayerEvents.Finish); this.emitter.emit(ReplayerEvents.Finish);
}; };
let finish_buffer = 50; // allow for checking whether new events aren't just about to be loaded in
if ( if (
event.type === EventType.IncrementalSnapshot && event.type === EventType.IncrementalSnapshot &&
event.data.source === IncrementalSource.MouseMove && event.data.source === IncrementalSource.MouseMove &&
event.data.positions.length event.data.positions.length
) { ) {
// defer finish event if the last event is a mouse move // extend finish event if the last event is a mouse move so that the timer isn't stopped by the service before checking the last event
setTimeout(() => { finish_buffer += Math.max(0, -event.data.positions[0].timeOffset);
finish();
}, Math.max(0, -event.data.positions[0].timeOffset + 50)); // Add 50 to make sure the timer would check the last mousemove event. Otherwise, the timer may be stopped by the service before checking the last event.
} else {
finish();
} }
setTimeout(finish, finish_buffer);
} }
this.emitter.emit(ReplayerEvents.EventCast, event); this.emitter.emit(ReplayerEvents.EventCast, event);

View File

@@ -223,7 +223,6 @@ export function createPlayerService(
}), }),
startLive: assign({ startLive: assign({
baselineTime: (ctx, event) => { baselineTime: (ctx, event) => {
ctx.timer.toggleLiveMode(true);
ctx.timer.start(); ctx.timer.start();
if (event.type === 'TO_LIVE' && event.payload.baselineTime) { if (event.type === 'TO_LIVE' && event.payload.baselineTime) {
return event.payload.baselineTime; return event.payload.baselineTime;

View File

@@ -10,64 +10,71 @@ export class Timer {
public speed: number; public speed: number;
private actions: actionWithDelay[]; private actions: actionWithDelay[];
private raf: number | null = null; private raf: number | true | null = null;
private liveMode: boolean; private lastTimestamp: number;
constructor( constructor(
actions: actionWithDelay[] = [], actions: actionWithDelay[] = [],
config: { config: {
speed: number; speed: number;
liveMode: boolean;
}, },
) { ) {
this.actions = actions; this.actions = actions;
this.speed = config.speed; this.speed = config.speed;
this.liveMode = config.liveMode;
} }
/** /**
* Add an action, possibly after the timer starts. * Add an action, possibly after the timer starts.
*/ */
public addAction(action: actionWithDelay) { public addAction(action: actionWithDelay) {
const rafWasActive = this.raf === true;
if ( if (
!this.actions.length || !this.actions.length ||
this.actions[this.actions.length - 1].delay <= action.delay this.actions[this.actions.length - 1].delay <= action.delay
) { ) {
// 'fast track' // 'fast track'
this.actions.push(action); this.actions.push(action);
return; } else {
// binary search - events can arrive out of order in a realtime context
const index = this.findActionIndex(action);
this.actions.splice(index, 0, action);
}
if (rafWasActive) {
this.raf = requestAnimationFrame(this.rafCheck.bind(this));
} }
// binary search - events can arrive out of order in a realtime context
const index = this.findActionIndex(action);
this.actions.splice(index, 0, action);
} }
public start() { public start() {
this.timeOffset = 0; this.timeOffset = 0;
let lastTimestamp = performance.now(); this.lastTimestamp = performance.now();
const check = () => { this.raf = requestAnimationFrame(this.rafCheck.bind(this));
const time = performance.now(); }
this.timeOffset += (time - lastTimestamp) * this.speed;
lastTimestamp = time;
while (this.actions.length) {
const action = this.actions[0];
if (this.timeOffset >= action.delay) { private rafCheck() {
this.actions.shift(); const time = performance.now();
action.doAction(); this.timeOffset += (time - this.lastTimestamp) * this.speed;
} else { this.lastTimestamp = time;
break; while (this.actions.length) {
} const action = this.actions[0];
if (this.timeOffset >= action.delay) {
this.actions.shift();
action.doAction();
} else {
break;
} }
if (this.actions.length > 0 || this.liveMode) { }
this.raf = requestAnimationFrame(check); if (this.actions.length > 0) {
} this.raf = requestAnimationFrame(this.rafCheck.bind(this));
}; } else {
this.raf = requestAnimationFrame(check); this.raf = true; // was active
}
} }
public clear() { public clear() {
if (this.raf) { if (this.raf) {
cancelAnimationFrame(this.raf); if (this.raf !== true) {
cancelAnimationFrame(this.raf);
}
this.raf = null; this.raf = null;
} }
this.actions.length = 0; this.actions.length = 0;
@@ -77,10 +84,6 @@ export class Timer {
this.speed = speed; this.speed = speed;
} }
public toggleLiveMode(mode: boolean) {
this.liveMode = mode;
}
public isActive() { public isActive() {
return this.raf !== null; return this.raf !== null;
} }