feat: slider
This commit is contained in:
@@ -46,7 +46,7 @@ const ProgressSlider = ({
|
|||||||
if (isPlaying && typeof onPause === "function") {
|
if (isPlaying && typeof onPause === "function") {
|
||||||
onPause();
|
onPause();
|
||||||
}
|
}
|
||||||
}, [disabled, safeDuration, isPlaying, onPause, onSeekStart]);
|
}, [disabled, safeDuration, isPlaying, onPause, onSeekStart, progress]);
|
||||||
|
|
||||||
const handleSeek = useCallback(
|
const handleSeek = useCallback(
|
||||||
(ratio) => {
|
(ratio) => {
|
||||||
@@ -56,7 +56,7 @@ const ProgressSlider = ({
|
|||||||
const targetMs = safeDuration * bounded;
|
const targetMs = safeDuration * bounded;
|
||||||
onSeek(targetMs);
|
onSeek(targetMs);
|
||||||
},
|
},
|
||||||
[disabled, onSeek, safeDuration],
|
[disabled, onSeek, safeDuration]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSeekEnd = useCallback(() => {
|
const handleSeekEnd = useCallback(() => {
|
||||||
@@ -77,7 +77,7 @@ const ProgressSlider = ({
|
|||||||
value={formatTime(
|
value={formatTime(
|
||||||
isSeeking && previewRatio != null
|
isSeeking && previewRatio != null
|
||||||
? safeDuration * previewRatio
|
? safeDuration * previewRatio
|
||||||
: safePosition,
|
: safePosition
|
||||||
)}
|
)}
|
||||||
maxValue={formatTime(safeDuration)}
|
maxValue={formatTime(safeDuration)}
|
||||||
progress={progress}
|
progress={progress}
|
||||||
|
|||||||
+13
-6
@@ -31,7 +31,7 @@ const hasCoverAsset = (project) => {
|
|||||||
...cover.options.flatMap((option) => [
|
...cover.options.flatMap((option) => [
|
||||||
option?.finalUrl,
|
option?.finalUrl,
|
||||||
option?.generatedUrl,
|
option?.generatedUrl,
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return coverCandidates.some(isValidUri);
|
return coverCandidates.some(isValidUri);
|
||||||
@@ -43,7 +43,7 @@ const hasThumbnailAsset = (project) =>
|
|||||||
const mergeProjectAssets = (
|
const mergeProjectAssets = (
|
||||||
project,
|
project,
|
||||||
firestoreData,
|
firestoreData,
|
||||||
{ mergeCover = false, mergeThumbnails = false } = {},
|
{ mergeCover = false, mergeThumbnails = false } = {}
|
||||||
) => {
|
) => {
|
||||||
if (!firestoreData || typeof firestoreData !== "object") {
|
if (!firestoreData || typeof firestoreData !== "object") {
|
||||||
return project;
|
return project;
|
||||||
@@ -51,7 +51,10 @@ const mergeProjectAssets = (
|
|||||||
const mergedProject = { ...project };
|
const mergedProject = { ...project };
|
||||||
|
|
||||||
if (mergeCover) {
|
if (mergeCover) {
|
||||||
if (!isValidUri(mergedProject.coverUrl) && isValidUri(firestoreData.coverUrl)) {
|
if (
|
||||||
|
!isValidUri(mergedProject.coverUrl) &&
|
||||||
|
isValidUri(firestoreData.coverUrl)
|
||||||
|
) {
|
||||||
mergedProject.coverUrl = firestoreData.coverUrl;
|
mergedProject.coverUrl = firestoreData.coverUrl;
|
||||||
}
|
}
|
||||||
const firestoreCover =
|
const firestoreCover =
|
||||||
@@ -67,7 +70,10 @@ const mergeProjectAssets = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mergeThumbnails) {
|
if (mergeThumbnails) {
|
||||||
if (!isValidUri(mergedProject.thumbnailUrl) && isValidUri(firestoreData.thumbnailUrl)) {
|
if (
|
||||||
|
!isValidUri(mergedProject.thumbnailUrl) &&
|
||||||
|
isValidUri(firestoreData.thumbnailUrl)
|
||||||
|
) {
|
||||||
mergedProject.thumbnailUrl = firestoreData.thumbnailUrl;
|
mergedProject.thumbnailUrl = firestoreData.thumbnailUrl;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -83,7 +89,7 @@ const mergeProjectAssets = (
|
|||||||
|
|
||||||
const hydrateProjects = async (
|
const hydrateProjects = async (
|
||||||
projects = [],
|
projects = [],
|
||||||
{ ensureCover = false, ensureThumbnails = false } = {},
|
{ ensureCover = false, ensureThumbnails = false } = {}
|
||||||
) => {
|
) => {
|
||||||
if (!Array.isArray(projects) || projects.length === 0) {
|
if (!Array.isArray(projects) || projects.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
@@ -112,7 +118,7 @@ const hydrateProjects = async (
|
|||||||
console.log("useSearch.hydrateProjects error", error?.message || error);
|
console.log("useSearch.hydrateProjects error", error?.message || error);
|
||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -132,6 +138,7 @@ const useSearch = () => {
|
|||||||
filters: `NOT objectID:${currentUID}`,
|
filters: `NOT objectID:${currentUID}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log("users are ", JSON.stringify(users, null, 2));
|
||||||
const { hits: playbacks, loading: playbackLoading } = useAlgoliaSearch({
|
const { hits: playbacks, loading: playbackLoading } = useAlgoliaSearch({
|
||||||
query: search,
|
query: search,
|
||||||
algoliaObject: AlgoliaProjectConfig,
|
algoliaObject: AlgoliaProjectConfig,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { Routes } from "../navigation/Routes";
|
|||||||
import { useUser } from "../providers/UserDataProvider";
|
import { useUser } from "../providers/UserDataProvider";
|
||||||
import { Palette } from "../styles";
|
import { Palette } from "../styles";
|
||||||
import { FONT_FAMILY } from "../styles/Fonts";
|
import { FONT_FAMILY } from "../styles/Fonts";
|
||||||
|
import { BlurView } from "expo-blur";
|
||||||
|
|
||||||
const LANGUAGE_STORAGE_KEY = "preferredLanguage";
|
const LANGUAGE_STORAGE_KEY = "preferredLanguage";
|
||||||
|
|
||||||
@@ -120,12 +121,7 @@ export default function LandingPage() {
|
|||||||
{selectedLanguage && (
|
{selectedLanguage && (
|
||||||
<View style={styles.actions}>
|
<View style={styles.actions}>
|
||||||
{showFreeCreditsPopup && (
|
{showFreeCreditsPopup && (
|
||||||
<LinearGradient
|
<BlurView style={styles.freeCreditsCard}>
|
||||||
colors={["#F94697", "#7023F7"]}
|
|
||||||
start={{ x: 0, y: 0 }}
|
|
||||||
end={{ x: 1, y: 0 }}
|
|
||||||
style={styles.freeCreditsCard}
|
|
||||||
>
|
|
||||||
<View style={styles.freeCreditsContent}>
|
<View style={styles.freeCreditsContent}>
|
||||||
<View style={styles.freeCreditsTextContainer}>
|
<View style={styles.freeCreditsTextContainer}>
|
||||||
<Text style={styles.freeCreditsTitle}>
|
<Text style={styles.freeCreditsTitle}>
|
||||||
@@ -147,7 +143,7 @@ export default function LandingPage() {
|
|||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</LinearGradient>
|
</BlurView>
|
||||||
)}
|
)}
|
||||||
<GradientButton
|
<GradientButton
|
||||||
title="Découvrir Musicland"
|
title="Découvrir Musicland"
|
||||||
@@ -158,9 +154,7 @@ export default function LandingPage() {
|
|||||||
title="Présentation de Musicland"
|
title="Présentation de Musicland"
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
setVideoUrl(
|
setVideoUrl(
|
||||||
Platform.OS === "web"
|
Platform.OS === "web" ? video?.landingWeb : video?.landing
|
||||||
? video?.landingWeb
|
|
||||||
: video?.landing,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
containerStyle={styles.actionButton}
|
containerStyle={styles.actionButton}
|
||||||
|
|||||||
@@ -8,13 +8,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import {
|
import { BackHandler, Text, TouchableOpacity, View } from "react-native";
|
||||||
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";
|
||||||
@@ -62,7 +56,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 recordingStartTimeRef = useRef(0); // Track recording start time for duration check
|
||||||
const manualRestartInFlightRef = useRef(false);
|
const manualRestartInFlightRef = useRef(false);
|
||||||
const stopRequestedAtRef = useRef(0);
|
const stopRequestedAtRef = useRef(0);
|
||||||
const activeRecordingPromiseRef = useRef(null);
|
const activeRecordingPromiseRef = useRef(null);
|
||||||
@@ -114,42 +108,8 @@ const RecordPlayback = ({ route }) => {
|
|||||||
isPausedRef.current = isPaused;
|
isPausedRef.current = isPaused;
|
||||||
}, [isPaused]);
|
}, [isPaused]);
|
||||||
|
|
||||||
// Monitor AppState to detect backgrounding/interruptions
|
// Removed AppState listener as it was too aggressive.
|
||||||
useEffect(() => {
|
// We now use duration validation at the end of recording.
|
||||||
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);
|
||||||
@@ -495,9 +455,7 @@ const RecordPlayback = ({ route }) => {
|
|||||||
try {
|
try {
|
||||||
stopRequestedRef.current = false;
|
stopRequestedRef.current = false;
|
||||||
stopRequestedAtRef.current = 0;
|
stopRequestedAtRef.current = 0;
|
||||||
stopRequestedRef.current = false;
|
recordingStartTimeRef.current = Date.now(); // Start timer
|
||||||
stopRequestedAtRef.current = 0;
|
|
||||||
interruptedRef.current = false; // Reset interruption flag
|
|
||||||
listenedMsRef.current = 0;
|
listenedMsRef.current = 0;
|
||||||
incrementDoneRef.current = false;
|
incrementDoneRef.current = false;
|
||||||
|
|
||||||
@@ -679,6 +637,7 @@ const RecordPlayback = ({ route }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const video = await recordPromise;
|
const video = await recordPromise;
|
||||||
|
const recordEndTime = Date.now();
|
||||||
log("Recording promise resolved", { hasVideo: !!video?.uri });
|
log("Recording promise resolved", { hasVideo: !!video?.uri });
|
||||||
activeRecordingPromiseRef.current = null;
|
activeRecordingPromiseRef.current = null;
|
||||||
stopRequestedRef.current = false;
|
stopRequestedRef.current = false;
|
||||||
@@ -707,21 +666,32 @@ 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;
|
// Duration check logic
|
||||||
|
const recordedDurationMs = recordEndTime - recordingStartTimeRef.current;
|
||||||
|
const expectedDurationMs = (player?.duration || 0) * 1000;
|
||||||
|
|
||||||
if (wasInterrupted) {
|
if (!exitRequested && !shouldRestart) {
|
||||||
log(
|
// If the app was backgrounded/interrupted, the camera stops early,
|
||||||
"Recording was interrupted (background/inactive), discarding and exiting"
|
// resulting in a duration significantly shorter than the song.
|
||||||
);
|
// We only check this if we have a valid song duration.
|
||||||
exitRequestedRef.current = false;
|
// Tolerance: 1.5s
|
||||||
restartRequestedRef.current = false;
|
if (
|
||||||
manualRestartInFlightRef.current = false;
|
expectedDurationMs > 0 &&
|
||||||
interruptedRef.current = false;
|
recordedDurationMs < expectedDurationMs - 1500
|
||||||
await discardRecordingFile(video?.uri, "interrupted");
|
) {
|
||||||
try {
|
log(
|
||||||
goBack();
|
"Recording too short compared to song duration - likely interrupted",
|
||||||
} catch (_) {}
|
{
|
||||||
return;
|
recordedDurationMs,
|
||||||
|
expectedDurationMs,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await discardRecordingFile(video?.uri, "too_short");
|
||||||
|
try {
|
||||||
|
goBack();
|
||||||
|
} catch (_) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exitRequested) {
|
if (exitRequested) {
|
||||||
@@ -1140,7 +1110,7 @@ const RecordPlayback = ({ route }) => {
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: bottom + 130,
|
bottom: bottom + 200,
|
||||||
paddingHorizontal: gutters,
|
paddingHorizontal: gutters,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user