last fixes, home and videos

This commit is contained in:
Thomas Demirdjian
2025-11-10 14:24:16 +01:00
parent d7d9211fbe
commit dd4cf9b4d4
40 changed files with 984 additions and 647 deletions
+62 -2
View File
@@ -7,7 +7,7 @@ import React, {
useRef,
useState,
} from "react";
import { Text, View } from "react-native";
import { 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";
@@ -30,6 +30,11 @@ const log =
? (...args) => console.log(LOG_PREFIX, ...args)
: () => {};
const CAMERA_FACING_OPTIONS = [
{ label: "Avant", value: "front" },
{ label: "Arrière", value: "back" },
];
const RecordPlayback = ({ route }) => {
const { top, bottom } = useSafeAreaInsets();
const { project } = route.params || {};
@@ -63,6 +68,7 @@ const RecordPlayback = ({ route }) => {
const [isRecording, setIsRecording] = useState(false);
const [showProgress, setShowProgress] = useState(false);
const [progressInfo, setProgressInfo] = useState({ pos: 0, dur: 0 });
const [cameraFacing, setCameraFacing] = useState("front");
// Musique
const songUrl = project?.songUrl || null;
@@ -612,7 +618,7 @@ const RecordPlayback = ({ route }) => {
<CameraView
ref={cameraRef}
style={{ flex: 1 }}
facing="front"
facing={cameraFacing}
mode="video"
mute
>
@@ -626,6 +632,14 @@ const RecordPlayback = ({ route }) => {
>
<MusicLandHeader progress={9} onPressBack={goBack} />
<View style={{ marginTop: 12, alignItems: "flex-end" }}>
<CameraFacingSelector
value={cameraFacing}
onChange={setCameraFacing}
disabled={!permissionsGranted || isPreparing || isRecording}
/>
</View>
<View style={{ flex: 1, marginTop: 11 }}>
{/* Overlay de compte à rebours : on affiche 5→1 pour éviter l'effet visuel à 1 */}
{isPreparing && countdown >= 1 && !showProgress && (
@@ -836,3 +850,49 @@ const KaraokeLines = ({ lines = [], currentLineIdx = -1 }) => {
</View>
);
};
const CameraFacingSelector = ({ value = "front", onChange, disabled = false }) => {
return (
<View
style={{
flexDirection: "row",
backgroundColor: "rgba(0,0,0,0.35)",
borderRadius: 999,
padding: 4,
gap: 4,
opacity: disabled ? 0.6 : 1,
}}
pointerEvents={disabled ? "none" : "auto"}
>
{CAMERA_FACING_OPTIONS.map((option) => {
const isActive = option.value === value;
return (
<TouchableOpacity
key={option.value}
activeOpacity={0.8}
onPress={() => {
if (typeof onChange === "function") onChange(option.value);
}}
disabled={isActive}
style={{
paddingVertical: 6,
paddingHorizontal: 14,
borderRadius: 999,
backgroundColor: isActive ? Palette.white : "transparent",
}}
>
<Text
style={{
color: isActive ? Palette.black : Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
fontSize: 14,
}}
>
{option.label}
</Text>
</TouchableOpacity>
);
})}
</View>
);
};
+180 -92
View File
@@ -1,6 +1,12 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Text, View } from "react-native";
import { background } from "../../assets";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { Image, Pressable, Text, View } from "react-native";
import { background, icons } from "../../assets";
import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
@@ -32,14 +38,22 @@ const RecordedPlayback = ({ route }) => {
const songUrl = project?.songUrl || null;
console.log("video uri is : ", videoUri);
// AUDIO PLAYER (expo-audio → seconds)
const audioPlayer = useSharedAudioPlayer(songUrl ? { uri: songUrl } : undefined, {
id: project?.id ? `recorded-${project.id}` : songUrl ? `recorded-${songUrl}` : undefined,
title: typeof project?.title === "string" ? project.title : "Sans titre",
artist: typeof project?.userName === "string" ? project.userName : "MusicLand",
artwork: project?.coverUrl || null,
coverUrl: project?.coverUrl || null,
metadata: { projectId: project?.id, screen: "RecordedPlayback" },
});
const audioPlayer = useSharedAudioPlayer(
songUrl ? { uri: songUrl } : undefined,
{
id: project?.id
? `recorded-${project.id}`
: songUrl
? `recorded-${songUrl}`
: undefined,
title: typeof project?.title === "string" ? project.title : "Sans titre",
artist:
typeof project?.userName === "string" ? project.userName : "MusicLand",
artwork: project?.coverUrl || null,
coverUrl: project?.coverUrl || null,
metadata: { projectId: project?.id, screen: "RecordedPlayback" },
},
);
// Optionnel: si tu veux quand même un rendu vidéo sur web si tu as une source
const videoElRef = useRef(null);
@@ -149,8 +163,11 @@ const RecordedPlayback = ({ route }) => {
};
const wasPlayingRef = useRef(false);
const onSeekStart = async () => {
const isSeekingRef = useRef(false);
const onSeekStart = useCallback(async () => {
try {
if (isSeekingRef.current) return;
isSeekingRef.current = true;
wasPlayingRef.current = !!audioPlayer?.playing;
playbackEndedRef.current = false;
if (audioPlayer?.playing) await audioPlayer.pause?.();
@@ -158,80 +175,151 @@ const RecordedPlayback = ({ route }) => {
videoElRef.current.pause();
}
} catch {}
};
const onSeekEnd = async () => {
}, [audioPlayer]);
const onSeekEnd = useCallback(async () => {
try {
if (!isSeekingRef.current) return;
isSeekingRef.current = false;
if (wasPlayingRef.current) {
if (audioPlayer) await audioPlayer.play?.();
if (videoElRef.current && videoUri)
videoElRef.current.play().catch(() => {});
}
} catch {}
};
}, [audioPlayer, videoUri]);
const handleTogglePlayback = useCallback(async () => {
try {
const duration = Number(progress?.durS || 0);
const position = Number(progress?.posS || 0);
const isAtEnd = duration > 0 && duration - position < 0.35;
const isCurrentlyPlaying = !!audioPlayer?.playing;
if (isCurrentlyPlaying) {
playbackEndedRef.current = false;
await stopPlayback();
return;
}
if (isAtEnd) {
if (audioPlayer) await audioPlayer.seekTo?.(0);
if (videoElRef.current && videoUri) {
videoElRef.current.currentTime = 0;
}
}
playbackEndedRef.current = false;
if (songUrl && audioPlayer) {
await audioPlayer.play?.();
}
if (videoElRef.current && videoUri) {
videoElRef.current.muted = true;
videoElRef.current.play().catch(() => {});
}
} catch {}
}, [audioPlayer, songUrl, stopPlayback, progress, videoUri]);
return (
<Page backgroundImg={background.playbackBG2} headerType="NONE">
<MusicLandHeader progress={19} onPressBack={goBack} />
<Page
containerStyle={{
alignItems: "none",
}}
backgroundImg={background.playbackBG2}
headerType="NONE"
>
<MusicLandHeader
progress={19}
onPressBack={goBack}
style={{
marginBottom: 40,
}}
/>
<View
style={{ flex: 1, paddingTop: 12, gap: 14, paddingBottom: gutters * 2 }}
style={{
width: "100%",
}}
>
<View style={{ flex: 1, gap: 22, alignItems: "center" }}>
{/* Bloc vidéo optionnel si jamais tu as un videoUri sur web */}
{videoUri ? (
<View
{/* Bloc vidéo optionnel si jamais tu as un videoUri sur web */}
{videoUri ? (
<Pressable
onPress={handleTogglePlayback}
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
aspectRatio: 9 / 16,
borderRadius: 12,
backgroundColor: "#00000066",
overflow: "hidden",
alignSelf: "center",
position: "relative",
}}
>
<video
ref={videoElRef}
src={videoUri}
muted
playsInline
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
aspectRatio: 9 / 16,
borderRadius: 12,
backgroundColor: "#00000066",
overflow: "hidden",
alignSelf: "center",
position: "relative",
width: "100%",
height: "100%",
objectFit: "cover",
backgroundColor: "black",
}}
>
<video
ref={videoElRef}
src={videoUri}
muted
playsInline
controls={false}
/>
{!progress.playing && (
<View
pointerEvents="none"
style={{
width: "100%",
height: "100%",
objectFit: "cover",
backgroundColor: "black",
position: "absolute",
left: "50%",
top: "50%",
transform: [{ translateX: -36 }, { translateY: -36 }],
width: 72,
height: 72,
borderRadius: 36,
alignItems: "center",
justifyContent: "center",
backgroundColor: "rgba(10, 5, 24, 0.75)",
borderWidth: 1,
borderColor: "#F94697",
}}
controls={false}
/>
</View>
) : (
// Placeholder quand pas de vidéo sur web
<View
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
aspectRatio: 9 / 16,
borderRadius: 12,
backgroundColor: "#00000066",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
alignSelf: "center",
}}
>
<Text style={{ color: Palette.white, opacity: 0.8 }}>
Aperçu vidéo non disponible sur le web
</Text>
</View>
)}
>
<Image source={icons.play} style={{ width: 26, height: 26 }} />
</View>
)}
</Pressable>
) : (
// Placeholder quand pas de vidéo sur web
<View
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
aspectRatio: 9 / 16,
borderRadius: 12,
backgroundColor: "#00000066",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
alignSelf: "center",
}}
>
<Text style={{ color: Palette.white, opacity: 0.8 }}>
Aperçu vidéo non disponible sur le web
</Text>
</View>
)}
<View
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
alignSelf: "center",
marginTop: 6,
alignItems: "center",
}}
>
<View style={{ width: "100%" }}>
<Slider
value={fmtSeconds(progress.posS)} // mm:ss (seconds)
maxValue={fmtSeconds(progress.durS)} // mm:ss (seconds)
@@ -243,36 +331,36 @@ const RecordedPlayback = ({ route }) => {
/>
</View>
</View>
</View>
<View
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
alignSelf: "center",
marginTop: 4,
gap: 12,
<View
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
alignSelf: "center",
gap: 12,
marginTop: 50,
}}
>
<GradientButton
title="Je valide"
onPress={() => {
navigate(Routes.DownloadSongs, {
action: "playback",
uri: videoUri || null, // peut être null sur web
project,
});
}}
>
<GradientButton
title="Je valide"
onPress={() => {
navigate(Routes.DownloadSongs, {
action: "playback",
uri: videoUri || null, // peut être null sur web
project,
});
}}
/>
<BorderGradientButton
title="Recommencer"
onPress={async () => {
try {
await stopPlayback();
} catch {}
navigate(Routes.RecordPlayback, { project });
}}
/>
</View>
/>
<BorderGradientButton
title="Recommencer"
onPress={async () => {
try {
await stopPlayback();
} catch {}
navigate(Routes.RecordPlayback, { project });
}}
/>
</View>
</Page>
);