Skip to content

Commit 254cd5e

Browse files
authored
Fix crash while scrubbing some audio files (#902)
<!-- Please read https://github.com/SableClient/Sable/blob/dev/CONTRIBUTING.md before submitting your pull request --> ### Description I don't think we technically do "scrubbing" but idk any other word for it. <!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. --> Fixes the crash while scrubbing audio files by pretty much just spamming infinity checks everywhere and adding a helper to calculate duration properly (previously the voice message content never specified duration so it would only appear after the file fully loaded in). #### Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ### Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings ### AI disclosure: - [ ] Partially AI assisted (clarify which code was AI assisted and briefly explain what it does). - [ ] Fully AI generated (explain what all the generated code does in moderate detail). <!-- Write any explanation required here, but do not generate the explanation using AI!! You must prove you understand what the code in this PR does. -->
2 parents 9038386 + 898d22c commit 254cd5e

5 files changed

Lines changed: 61 additions & 13 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: patch
3+
---
4+
5+
Fix crash during scrubbing before duration duration appears.

src/app/components/message/MsgTypeRenderers.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,28 @@ export function MVideo({ content, renderAsFile, renderVideoContent, outlined }:
511511
);
512512
}
513513

514+
const getAudioDurationMs = (content: IAudioContent, info?: IAudioInfo): number | undefined => {
515+
const fromInfo = info?.duration;
516+
if (typeof fromInfo === 'number' && Number.isFinite(fromInfo) && fromInfo > 0) {
517+
return fromInfo;
518+
}
519+
const voiceV2 = (content as Record<string, unknown>)['org.matrix.msc3245.voice.v2'];
520+
if (voiceV2 && typeof voiceV2 === 'object') {
521+
const seconds = (voiceV2 as { duration?: number }).duration;
522+
if (typeof seconds === 'number' && Number.isFinite(seconds) && seconds > 0) {
523+
return seconds * 1000;
524+
}
525+
}
526+
const msc1767Audio = (content as Record<string, unknown>)['org.matrix.msc1767.audio'];
527+
if (msc1767Audio && typeof msc1767Audio === 'object') {
528+
const ms = (msc1767Audio as { duration?: number }).duration;
529+
if (typeof ms === 'number' && Number.isFinite(ms) && ms > 0) {
530+
return ms;
531+
}
532+
}
533+
return undefined;
534+
};
535+
514536
type RenderAudioContentProps = {
515537
info: IAudioInfo;
516538
mimeType: string;
@@ -536,6 +558,9 @@ export function MAudio({ content, renderAsFile, renderAudioContent, outlined }:
536558
}
537559

538560
const filename = content.filename ?? content.body ?? 'Audio';
561+
const durationMs = getAudioDurationMs(content, audioInfo);
562+
const resolvedInfo =
563+
durationMs !== undefined ? { ...audioInfo, duration: durationMs } : audioInfo;
539564
return (
540565
<Attachment outlined={outlined}>
541566
<AttachmentHeader>
@@ -555,7 +580,7 @@ export function MAudio({ content, renderAsFile, renderAudioContent, outlined }:
555580
<AttachmentBox>
556581
<AttachmentContent>
557582
{renderAudioContent({
558-
info: audioInfo,
583+
info: resolvedInfo,
559584
mimeType: safeMimeType,
560585
url: mxcUrl,
561586
encInfo: content.file,

src/app/components/message/content/AudioContent.tsx

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,26 @@ export function AudioContent({
7272

7373
const [currentTime, setCurrentTime] = useState(0);
7474
// duration in seconds. (NOTE: info.duration is in milliseconds)
75-
const infoDuration = info.duration ?? 0;
76-
const [duration, setDuration] = useState((infoDuration >= 0 ? infoDuration : 0) / 1000);
75+
const infoDurationMs = info.duration ?? 0;
76+
const initialDurationSec =
77+
Number.isFinite(infoDurationMs) && infoDurationMs > 0 ? infoDurationMs / 1000 : 0;
78+
const [duration, setDuration] = useState(initialDurationSec);
7779

7880
const getAudioRef = useCallback(() => audioRef.current, []);
7981
const { loading } = useMediaLoading(getAudioRef);
8082
const { playing, setPlaying } = useMediaPlay(getAudioRef);
8183
const { seek } = useMediaSeek(getAudioRef);
8284
const { volume, mute, setMute, setVolume } = useMediaVolume(getAudioRef);
8385
const handlePlayTimeCallback: PlayTimeCallback = useCallback((d, ct) => {
84-
setDuration(d);
85-
setCurrentTime(ct);
86+
if (Number.isFinite(d) && d > 0) setDuration(d);
87+
if (Number.isFinite(ct) && ct >= 0) setCurrentTime(ct);
8688
}, []);
89+
90+
const trackMax = duration > 0 ? duration : 1;
91+
const trackTime =
92+
duration > 0 ? Math.min(Number.isFinite(currentTime) ? currentTime : 0, duration) : 0;
93+
const displayDuration = duration > 0 ? duration : 0;
94+
const displayCurrentTime = Number.isFinite(currentTime) && currentTime >= 0 ? currentTime : 0;
8795
useMediaPlayTimeCallback(
8896
getAudioRef,
8997
useThrottle(handlePlayTimeCallback, PLAY_TIME_THROTTLE_OPS)
@@ -102,9 +110,14 @@ export function AudioContent({
102110
<Range
103111
step={1}
104112
min={0}
105-
max={duration || 1}
106-
values={[currentTime]}
107-
onChange={(values) => seek(values[0] ?? 0)}
113+
max={trackMax}
114+
values={[trackTime]}
115+
onChange={(values) => {
116+
if (!(duration > 0)) return;
117+
const next = values[0] ?? 0;
118+
if (!Number.isFinite(next)) return;
119+
seek(Math.max(0, Math.min(next, duration)));
120+
}}
108121
renderTrack={(params) => {
109122
const { key, ...restProps } = params.props as unknown as {
110123
key?: string;
@@ -118,8 +131,8 @@ export function AudioContent({
118131
variant="Secondary"
119132
size="300"
120133
min={0}
121-
max={duration}
122-
value={currentTime}
134+
max={trackMax}
135+
value={trackTime}
123136
radii="300"
124137
/>
125138
</div>
@@ -168,8 +181,8 @@ export function AudioContent({
168181
</Chip>
169182

170183
<Text size="T200">{`${secondsToMinutesAndSeconds(
171-
currentTime
172-
)} / ${secondsToMinutesAndSeconds(duration)}`}</Text>
184+
displayCurrentTime
185+
)} / ${secondsToMinutesAndSeconds(displayDuration)}`}</Text>
173186
</>
174187
),
175188
rightControl: (

src/app/components/upload-card/UploadCardRenderer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ function PreviewVideo({ fileItem }: Readonly<PreviewVideoProps>) {
8282
const BAR_COUNT = 44;
8383

8484
function formatAudioTime(s: number): string {
85+
if (!Number.isFinite(s) || s < 0) return '0:00';
8586
const m = Math.floor(s / 60);
8687
const sec = Math.floor(s % 60);
8788
return `${m}:${sec.toString().padStart(2, '0')}`;

src/app/hooks/media/useMediaPlayTimeCallback.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ export const useMediaPlayTimeCallback = (
1010
const targetEl = getTargetElement();
1111
const handleChange = () => {
1212
if (!targetEl) return;
13-
onPlayTimeCallback(targetEl.duration, targetEl.currentTime);
13+
const { duration, currentTime } = targetEl;
14+
onPlayTimeCallback(
15+
duration,
16+
Number.isFinite(currentTime) && currentTime >= 0 ? currentTime : 0
17+
);
1418
};
1519
targetEl?.addEventListener('timeupdate', handleChange);
1620
targetEl?.addEventListener('loadedmetadata', handleChange);

0 commit comments

Comments
 (0)