Ignore firstFullSnapshot once only after initial 'poster' build (#608)

* Encountered a bug where firstFullSnapshot was played twice because timer was immediately started and reached the snapshot before the setTimeout returned

* Ignoring a FullSnapshot needs to be a one-time only thing, as otherwise we'll ignore it after scrubbing (restarting play head at a particular time). This is a problem if mutations have altered the player state, and we try to replay those mutations, so we e.g. try to remove an element that has already been removed because we haven't reset the FullSnapshot state

* Some `npm run typings` related fixups
This commit is contained in:
Eoghan Murray
2026-04-01 12:00:00 +08:00
committed by GitHub
parent 045e125e37
commit fb381a1254
2 changed files with 16 additions and 9 deletions

View File

@@ -89,8 +89,8 @@ export class Replayer {
private imageMap: Map<eventWithTime, HTMLImageElement> = new Map();
private mirror: Mirror = createMirror();
/** The first time the player is playing. */
private firstPlayedEvent: eventWithTime | null = null;
private firstFullSnapshot: eventWithTime | true | null = null;
private newDocumentQueue: addedNodeMutation[] = [];
@@ -145,7 +145,7 @@ export class Replayer {
}
});
this.emitter.on(ReplayerEvents.PlayBack, () => {
this.firstPlayedEvent = null;
this.firstFullSnapshot = null;
this.mirror.reset();
});
@@ -207,10 +207,11 @@ export class Replayer {
if (firstFullsnapshot) {
setTimeout(() => {
// when something has been played, there is no need to rebuild poster
if (this.firstPlayedEvent) {
if (this.firstFullSnapshot) {
// true if any other fullSnapshot has been executed by Timer already
return;
}
this.firstPlayedEvent = firstFullsnapshot;
this.firstFullSnapshot = firstFullsnapshot;
this.rebuildFullSnapshot(
firstFullsnapshot as fullSnapshotEvent & { timestamp: number },
);
@@ -429,9 +430,15 @@ export class Replayer {
break;
case EventType.FullSnapshot:
castFn = () => {
// Don't build a full snapshot during the first play through since we've already built it when the player was mounted.
if (this.firstPlayedEvent && this.firstPlayedEvent === event) {
return;
if (this.firstFullSnapshot) {
if (this.firstFullSnapshot === event) {
// we've already built this exact FullSnapshot when the player was mounted, and haven't built any other FullSnapshot since
this.firstFullSnapshot = true; // forget as we might need to re-execute this FullSnapshot later e.g. to rebuild after scrubbing
return;
}
} else {
// Timer (requestAnimationFrame) can be faster than setTimeout(..., 1)
this.firstFullSnapshot = true;
}
this.rebuildFullSnapshot(event, isSync);
this.iframe.contentWindow!.scrollTo(event.data.initialOffset);

View File

@@ -20,7 +20,7 @@ export declare class Replayer {
private elementStateMap;
private imageMap;
private mirror;
private firstPlayedEvent;
private firstFullSnapshot;
private newDocumentQueue;
constructor(events: Array<eventWithTime | string>, config?: Partial<playerConfig>);
on(event: string, handler: Handler): this;