fix(mobile): properly pause and resume other audio during recording
This commit is contained in:
@@ -3,11 +3,14 @@ import { Pressable, StyleSheet, Text, View } from "react-native";
|
||||
import { Mic } from "lucide-react-native";
|
||||
import {
|
||||
RecordingPresets,
|
||||
setAudioModeAsync,
|
||||
useAudioRecorder,
|
||||
useAudioRecorderState,
|
||||
} from "expo-audio";
|
||||
import { logError } from "@/lib/errors";
|
||||
import {
|
||||
acquireRecordingAudioSession,
|
||||
releaseRecordingAudioSession,
|
||||
} from "@/lib/recording-audio-session";
|
||||
|
||||
const MAX_DURATION_S = 60;
|
||||
|
||||
@@ -28,10 +31,7 @@ export function AudioRecordingOverlay({
|
||||
let active = true;
|
||||
(async () => {
|
||||
try {
|
||||
await setAudioModeAsync({
|
||||
allowsRecording: true,
|
||||
playsInSilentMode: true,
|
||||
});
|
||||
await acquireRecordingAudioSession();
|
||||
await recorder.prepareToRecordAsync();
|
||||
if (!active) return;
|
||||
recorder.record();
|
||||
@@ -47,10 +47,9 @@ export function AudioRecordingOverlay({
|
||||
finalizedRef.current = true;
|
||||
recorder.stop().catch(() => {});
|
||||
}
|
||||
void setAudioModeAsync({
|
||||
allowsRecording: false,
|
||||
playsInSilentMode: true,
|
||||
}).catch((err) => logError(err, { scope: "compose.audio.exit" }));
|
||||
void releaseRecordingAudioSession().catch((err) =>
|
||||
logError(err, { scope: "compose.audio.exit" }),
|
||||
);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@@ -2,6 +2,10 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { Platform, Pressable, StyleSheet, Text, View } from "react-native";
|
||||
import { CameraView } from "expo-camera";
|
||||
import { logError } from "@/lib/errors";
|
||||
import {
|
||||
acquireRecordingAudioSession,
|
||||
releaseRecordingAudioSession,
|
||||
} from "@/lib/recording-audio-session";
|
||||
|
||||
const MAX_DURATION_S = 60;
|
||||
const VIDEO_BITRATE_BPS = 1_200_000;
|
||||
@@ -25,6 +29,9 @@ export function VideoRecordingOverlay({
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cancelledRef.current = true;
|
||||
void releaseRecordingAudioSession().catch((err) =>
|
||||
logError(err, { scope: "compose.video.exit" }),
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -32,6 +39,14 @@ export function VideoRecordingOverlay({
|
||||
const cam = cameraRef.current;
|
||||
if (!cam || recording || !cameraReady) return;
|
||||
|
||||
try {
|
||||
await acquireRecordingAudioSession();
|
||||
} catch (err) {
|
||||
logError(err, { scope: "compose.video.audioSession" });
|
||||
onCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
setRecording(true);
|
||||
startedAtRef.current = Date.now();
|
||||
|
||||
@@ -46,6 +61,10 @@ export function VideoRecordingOverlay({
|
||||
logError(err, { scope: "compose.video.recordAsync" });
|
||||
onCancel();
|
||||
return;
|
||||
} finally {
|
||||
void releaseRecordingAudioSession().catch((err) =>
|
||||
logError(err, { scope: "compose.video.release" }),
|
||||
);
|
||||
}
|
||||
if (cancelledRef.current) return;
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { setAudioModeAsync, setIsAudioActiveAsync } from "expo-audio";
|
||||
|
||||
// Around camera/mic recording we switch the iOS audio session to playAndRecord
|
||||
// with `doNotMix`, which cleanly interrupts other apps' audio (Spotify, Apple
|
||||
// Music, podcasts). On release we restore a mixable mode and then *deactivate*
|
||||
// the session — the deactivation is what passes notifyOthersOnDeactivation to
|
||||
// AVAudioSession, which is the signal that lets the previously-playing app
|
||||
// auto-resume. Just changing the category is not enough.
|
||||
export async function acquireRecordingAudioSession() {
|
||||
await setAudioModeAsync({
|
||||
allowsRecording: true,
|
||||
playsInSilentMode: true,
|
||||
interruptionMode: "doNotMix",
|
||||
});
|
||||
}
|
||||
|
||||
export async function releaseRecordingAudioSession() {
|
||||
await setAudioModeAsync({
|
||||
allowsRecording: false,
|
||||
playsInSilentMode: true,
|
||||
interruptionMode: "mixWithOthers",
|
||||
});
|
||||
await setIsAudioActiveAsync(false);
|
||||
}
|
||||
Reference in New Issue
Block a user