Skip to content

Conversation

@morepriyam
Copy link
Collaborator

Summary

Implements comprehensive audio interruption handling for video recording using expo-audio to manage audio sessions and AppState monitoring to handle background transitions gracefully. This ensures recordings stop automatically when the app is backgrounded and all UI states reset to a clean initial state when the app becomes active again.

Problem

Previously, the app had no handling for:

  • ❌ App going to background during recording (recording would continue)
  • ❌ Phone calls interrupting recording
  • ❌ Other apps taking audio focus
  • ❌ UI animations/states not resetting after interruptions
  • ❌ No audio session management for better interruption resilience

Solution

1. Audio Session Configuration (expo-audio)

  • Added expo-audio package and plugin configuration
  • Configure audio session before each recording with setAudioModeAsync()
  • Use interruptionMode: "doNotMix" to request exclusive audio focus
  • Ensures other apps pause their audio when recording starts

2. Background/Foreground Handling

  • Added AppState listener to detect when app goes to background/inactive
  • Automatically stops recording when app is backgrounded
  • Manages audio subsystem (enable/disable) based on app state
  • Resets all animations and UI states when app becomes active again

3. Error Handling Improvements

  • Added camera mount error handling with retry option
  • Enhanced recording promise error handling for interruptions
  • Added comprehensive debug logging with emoji markers for easy identification

4. State Reset on App Activation

  • Added reset() method to RecordButton component (exposed via ref)
  • Resets all animation values to initial state
  • Resets zoom, touch states, and recording states
  • Ensures clean UI state when returning from background

Changes

New Dependencies

  • expo-audio (~1.1.1) - Audio session management

Files Modified

app.json

  • Added expo-audio plugin configuration with microphone permission

components/RecordButton.tsx

  • Added expo-audio import and setAudioModeAsync() call
  • Configure audio session before starting recording
  • Converted to forwardRef to expose reset() method
  • Added reset() function to reset all animations and states
  • Enhanced error handling for interruptions
  • Added debug logging

app/(camera)/shorts.tsx

  • Added AppState monitoring during recording
  • Stop recording when app goes to background/inactive
  • Reset all states when app becomes active again
  • Added camera mount error handling
  • Added onCameraReady callback
  • Added recordButtonRef to access reset functionality

Technical Details

Audio Session Configuration

await setAudioModeAsync({
  allowsRecording: true,
  playsInSilentMode: true,
  interruptionMode: "doNotMix", // Exclusive audio focus
  shouldPlayInBackground: false,
});

AppState Monitoring

// Stops recording on background
if (nextAppState === "background" || nextAppState === "inactive") {
  cameraRef.current?.stopRecording();
  await setIsAudioActiveAsync(false);
}

// Resets state on active
if (nextAppState === "active") {
  await setIsAudioActiveAsync(true);
  recordButtonRef.current?.reset();
  // Reset zoom, touch states, etc.
}

Testing

Test Scenarios

  1. Background Interruption: Start recording → Press home button → Recording stops automatically
  2. Audio Session: Console shows "Audio session configured successfully" when recording starts
  3. State Reset: Return to app → All animations and states reset to initial values
  4. Phone Calls: Recording handles phone call interruptions gracefully
  5. Error Handling: Camera mount errors show retry option

Console Logs to Verify

  • [RecordButton] ✅ Audio session configured successfully
  • [ShortsScreen] 📱 App state changed to: inactive
  • [ShortsScreen] 🛑 Stopping recording due to background/inactive state
  • [RecordButton] 🔄 Reset all animations and states
  • [ShortsScreen] 🔄 Reset all animations and states

Benefits

  • Better UX: Recordings stop automatically when app is backgrounded
  • Clean State: All UI resets to initial state when returning from background
  • Audio Focus: Exclusive audio focus prevents conflicts with other apps
  • Error Recovery: Better error handling and recovery options
  • Debugging: Comprehensive logging for troubleshooting

Breaking Changes

None - This is a backward-compatible enhancement.

Screenshots/Logs

Before

  • Recording continues when app goes to background
  • No audio session management
  • UI states can be inconsistent after interruptions

After

  • Recording stops automatically on background
  • Audio session properly configured
  • Clean state reset on app activation

Related Issues

Fixes issues with:

  • Recording continuing in background
  • Audio conflicts with other apps
  • Inconsistent UI state after interruptions

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Comments added for complex logic
  • Documentation updated (if applicable)
  • No new warnings generated
  • Tests pass (manual testing completed)
  • Build succeeds

Notes

  • expo-audio is used primarily for audio session configuration, not for recording (expo-camera handles recording)
  • The doNotMix interruption mode ensures exclusive audio focus, which is ideal for video recording
  • State reset ensures consistent UI behavior when users return to the app

- Added expo-audio dependency to manage audio recording and permissions.
- Updated app.json to include microphone permission description for audio recording.
- Modified AndroidManifest.xml to request MODIFY_AUDIO_SETTINGS permission.
- Implemented audio session configuration in RecordButton for better interruption handling.
- Enhanced ShortsScreen to manage audio state during app lifecycle changes.
- Added error handling for camera mount issues and improved user feedback.

This update improves the audio recording experience and ensures proper handling of audio interruptions during recording sessions.
@morepriyam morepriyam linked an issue Dec 27, 2025 that may be closed by this pull request
Copy link
Collaborator

@adithya1012 adithya1012 left a comment

Choose a reason for hiding this comment

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

Hello @morepriyam 👋

I tried testing this branch locally on Android, but it doesn’t seem to be working as expected.

Observed behavior

The app builds successfully, but video recording fails on Android. Below are the logs I’m seeing:

Android Bundled 114ms node_modules/expo-router/entry.js (1 module)
LOG  🔗 Deeplink params: {}
LOG  [ShortsScreen] ✅ Camera ready
LOG  [RecordButton] ✅ Audio session configured successfully
LOG  [RecordButton] 🔄 Reset all animations and states
LOG  [ShortsScreen] 🔄 Reset all animations and states
LOG  [RecordButton] 🔄 Reset all animations and states
LOG  [ShortsScreen] 🔄 Reset all animations and states
ERROR  [RecordButton] ❌ Recording failed (unexpected): 
[Error: Video recording failed: Video recording Failed: Unknown error]
LOG  [RecordButton] 🔄 Reset all animations and states
LOG  [ShortsScreen] 🔄 Reset all animations and states

Screenshot

image

Steps to reproduce (Android)

  1. git checkout 257-audio-issues-in-camera
  2. npm install
  3. npx expo run:android

Please let me know if I’m missing any setup steps on Android.
I’m also looking into this from my side to see if I can identify or fix the issue.

Thanks!

On Android, React Native's AppState incorrectly triggers background/active
transitions when any UI overlay appears
causing recording to fail with "Video recording Failed: Unknown error".

Changes:

- Track previous app state to detect genuine background transitions
- Add 500ms threshold to filter rapid false state changes on Android
- Use 'focus' event on Android instead of 'change' event for more reliable detection
- Only reset recording state on genuine background→active transitions
@adithya1012 adithya1012 self-requested a review January 5, 2026 23:14
@adithya1012
Copy link
Collaborator

Fix: Android recording fails due to rapid AppState switching

Problem

When starting a recording on Android, the app immediately fails with:

ERROR [RecordButton] ❌ Recording failed (unexpected): [Error: Video recording failed: Video recording Failed: Unknown error]

The logs showed AppState rapidly toggling between active and background states in an infinite loop:

LOG  isRecording: true active
LOG  isRecording: true background
LOG  isRecording: true active
LOG  isRecording: true background
...

This caused the recording to be stopped immediately after starting, resulting in failure.

Root Cause

This is a known issue with React Native's AppState on Android (facebook/react-native#25886).

On Android, React Native uses the Activity's onPause callback to trigger the background state. However, onPause is called whenever any UI element comes in front of the activity, including:

  • Permission dialogs
  • File pickers
  • System dialogs
  • Any overlay UI

This is different from iOS, where background truly means the app went to the background.

In our case, the audio session configuration (setAudioModeAsync) or camera initialization was triggering some system-level activity that caused onPause to fire, which our code interpreted as "app went to background" → stopped recording → which caused another state change → infinite loop.

Solution

Based on research from multiple Stack Overflow threads and GitHub issues:

The fix implements three safeguards:

  1. Track previous state: Store the previous AppState and only act on genuine transitions (from inactive|backgroundactive), not duplicate events.

  2. Time threshold filter: On Android, ignore state changes if the app was in "background" for less than 500ms. Real background transitions (user switching apps) take longer, while false triggers (dialogs/overlays) are nearly instantaneous.

  3. Use focus event on Android: The focus event is more appropriate for Android as it doesn't trigger for overlays, only for genuine app focus changes. iOS continues to use the change event which works correctly.

Code Changes

// Track previous app state for Android (to detect genuine background transitions)
const appStateRef = React.useRef<AppStateStatus>(AppState.currentState);
const lastBackgroundTimeRef = React.useRef<number>(0);

// In the effect:
// 1. Filter rapid state changes on Android (< 500ms)
if (Platform.OS === "android") {
  if (nextAppState === "background" || nextAppState === "inactive") {
    lastBackgroundTimeRef.current = Date.now();
  } else if (nextAppState === "active" && prevState !== "active") {
    const timeInBackground = Date.now() - lastBackgroundTimeRef.current;
    if (timeInBackground < 500) {
      // Ignore rapid false trigger
      return;
    }
  }
}

// 2. Only act on genuine transitions
if (prevState === nextAppState) return;
if (nextAppState === "active" && prevState.match(/inactive|background/)) {
  // Handle genuine return from background
}

// 3. Use focus event on Android
const eventType = Platform.OS === "android" ? "focus" : "change";
const subscription = AppState.addEventListener(eventType, handleAppStateChange);

Testing

  • Recording works on Android without immediate failure
  • Recording properly stops when app genuinely goes to background (home button)
  • State resets properly when returning from genuine background

Related Issues

Fixes #257

@horner
Copy link
Member

horner commented Jan 6, 2026

The first comment on the PR should be a summary of the PR at the latest moment with a screenshot(s) and video short.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements audio interruption handling for video recording by integrating expo-audio for audio session management and AppState monitoring to handle background transitions. The goal is to automatically stop recordings when the app is backgrounded and reset all UI states when returning to the foreground.

  • Added expo-audio (~0.4.9) for audio session configuration with exclusive audio focus
  • Implemented AppState monitoring to stop recordings when app goes to background/inactive
  • Added reset functionality to RecordButton component via forwardRef to clean up animations and states

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
package.json Added expo-audio dependency (~0.4.9)
package-lock.json Resolved expo-audio dependency with version 0.4.9
ios/Podfile.lock Added ExpoAudio iOS native dependency
components/RecordButton.tsx Added audio session configuration, forwardRef with reset method, and enhanced error handling for interruptions
app/(camera)/shorts.tsx Implemented AppState listener to stop recording on background, reset states on foreground, with Android-specific rapid state change filtering
app.json Added expo-audio plugin configuration with microphone permissions
android/app/src/main/AndroidManifest.xml Added MODIFY_AUDIO_SETTINGS permission for audio session management

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +342 to +346
// Use 'focus' event on Android to avoid false triggers, 'change' on iOS
const eventType = Platform.OS === "android" ? "focus" : "change";
const subscription = AppState.addEventListener(eventType, handleAppStateChange);
return () => subscription.remove();
}, [isRecording]);
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

Using "focus" event type for Android's AppState listener is unusual and may not work as expected. The AppState API typically uses "change" for both platforms. The "focus" event is more commonly associated with blur events in React Native, not AppState changes. According to React Native documentation, AppState.addEventListener should use "change" event type on both platforms. This could cause the handler to never be called on Android. Consider using "change" event for both platforms and relying on the rapid state change detection logic (lines 286-296) to filter out false triggers instead.

Suggested change
// Use 'focus' event on Android to avoid false triggers, 'change' on iOS
const eventType = Platform.OS === "android" ? "focus" : "change";
const subscription = AppState.addEventListener(eventType, handleAppStateChange);
return () => subscription.remove();
}, [isRecording]);
// Use 'change' event on both platforms; rely on timing logic above to filter false triggers
const subscription = AppState.addEventListener("change", handleAppStateChange);
return () => subscription.remove();
}, [isRecording]);
}, [isRecording]);

Copilot uses AI. Check for mistakes.
Comment on lines +278 to +346
React.useEffect(() => {
if (!isRecording) return;

const handleAppStateChange = async (nextAppState: AppStateStatus) => {
const prevState = appStateRef.current;

// On Android, ignore rapid state changes (less than 500ms in background)
// This prevents false triggers from permission dialogs, file pickers, etc.
if (Platform.OS === "android") {
if (nextAppState === "background" || nextAppState === "inactive") {
lastBackgroundTimeRef.current = Date.now();
} else if (nextAppState === "active" && prevState !== "active") {
const timeInBackground = Date.now() - lastBackgroundTimeRef.current;
if (timeInBackground < 500) {
console.log(`[ShortsScreen] Ignoring rapid state change (${timeInBackground}ms in background)`);
appStateRef.current = nextAppState;
return;
}
}
}

console.log("[ShortsScreen] AppState:", prevState, "->", nextAppState);

// Only act on genuine transitions
if (prevState === nextAppState) {
return;
}

if (nextAppState === "background" || nextAppState === "inactive") {
// Stop recording immediately when app goes to background
if (cameraRef.current) {
try {
cameraRef.current.stopRecording();
// Disable audio when going to background
await setIsAudioActiveAsync(false);
} catch (error) {
console.warn("[ShortsScreen] Error stopping recording on background:", error);
}
}
} else if (nextAppState === "active" && prevState.match(/inactive|background/)) {
// Re-enable audio when app becomes active again (only from genuine background)
try {
await setIsAudioActiveAsync(true);
} catch (error) {
console.warn("[ShortsScreen] Error re-enabling audio:", error);
}

// Reset all animations and button states to initial state
recordButtonRef.current?.reset();

// Reset zoom and touch states
setZoom(0);
savedZoom.value = 0;
currentZoom.value = 0;
setScreenTouchActive(false);
isHoldRecording.value = false;
recordingModeShared.value = "";

console.log("[ShortsScreen] 🔄 Reset all animations and states");
}

appStateRef.current = nextAppState;
};

// Use 'focus' event on Android to avoid false triggers, 'change' on iOS
const eventType = Platform.OS === "android" ? "focus" : "change";
const subscription = AppState.addEventListener(eventType, handleAppStateChange);
return () => subscription.remove();
}, [isRecording]);
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The AppState effect's handleAppStateChange function references several values from the component scope (savedZoom, currentZoom, setZoom, setScreenTouchActive, isHoldRecording, recordingModeShared) but these are not included in the effect's dependency array. While these are mostly refs and shared values that are stable, this could potentially cause stale closures. However, since the effect is recreated whenever isRecording changes, and these values are refs/shared values, this is likely fine. Consider adding a comment explaining why these dependencies are not included.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +70
[
"expo-audio",
{
"microphonePermission": "$(PRODUCT_NAME) needs microphone access to record audio with your videos and manage audio sessions. This allows the app to capture your voice narration during recordings and handle interruptions from phone calls or other apps gracefully.",
"recordAudioAndroid": true
}
],
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

Both expo-camera and expo-audio plugins are configured with microphone permissions. This creates duplicate permission requests and may lead to confusion. Since expo-camera already handles both camera and microphone permissions for video recording (lines 57-63), the expo-audio plugin's microphone permission configuration is redundant. The expo-audio package should only be used for audio session management (setAudioModeAsync), not for requesting permissions. Consider removing the microphonePermission and recordAudioAndroid settings from the expo-audio plugin configuration, or add a comment explaining why duplicate permission configurations are necessary.

Suggested change
[
"expo-audio",
{
"microphonePermission": "$(PRODUCT_NAME) needs microphone access to record audio with your videos and manage audio sessions. This allows the app to capture your voice narration during recordings and handle interruptions from phone calls or other apps gracefully.",
"recordAudioAndroid": true
}
],
"expo-audio",

Copilot uses AI. Check for mistakes.
Comment on lines +200 to 211
// Check for specific error types
if (
error.message?.includes("interrupted") ||
error.message?.includes("stopped") ||
error.message?.includes("background") ||
error.message?.includes("cancelled")
) {
console.warn("[RecordButton] ⚠️ Recording interrupted (expected):", error.message);
// Don't show error alert for expected interruptions
} else {
console.error("[RecordButton] ❌ Recording failed (unexpected):", error);
}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The error handling logic checks for multiple error message patterns using string inclusion checks. This approach is fragile as it depends on specific error message text which could vary across platforms or expo-camera versions. Consider checking for error types/codes instead of message strings if expo-camera provides them, or document the expected error messages for different scenarios. Additionally, the current logic silently ignores interruption errors without providing any feedback mechanism to the parent component, which might need to know that recording was interrupted versus manually stopped.

Copilot uses AI. Check for mistakes.
setButtonInitiatedRecording(false);
isHoldingRef.current = false;
manuallyStoppedRef.current = false;
recordingPromiseRef.current = null;
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The reset function doesn't reset all state that might be affected during recording. Specifically, it doesn't reset the recordingStartTimeRef which tracks when recording started. If the app is backgrounded and then foregrounded, and recording starts again without this being reset, duration calculations could be incorrect. Consider resetting recordingStartTimeRef to 0 or Date.now() to ensure clean state.

Suggested change
recordingPromiseRef.current = null;
recordingPromiseRef.current = null;
recordingStartTimeRef.current = 0;

Copilot uses AI. Check for mistakes.
manuallyStoppedRef.current = false;
recordingPromiseRef.current = null;

console.log("[RecordButton] 🔄 Reset all animations and states");
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The reset function in useCallback has an empty dependency array, but it references state setters (setIsHoldingForRecord, setIsRecording, setRecordingMode, setButtonInitiatedRecording) and animated values (scaleAnim, borderRadiusAnim, outerBorderScaleAnim). While the animated values and refs don't need to be in the dependency array (they're stable references), and state setters are also stable, the empty array is technically correct. However, for clarity and to follow React best practices, consider adding a comment explaining why the dependency array is empty, since ESLint rules often flag this pattern.

Suggested change
console.log("[RecordButton] 🔄 Reset all animations and states");
console.log("[RecordButton] 🔄 Reset all animations and states");
// Dependency array is intentionally empty: this callback only uses stable
// refs, Animated values, and React state setters, which do not change.

Copilot uses AI. Check for mistakes.
Comment on lines +114 to +126
// Configure audio session for better interruption handling
try {
await setAudioModeAsync({
allowsRecording: true,
playsInSilentMode: true,
interruptionMode: "doNotMix", // Request exclusive audio focus
shouldPlayInBackground: false, // Don't play in background
});
console.log("[RecordButton] ✅ Audio session configured successfully");
} catch (error) {
console.warn("[RecordButton] ⚠️ Failed to configure audio session:", error);
// Continue with recording even if audio session config fails
}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

When setAudioModeAsync fails, the code logs a warning but continues with recording. However, this means the recording will proceed without proper audio session configuration, which defeats the purpose of this PR (handling audio interruptions). Consider whether recording should be prevented or if the user should be alerted when audio session configuration fails, especially if this is a critical requirement for proper interruption handling. Alternatively, add a comment explaining that recording can proceed safely without audio session configuration.

Copilot uses AI. Check for mistakes.
Comment on lines +311 to +323
// Disable audio when going to background
await setIsAudioActiveAsync(false);
} catch (error) {
console.warn("[ShortsScreen] Error stopping recording on background:", error);
}
}
} else if (nextAppState === "active" && prevState.match(/inactive|background/)) {
// Re-enable audio when app becomes active again (only from genuine background)
try {
await setIsAudioActiveAsync(true);
} catch (error) {
console.warn("[ShortsScreen] Error re-enabling audio:", error);
}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The use of setIsAudioActiveAsync(false/true) to enable/disable audio when the app goes to background/foreground is not a standard pattern for expo-audio. The expo-audio package's setIsAudioActiveAsync function is typically used to activate/deactivate the audio subsystem for playback scenarios. For recording interruption handling, the audio session configuration via setAudioModeAsync (which is already done in RecordButton.tsx) should be sufficient. Calling setIsAudioActiveAsync(false) when going to background and setIsAudioActiveAsync(true) when returning to foreground may cause unintended side effects or conflicts with the audio session already configured for recording. Consider whether these calls are actually necessary, or document the specific reasoning for this pattern.

Copilot uses AI. Check for mistakes.
Comment on lines +308 to +316
if (cameraRef.current) {
try {
cameraRef.current.stopRecording();
// Disable audio when going to background
await setIsAudioActiveAsync(false);
} catch (error) {
console.warn("[ShortsScreen] Error stopping recording on background:", error);
}
}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

There's a potential race condition when the app goes to background. The code calls both cameraRef.current.stopRecording() and setIsAudioActiveAsync(false) in a try-catch block, but doesn't await the stopRecording() call before disabling audio. Since stopRecording() is async (it waits for the recording to finish), the audio session might be disabled while the camera is still completing the recording. Consider awaiting stopRecording or restructuring the order of operations to ensure proper cleanup sequence.

Copilot uses AI. Check for mistakes.
Comment on lines +278 to +346
React.useEffect(() => {
if (!isRecording) return;

const handleAppStateChange = async (nextAppState: AppStateStatus) => {
const prevState = appStateRef.current;

// On Android, ignore rapid state changes (less than 500ms in background)
// This prevents false triggers from permission dialogs, file pickers, etc.
if (Platform.OS === "android") {
if (nextAppState === "background" || nextAppState === "inactive") {
lastBackgroundTimeRef.current = Date.now();
} else if (nextAppState === "active" && prevState !== "active") {
const timeInBackground = Date.now() - lastBackgroundTimeRef.current;
if (timeInBackground < 500) {
console.log(`[ShortsScreen] Ignoring rapid state change (${timeInBackground}ms in background)`);
appStateRef.current = nextAppState;
return;
}
}
}

console.log("[ShortsScreen] AppState:", prevState, "->", nextAppState);

// Only act on genuine transitions
if (prevState === nextAppState) {
return;
}

if (nextAppState === "background" || nextAppState === "inactive") {
// Stop recording immediately when app goes to background
if (cameraRef.current) {
try {
cameraRef.current.stopRecording();
// Disable audio when going to background
await setIsAudioActiveAsync(false);
} catch (error) {
console.warn("[ShortsScreen] Error stopping recording on background:", error);
}
}
} else if (nextAppState === "active" && prevState.match(/inactive|background/)) {
// Re-enable audio when app becomes active again (only from genuine background)
try {
await setIsAudioActiveAsync(true);
} catch (error) {
console.warn("[ShortsScreen] Error re-enabling audio:", error);
}

// Reset all animations and button states to initial state
recordButtonRef.current?.reset();

// Reset zoom and touch states
setZoom(0);
savedZoom.value = 0;
currentZoom.value = 0;
setScreenTouchActive(false);
isHoldRecording.value = false;
recordingModeShared.value = "";

console.log("[ShortsScreen] 🔄 Reset all animations and states");
}

appStateRef.current = nextAppState;
};

// Use 'focus' event on Android to avoid false triggers, 'change' on iOS
const eventType = Platform.OS === "android" ? "focus" : "change";
const subscription = AppState.addEventListener(eventType, handleAppStateChange);
return () => subscription.remove();
}, [isRecording]);
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The AppState effect only runs when isRecording is true, but the cleanup function removes the subscription whenever the effect re-runs or unmounts. If isRecording becomes false while the app is in the background (which can happen when recording is stopped), the effect will unmount and clean up the subscription. When isRecording becomes true again, a new subscription is created. However, consider that if recording stops while in background, and the app returns to active state, the reset logic in lines 317-337 won't run because the effect isn't active (isRecording is false). This means UI states might not reset properly. Consider adding a separate useEffect that always monitors app state (not just during recording) to handle the reset logic when returning from background, regardless of recording state.

Copilot uses AI. Check for mistakes.
};

const handleCameraReady = () => {
console.log("[ShortsScreen] ✅ Camera ready");
Copy link
Member

Choose a reason for hiding this comment

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

Not a fan of logging normal state unless it's in the bug

@horner
Copy link
Member

horner commented Jan 6, 2026

I wonder if we shouldn't try to block incoming phone calls if opposed to blocking the recording

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audio Issues in Video Merging and Segment Playback

4 participants