feat: playbacks

This commit is contained in:
2025-12-08 14:27:27 +01:00
parent b0cf4a20ab
commit c97fb0a856
3 changed files with 129 additions and 47 deletions
+19 -4
View File
@@ -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 }) => {
<VideoView
player={player}
nativeControls={false}
contentFit="cover"
style={{ position: "absolute", top: 0, bottom: 0, left: 0, right: 0 }}
contentFit="contain"
height="100%"
width="100%"
// style={{
// position: "absolute",
// top: 0,
// bottom: 0,
// left: 24,
// right: 24,
// }}
/>
<Pressable
onPress={handleClose}
+41 -40
View File
@@ -63,8 +63,8 @@ const HIDDEN_ROUTE_NAMES = new Set([
Routes.Register,
]);
const noopAsync = async () => { };
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);
}
+69 -3
View File
@@ -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;