diff --git a/package.json b/package.json index 042226d..2d9b274 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "react-dom": "18.3.1", "react-native": "0.76.9", "react-native-actions-sheet": "^0.9.7", - "react-native-compressor": "^1.8.24", + "react-native-compressor": "^1.12.0", "react-native-country-picker-modal": "^2.0.0", "react-native-dialog": "^9.3.0", "react-native-figma-squircle": "^0.3.4", diff --git a/src/helpers/uploadToFirebase.js b/src/helpers/uploadToFirebase.js index 1e59b1f..742b4c6 100644 --- a/src/helpers/uploadToFirebase.js +++ b/src/helpers/uploadToFirebase.js @@ -1,13 +1,36 @@ import { Platform } from "react-native"; - +import Compressor from "react-native-compressor"; import firebase from "../config/firebase"; -export function uploadFileToFirebase({ uri, path }) { +export function uploadFileToFirebase({ + uri, + path, + shouldCompress = false, + fileType = "", +}) { return new Promise(async (resolve, reject) => { try { - let resultResize = { uri }; + let workingURI = uri; - const response = await fetch(resultResize.uri); + // Optionally compress before upload + try { + if (shouldCompress && Platform.OS !== "web") { + if (fileType === "VIDEO") { + console.log("Compressing video..."); + workingURI = await Compressor.Video.compress(workingURI); + } else if (fileType === "IMAGE") { + // Basic image compression + workingURI = await Compressor.Image.compress(workingURI, { + compressionMethod: "auto", + }); + } + } + } catch (e) { + console.warn("Compression failed, uploading original file", e?.message); + workingURI = uri; + } + + const response = await fetch(workingURI); const blob = await response.blob(); const uploadTask = firebase.storage().ref(path).put(blob); diff --git a/src/screens/Playbacks.js b/src/screens/Playbacks.js index 383cfa4..400c8a9 100644 --- a/src/screens/Playbacks.js +++ b/src/screens/Playbacks.js @@ -1,126 +1,240 @@ -import { View, Text, Image, Pressable, Platform } from "react-native"; -import React from "react"; +import { useAudioPlayer } from "expo-audio"; +import { BlurView } from "expo-blur"; +import { VideoView, useVideoPlayer } from "expo-video"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { Image, Platform, Pressable, Text, View } from "react-native"; +import Carousel from "react-native-reanimated-carousel"; import { responsiveHeight } from "react-native-responsive-dimensions"; import { icons, img } from "../assets"; -import { size } from "../styles/Style"; -import { BlurView } from "expo-blur"; +import { projectsRef } from "../config/firebase"; +import useDataFromRef from "../hooks/useDataFromRef"; import { Palette } from "../styles"; import { FONT_FAMILY } from "../styles/Fonts"; -import Carousel from "react-native-reanimated-carousel"; +import { size } from "../styles/Style"; + +const PlaybackItem = ({ item, isActive }) => { + const videoUrl = item?.playbackUrl || null; + const audioSource = useMemo(() => { + const fromSong = item?.songUrl ? { uri: item.songUrl } : null; + return fromSong; + }, [item]); + + const hasExternalAudio = !!audioSource; + console.log("has external audio : ", hasExternalAudio); + + const audioPlayer = useAudioPlayer(audioSource || undefined); + const videoPlayer = useVideoPlayer(videoUrl || null, (p) => { + p.loop = false; // vidéo et musique ont la même durée + p.muted = !!hasExternalAudio; // mute seulement si audio externe + p.timeUpdateEventInterval = 0.2; + }); + + useEffect(() => { + const toggle = async () => { + try { + if (isActive) { + try { + if (audioPlayer && hasExternalAudio) await audioPlayer.seekTo?.(0); + } catch (e) {} + try { + if (videoPlayer) videoPlayer.currentTime = 0; + } catch (e) {} + + // Lancer quasi simultanément (éviter await pour limiter le décalage) + try { + if (videoPlayer) videoPlayer.play(); + } catch (e) {} + try { + if (audioPlayer && hasExternalAudio) audioPlayer.play?.(); + } catch (e) {} + } else { + if (audioPlayer?.playing) await audioPlayer.pause?.(); + if (videoPlayer?.playing) videoPlayer.pause(); + } + } catch (e) {} + }; + toggle(); + }, [isActive, audioPlayer, videoPlayer, hasExternalAudio]); + + // Micro-correction initiale uniquement pour absorber un léger décalage réseau + useEffect(() => { + if (!isActive || !hasExternalAudio) return; + const t = setTimeout(() => { + try { + const a = audioPlayer?.currentTime || 0; + const v = videoPlayer?.currentTime || 0; + if (Math.abs(a - v) > 0.2 && videoPlayer) { + videoPlayer.currentTime = Math.max(0, a); + } + } catch (e) {} + }, 300); + return () => clearTimeout(t); + }, [isActive, hasExternalAudio, audioPlayer, videoPlayer]); + + useEffect(() => { + return () => { + try { + if (audioPlayer?.playing) audioPlayer.pause?.(); + if (videoPlayer?.playing) videoPlayer.pause(); + } catch (e) {} + }; + }, [audioPlayer, videoPlayer]); -const Playbacks = () => { return ( - - ( - + + {!!videoUrl ? ( + + ) : ( + + )} + + {/* Right side actions */} + + + + + + + + + + Suivre + + + + + - + + + + + + + - - - - - - - - - Suivre - - - - - - - - - - - - - - - Description chanson. Viverra enim risus enim enim placerat. - Integer pulvinar tristique suscipit risus. Id hendrerit in - odio phasellus interdum - - - - - + {item?.title || "Description chanson"} + + + + + + ); +}; + +const Playbacks = () => { + const [activeIndex, setActiveIndex] = useState(0); + const { data: playbacks = [], loadMore } = useDataFromRef({ + ref: projectsRef.where("playbackUrl", "!=", null), + simpleRef: false, + listener: false, + usePagination: true, + batchSize: 2, + }); + + const onSnap = useCallback( + (index) => { + setActiveIndex(index); + // Pré-charge la suite 2 par 2 + if (index >= (playbacks?.length || 0) - 2) { + loadMore?.(); + } + }, + [playbacks?.length, loadMore] + ); + + return ( + + ( + )} /> diff --git a/src/screens/Production/DownloadSongs.js b/src/screens/Production/DownloadSongs.js index 9b96a4a..52e621e 100644 --- a/src/screens/Production/DownloadSongs.js +++ b/src/screens/Production/DownloadSongs.js @@ -31,11 +31,14 @@ const DownloadSongs = ({ route }) => { const handleDownloadUri = async () => { if (action === "playback" && project?.id) { // Publication du playback + console.log("project id : ", project.id); try { setIsLoading(true); const { resultURI = null } = await uploadFileToFirebase({ uri: uri, path: `musics/${project.id}/playback.mp4`, + shouldCompress: true, + fileType: "VIDEO", }); if (!resultURI) throw new Error("Téléversement de l'image impossible"); @@ -59,6 +62,7 @@ const DownloadSongs = ({ route }) => { }); } } catch (error) { + console.log("error upload playback", error); setTooltip({ type: "error", text: "Erreur lors de la publication du playback", diff --git a/yarn.lock b/yarn.lock index 5983b04..49c6f75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8665,7 +8665,7 @@ react-native-calendars@^1.1300.0: optionalDependencies: moment "^2.29.4" -react-native-compressor@^1.8.24: +react-native-compressor@^1.12.0: version "1.12.0" resolved "https://registry.yarnpkg.com/react-native-compressor/-/react-native-compressor-1.12.0.tgz#4c387f100ec6d98adbee10c22496d0e42397d8bf" integrity sha512-NMAYpXnTLwx/KecwlLF+9Dnrn/tKV5UVfta8Lk9eROsb05JYnUoKLprRzSSpHcyLjIdQAVaTtx2lPYsappMezw==