Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2649af8
add refreshSkipState
srest2021 Sep 18, 2025
7393768
formatting
srest2021 Sep 18, 2025
6dfb046
Apply formatting changes
srest2021 Sep 18, 2025
0fb47e1
ref
srest2021 Sep 18, 2025
1ae1938
binary search event index tests
srest2021 Sep 18, 2025
b055a41
formatting
srest2021 Sep 18, 2025
59d5e55
get cached event index tests
srest2021 Sep 18, 2025
ad0b418
starting integration tests
srest2021 Sep 19, 2025
0d949c0
starting integration tests
srest2021 Sep 19, 2025
6f61b49
integration tests & formatting
srest2021 Sep 19, 2025
ae7a1bf
tentatively finish tests
srest2021 Sep 19, 2025
2c76eb8
remove caching
srest2021 Sep 19, 2025
f800088
formatting
srest2021 Sep 19, 2025
38b44c3
ref tests
srest2021 Sep 19, 2025
81f059a
ref tests
srest2021 Sep 19, 2025
07e0f86
renaming
srest2021 Sep 19, 2025
1aa27be
Apply formatting changes
srest2021 Sep 19, 2025
e801a09
try to retrigger cli checks
srest2021 Sep 19, 2025
6fd3a4e
call fastforward stuff internally
srest2021 Sep 23, 2025
7485fa8
integration tests
srest2021 Sep 23, 2025
8cf5a94
make reevaluate function private
srest2021 Sep 23, 2025
5ef7ee4
cleaning up
srest2021 Sep 23, 2025
e198513
more cleaning up
srest2021 Sep 23, 2025
901f92a
rename & move getCurrentEventIndex
srest2021 Sep 23, 2025
10df1fd
empty array check in getCurrentEventIndex
srest2021 Sep 23, 2025
738c036
remove redundant call
srest2021 Sep 23, 2025
9b6ae58
dont import replay module twice
srest2021 Sep 23, 2025
bfac522
rename getEventIndex; remove unnecessary test
srest2021 Sep 24, 2025
921ddcf
rename getEventIndex arg
srest2021 Sep 24, 2025
af1cf0e
update comment
srest2021 Sep 24, 2025
d0fa140
linter
srest2021 Sep 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions packages/rrweb/src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@
}

public setConfig(config: Partial<playerConfig>) {
const previousSkipInactive = this.config.skipInactive;
Object.keys(config).forEach((key) => {
const newConfigValue = config[key as keyof playerConfig];
(this.config as Record<keyof playerConfig, typeof newConfigValue>)[
Expand All @@ -490,6 +491,11 @@
});
if (!this.config.skipInactive) {
this.backToNormal();
} else if (
previousSkipInactive === false &&
this.config.skipInactive === true
) {
this.reevaluateFastForward();
}
if (typeof config.speed !== 'undefined') {
this.speedService.send({
Expand Down Expand Up @@ -557,6 +563,12 @@
* @param timeOffset - number
*/
public play(timeOffset = 0) {
if (
this.config.skipInactive &&
this.speedService.state.matches('skipping')
) {
this.backToNormal();
}
if (this.service.state.matches('paused')) {
this.service.send({ type: 'PLAY', payload: { timeOffset } });
} else {
Expand All @@ -568,6 +580,9 @@
?.getElementsByTagName('html')[0]
?.classList.remove('rrweb-paused');
this.emitter.emit(ReplayerEvents.Start);
if (this.config.skipInactive) {
this.reevaluateFastForward();
}
}

public pause(timeOffset?: number) {
Expand All @@ -577,6 +592,9 @@
if (typeof timeOffset === 'number') {
this.play(timeOffset);
this.service.send({ type: 'PAUSE' });
if (this.config.skipInactive) {
this.reevaluateFastForward();
}
}
const iframeDoc = getIFrameContentDocument(this.iframe);
iframeDoc?.getElementsByTagName('html')[0]?.classList.add('rrweb-paused');
Expand Down Expand Up @@ -633,6 +651,77 @@
this.cache = createCache();
}

private binarySearchEventIndex(
events: eventWithTime[],
currentEventTime: number,
): number {
let left = 0,
right = events.length - 1;
let result = -1;

while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (events[mid].timestamp <= currentEventTime) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}

private reevaluateFastForward(): void {
if (!this.config.skipInactive) {
return;
}

// Clear stale state
this.nextUserInteractionEvent = null;

// Get current time and convert to event-relative time
const events = this.service.state.context.events;
const firstEvent = events[0];
if (!firstEvent) {
return;
}
const currentEventTime = firstEvent.timestamp + this.getCurrentTime();

// Find current event index using binary search (O(log n))
const currentEventIndex = this.binarySearchEventIndex(
events,
currentEventTime,
);
if (currentEventIndex === -1) {
return;
}

// Find next user interaction event starting from the current event index
const currentEvent = events[currentEventIndex];
const threshold =
this.config.inactivePeriodThreshold *
this.speedService.state.context.timer.speed;
for (let i = currentEventIndex + 1; i < events.length; i++) {
const event = events[i];
if (this.isUserInteraction(event)) {
const gapTime = event.timestamp - currentEvent.timestamp;
// Fast forward if the gap time is greater than the threshold
if (gapTime > threshold) {
this.nextUserInteractionEvent = event;
const payload = {
speed: Math.min(
Math.round(gapTime / SKIP_TIME_INTERVAL),
this.config.maxSpeed,
),
};
this.speedService.send({ type: 'FAST_FORWARD', payload });
this.emitter.emit(ReplayerEvents.SkipStart, payload);
}
break;
}
}
}

private setupDom() {
this.wrapper = document.createElement('div');
this.wrapper.classList.add('replayer-wrapper');
Expand Down Expand Up @@ -1090,7 +1179,7 @@
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const imgd = ctx?.createImageData(canvas.width, canvas.height);
ctx?.putImageData(imgd!, 0, 0);

Check warning on line 1182 in packages/rrweb/src/replay/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/replay/index.ts#L1182

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
}
}
private async deserializeAndPreloadCanvasEvents(
Expand Down Expand Up @@ -1586,7 +1675,7 @@
// If the parent is attached a shadow dom after it's created, it won't have a shadow root.
if (!hasShadowRoot(parent)) {
(parent as Element | RRElement).attachShadow({ mode: 'open' });
parent = (parent as Element | RRElement).shadowRoot! as Node | RRNode;

Check warning on line 1678 in packages/rrweb/src/replay/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/replay/index.ts#L1678

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
} else parent = parent.shadowRoot as Node | RRNode;
}

Expand Down
Loading
Loading