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
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.ts — seek() / 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)
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.m4aon purpose (seesrc/hooks/useRecorder.ts):moovatom is written on a cleanstop(). A process kill mid-recording leaves it unplayable and un-appendable.The trade-off: ADTS AAC has no container index /
moovatom, 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
.aac. Playback works, but scrubbing to an arbitrary position is not reliably accurate.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
.aacinto an M4A (MP4/AAC) file with a propermoovatom.commitRecordinginsrc/hooks/useRecorder.ts), and resolve the committed take's playable URI to the M4A output.src/hooks/useAudioPlayback.ts) that backs the Review scrub UI (src/app/tabs/drafting/record/components/RecordingWaveform.tsx).Scope
In scope
Out of scope
Acceptance criteria
Verification / test plan
File pointers
src/hooks/useRecorder.ts— AAC/ADTS capture decision (comment near theuseAudioRecorderconfig) andcommitRecording.src/hooks/useAudioPlayback.ts—seek()/ 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)