diff --git a/src/components/FullscreenIntroVideo.native.js b/src/components/FullscreenIntroVideo.native.js
index ab20580..9126cfe 100644
--- a/src/components/FullscreenIntroVideo.native.js
+++ b/src/components/FullscreenIntroVideo.native.js
@@ -63,7 +63,10 @@ const FullscreenIntroVideo = ({ url, visible = true, onClose }) => {
return;
}
const remaining = player.duration - currentTime;
- if (Number.isFinite(remaining) && remaining <= CLOSE_THRESHOLD_SECONDS) {
+ if (
+ Number.isFinite(remaining) &&
+ remaining <= CLOSE_THRESHOLD_SECONDS
+ ) {
handleClose();
}
}
@@ -75,7 +78,10 @@ const FullscreenIntroVideo = ({ url, visible = true, onClose }) => {
return;
}
const remaining = player.duration - (player.currentTime ?? 0);
- if (Number.isFinite(remaining) && remaining <= CLOSE_THRESHOLD_SECONDS) {
+ if (
+ Number.isFinite(remaining) &&
+ remaining <= CLOSE_THRESHOLD_SECONDS
+ ) {
handleClose();
}
}
@@ -99,6 +105,7 @@ const FullscreenIntroVideo = ({ url, visible = true, onClose }) => {
pointerEvents="box-none"
style={{
position: "absolute",
+ paddingHorizontal: 10,
top: 0,
left: 0,
right: 0,
@@ -112,8 +119,16 @@ const FullscreenIntroVideo = ({ url, visible = true, onClose }) => {
{ };
-const noop = () => { };
+const noopAsync = async () => {};
+const noop = () => {};
const DEFAULT_CONTEXT = {
currentTrack: null,
@@ -274,36 +274,36 @@ const PlayerProvider = ({ children }) => {
(items = [], options = {}) => {
const normalized = Array.isArray(items)
? items
- .map((item) => {
- if (!item) return null;
- if (typeof item === "object") {
- const { metadata, context, ...rest } = item;
- const safeMetadata =
- metadata && typeof metadata === "object"
- ? { ...metadata }
- : undefined;
- if (safeMetadata) {
- delete safeMetadata.queue;
+ .map((item) => {
+ if (!item) return null;
+ if (typeof item === "object") {
+ const { metadata, context, ...rest } = item;
+ const safeMetadata =
+ metadata && typeof metadata === "object"
+ ? { ...metadata }
+ : undefined;
+ if (safeMetadata) {
+ delete safeMetadata.queue;
+ }
+ const safeContext =
+ context && typeof context === "object"
+ ? { ...context }
+ : undefined;
+ if (safeContext) {
+ delete safeContext.queue;
+ }
+ return normalizeTrack(
+ {
+ ...rest,
+ ...(safeMetadata ? { metadata: safeMetadata } : {}),
+ ...(safeContext ? { context: safeContext } : {}),
+ },
+ {}
+ );
}
- const safeContext =
- context && typeof context === "object"
- ? { ...context }
- : undefined;
- if (safeContext) {
- delete safeContext.queue;
- }
- return normalizeTrack(
- {
- ...rest,
- ...(safeMetadata ? { metadata: safeMetadata } : {}),
- ...(safeContext ? { context: safeContext } : {}),
- },
- {}
- );
- }
- return normalizeTrack(item, {});
- })
- .filter((track) => !!track?.source)
+ return normalizeTrack(item, {});
+ })
+ .filter((track) => !!track?.source)
: [];
queueRef.current = normalized;
@@ -450,10 +450,9 @@ const PlayerProvider = ({ children }) => {
if (!player) return;
try {
player.loop = !!isLooping;
- } catch (_err) { }
+ } catch (_err) {}
}, [player, isLooping]);
-
const seekDebounceRef = useRef(null);
const pendingSeekPosRef = useRef(null);
const isSeekingRef = useRef(false);
@@ -511,9 +510,12 @@ const PlayerProvider = ({ children }) => {
const seekBy = useCallback(
async (deltaMs) => {
- const currentPos = pendingSeekPosRef.current !== null
- ? (Platform.OS === "web" ? pendingSeekPosRef.current : pendingSeekPosRef.current * 1000)
- : playback.positionMs;
+ const currentPos =
+ pendingSeekPosRef.current !== null
+ ? Platform.OS === "web"
+ ? pendingSeekPosRef.current
+ : pendingSeekPosRef.current * 1000
+ : playback.positionMs;
const next = Math.max(0, currentPos + Number(deltaMs || 0));
await seekTo(next);
},
@@ -548,7 +550,6 @@ const PlayerProvider = ({ children }) => {
await waitForActiveSeek();
}, [player, status?.isLoaded, waitForActiveSeek]);
-
const play = useCallback(
async (trackInput, options = {}) => {
const normalized = normalizeTrack(trackInput, options);
@@ -680,8 +681,6 @@ const PlayerProvider = ({ children }) => {
[currentTrack, playback.isPlaying, pause, play, resume]
);
-
-
const stop = useCallback(async () => {
try {
autoPlayRef.current = false;
@@ -720,7 +719,9 @@ const PlayerProvider = ({ children }) => {
}
return;
}
- const idx = queueRef.current.findIndex((item) => item.id === currentTrack.id);
+ const idx = queueRef.current.findIndex(
+ (item) => item.id === currentTrack.id
+ );
if (idx !== queueIndexRef.current) {
setQueueIndexValue(idx);
}
diff --git a/src/screens/Playback/RecordPlayback.js b/src/screens/Playback/RecordPlayback.js
index ae99255..8e6f03c 100644
--- a/src/screens/Playback/RecordPlayback.js
+++ b/src/screens/Playback/RecordPlayback.js
@@ -8,7 +8,13 @@ import React, {
useRef,
useState,
} from "react";
-import { BackHandler, Text, TouchableOpacity, View } from "react-native";
+import {
+ AppState,
+ BackHandler,
+ Text,
+ TouchableOpacity,
+ View,
+} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Svg, { Circle } from "react-native-svg";
import alert from "../../components/Alert";
@@ -56,6 +62,7 @@ const RecordPlayback = ({ route }) => {
const isPausedRef = useRef(false);
const stopRequestedRef = useRef(false);
const restartRequestedRef = useRef(false);
+ const interruptedRef = useRef(false); // New ref to track interruptions
const manualRestartInFlightRef = useRef(false);
const stopRequestedAtRef = useRef(0);
const activeRecordingPromiseRef = useRef(null);
@@ -107,6 +114,43 @@ const RecordPlayback = ({ route }) => {
isPausedRef.current = isPaused;
}, [isPaused]);
+ // Monitor AppState to detect backgrounding/interruptions
+ useEffect(() => {
+ const subscription = AppState.addEventListener("change", (nextAppState) => {
+ if (
+ nextAppState.match(/inactive|background/) &&
+ (isRecording || isPreparing)
+ ) {
+ log("App backgrounded/interrupted during recording session");
+ interruptedRef.current = true;
+
+ // If we are merely preparing (countdown), we can just stop the timer
+ if (isPreparing) {
+ if (countdownTimerRef.current) {
+ clearInterval(countdownTimerRef.current);
+ countdownTimerRef.current = null;
+ }
+ setIsPreparing(false);
+ setCountdown(0);
+ }
+
+ // If recording, request stop immediately.
+ // The recording promise will resolve, and we will check interruptedRef there.
+ if (isRecording) {
+ stopRequestedRef.current = true;
+ stopRequestedAtRef.current = Date.now();
+ try {
+ cameraRef.current?.stopRecording?.();
+ } catch (_) {}
+ }
+ }
+ });
+
+ return () => {
+ subscription.remove();
+ };
+ }, [isRecording, isPreparing]);
+
const musicIndex = useMemo(() => {
const i = Number(songIndex);
return Number.isFinite(i) && i >= 0 ? i : 0;
@@ -451,6 +495,9 @@ const RecordPlayback = ({ route }) => {
try {
stopRequestedRef.current = false;
stopRequestedAtRef.current = 0;
+ stopRequestedRef.current = false;
+ stopRequestedAtRef.current = 0;
+ interruptedRef.current = false; // Reset interruption flag
listenedMsRef.current = 0;
incrementDoneRef.current = false;
@@ -470,7 +517,9 @@ const RecordPlayback = ({ route }) => {
});
if (activeRecordingPromiseRef.current) {
- log("Waiting for previous recording to finish before starting a new one");
+ log(
+ "Waiting for previous recording to finish before starting a new one"
+ );
try {
await activeRecordingPromiseRef.current;
} catch (error) {
@@ -586,7 +635,8 @@ const RecordPlayback = ({ route }) => {
if (!player) return;
if (isPausedRef.current) return;
if (stopRequestedRef.current) {
- const sinceLastRequest = Date.now() - (stopRequestedAtRef.current || 0);
+ const sinceLastRequest =
+ Date.now() - (stopRequestedAtRef.current || 0);
if (sinceLastRequest >= 1200) {
stopRequestedAtRef.current = Date.now();
try {
@@ -657,6 +707,22 @@ const RecordPlayback = ({ route }) => {
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
const exitRequested = exitRequestedRef.current;
const shouldRestart = restartRequestedRef.current;
+ const wasInterrupted = interruptedRef.current;
+
+ if (wasInterrupted) {
+ log(
+ "Recording was interrupted (background/inactive), discarding and exiting"
+ );
+ exitRequestedRef.current = false;
+ restartRequestedRef.current = false;
+ manualRestartInFlightRef.current = false;
+ interruptedRef.current = false;
+ await discardRecordingFile(video?.uri, "interrupted");
+ try {
+ goBack();
+ } catch (_) {}
+ return;
+ }
if (exitRequested) {
exitRequestedRef.current = false;