diff --git a/src/screens/Library/MusicDetails.js b/src/screens/Library/MusicDetails.js
index ee86413..d257a7a 100644
--- a/src/screens/Library/MusicDetails.js
+++ b/src/screens/Library/MusicDetails.js
@@ -1,8 +1,15 @@
import { useRoute } from "@react-navigation/core";
import { Audio } from "expo-audio";
-import React, { useEffect, useMemo, useState } from "react";
-import { Pressable, ScrollView, StyleSheet, Text, View, Image as RNImage } from "react-native";
import { Image as ExpoImage } from "expo-image";
+import React, { useEffect, useMemo, useState } from "react";
+import {
+ Pressable,
+ Image as RNImage,
+ ScrollView,
+ StyleSheet,
+ Text,
+ View,
+} from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { useGlobal } from "reactn";
import { background, icons, img } from "../../assets";
@@ -336,7 +343,7 @@ const MusicDetails = () => {
/>
{/* Previous (rewind 10s) */}
- seekBy(-10)}>
+ seekBy(-10)}>
{
? currentProjet.lyrics.length > 0
: !!currentProjet?.lyrics;
const hasCover = !!currentProjet?.coverUrl;
+ console.log("current projet cover", currentProjet?.coverUrl);
const isLocked = (index) => {
// 0: Songwriter, 1: Beatmaker, 2: Producer, 3: Director
- if (index === 3) return true; // Director toujours verrouillé
- if (!hasProject) return index !== 0; // seulement Songwriter
- if (hasCover) return index !== 2; // seulement Producer
- if (hasLyrics) return !(index === 0 || index === 1); // Songwriter + Beatmaker
- return index !== 0; // par défaut seulement Songwriter
+ // Rules:
+ // - If no project: only Songwriter (0) is available.
+ // - If project exists: Songwriter (0) is always available.
+ // - If lyrics exist: Beatmaker (1) becomes available.
+ // - If cover exists: Producer (2) and Director (3) become available.
+ if (!hasProject) return index !== 0;
+
+ const allowed = new Set([0]); // songwriter always allowed when project exists
+ if (hasLyrics) {
+ allowed.add(1);
+ }
+ if (hasCover) {
+ allowed.add(2);
+ allowed.add(3);
+ }
+
+ return !allowed.has(index);
};
const onPressOption = (index, item) => {
@@ -80,6 +93,12 @@ const NewMusicOptions = ({ route }) => {
case 2:
navigate(Routes.Production, { action: item.type });
break;
+ case 3:
+ console.log("test");
+ navigate(Routes.Playback, {
+ action: item.type,
+ project: currentProjet,
+ });
default:
break;
}
diff --git a/src/screens/Playback/Playback.js b/src/screens/Playback/Playback.js
index b9db567..9790bfd 100644
--- a/src/screens/Playback/Playback.js
+++ b/src/screens/Playback/Playback.js
@@ -1,15 +1,16 @@
-import { View, Text, StyleSheet, Image } from "react-native";
import React from "react";
+import { Image, StyleSheet, View } from "react-native";
import { ai, background } from "../../assets";
-import MusicLandHeader from "../../components/MusicLandHeader";
-import { goBack, navigate } from "../../navigation/NavigationService";
-import { gutters } from "../../styles";
-import Page from "../../layouts/Page";
import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton";
+import MusicLandHeader from "../../components/MusicLandHeader";
+import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
+import { goBack, navigate } from "../../navigation/NavigationService";
+import { gutters } from "../../styles";
-const Playback = () => {
+const Playback = ({ route }) => {
+ const { project } = route.params;
return (
@@ -23,10 +24,10 @@ const Playback = () => {
>
-
+ {/* */}
navigate(Routes.RecordPlayback)}
+ onPress={() => navigate(Routes.RecordPlayback, { project })}
/>
diff --git a/src/screens/Playback/RecordPlayback.js b/src/screens/Playback/RecordPlayback.js
index 56941a1..dadd0b7 100644
--- a/src/screens/Playback/RecordPlayback.js
+++ b/src/screens/Playback/RecordPlayback.js
@@ -1,21 +1,181 @@
-import { Text, View } from "react-native";
+import { Audio } from "expo-audio";
+import { CameraView, useCameraPermissions } from "expo-camera";
import React from "react";
-import { CameraView } from "expo-camera";
-import { gutters, Palette } from "../../styles";
-import MusicLandHeader from "../../components/MusicLandHeader";
-import { goBack, navigate } from "../../navigation/NavigationService";
+import { Text, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
-import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
-import { FONT_FAMILY } from "../../styles/Fonts";
import GradientButton from "../../components/GradientButton";
+import MusicLandHeader from "../../components/MusicLandHeader";
import { Routes } from "../../navigation";
+import { goBack, navigate } from "../../navigation/NavigationService";
+import { gutters, Palette } from "../../styles";
+import { FONT_FAMILY } from "../../styles/Fonts";
+import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
-const RecordPlayback = () => {
+const RecordPlayback = ({ route }) => {
const { top } = useSafeAreaInsets();
+ const { project } = route.params || {};
+
+ // Permissions
+ const [cameraPermission, requestCameraPermission] = useCameraPermissions();
+
+ // Refs
+ const cameraRef = React.useRef(null);
+ const soundRef = React.useRef(null);
+ const countdownTimerRef = React.useRef(null);
+ const stopRequestedRef = React.useRef(false);
+
+ // UI / state
+ const [isPreparing, setIsPreparing] = React.useState(false);
+ const [countdown, setCountdown] = React.useState(0);
+ const [isRecording, setIsRecording] = React.useState(false);
+
+ // Derive song URL robustly
+ const songUrl = project?.songUrl || null;
+
+ React.useEffect(() => {
+ // Request permissions on mount if not granted
+ (async () => {
+ try {
+ if (!cameraPermission?.granted) await requestCameraPermission();
+ } catch (_) {}
+ })();
+ return () => {
+ // Cleanup timers and audio on unmount
+ try {
+ if (countdownTimerRef.current) {
+ global.clearInterval(countdownTimerRef.current);
+ countdownTimerRef.current = null;
+ }
+ (async () => {
+ try {
+ if (soundRef.current) {
+ soundRef.current.setOnPlaybackStatusUpdate(null);
+ await soundRef.current.unloadAsync();
+ soundRef.current = null;
+ }
+ } catch (_) {}
+ })();
+ } catch (_) {}
+ };
+ }, []);
+
+ const startCountdownThenRecord = async () => {
+ console.log("start countdown");
+ if (!songUrl) return;
+ console.log("songUrl exists");
+ setIsPreparing(true);
+ setCountdown(5);
+ // Start music + recording immediately when countdown starts
+ void startRecordingWithMusic();
+ // 5 -> 0 countdown display only
+ countdownTimerRef.current = global.setInterval(() => {
+ setCountdown((c) => {
+ const next = (c || 0) - 1;
+ if (next <= 0) {
+ global.clearInterval(countdownTimerRef.current);
+ countdownTimerRef.current = null;
+ }
+ return Math.max(0, next);
+ });
+ }, 1000);
+ };
+
+ const startRecordingWithMusic = async () => {
+ try {
+ stopRequestedRef.current = false;
+ console.log("stop requested ref is : ", soundRef.current);
+ // Prepare audio
+ if (soundRef.current) {
+ console.log("if sound ref current");
+ try {
+ soundRef.current.setOnPlaybackStatusUpdate(null);
+ await soundRef.current.unloadAsync();
+ console.log("unload sound ref");
+ } catch (_) {}
+ soundRef.current = null;
+ }
+
+ // Ensure audio mode allows playback alongside camera recording
+ try {
+ await Audio.setAudioModeAsync({
+ playsInSilentMode: true,
+ interruptionMode: "mixWithOthers",
+ allowsRecording: true,
+ shouldPlayInBackground: false,
+ });
+ } catch (_) {}
+
+ const { sound } = await Audio.Sound.createAsync(
+ { uri: songUrl },
+ { shouldPlay: false }
+ );
+
+ sound.setOnPlaybackStatusUpdate((status) => {
+ if (!status || !status.isLoaded) return;
+ if (status.didJustFinish && !stopRequestedRef.current) {
+ stopRequestedRef.current = true;
+ try {
+ cameraRef.current?.stopRecording?.();
+ } catch (_) {}
+ }
+ });
+ soundRef.current = sound;
+
+ // Start recording
+ setIsRecording(true);
+ const recordPromise = cameraRef.current?.recordAsync?.({
+ mute: true,
+ maxDuration: 600, // safety cap (10 min)
+ });
+
+ // Start playing
+ await sound.playAsync();
+
+ // Wait for recording to stop (either by song end or manual stop)
+ const video = await recordPromise;
+
+ // Ensure audio stops and cleanup
+ try {
+ const s = soundRef.current;
+ if (s) {
+ s.setOnPlaybackStatusUpdate(null);
+ await s.stopAsync().catch(() => {});
+ await s.unloadAsync().catch(() => {});
+ }
+ } catch (_) {}
+ soundRef.current = null;
+
+ setIsRecording(false);
+ setIsPreparing(false);
+
+ // Navigate to next screen with video uri if available
+ if (video?.uri) {
+ navigate(Routes.RecordedPlayback, { videoUri: video.uri, project });
+ } else {
+ navigate(Routes.RecordedPlayback, { project });
+ }
+ } catch (e) {
+ // Fallback on error
+ setIsRecording(false);
+ setIsPreparing(false);
+ try {
+ soundRef.current?.setOnPlaybackStatusUpdate?.(null);
+ await soundRef.current?.unloadAsync?.();
+ } catch (_) {}
+ soundRef.current = null;
+ }
+ };
+
+ const permissionsGranted = !!cameraPermission?.granted;
return (
-
+
{
musique, et s'arrêtera à la fin du morceau.
+
+ {/* Centered countdown overlay */}
+ {(isPreparing || isRecording) && countdown > 0 && (
+
+
+ {countdown}
+
+
+ )}
+
+ {/* Permission prompt */}
+ {!permissionsGranted && (
+
+ {
+ try {
+ if (!cameraPermission?.granted)
+ await requestCameraPermission();
+ } catch (_) {}
+ }}
+ />
+
+ )}
- navigate(Routes.RecordedPlayback)}
- />
+
+ {/* Start button */}
+ {permissionsGranted && !isPreparing && !isRecording && (
+
+ )}
diff --git a/src/screens/Playback/RecordedPlayback.js b/src/screens/Playback/RecordedPlayback.js
index 999f9c1..c4e4eb3 100644
--- a/src/screens/Playback/RecordedPlayback.js
+++ b/src/screens/Playback/RecordedPlayback.js
@@ -1,14 +1,14 @@
-import { View, Text, Image } from "react-native";
import React from "react";
-import Page from "../../layouts/Page";
+import { Image, View } from "react-native";
import { background, img } from "../../assets";
+import BorderGradientButton from "../../components/BorderGradientButton";
+import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
+import Slider from "../../components/Slider";
+import Page from "../../layouts/Page";
+import { Routes } from "../../navigation";
import { goBack, navigate } from "../../navigation/NavigationService";
import { gutters } from "../../styles";
-import Slider from "../../components/Slider";
-import GradientButton from "../../components/GradientButton";
-import { Routes } from "../../navigation";
-import BorderGradientButton from "../../components/BorderGradientButton";
const RecordedPlayback = () => {
return (
@@ -32,9 +32,7 @@ const RecordedPlayback = () => {
-
+
navigate(Routes.ChooseDecor)}