feat(voice): pause / resume while recording

The voice recorder was a single continuous take — an interruption meant
stopping early or starting over (and the Stop button confusingly used a
Pause icon). Add real pause/resume.

- MediaRecorder.pause()/resume() with a new 'paused' state.
- Duration now accumulates only active-recording time: an accumulate-on-
  pause model (accumulatedMsRef + segmentStartRef) replaces the wall-clock
  startTime, so paused time is excluded from both the live timer and the
  finalized preview duration.
- Extracted startMeters/stopMeters so the waveform rAF + timer interval are
  reused across start/resume; stopMeters keeps the audio graph alive for
  resume while stopAll tears it down.
- Recording view now also renders the 'paused' state: a Pause/Resume toggle
  (Pause vs Play icon), the record dot stops pulsing (dimmed), and the
  waveform/timer freeze. Stop/Cancel/unmount all handle a paused recorder.
- Fixed the mislabeled finish button: it now shows a checkmark (it advances
  to the preview step) instead of a pause icon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 13:58:01 -04:00
co-authored by Claude Opus 4.8
parent 87b1c1a702
commit 13304f1571
2 changed files with 88 additions and 34 deletions
+1
View File
@@ -859,6 +859,7 @@ player.kick).
- **Waveform scrubbing** — voice messages carry an MSC1767 waveform (`org.matrix.msc1767.audio.waveform`, sent by the recorder). Playback renders it as bars that fill with the accent as the clip plays (TDS green under Lotus Terminal), and the waveform **is** the seek control — click, drag, or keyboard (arrows ±5s, Home/End) to scrub (`role="slider"`, ARIA value text). Threaded through `MAudio` (`MsgTypeRenderers.tsx`) + passed directly by the gallery. Regular audio with no waveform keeps the plain seek bar.
- **Playback speed** — a cycle button (`[0.75, 1, 1.5, 2]×`); a `useEffect` sets `audioElement.playbackRate` and re-applies it on (re)load (the browser resets it).
- **Recording pause / resume** — `VoiceMessageRecorder.tsx` supports a `paused` state via `MediaRecorder.pause()/resume()`, so you can pause mid-recording and continue without a gap. The duration timer accumulates only active-recording time (paused time is excluded), the waveform/meters freeze while paused and the record dot stops pulsing, and the finish button (which advances to the preview/review step) is a checkmark distinct from the Pause control.
---