diff --git a/src/assets/index.js b/src/assets/index.js index 12a95b5..1d0550e 100644 --- a/src/assets/index.js +++ b/src/assets/index.js @@ -210,10 +210,6 @@ export const tutorial = { tasks: tutorialTasks, }; -export const mockups = { - loginAppDashboard, -}; - export const background = { writingBG, studioBG, diff --git a/src/assets/progress.png b/src/assets/progress.png new file mode 100644 index 0000000..11df4ce Binary files /dev/null and b/src/assets/progress.png differ diff --git a/src/hooks/useDataFromRef.js b/src/hooks/useDataFromRef.js index a345d13..88549bb 100644 --- a/src/hooks/useDataFromRef.js +++ b/src/hooks/useDataFromRef.js @@ -55,9 +55,6 @@ export default function useDataFromRef({ if (!_.isEqual(data, initialState)) { onUpdate(initialState); } - - console.log("Reset state", initialState); - setEndReached(false); setLastVisible(null); setData(initialState); @@ -77,10 +74,10 @@ export default function useDataFromRef({ paginate && lastVisible ? ref.startAfter(lastVisible).limit(batchSize) : initialDoc - ? ref.startAt(initialDoc).limit(batchSize) - : paginate - ? ref.limit(batchSize) - : ref; + ? ref.startAt(initialDoc).limit(batchSize) + : paginate + ? ref.limit(batchSize) + : ref; const dataSnap = await dynamicRef.get(); @@ -113,7 +110,7 @@ export default function useDataFromRef({ if (e.code === "firestore/permission-denied") { console.warn( "Permission denied for ref: ", - ref?._collectionPath?.relativeName + ref?._collectionPath?.relativeName, ); } else { console.log(e); @@ -143,14 +140,14 @@ export default function useDataFromRef({ if (e.code === "firestore/permission-denied") { console.warn( "Permission denied for ref: ", - ref?._collectionPath?.relativeName + ref?._collectionPath?.relativeName, ); } else { console.log(e); } await updateData([]); setLoading(false); - } + }, ); }; diff --git a/src/screens/Library/Library.js b/src/screens/Library/Library.js index d0c26ac..5f51be1 100644 --- a/src/screens/Library/Library.js +++ b/src/screens/Library/Library.js @@ -40,10 +40,10 @@ const Library = () => { - + {/**/} - + {/**/} diff --git a/src/screens/Library/MusicDetails.js b/src/screens/Library/MusicDetails.js index d257a7a..28ad34c 100644 --- a/src/screens/Library/MusicDetails.js +++ b/src/screens/Library/MusicDetails.js @@ -1,5 +1,5 @@ import { useRoute } from "@react-navigation/core"; -import { Audio } from "expo-audio"; +import { useAudioPlayer } from "expo-audio"; import { Image as ExpoImage } from "expo-image"; import React, { useEffect, useMemo, useState } from "react"; import { @@ -11,10 +11,14 @@ import { View, } from "react-native"; import { SheetManager } from "react-native-actions-sheet"; +import { + responsiveHeight, + responsiveWidth, +} from "react-native-responsive-dimensions"; import { useGlobal } from "reactn"; -import { background, icons, img } from "../../assets"; +import { background, icons } from "../../assets"; import Slider from "../../components/Slider"; -import firebase, { +import { arrayRemove, arrayUnion, increment, @@ -44,9 +48,7 @@ const MusicDetails = () => { const timerRef = React.useRef(null); const { data: project } = useDataFromRef({ - ref: projectId - ? firebase.firestore().collection("projects").doc(projectId) - : null, + ref: projectId ? projectsRef.doc(projectId) : null, simpleRef: true, listener: true, condition: !!projectId, @@ -71,61 +73,27 @@ const MusicDetails = () => { const title = project?.title || "Sans titre"; const artist = owner?.userName || "MusicLand"; const coverUrl = project?.coverUrl || null; - const songUrl = useMemo(() => { - if (project?.song?.url) return project.song.url; - const arr = Array.isArray(project?.musicUrls) ? project.musicUrls : []; - return arr[0] || null; - }, [project]); + const songUrl = project?.songUrl || null; - const soundRef = React.useRef(null); + const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined); - // Load/unload audio with expo-audio for reliable status updates on iOS/Android + // Reset counters when the track changes useEffect(() => { - let isMounted = true; - const load = async () => { - try { - // Unload previous - if (soundRef.current) { - await soundRef.current.unloadAsync(); - soundRef.current.setOnPlaybackStatusUpdate(null); - soundRef.current = null; - } - // Reset listen tracking per song load - listenedMsRef.current = 0; - incrementDoneRef.current = false; - if (!songUrl) return; - const { sound } = await Audio.Sound.createAsync( - { uri: songUrl }, - { shouldPlay: false } - ); - sound.setOnPlaybackStatusUpdate((status) => { - if (!isMounted) return; - if (!status || !status.isLoaded) return; - const pos = status.positionMillis || 0; - const dur = status.durationMillis || 0; - setProgressInfo({ pos, dur }); - setIsPlaying(!!status.isPlaying); - }); - soundRef.current = sound; - } catch (e) { - console.log("Audio load error", e?.message); - } - }; - load(); - return () => { - isMounted = false; - (async () => { - try { - if (soundRef.current) { - await soundRef.current.unloadAsync(); - soundRef.current.setOnPlaybackStatusUpdate(null); - soundRef.current = null; - } - } catch (_) {} - })(); - }; + listenedMsRef.current = 0; + incrementDoneRef.current = false; }, [songUrl]); + // Poll player state to update progress and play state + useEffect(() => { + const id = setInterval(() => { + const dur = (player?.duration || 0) * 1000; + const pos = (player?.currentTime || 0) * 1000; + setProgressInfo({ pos, dur }); + setIsPlaying(!!player?.playing); + }, 300); + return () => clearInterval(id); + }, [player]); + // Start/stop a timer to accumulate listened milliseconds while playing useEffect(() => { const clearTimer = () => { @@ -170,15 +138,13 @@ const MusicDetails = () => { }; const togglePlay = async () => { - const sound = soundRef.current; - if (!sound || !songUrl) return; + if (!player || !songUrl) return; try { - const status = await sound.getStatusAsync(); - if (status?.isLoaded && status.isPlaying) { - await sound.pauseAsync(); + if (player.playing) { + await player.pause?.(); setIsPlaying(false); } else { - await sound.playAsync(); + await player.play?.(); setIsPlaying(true); } } catch (e) { @@ -190,9 +156,8 @@ const MusicDetails = () => { try { const dur = progressInfo.dur || 0; const pos = Math.floor(dur * ratio); - const sound = soundRef.current; - if (sound && dur > 0) { - await sound.setPositionAsync(pos); + if (player && dur > 0) { + await player.seekTo?.(Math.floor((pos || 0) / 1000)); } } catch (e) { console.log("MusicDetails seek error", e?.message); @@ -203,8 +168,7 @@ const MusicDetails = () => { try { const cur = Math.floor((progressInfo.pos || 0) / 1000); const next = Math.max(0, cur + deltaSeconds); - const sound = soundRef.current; - if (sound) await sound.setPositionAsync(next * 1000); + if (player) await player.seekTo?.(next); } catch (e) { console.log("MusicDetails seekBy error", e?.message); } @@ -248,10 +212,9 @@ const MusicDetails = () => { paddingTop: 20, paddingBottom: gutters * 2, }} - // stickyHeaderIndices={[1]} > - {coverUrl ? ( + {coverUrl && ( { transition={150} style={styles.img} /> - ) : ( - )} @@ -305,79 +266,81 @@ const MusicDetails = () => { - - { - try { - const sound = soundRef.current; - const status = await sound?.getStatusAsync?.(); - wasPlayingBeforeSeek.current = - !!status?.isLoaded && !!status?.isPlaying; - if (status?.isLoaded && status?.isPlaying) { - await sound.pauseAsync(); - setIsPlaying(false); - } - } catch (e) { - console.log("Pause on seek start error", e?.message); + {songUrl && ( + + { - try { - const sound = soundRef.current; - if (sound && wasPlayingBeforeSeek.current) { - await sound.playAsync(); - setIsPlaying(true); + seekEnabled={!!songUrl} + onSeekStart={async () => { + try { + wasPlayingBeforeSeek.current = !!player?.playing; + if (player?.playing) { + await player.pause?.(); + setIsPlaying(false); + } + } catch (e) { + console.log("Pause on seek start error", e?.message); } - wasPlayingBeforeSeek.current = false; - } catch (e) { - console.log("Resume after seek error", e?.message); - } - }} - /> - - {/* Previous (rewind 10s) */} - seekBy(-10)}> - - - {/* Play / Pause */} - { + try { + if (player && wasPlayingBeforeSeek.current) { + await player.play?.(); + setIsPlaying(true); + } + wasPlayingBeforeSeek.current = false; + } catch (e) { + console.log("Resume after seek error", e?.message); + } + }} + /> + - - - {/* Next (forward 10s) */} - seekBy(10)}> - seekBy(-10)}> + + + {/* Play / Pause */} + - + onPress={togglePlay} + > + + + {/* Next (forward 10s) */} + seekBy(10)}> + + + - + )} {description?.length > 0 && ( { /> - {(!selected && - !projectsLoading && - !usersLoading && - filteredProjects.length === 0 && - filteredUsers.length === 0) && ( - - )} {((!selected && (filteredProjects.length > 0 || projectsLoading)) || selected === "Musiques") && ( diff --git a/src/screens/Library/components/MusicCard.js b/src/screens/Library/components/MusicCard.js index 912afe9..9593c53 100644 --- a/src/screens/Library/components/MusicCard.js +++ b/src/screens/Library/components/MusicCard.js @@ -1,6 +1,13 @@ import { BlurView } from "expo-blur"; import React, { useEffect, useRef, useState } from "react"; -import { Platform, Pressable, StyleSheet, Text, View, Image as RNImage } from "react-native"; +import { + Platform, + Pressable, + StyleSheet, + Text, + View, + Image as RNImage, +} from "react-native"; import { Image as ExpoImage } from "expo-image"; import { useGlobal } from "reactn"; import { icons, img } from "../../../assets"; @@ -8,6 +15,7 @@ import { arrayRemove, arrayUnion, projectsRef } from "../../../config/firebase"; import { Palette, Style } from "../../../styles"; import { FONT_FAMILY } from "../../../styles/Fonts"; import { size } from "../../../styles/Style"; +import { responsiveWidth } from "react-native-responsive-dimensions"; const MusicCard = ({ onPress, @@ -90,7 +98,7 @@ const MusicCard = ({ /> ) : ( )} @@ -107,7 +115,9 @@ const MusicCard = ({ } > - {title || "Sans titre"} + + {title || "Sans titre"} + {subtitle || "MusicLand"} { if (isLocked(index)) return; switch (index) { case 0: - navigate(Routes.WritingLyrics, {}); + navigate(Routes.WritingLyrics, { + projectId: currentProjet?.id || null, + }); break; case 1: - navigate(Routes.Studio, { action: item.type }); + navigate(Routes.Compose, { projectId: currentProjet.id }); break; case 2: - navigate(Routes.Production, { action: item.type }); + navigate(Routes.PouchReady, { projectId: currentProjet.id }); break; case 3: console.log("test"); navigate(Routes.Playback, { - action: item.type, project: currentProjet, }); default: diff --git a/src/screens/Playback/RecordPlayback.js b/src/screens/Playback/RecordPlayback.js index dadd0b7..9396382 100644 --- a/src/screens/Playback/RecordPlayback.js +++ b/src/screens/Playback/RecordPlayback.js @@ -1,4 +1,3 @@ -import { Audio } from "expo-audio"; import { CameraView, useCameraPermissions } from "expo-camera"; import React from "react"; import { Text, View } from "react-native"; @@ -10,7 +9,12 @@ import { goBack, navigate } from "../../navigation/NavigationService"; import { gutters, Palette } from "../../styles"; import { FONT_FAMILY } from "../../styles/Fonts"; import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; - +// ADD: Firestore helpers to increment views +import { useFocusEffect } from "@react-navigation/native"; +import { useAudioPlayer } from "expo-audio"; +import { useCallback, useEffect, useRef, useState } from "react"; +import Svg, { Circle } from "react-native-svg"; +import { increment, projectsRef } from "../../config/firebase"; const RecordPlayback = ({ route }) => { const { top } = useSafeAreaInsets(); const { project } = route.params || {}; @@ -19,20 +23,48 @@ const RecordPlayback = ({ route }) => { 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); + const cameraRef = useRef(null); + const countdownTimerRef = useRef(null); + const stopRequestedRef = useRef(false); + + // Listen counter refs (inspired by MusicDetails) + const listenedMsRef = useRef(0); + const incrementDoneRef = useRef(false); + const timerRef = useRef(null); + const timeBeforeIncrement = 20000; // 20 seconds // UI / state - const [isPreparing, setIsPreparing] = React.useState(false); - const [countdown, setCountdown] = React.useState(0); - const [isRecording, setIsRecording] = React.useState(false); + const [isPreparing, setIsPreparing] = useState(false); + const [countdown, setCountdown] = useState(0); + const [isRecording, setIsRecording] = useState(false); + const [showProgress, setShowProgress] = useState(false); + const [progressInfo, setProgressInfo] = useState({ pos: 0, dur: 0 }); // Derive song URL robustly const songUrl = project?.songUrl || null; + const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined); + // useEffect(() => { + // setVideo(null); + // }, []); + useEffect(() => { + listenedMsRef.current = 0; + incrementDoneRef.current = false; + }, [songUrl]); - React.useEffect(() => { + // Poll player state to update progress ring + useEffect(() => { + if (!player) return; + const id = global.setInterval(() => { + try { + const dur = (player?.duration || 0) * 1000; + const pos = (player?.currentTime || 0) * 1000; + setProgressInfo({ pos, dur }); + } catch (_) {} + }, 250); + return () => global.clearInterval(id); + }, [player]); + + useEffect(() => { // Request permissions on mount if not granted (async () => { try { @@ -40,40 +72,88 @@ const RecordPlayback = ({ route }) => { } catch (_) {} })(); return () => { - // Cleanup timers and audio on unmount + // Cleanup timers 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 (_) {} - })(); + if (timerRef.current) { + global.clearInterval(timerRef.current); + timerRef.current = null; + } } catch (_) {} }; }, []); + // Fully reset audio and recording state (used on focus and before new session) + const resetSession = useCallback(async () => { + try { + if (countdownTimerRef.current) { + global.clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } + if (timerRef.current) { + global.clearInterval(timerRef.current); + timerRef.current = null; + } + stopRequestedRef.current = false; + listenedMsRef.current = 0; + incrementDoneRef.current = false; + setIsPreparing(false); + setIsRecording(false); + setShowProgress(false); + setCountdown(0); + if (player) { + try { + if (player.playing) await player.pause?.(); + await player.seekTo?.(0); + } catch (_) {} + } + } catch (_) {} + }, [player]); + + // Reset when screen gains focus (coming from "Recommencer", etc.) + useFocusEffect( + useCallback(() => { + void resetSession(); + return () => {}; + }, [resetSession]) + ); + const startCountdownThenRecord = async () => { console.log("start countdown"); if (!songUrl) return; console.log("songUrl exists"); + // Ensure clean state and audio at t=0 + await resetSession(); setIsPreparing(true); setCountdown(5); - // Start music + recording immediately when countdown starts - void startRecordingWithMusic(); - // 5 -> 0 countdown display only + setShowProgress(false); + + // Start countdown display; start recording+music WHEN countdown reaches 0 + if (countdownTimerRef.current) { + global.clearInterval(countdownTimerRef.current); + countdownTimerRef.current = null; + } countdownTimerRef.current = global.setInterval(() => { setCountdown((c) => { const next = (c || 0) - 1; if (next <= 0) { + console.log("clear timer"); + // clear timer global.clearInterval(countdownTimerRef.current); countdownTimerRef.current = null; + // Ensure countdown overlay disappears and progress shows immediately + setIsPreparing(false); + setShowProgress(true); + // also set countdown to 0 explicitly in case of frame drop + if (c !== 0) { + // guard against stale value + setCountdown(0); + } + // start recording + music at the same time + void startRecordingWithMusic(); } return Math.max(0, next); }); @@ -83,70 +163,90 @@ const RecordPlayback = ({ route }) => { 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; + // Reset listen counters for this track/session + listenedMsRef.current = 0; + incrementDoneRef.current = false; // Start recording setIsRecording(true); + setCountdown(0); + setIsPreparing(false); + setShowProgress(true); const recordPromise = cameraRef.current?.recordAsync?.({ mute: true, maxDuration: 600, // safety cap (10 min) }); - // Start playing - await sound.playAsync(); + // Start playing with useAudioPlayer (like MusicDetails) + if (player && songUrl) { + try { + await player.seekTo?.(0); + } catch (_) {} + await player.play?.(); + } - // Wait for recording to stop (either by song end or manual stop) + // Start timer to track listening time and increment views (like MusicDetails) + if (!timerRef.current && project?.id) { + timerRef.current = global.setInterval(async () => { + try { + if (player?.playing) { + listenedMsRef.current += 500; + if ( + !incrementDoneRef.current && + listenedMsRef.current >= timeBeforeIncrement + ) { + incrementDoneRef.current = true; + try { + await projectsRef + .doc(project.id) + .set({ views: increment(1) }, { merge: true }); + } catch (_) {} + } + } + } catch (_) {} + }, 500); + } + + // Monitor when song ends to stop recording + const checkSongEnd = global.setInterval(async () => { + try { + if (player && !player.playing && !stopRequestedRef.current) { + const duration = (player?.duration || 0) * 1000; + const currentTime = (player?.currentTime || 0) * 1000; + + // If we're near the end or stopped, stop recording + if (duration > 0 && currentTime >= duration - 1000) { + stopRequestedRef.current = true; + global.clearInterval(checkSongEnd); + try { + cameraRef.current?.stopRecording?.(); + } catch (_) {} + } + } + } catch (_) {} + }, 1000); + + // Wait for recording to stop const video = await recordPromise; + global.clearInterval(checkSongEnd); - // Ensure audio stops and cleanup + // Stop audio try { - const s = soundRef.current; - if (s) { - s.setOnPlaybackStatusUpdate(null); - await s.stopAsync().catch(() => {}); - await s.unloadAsync().catch(() => {}); + if (player?.playing) { + await player.pause?.(); } } catch (_) {} - soundRef.current = null; + + // Clear listen timer after recording ends + if (timerRef.current) { + global.clearInterval(timerRef.current); + timerRef.current = null; + } setIsRecording(false); setIsPreparing(false); + setShowProgress(false); // Navigate to next screen with video uri if available if (video?.uri) { @@ -156,13 +256,14 @@ const RecordPlayback = ({ route }) => { } } catch (e) { // Fallback on error + console.log("error : ", e); setIsRecording(false); setIsPreparing(false); - try { - soundRef.current?.setOnPlaybackStatusUpdate?.(null); - await soundRef.current?.unloadAsync?.(); - } catch (_) {} - soundRef.current = null; + setShowProgress(false); + if (timerRef.current) { + global.clearInterval(timerRef.current); + timerRef.current = null; + } } }; @@ -200,7 +301,7 @@ const RecordPlayback = ({ route }) => { {/* Centered countdown overlay */} - {(isPreparing || isRecording) && countdown > 0 && ( + {isPreparing && countdown > 0 && !showProgress && ( { onPress={startCountdownThenRecord} /> )} + + {/* Circular progress when countdown finished / recording */} + {permissionsGranted && (isRecording || showProgress) && ( + + {/* Dimmed circular backdrop */} + + {/* Progress ring */} + + {/* Center white button-like circle */} + + + + )} ); }; +// Simple SVG circular progress ring +const ProgressRing = ({ size = 50, strokeWidth = 12, progress = 0 }) => { + const r = size / 2 - strokeWidth / 2; + const c = 2 * Math.PI * r; + const clamped = Math.max(0, Math.min(1, progress || 0)); + const offset = c * (1 - clamped); + return ( + + + + ); +}; + export default RecordPlayback; diff --git a/src/screens/Playback/RecordedPlayback.js b/src/screens/Playback/RecordedPlayback.js index c4e4eb3..e2076be 100644 --- a/src/screens/Playback/RecordedPlayback.js +++ b/src/screens/Playback/RecordedPlayback.js @@ -1,6 +1,9 @@ -import React from "react"; -import { Image, View } from "react-native"; -import { background, img } from "../../assets"; +import { useAudioPlayer } from "expo-audio"; +import * as FileSystem from "expo-file-system"; +import { VideoView, useVideoPlayer } from "expo-video"; +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { View } from "react-native"; +import { background } from "../../assets"; import BorderGradientButton from "../../components/BorderGradientButton"; import GradientButton from "../../components/GradientButton"; import MusicLandHeader from "../../components/MusicLandHeader"; @@ -9,8 +12,100 @@ import Page from "../../layouts/Page"; import { Routes } from "../../navigation"; import { goBack, navigate } from "../../navigation/NavigationService"; import { gutters } from "../../styles"; +const RecordedPlayback = ({ route }) => { + const { videoUri, project } = route.params || {}; + const songUrl = project?.songUrl || null; + + const audioPlayer = useAudioPlayer(songUrl ? { uri: songUrl } : undefined); + const videoPlayer = useVideoPlayer(videoUri || null, (p) => { + p.loop = false; + p.muted = true; // recorded video has no audio; keep muted anyway + p.timeUpdateEventInterval = 0.2; + }); + + const [progressInfo, setProgressInfo] = useState({ pos: 0, dur: 0 }); + const wasPlayingBeforeSeek = useRef(false); + + // Format mm:ss + const fmt = (ms) => { + const total = Math.max(0, Math.floor((ms || 0) / 1000)); + const m = Math.floor(total / 60) + .toString() + .padStart(1, "0"); + const s = (total % 60).toString().padStart(2, "0"); + return `${m}:${s}`; + }; + + // Start both players on mount + useEffect(() => { + const start = async () => { + try { + if (audioPlayer && songUrl) await audioPlayer.play?.(); + if (videoPlayer) videoPlayer.play(); + } catch (e) {} + }; + start(); + return () => { + try { + if (audioPlayer?.playing) audioPlayer.pause?.(); + if (videoPlayer?.playing) videoPlayer.pause(); + } catch (e) {} + }; + }, [audioPlayer, videoPlayer, songUrl]); + + // Poll from audio player for progress display; keep video in sync if drifting + useEffect(() => { + const id = global.setInterval(() => { + try { + const dur = (audioPlayer?.duration || 0) * 1000; + const pos = (audioPlayer?.currentTime || 0) * 1000; + setProgressInfo({ pos, dur }); + + // basic drift correction: if desync > 300ms, align video + if (videoPlayer && !Number.isNaN(videoPlayer.currentTime)) { + const v = (videoPlayer.currentTime || 0) * 1000; + const drift = Math.abs(v - pos); + if (drift > 350) { + videoPlayer.currentTime = Math.max(0, (pos || 0) / 1000); + } + } + } catch (e) {} + }, 250); + return () => global.clearInterval(id); + }, [audioPlayer, videoPlayer]); + + const onSeek = async (ratio) => { + try { + const dur = progressInfo.dur || 0; + const pos = Math.floor(dur * ratio); + if (audioPlayer && dur > 0) + await audioPlayer.seekTo?.(Math.floor(pos / 1000)); + if (videoPlayer) videoPlayer.currentTime = Math.max(0, pos / 1000); + } catch (e) {} + }; + + const onSeekStart = async () => { + try { + wasPlayingBeforeSeek.current = !!audioPlayer?.playing; + if (audioPlayer?.playing) await audioPlayer.pause?.(); + if (videoPlayer?.playing) videoPlayer.pause(); + } catch (e) {} + }; + const onSeekEnd = async () => { + try { + if (wasPlayingBeforeSeek.current) { + if (audioPlayer) await audioPlayer.play?.(); + if (videoPlayer) videoPlayer.play(); + } + } catch (e) {} + }; + + const sliderProgress = useMemo(() => { + return progressInfo.dur + ? Math.min(1, (progressInfo.pos || 0) / progressInfo.dur) + : 0; + }, [progressInfo]); -const RecordedPlayback = () => { return ( @@ -18,26 +113,54 @@ const RecordedPlayback = () => { style={{ flex: 1, paddingTop: 12, gap: 14, paddingBottom: gutters * 2 }} > - + )} + - navigate(Routes.ChooseDecor)} + title="Recommencer" + onPress={async () => { + try { + if (audioPlayer?.playing) await audioPlayer.pause?.(); + if (videoPlayer?.playing) videoPlayer.pause(); + } catch (e) {} + try { + if (videoUri) { + const info = await FileSystem.getInfoAsync(videoUri); + if (info?.exists) + await FileSystem.deleteAsync(videoUri, { + idempotent: true, + }); + } + } catch (e) {} + navigate(Routes.RecordPlayback, { project }); + }} /> - diff --git a/src/screens/Studio/SongReady.js b/src/screens/Studio/SongReady.js index 677fbf5..b22102d 100644 --- a/src/screens/Studio/SongReady.js +++ b/src/screens/Studio/SongReady.js @@ -124,7 +124,7 @@ const SongReady = () => { .doc(projectId) .set( { - song: { index: selectedIndex, url }, + songUrl: url, updatedAt: firebase.firestore.FieldValue.serverTimestamp(), }, { merge: true },