Skip to content

Seekable playback: convert committed AAC take to M4A for Review scrubbing #176

Description

@fel-cesar

Summary

The Record Tab Review state (#49) requires scrubbing the draft waveform to any position. Recording commits takes as ADTS AAC (.aac), which is not reliably seekable, so #49 waives the scrubbing acceptance criterion and defers it here to be solved and reviewed separately.

This issue covers both halves of the problem: the commit format and the scrubbing it needs to enable.

Background / why the format is the way it is

Recording captures into ADTS AAC (.aac) rather than the preset .m4a on purpose (see src/hooks/useRecorder.ts):

  • An MP4/M4A file is only valid once its moov atom is written on a clean stop(). A process kill mid-recording leaves it unplayable and un-appendable.
  • ADTS is a self-framing stream: a killed segment stays playable, and multiple segments concatenate by byte-append. This is what makes pause / resume-after-kill possible.

The trade-off: ADTS AAC has no container index / moov atom, so accurate absolute seeking (scrubbing) is unreliable. We do not want to give up kill-safe resumable capture to get seeking, so the two formats must coexist.

Current vs expected

  • Current: committed takes are stored as ADTS .aac. Playback works, but scrubbing to an arbitrary position is not reliably accurate.
  • Expected: Review can scrub to any position accurately, reading from a seekable file, without regressing resumable capture.

Proposed approach

Build a local (native) module — an Expo local/native module or config-plugin-backed module, since this is CNG / Android-only — that, after a take is committed, converts/remuxes the committed ADTS .aac into an M4A (MP4/AAC) file with a proper moov atom.

  • Keep ADTS AAC as the capture/resume format (do not regress kill-safe resumable takes).
  • Run the AAC → M4A conversion as part of / immediately after commit (commitRecording in src/hooks/useRecorder.ts), and resolve the committed take's playable URI to the M4A output.
  • Feed the seekable M4A into the existing playback/seek path (src/hooks/useAudioPlayback.ts) that backs the Review scrub UI (src/app/tabs/drafting/record/components/RecordingWaveform.tsx).

Scope

In scope

  • Local native module to convert/remux a committed AAC take to seekable M4A.
  • Wiring the converted M4A into the commit flow and playback URI resolution.
  • Accurate scrubbing in the Review state on the converted file.

Out of scope

  • Changing the capture/resume format (stays ADTS AAC).
  • Waveform amplitude generation / visual design beyond enabling accurate seek.

Acceptance criteria

  • A committed take is converted to a seekable M4A (MP4/AAC) file after commit.
  • Review-state scrubbing seeks accurately to arbitrary positions on the converted file (satisfies the Build Record Tab with Draft Recording and Playback #49-waived scrubbing AC).
  • Capture, pause, and resume-after-kill continue to work unchanged (still ADTS AAC).
  • Conversion failure is handled gracefully (logged; take remains playable even if non-seekable) — no data loss.
  • Android device QA recorded on the PR (native module + audio path).

Verification / test plan

  • Unit-test the commit → convert → resolve-URI path (mock the native module).
  • Android device QA: record a take, commit, then scrub the Review waveform to multiple positions and confirm playback resumes accurately from each.

File pointers

  • src/hooks/useRecorder.ts — AAC/ADTS capture decision (comment near the useAudioRecorder config) and commitRecording.
  • src/hooks/useAudioPlayback.tsseek() / playback used by Review.
  • src/app/tabs/drafting/record/components/RecordingWaveform.tsx — Review scrub UI (onSeek, durationMs).
  • app.config.ts / plugins/ — where a local native module / config plugin would be wired (CNG, Android-only).

Links

Refs #49
Related: #96 (playback engine + waveform decision)

Metadata

Metadata

Assignees

Labels

Fields

No fields configured for Feature.

Projects

Status
In PR Review

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions