Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
bd202a2
update utils.ts: add a tool function to detect inactive periods
fengyun5264 Oct 29, 2022
6e53496
update Controller.svelte: add a fixed div element as an indicator
HurricaHjz Oct 29, 2022
e344e9c
update Controller.svelte: add one blank space at the end
HurricaHjz Oct 29, 2022
081277c
Merge pull request #6 from HurricaHjz/4-1-add-div-element
fengyun5264 Oct 29, 2022
d41f4fb
update Controller.svelte: add a variable inactivePeriods and use util…
MengZihan712 Oct 29, 2022
dbf84fb
Merge pull request #7 from HurricaHjz/4-2-add-a-variable-inactivePeri…
HurricaHjz Oct 29, 2022
ed34a77
update Controller.svelte: add width property for inactive activity in…
u6924169 Oct 29, 2022
2fa4595
Merge pull request #8 from HurricaHjz/4-3-add-width-property
MengZihan712 Oct 29, 2022
6e4c255
update Controller.svelte: combine calculation value with indicator UI
DexxDing Oct 29, 2022
dbc34e5
Merge pull request #9 from HurricaHjz/4-4-combine-calculation-value-w…
fengyun5264 Oct 29, 2022
db34a41
update utils.ts: fix error https://github.com/HurricaHjz/rrweb_2120_g…
fengyun5264 Oct 29, 2022
cb025d5
Merge pull request #10 from HurricaHjz/4-5-apply-code-review-suggestions
MengZihan712 Oct 29, 2022
8f117a7
Merge pull request #5 from HurricaHjz/4-inactive-indicator-progressbar
HurricaHjz Oct 29, 2022
47940d1
update Controller.svelte: make the color of indicator customizable
fengyun5264 Oct 31, 2022
8c09efe
Merge pull request #11 from HurricaHjz/4-6-apply-review-suggestion
MengZihan712 Oct 31, 2022
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
128 changes: 94 additions & 34 deletions packages/rrweb-player/src/Controller.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
createEventDispatcher,
afterUpdate,
} from 'svelte';
import { formatTime } from './utils';
import { formatTime, getInactivePeriods } from './utils';
import Switch from './components/Switch.svelte';

const dispatch = createEventDispatcher();
Expand All @@ -24,6 +24,7 @@
export let speedOption: number[];
export let speed = speedOption.length ? speedOption[0] : 1;
export let tags: Record<string, string> = {};
export let inactiveColor: string;

let currentTime = 0;
$: {
Expand Down Expand Up @@ -59,6 +60,21 @@
background: string;
position: string;
};

/**
* Calculate the tag position (percent) to be displayed on the progress bar.
* @param startTime - The start time of the session.
* @param endTime - The end time of the session.
* @param tagTime - The time of the tag.
* @returns The position of the tag. unit: percentage
*/
function position(startTime: number, endTime: number, tagTime: number) {
const sessionDuration = endTime - startTime;
const eventDuration = endTime - tagTime;
const eventPosition = 100 - (eventDuration / sessionDuration) * 100;
return eventPosition.toFixed(2);
}

let customEvents: CustomEvent[];
$: customEvents = (() => {
const { context } = replayer.service.state;
Expand All @@ -67,15 +83,6 @@
const end = context.events[totalEvents - 1].timestamp;
const customEvents: CustomEvent[] = [];

// calculate tag position.
const position = (startTime: number, endTime: number, tagTime: number) => {
const sessionDuration = endTime - startTime;
const eventDuration = endTime - tagTime;
const eventPosition = 100 - (eventDuration / sessionDuration) * 100;

return eventPosition.toFixed(2);
};

// loop through all the events and find out custom event.
context.events.forEach((event) => {
/**
Expand All @@ -95,6 +102,43 @@
return customEvents;
})();

let inactivePeriods: {
name: string;
background: string;
position: string;
width: string;
}[];
$: inactivePeriods = (() => {
try {
const { context } = replayer.service.state;
const totalEvents = context.events.length;
const start = context.events[0].timestamp;
const end = context.events[totalEvents - 1].timestamp;
const periods = getInactivePeriods(context.events);
// calculate the indicator width.
const getWidth = (
startTime: number,
endTime: number,
tagStart: number,
tagEnd: number,
) => {
const sessionDuration = endTime - startTime;
const eventDuration = tagEnd - tagStart;
const width = (eventDuration / sessionDuration) * 100;
return width.toFixed(2);
};
return periods.map((period) => ({
name: 'inactive period',
background: inactiveColor,
position: `${position(start, end, period[0])}%`,
width: `${getWidth(start, end, period[0], period[1])}%`,
}));
} catch (e) {
// For safety concern, if there is any error, the main function won't be affected.
return [];
}
})();

const loopTimer = () => {
stopTimer();

Expand Down Expand Up @@ -174,11 +218,11 @@
};

export const playRange = (
timeOffset: number,
endTimeOffset: number,
startLooping: boolean = false,
afterHook: undefined | (() => void) = undefined,
) => {
timeOffset: number,
endTimeOffset: number,
startLooping: boolean = false,
afterHook: undefined | (() => void) = undefined,
) => {
if (startLooping) {
loop = {
start: timeOffset,
Expand All @@ -193,7 +237,6 @@
replayer.play(timeOffset);
};


const handleProgressClick = (event: MouseEvent) => {
if (speedState === 'skipping') {
return;
Expand All @@ -207,7 +250,7 @@
percent = 1;
}
const timeOffset = meta.totalTime * percent;
finished = false
finished = false;
goto(timeOffset);
};

Expand All @@ -230,18 +273,18 @@
export const triggerUpdateMeta = () => {
return Promise.resolve().then(() => {
meta = replayer.getMetaData();
})
}
});
};

onMount(() => {
playerState = replayer.service.state.value;
speedState = replayer.speedService.state.value ;
speedState = replayer.speedService.state.value;
replayer.on(
'state-change',
(states: { player?: PlayerMachineState; speed?: SpeedMachineState }) => {
const { player, speed } = states;
if (player?.value && playerState !== player.value) {
playerState = player.value ;
playerState = player.value;
switch (playerState) {
case 'playing':
loopTimer();
Expand All @@ -254,7 +297,7 @@
}
}
if (speed?.value && speedState !== speed.value) {
speedState = speed.value ;
speedState = speed.value;
}
},
);
Expand Down Expand Up @@ -384,17 +427,27 @@
class="rr-progress"
class:disabled={speedState === 'skipping'}
bind:this={progress}
on:click={handleProgressClick}>
on:click={handleProgressClick}
>
<div
class="rr-progress__step"
bind:this={step}
style="width: {percentage}" />
style="width: {percentage}"
/>
{#each inactivePeriods as period}
<div
title={period.name}
style="width: {period.width};height: 4px;position: absolute;background: {period.background};left:
{period.position};"
/>
{/each}
{#each customEvents as event}
<div
title={event.name}
style="width: 10px;height: 5px;position: absolute;top:
2px;transform: translate(-50%, -50%);background: {event.background};left:
{event.position};" />
{event.position};"
/>
{/each}

<div class="rr-progress__handler" style="left: {percentage}" />
Expand All @@ -411,7 +464,8 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="16"
height="16">
height="16"
>
<path
d="M682.65984 128q53.00224 0 90.50112 37.49888t37.49888 90.50112l0
512q0 53.00224-37.49888 90.50112t-90.50112
Expand All @@ -426,7 +480,8 @@
12.4928-30.16704l0-512q0-17.67424-12.4928-30.16704t-30.16704-12.4928zM682.65984
213.34016q-17.67424 0-30.16704 12.4928t-12.4928 30.16704l0 512q0
17.67424 12.4928 30.16704t30.16704 12.4928 30.16704-12.4928
12.4928-30.16704l0-512q0-17.67424-12.4928-30.16704t-30.16704-12.4928z" />
12.4928-30.16704l0-512q0-17.67424-12.4928-30.16704t-30.16704-12.4928z"
/>
</svg>
{:else}
<svg
Expand All @@ -436,26 +491,30 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="16"
height="16">
height="16"
>
<path
d="M170.65984 896l0-768 640 384zM644.66944
512l-388.66944-233.32864 0 466.65728z" />
512l-388.66944-233.32864 0 466.65728z"
/>
</svg>
{/if}
</button>
{#each speedOption as s}
<button
class:active={s === speed && speedState !== 'skipping'}
on:click={() => setSpeed(s)}
disabled={speedState === 'skipping'}>
disabled={speedState === 'skipping'}
>
{s}x
</button>
{/each}
<Switch
id="skip"
bind:checked={skipInactive}
disabled={speedState === 'skipping'}
label="skip inactive" />
label="skip inactive"
/>
<button on:click={() => dispatch('fullscreen')}>
<svg
class="icon"
Expand All @@ -464,10 +523,10 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="16"
height="16">
height="16"
>
<defs>
<style type="text/css">

</style>
</defs>
<path
Expand All @@ -478,7 +537,8 @@
48s-21.6 48-48 48l-224 0c-26.4 0-48-21.6-48-48l0-224c0-26.4 21.6-48
48-48 26.4 0 48 21.6 48 48L164 792l253.6-253.6c18.4-18.4 48.8-18.4
68 0 18.4 18.4 18.4 48.8 0 68L231.2 860z"
p-id="1286" />
p-id="1286"
/>
</svg>
</button>
</div>
Expand Down
3 changes: 3 additions & 0 deletions packages/rrweb-player/src/Player.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
export let speed = 1;
export let showController = true;
export let tags: Record<string, string> = {};
// color of inactive periods indicator
export let inactiveColor = '#D4D4D4';

let replayer: Replayer;

Expand Down Expand Up @@ -229,6 +231,7 @@
{speedOption}
{skipInactive}
{tags}
{inactiveColor}
on:fullscreen={() => toggleFullscreen()}
/>
{/if}
Expand Down
40 changes: 40 additions & 0 deletions packages/rrweb-player/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ declare global {
}
}

import { EventType, IncrementalSource } from 'rrweb';
import type { eventWithTime } from 'rrweb/typings/types';

export function inlineCss(cssObj: Record<string, string>): string {
let style = '';
Object.keys(cssObj).forEach((key) => {
Expand Down Expand Up @@ -141,3 +144,40 @@ export function typeOf(
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
return map[toString.call(obj)];
}

/**
* Forked from 'rrweb' replay/index.ts. The original function is not exported.
* Determine whether the event is a user interaction event
* @param event - event to be determined
* @returns true if the event is a user interaction event
*/
function isUserInteraction(event: eventWithTime): boolean {
if (event.type !== EventType.IncrementalSnapshot) {
return false;
}
return (
event.data.source > IncrementalSource.Mutation &&
event.data.source <= IncrementalSource.Input
);
}

// Forked from 'rrweb' replay/index.ts. A const threshold of inactive time.
const SKIP_TIME_THRESHOLD = 10 * 1000;

/**
* Get periods of time when no user interaction happened from a list of events.
* @param events - all events
* @returns periods of time consist with [start time, end time]
*/
export function getInactivePeriods(events: eventWithTime[]) {
const inactivePeriods: [number, number][] = [];
let lastActiveTime = events[0].timestamp;
for (const event of events) {
if (!isUserInteraction(event)) continue;
if (event.timestamp - lastActiveTime > SKIP_TIME_THRESHOLD) {
inactivePeriods.push([lastActiveTime, event.timestamp]);
}
lastActiveTime = event.timestamp;
}
return inactivePeriods;
}
5 changes: 5 additions & 0 deletions packages/rrweb-player/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export type RRwebPlayerOptions = {
* @defaultValue `{}`
*/
tags?: Record<string, string>;
/**
* Customize the color of inactive periods indicator in the progress bar with a valid CSS color string.
* @defaultValue `#D4D4D4`
*/
inactiveColor?: string;
} & Partial<playerConfig>;
};

Expand Down