Skip to content

Commit cbabee7

Browse files
jas-kascursoragentgetsantry[bot]michellewzhang
authored andcommitted
feat(replay): add time duration for each playback speed (#94888)
Provide users with immediate insight into the actual duration of a replay when viewed at different playback speeds, improving clarity and user experience. <img width="228" alt="SCR-20250707-nzrx" src="https://github.com/user-attachments/assets/a6bb864f-cdc9-4f53-bcaa-c2430bb0382b" /> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com> Co-authored-by: Michelle Zhang <[email protected]>
1 parent d26c9ec commit cbabee7

File tree

1 file changed

+53
-4
lines changed

1 file changed

+53
-4
lines changed

static/app/components/replays/preferences/replayPreferenceDropdown.tsx

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import styled from '@emotion/styled';
2+
13
import {Button} from 'sentry/components/core/button';
24
import {CompositeSelect} from 'sentry/components/core/compactSelect/composite';
5+
import {Flex} from 'sentry/components/core/layout';
6+
import {useReplayContext} from 'sentry/components/replays/replayContext';
37
import {IconSettings} from 'sentry/icons';
48
import {t} from 'sentry/locale';
9+
import formatDuration from 'sentry/utils/duration/formatDuration';
510
import {useReplayPrefs} from 'sentry/utils/replays/playback/providers/replayPreferencesContext';
611
import {toTitleCase} from 'sentry/utils/string/toTitleCase';
712

@@ -17,9 +22,24 @@ export default function ReplayPreferenceDropdown({
1722
isLoading?: boolean;
1823
}) {
1924
const [prefs, setPrefs] = useReplayPrefs();
25+
const {isFetching, replay} = useReplayContext();
2026

2127
const SKIP_OPTION_VALUE = 'skip';
2228

29+
// Calculate adjusted duration for each speed, rounded up to the nearest second.
30+
// Returns in seconds
31+
const calculateAdjustedDuration = (originalDurationMs: number, speed: number) => {
32+
if (speed === 1) {
33+
return originalDurationMs / 1000;
34+
}
35+
36+
return Math.ceil(originalDurationMs / speed / 1000);
37+
};
38+
39+
// Check if we should show duration (data is loaded and duration is available)
40+
const shouldShowDuration =
41+
!isLoading && !isFetching && replay && replay.getDurationMs() > 0;
42+
2343
return (
2444
<CompositeSelect
2545
disabled={isLoading}
@@ -37,10 +57,35 @@ export default function ReplayPreferenceDropdown({
3757
label={t('Playback Speed')}
3858
value={prefs.playbackSpeed}
3959
onChange={opt => setPrefs({playbackSpeed: opt.value})}
40-
options={speedOptions.map(option => ({
41-
label: `${option}x`,
42-
value: option,
43-
}))}
60+
options={speedOptions.map(option => {
61+
const baseLabel = `${option}x`;
62+
63+
if (shouldShowDuration) {
64+
const adjustedDurationMs = calculateAdjustedDuration(
65+
replay.getDurationMs(),
66+
option
67+
);
68+
const durationDisplay = formatDuration({
69+
duration: [adjustedDurationMs, 'sec'],
70+
precision: 'sec',
71+
style: 'h:mm:ss',
72+
});
73+
return {
74+
label: (
75+
<Flex justify="space-between">
76+
<span>{baseLabel}</span>
77+
<DurationDisplay>{durationDisplay}</DurationDisplay>
78+
</Flex>
79+
),
80+
value: option,
81+
};
82+
}
83+
84+
return {
85+
label: baseLabel,
86+
value: option,
87+
};
88+
})}
4489
/>
4590
<CompositeSelect.Region
4691
label={t('Timestamps')}
@@ -68,3 +113,7 @@ export default function ReplayPreferenceDropdown({
68113
</CompositeSelect>
69114
);
70115
}
116+
117+
const DurationDisplay = styled('span')`
118+
color: ${p => p.theme.subText};
119+
`;

0 commit comments

Comments
 (0)