From 69315dc2ed78965f0d10836b22e54d9023e4dc4a Mon Sep 17 00:00:00 2001 From: 7w1 Date: Sun, 24 May 2026 21:50:15 -0500 Subject: [PATCH 1/2] fix: various hardening for audio messages to ensure finite-ness --- .../components/message/MsgTypeRenderers.tsx | 27 +++++++++++++- .../message/content/AudioContent.tsx | 35 +++++++++++++------ .../upload-card/UploadCardRenderer.tsx | 1 + .../hooks/media/useMediaPlayTimeCallback.ts | 6 +++- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/app/components/message/MsgTypeRenderers.tsx b/src/app/components/message/MsgTypeRenderers.tsx index cb91c3474..7d420655c 100644 --- a/src/app/components/message/MsgTypeRenderers.tsx +++ b/src/app/components/message/MsgTypeRenderers.tsx @@ -511,6 +511,28 @@ export function MVideo({ content, renderAsFile, renderVideoContent, outlined }: ); } +const getAudioDurationMs = (content: IAudioContent, info?: IAudioInfo): number | undefined => { + const fromInfo = info?.duration; + if (typeof fromInfo === 'number' && Number.isFinite(fromInfo) && fromInfo > 0) { + return fromInfo; + } + const voiceV2 = (content as Record)['org.matrix.msc3245.voice.v2']; + if (voiceV2 && typeof voiceV2 === 'object') { + const seconds = (voiceV2 as { duration?: number }).duration; + if (typeof seconds === 'number' && Number.isFinite(seconds) && seconds > 0) { + return seconds * 1000; + } + } + const msc1767Audio = (content as Record)['org.matrix.msc1767.audio']; + if (msc1767Audio && typeof msc1767Audio === 'object') { + const ms = (msc1767Audio as { duration?: number }).duration; + if (typeof ms === 'number' && Number.isFinite(ms) && ms > 0) { + return ms; + } + } + return undefined; +}; + type RenderAudioContentProps = { info: IAudioInfo; mimeType: string; @@ -536,6 +558,9 @@ export function MAudio({ content, renderAsFile, renderAudioContent, outlined }: } const filename = content.filename ?? content.body ?? 'Audio'; + const durationMs = getAudioDurationMs(content, audioInfo); + const resolvedInfo = + durationMs !== undefined ? { ...audioInfo, duration: durationMs } : audioInfo; return ( @@ -555,7 +580,7 @@ export function MAudio({ content, renderAsFile, renderAudioContent, outlined }: {renderAudioContent({ - info: audioInfo, + info: resolvedInfo, mimeType: safeMimeType, url: mxcUrl, encInfo: content.file, diff --git a/src/app/components/message/content/AudioContent.tsx b/src/app/components/message/content/AudioContent.tsx index f20bbca64..89674edd9 100644 --- a/src/app/components/message/content/AudioContent.tsx +++ b/src/app/components/message/content/AudioContent.tsx @@ -72,8 +72,10 @@ export function AudioContent({ const [currentTime, setCurrentTime] = useState(0); // duration in seconds. (NOTE: info.duration is in milliseconds) - const infoDuration = info.duration ?? 0; - const [duration, setDuration] = useState((infoDuration >= 0 ? infoDuration : 0) / 1000); + const infoDurationMs = info.duration ?? 0; + const initialDurationSec = + Number.isFinite(infoDurationMs) && infoDurationMs > 0 ? infoDurationMs / 1000 : 0; + const [duration, setDuration] = useState(initialDurationSec); const getAudioRef = useCallback(() => audioRef.current, []); const { loading } = useMediaLoading(getAudioRef); @@ -81,9 +83,15 @@ export function AudioContent({ const { seek } = useMediaSeek(getAudioRef); const { volume, mute, setMute, setVolume } = useMediaVolume(getAudioRef); const handlePlayTimeCallback: PlayTimeCallback = useCallback((d, ct) => { - setDuration(d); - setCurrentTime(ct); + if (Number.isFinite(d) && d > 0) setDuration(d); + if (Number.isFinite(ct) && ct >= 0) setCurrentTime(ct); }, []); + + const trackMax = duration > 0 ? duration : 1; + const trackTime = + duration > 0 ? Math.min(Number.isFinite(currentTime) ? currentTime : 0, duration) : 0; + const displayDuration = duration > 0 ? duration : 0; + const displayCurrentTime = Number.isFinite(currentTime) && currentTime >= 0 ? currentTime : 0; useMediaPlayTimeCallback( getAudioRef, useThrottle(handlePlayTimeCallback, PLAY_TIME_THROTTLE_OPS) @@ -102,9 +110,14 @@ export function AudioContent({ seek(values[0] ?? 0)} + max={trackMax} + values={[trackTime]} + onChange={(values) => { + if (!(duration > 0)) return; + const next = values[0] ?? 0; + if (!Number.isFinite(next)) return; + seek(Math.max(0, Math.min(next, duration))); + }} renderTrack={(params) => { const { key, ...restProps } = params.props as unknown as { key?: string; @@ -118,8 +131,8 @@ export function AudioContent({ variant="Secondary" size="300" min={0} - max={duration} - value={currentTime} + max={trackMax} + value={trackTime} radii="300" /> @@ -168,8 +181,8 @@ export function AudioContent({ {`${secondsToMinutesAndSeconds( - currentTime - )} / ${secondsToMinutesAndSeconds(duration)}`} + displayCurrentTime + )} / ${secondsToMinutesAndSeconds(displayDuration)}`} ), rightControl: ( diff --git a/src/app/components/upload-card/UploadCardRenderer.tsx b/src/app/components/upload-card/UploadCardRenderer.tsx index 633babdaa..54fb22f53 100644 --- a/src/app/components/upload-card/UploadCardRenderer.tsx +++ b/src/app/components/upload-card/UploadCardRenderer.tsx @@ -82,6 +82,7 @@ function PreviewVideo({ fileItem }: Readonly) { const BAR_COUNT = 44; function formatAudioTime(s: number): string { + if (!Number.isFinite(s) || s < 0) return '0:00'; const m = Math.floor(s / 60); const sec = Math.floor(s % 60); return `${m}:${sec.toString().padStart(2, '0')}`; diff --git a/src/app/hooks/media/useMediaPlayTimeCallback.ts b/src/app/hooks/media/useMediaPlayTimeCallback.ts index c70ddc6e8..a67575130 100644 --- a/src/app/hooks/media/useMediaPlayTimeCallback.ts +++ b/src/app/hooks/media/useMediaPlayTimeCallback.ts @@ -10,7 +10,11 @@ export const useMediaPlayTimeCallback = ( const targetEl = getTargetElement(); const handleChange = () => { if (!targetEl) return; - onPlayTimeCallback(targetEl.duration, targetEl.currentTime); + const { duration, currentTime } = targetEl; + onPlayTimeCallback( + duration, + Number.isFinite(currentTime) && currentTime >= 0 ? currentTime : 0 + ); }; targetEl?.addEventListener('timeupdate', handleChange); targetEl?.addEventListener('loadedmetadata', handleChange); From 898d22ca52ef43e84c57575e49d5f745ea899d8a Mon Sep 17 00:00:00 2001 From: 7w1 Date: Sun, 24 May 2026 21:52:32 -0500 Subject: [PATCH 2/2] changeset --- .changeset/fix-audio-inifinity-length.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-audio-inifinity-length.md diff --git a/.changeset/fix-audio-inifinity-length.md b/.changeset/fix-audio-inifinity-length.md new file mode 100644 index 000000000..b8197faec --- /dev/null +++ b/.changeset/fix-audio-inifinity-length.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix crash during scrubbing before duration duration appears.