Skip to content

Moss/m4a recording#778

Open
Lomacar wants to merge 171 commits intodevfrom
Moss/m4a-recording
Open

Moss/m4a recording#778
Lomacar wants to merge 171 commits intodevfrom
Moss/m4a-recording

Conversation

@Lomacar
Copy link
Copy Markdown
Collaborator

@Lomacar Lomacar commented Mar 1, 2026

This branch is based off of the trim-audio-feature branch. It addresses two problems:

  1. VAD was saving as WAV, manual recording was saving as (128-bit M4A)
  2. Manual recording had a noticeable "start up" issue. When trying to record "one two three..." immediately after pressing the record button, it would typically only catch "three..." or "oo, three..." at best. This was do to shortcomings of M4A files.

It is better to store everything as M4A files since they can take 5 or 6 times less space at 128-bit, and while it is lossy it is still very good quality at that bitrate for mono audio.

With this update both VAD and manual recording now record direct as WAV and then convert immediately to M4A when saving. This resolves initial clipping issue with manual recording and saves space on phones and DB in the long run.


Note

High Risk
Touches native audio recording/playback, adds a schema migration, and introduces destructive local asset merge/unmerge transactions; regressions could break recording, playback progress, or local data integrity.

Overview
Audio recording/playback is refactored around segment-based pipelines. WalkieTalkieRecorder drops expo-audio recording and instead uses the native MicrophoneEnergyModule to record WAV segments, stream energy for live waveform/duration updates, then convert the final file to M4A on save; AudioRecorder also tightens the max recording size (50MB → 10MB).

Playback now supports multi-segment clips and trim windows. AudioContext’s playSound accepts URIs or { uri, startMs, endMs } segments (sequence playback kept as deprecated wrapper), adds waitForPlaybackEnd, and implements new progress tracking (preloaded durations + UI-thread withTiming animation + segment preloading/endpoint timers).

Local asset operations and persistence are extended. Adds assetMergeService for transactional merge/unmerge of local-only assets (preserving audio files), adds shared createLocalAssetInTx, and introduces asset_content_link.metadata plus a 2.3→2.4 migration; asset metadata is typed as JSON and asset queries now normalize tag_ids/metadata parsing.

Also adds native waveform extraction (MicrophoneEnergyModule.extractWaveform), a flash-highlight hook + merge/unmerge cleanup helper, plus repo-wide newline/EOL settings (.editorconfig, .gitattributes, VSCode) and EAS build profile tweaks.

Written by Cursor Bugbot for commit e1d8dd1. This will update automatically on new commits. Configure here.

@keeandev
Copy link
Copy Markdown
Contributor

keeandev commented Mar 9, 2026

@cursoragent fix format and typecheck

@cursor
Copy link
Copy Markdown

cursor bot commented Mar 9, 2026

Fixed both issues:

  1. Typecheck error: Removed the undefined lastProcessedDurationRef reference on line 286 in components/WalkieTalkieRecorder.tsx. It was leftover code that wasn't declared.

  2. Format error: Fixed formatting issues in .cursor/mcp.json using Prettier.

Both npm run format and npm run typecheck now pass. The changes are ready to commit.

Open in Web Open in Cursor 

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Free Tier Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Pause misinterpreted as playback completion, destroying audio player
    • I tracked pause state in a ref and excluded paused status updates from the playing→false completion fallback so pause no longer releases the player or advances the sequence.

Create PR

Or push these changes by commenting:

@cursor push 7afe676801
Preview (7afe676801)
diff --git a/contexts/AudioContext.tsx b/contexts/AudioContext.tsx
--- a/contexts/AudioContext.tsx
+++ b/contexts/AudioContext.tsx
@@ -87,6 +87,7 @@
   const lastReactStateUpdateMs = useRef(0);
   const currentAudioIdRef = useRef<string | null>(null);
   const isPlayingRef = useRef(false);
+  const isPausedRef = useRef(false);
   const isAdvancingSegmentRef = useRef(false);
   const useSequenceLevelProgressRef = useRef(false);
   const hasRetimedLastSegmentRef = useRef(false);
@@ -129,6 +130,11 @@
     setIsPlaying(playing);
   };
 
+  const setIsPausedState = (paused: boolean) => {
+    isPausedRef.current = paused;
+    setIsPaused(paused);
+  };
+
   // expo-audio players load asynchronously; wait until duration/position are usable.
   const waitForPlayerLoaded = async (player: AudioPlayer): Promise<void> => {
     if (player.isLoaded) return;
@@ -325,7 +331,7 @@
     await unloadPreloadedSegment();
 
     setIsPlayingState(false);
-    setIsPaused(false);
+    setIsPausedState(false);
     setCurrentAudioIdState(null);
     setPositionState(0);
     setDuration(0);
@@ -337,9 +343,9 @@
 
   const pauseSound = async () => {
     if (soundRef.current && isPlaying) {
+      setIsPausedState(true);
       soundRef.current.pause();
       setIsPlayingState(false);
-      setIsPaused(true);
     }
   };
 
@@ -347,7 +353,7 @@
     if (soundRef.current && isPaused) {
       soundRef.current.play();
       setIsPlayingState(true);
-      setIsPaused(false);
+      setIsPausedState(false);
     }
   };
 
@@ -383,7 +389,7 @@
       positionSharedRef.current.value = 0;
       durationSharedRef.current.value = 0;
       setIsPlayingState(false);
-      setIsPaused(false);
+      setIsPausedState(false);
       resolvePlaybackWaiters(currentAudioIdRef.current);
       setCurrentAudioIdState(null);
       setPositionState(0);
@@ -434,7 +440,7 @@
       }
 
       soundRef.current = sound;
-      setIsPaused(false);
+      setIsPausedState(false);
 
       if (audioId) {
         setCurrentAudioIdState(audioId);
@@ -593,7 +599,10 @@
           // expo-audio doesn't fire didJustFinish reliably on all platforms.
           // Fallback: treat playing→false (after having started) as completion,
           // since the listener is always removed before any manual pause/stop.
-          if (status.didJustFinish || (hasStartedPlaying && !status.playing)) {
+          if (
+            status.didJustFinish ||
+            (hasStartedPlaying && !status.playing && !isPausedRef.current)
+          ) {
             if (isAdvancingSegmentRef.current) return;
             isAdvancingSegmentRef.current = true;
             clearStatusListener();
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Co-authored-by: Keean <keeandev@users.noreply.github.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

setTimeout(() => {
clearInterval(check);
});
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interval timer leaks when player load times out

Low Severity

In waitForPlayerLoaded, when the 5-second timeout fires and resolves the promise, the setInterval polling loop (checkId) is never cleared. It continues running every 10ms indefinitely (or until the player happens to load). If the player was released or never loads, this interval leaks permanently, accumulating across repeated failures.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants