From b8ee426e72f5b323ec2cbbfe15613a25dd11bd94 Mon Sep 17 00:00:00 2001 From: Kresna Date: Sat, 1 Aug 2026 14:26:52 +0700 Subject: [PATCH] fix(voice-to-text): make the recorded-audio player pausable/seekable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MediaRecorder blobs are a streaming container with no duration in the header, so the browser reports duration=Infinity and treats the clip like a live stream — freezing the native pause/seek controls after a recording (uploaded files were fine). On loadedmetadata, seek to the end once to force the browser to compute a real duration, then reset to 0; the controls then work normally. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/islands/media/VoiceToText.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/islands/media/VoiceToText.tsx b/src/islands/media/VoiceToText.tsx index 6cb0f2e..cc8b459 100644 --- a/src/islands/media/VoiceToText.tsx +++ b/src/islands/media/VoiceToText.tsx @@ -63,6 +63,17 @@ export default function VoiceToText() { if (f) setAudio(f); }; + // MediaRecorder blobs have no duration in their header, so the browser reports + // duration=Infinity and treats the clip like a live stream — which freezes the + // native pause/seek controls. Seek to the end once to force a real duration. + const fixAudioDuration = () => { + const el = audioRef.current; + if (!el || el.duration !== Infinity) return; + const onUpdate = () => { el.removeEventListener('timeupdate', onUpdate); el.currentTime = 0; }; + el.addEventListener('timeupdate', onUpdate); + try { el.currentTime = 1e101; } catch { /* ignore */ } + }; + const toggleRecord = () => { if (recorder.recording) { recorder.stop(); @@ -138,7 +149,7 @@ export default function VoiceToText() { {recorder.error && {recorder.error.message}} {audioUrl && ( -