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; 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}
+8 -7
View File
@@ -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);
} }
+69 -3
View File
@@ -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;