feat: playbacks
This commit is contained in:
@@ -63,7 +63,10 @@ const FullscreenIntroVideo = ({ url, visible = true, onClose }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const remaining = player.duration - currentTime;
|
const remaining = player.duration - currentTime;
|
||||||
if (Number.isFinite(remaining) && remaining <= CLOSE_THRESHOLD_SECONDS) {
|
if (
|
||||||
|
Number.isFinite(remaining) &&
|
||||||
|
remaining <= CLOSE_THRESHOLD_SECONDS
|
||||||
|
) {
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +78,10 @@ const FullscreenIntroVideo = ({ url, visible = true, onClose }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const remaining = player.duration - (player.currentTime ?? 0);
|
const remaining = player.duration - (player.currentTime ?? 0);
|
||||||
if (Number.isFinite(remaining) && remaining <= CLOSE_THRESHOLD_SECONDS) {
|
if (
|
||||||
|
Number.isFinite(remaining) &&
|
||||||
|
remaining <= CLOSE_THRESHOLD_SECONDS
|
||||||
|
) {
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,6 +105,7 @@ const FullscreenIntroVideo = ({ url, visible = true, onClose }) => {
|
|||||||
pointerEvents="box-none"
|
pointerEvents="box-none"
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
|
paddingHorizontal: 10,
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@@ -112,8 +119,16 @@ const FullscreenIntroVideo = ({ url, visible = true, onClose }) => {
|
|||||||
<VideoView
|
<VideoView
|
||||||
player={player}
|
player={player}
|
||||||
nativeControls={false}
|
nativeControls={false}
|
||||||
contentFit="cover"
|
contentFit="contain"
|
||||||
style={{ position: "absolute", top: 0, bottom: 0, left: 0, right: 0 }}
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
// style={{
|
||||||
|
// position: "absolute",
|
||||||
|
// top: 0,
|
||||||
|
// bottom: 0,
|
||||||
|
// left: 24,
|
||||||
|
// right: 24,
|
||||||
|
// }}
|
||||||
/>
|
/>
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={handleClose}
|
onPress={handleClose}
|
||||||
|
|||||||
@@ -453,7 +453,6 @@ const PlayerProvider = ({ children }) => {
|
|||||||
} catch (_err) {}
|
} catch (_err) {}
|
||||||
}, [player, isLooping]);
|
}, [player, isLooping]);
|
||||||
|
|
||||||
|
|
||||||
const seekDebounceRef = useRef(null);
|
const seekDebounceRef = useRef(null);
|
||||||
const pendingSeekPosRef = useRef(null);
|
const pendingSeekPosRef = useRef(null);
|
||||||
const isSeekingRef = useRef(false);
|
const isSeekingRef = useRef(false);
|
||||||
@@ -511,8 +510,11 @@ const PlayerProvider = ({ children }) => {
|
|||||||
|
|
||||||
const seekBy = useCallback(
|
const seekBy = useCallback(
|
||||||
async (deltaMs) => {
|
async (deltaMs) => {
|
||||||
const currentPos = pendingSeekPosRef.current !== null
|
const currentPos =
|
||||||
? (Platform.OS === "web" ? pendingSeekPosRef.current : pendingSeekPosRef.current * 1000)
|
pendingSeekPosRef.current !== null
|
||||||
|
? Platform.OS === "web"
|
||||||
|
? pendingSeekPosRef.current
|
||||||
|
: pendingSeekPosRef.current * 1000
|
||||||
: playback.positionMs;
|
: playback.positionMs;
|
||||||
const next = Math.max(0, currentPos + Number(deltaMs || 0));
|
const next = Math.max(0, currentPos + Number(deltaMs || 0));
|
||||||
await seekTo(next);
|
await seekTo(next);
|
||||||
@@ -548,7 +550,6 @@ const PlayerProvider = ({ children }) => {
|
|||||||
await waitForActiveSeek();
|
await waitForActiveSeek();
|
||||||
}, [player, status?.isLoaded, waitForActiveSeek]);
|
}, [player, status?.isLoaded, waitForActiveSeek]);
|
||||||
|
|
||||||
|
|
||||||
const play = useCallback(
|
const play = useCallback(
|
||||||
async (trackInput, options = {}) => {
|
async (trackInput, options = {}) => {
|
||||||
const normalized = normalizeTrack(trackInput, options);
|
const normalized = normalizeTrack(trackInput, options);
|
||||||
@@ -680,8 +681,6 @@ const PlayerProvider = ({ children }) => {
|
|||||||
[currentTrack, playback.isPlaying, pause, play, resume]
|
[currentTrack, playback.isPlaying, pause, play, resume]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const stop = useCallback(async () => {
|
const stop = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
autoPlayRef.current = false;
|
autoPlayRef.current = false;
|
||||||
@@ -720,7 +719,9 @@ const PlayerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
return;
|
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) {
|
if (idx !== queueIndexRef.current) {
|
||||||
setQueueIndexValue(idx);
|
setQueueIndexValue(idx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,13 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} 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 { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import Svg, { Circle } from "react-native-svg";
|
import Svg, { Circle } from "react-native-svg";
|
||||||
import alert from "../../components/Alert";
|
import alert from "../../components/Alert";
|
||||||
@@ -56,6 +62,7 @@ const RecordPlayback = ({ route }) => {
|
|||||||
const isPausedRef = useRef(false);
|
const isPausedRef = useRef(false);
|
||||||
const stopRequestedRef = useRef(false);
|
const stopRequestedRef = useRef(false);
|
||||||
const restartRequestedRef = useRef(false);
|
const restartRequestedRef = useRef(false);
|
||||||
|
const interruptedRef = useRef(false); // New ref to track interruptions
|
||||||
const manualRestartInFlightRef = useRef(false);
|
const manualRestartInFlightRef = useRef(false);
|
||||||
const stopRequestedAtRef = useRef(0);
|
const stopRequestedAtRef = useRef(0);
|
||||||
const activeRecordingPromiseRef = useRef(null);
|
const activeRecordingPromiseRef = useRef(null);
|
||||||
@@ -107,6 +114,43 @@ const RecordPlayback = ({ route }) => {
|
|||||||
isPausedRef.current = isPaused;
|
isPausedRef.current = isPaused;
|
||||||
}, [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 musicIndex = useMemo(() => {
|
||||||
const i = Number(songIndex);
|
const i = Number(songIndex);
|
||||||
return Number.isFinite(i) && i >= 0 ? i : 0;
|
return Number.isFinite(i) && i >= 0 ? i : 0;
|
||||||
@@ -451,6 +495,9 @@ const RecordPlayback = ({ route }) => {
|
|||||||
try {
|
try {
|
||||||
stopRequestedRef.current = false;
|
stopRequestedRef.current = false;
|
||||||
stopRequestedAtRef.current = 0;
|
stopRequestedAtRef.current = 0;
|
||||||
|
stopRequestedRef.current = false;
|
||||||
|
stopRequestedAtRef.current = 0;
|
||||||
|
interruptedRef.current = false; // Reset interruption flag
|
||||||
listenedMsRef.current = 0;
|
listenedMsRef.current = 0;
|
||||||
incrementDoneRef.current = false;
|
incrementDoneRef.current = false;
|
||||||
|
|
||||||
@@ -470,7 +517,9 @@ const RecordPlayback = ({ route }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (activeRecordingPromiseRef.current) {
|
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 {
|
try {
|
||||||
await activeRecordingPromiseRef.current;
|
await activeRecordingPromiseRef.current;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -586,7 +635,8 @@ const RecordPlayback = ({ route }) => {
|
|||||||
if (!player) return;
|
if (!player) return;
|
||||||
if (isPausedRef.current) return;
|
if (isPausedRef.current) return;
|
||||||
if (stopRequestedRef.current) {
|
if (stopRequestedRef.current) {
|
||||||
const sinceLastRequest = Date.now() - (stopRequestedAtRef.current || 0);
|
const sinceLastRequest =
|
||||||
|
Date.now() - (stopRequestedAtRef.current || 0);
|
||||||
if (sinceLastRequest >= 1200) {
|
if (sinceLastRequest >= 1200) {
|
||||||
stopRequestedAtRef.current = Date.now();
|
stopRequestedAtRef.current = Date.now();
|
||||||
try {
|
try {
|
||||||
@@ -657,6 +707,22 @@ const RecordPlayback = ({ route }) => {
|
|||||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||||
const exitRequested = exitRequestedRef.current;
|
const exitRequested = exitRequestedRef.current;
|
||||||
const shouldRestart = restartRequestedRef.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) {
|
if (exitRequested) {
|
||||||
exitRequestedRef.current = false;
|
exitRequestedRef.current = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user