This commit is contained in:
2025-09-02 15:51:07 +02:00
parent 8223b572b9
commit 1c9974d362
5 changed files with 259 additions and 118 deletions
+1 -1
View File
@@ -65,7 +65,7 @@
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-native": "0.76.9", "react-native": "0.76.9",
"react-native-actions-sheet": "^0.9.7", "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-country-picker-modal": "^2.0.0",
"react-native-dialog": "^9.3.0", "react-native-dialog": "^9.3.0",
"react-native-figma-squircle": "^0.3.4", "react-native-figma-squircle": "^0.3.4",
+27 -4
View File
@@ -1,13 +1,36 @@
import { Platform } from "react-native"; import { Platform } from "react-native";
import Compressor from "react-native-compressor";
import firebase from "../config/firebase"; import firebase from "../config/firebase";
export function uploadFileToFirebase({ uri, path }) { export function uploadFileToFirebase({
uri,
path,
shouldCompress = false,
fileType = "",
}) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { 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 blob = await response.blob();
const uploadTask = firebase.storage().ref(path).put(blob); const uploadTask = firebase.storage().ref(path).put(blob);
+226 -112
View File
@@ -1,126 +1,240 @@
import { View, Text, Image, Pressable, Platform } from "react-native"; import { useAudioPlayer } from "expo-audio";
import React from "react"; 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 { responsiveHeight } from "react-native-responsive-dimensions";
import { icons, img } from "../assets"; import { icons, img } from "../assets";
import { size } from "../styles/Style"; import { projectsRef } from "../config/firebase";
import { BlurView } from "expo-blur"; import useDataFromRef from "../hooks/useDataFromRef";
import { Palette } from "../styles"; import { Palette } from "../styles";
import { FONT_FAMILY } from "../styles/Fonts"; 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 ( return (
<View style={{ flex: 1 }}> <View
<Carousel style={{
data={Array.from({ length: 5 })} height: responsiveHeight(100),
vertical position: "relative",
height={responsiveHeight(100)} backgroundColor: "black",
renderItem={() => ( }}
<View >
style={{ {!!videoUrl ? (
height: responsiveHeight(100), <VideoView
position: "relative", player={videoPlayer}
}} nativeControls={false}
> contentFit="cover"
style={{ width: "100%", height: "100%" }}
/>
) : (
<Image
source={img.placeholder3}
style={{ width: "100%", height: "100%" }}
/>
)}
{/* Right side actions */}
<View
style={{
position: "absolute",
width: "100%",
bottom: 130,
gap: 18,
}}
>
<View
style={{
alignSelf: "flex-end",
alignItems: "center",
gap: 20,
paddingHorizontal: 13,
}}
>
<View style={{ gap: 6, alignItems: "center" }}>
<Pressable>
<Image
source={img.profile}
style={{
...size({ size: 45 }),
borderRadius: 100,
}}
/>
</Pressable>
<Pressable>
<BlurView
tint="dark"
intensity={20}
style={{
paddingVertical: 6,
paddingHorizontal: 12,
borderRadius: 12,
borderWidth: 1,
borderColor: Palette.white,
overflow: "hidden",
}}
experimentalBlurMethod={
Platform.OS !== "ios" ? "dimezisBlurView" : "none"
}
>
<Text
style={{
fontSize: 13,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
}}
>
Suivre
</Text>
</BlurView>
</Pressable>
</View>
<Pressable>
<Image <Image
source={img.placeholder3} source={icons.heartOutline}
style={{ width: "100%", height: "100%" }} style={size({ size: 26 })}
resizeMode="contain"
/> />
<View </Pressable>
<Pressable>
<Image source={icons.share} style={size({ size: 26 })} />
</Pressable>
</View>
<View style={{ paddingHorizontal: 28 }}>
<BlurView
tint="dark"
intensity={20}
style={{
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 20,
backgroundColor: Palette.glass,
overflow: "hidden",
}}
experimentalBlurMethod={
Platform.OS !== "ios" ? "dimezisBlurView" : "none"
}
>
<Text
style={{ style={{
position: "absolute", fontSize: 12,
width: "100%", color: Palette.white,
bottom: 130, fontFamily: FONT_FAMILY.InterRegular,
gap: 18,
}} }}
> >
<View {item?.title || "Description chanson"}
style={{ </Text>
alignSelf: "flex-end", </BlurView>
alignItems: "center", </View>
gap: 20, </View>
paddingHorizontal: 13, </View>
}} );
> };
<View style={{ gap: 6, alignItems: "center" }}>
<Pressable> const Playbacks = () => {
<Image const [activeIndex, setActiveIndex] = useState(0);
source={img.profile} const { data: playbacks = [], loadMore } = useDataFromRef({
style={{ ref: projectsRef.where("playbackUrl", "!=", null),
...size({ size: 45 }), simpleRef: false,
borderRadius: 100, listener: false,
}} usePagination: true,
/> batchSize: 2,
</Pressable> });
<Pressable>
<BlurView const onSnap = useCallback(
tint="dark" (index) => {
intensity={20} setActiveIndex(index);
style={{ // Pré-charge la suite 2 par 2
paddingVertical: 6, if (index >= (playbacks?.length || 0) - 2) {
paddingHorizontal: 12, loadMore?.();
borderRadius: 12, }
borderWidth: 1, },
borderColor: Palette.white, [playbacks?.length, loadMore]
overflow: "hidden", );
}}
experimentalBlurMethod={ return (
Platform.OS !== "ios" ? "dimezisBlurView" : "none" <View style={{ flex: 1, backgroundColor: "black" }}>
} <Carousel
> data={playbacks}
<Text vertical
style={{ height={responsiveHeight(100)}
fontSize: 13, pagingEnabled
color: Palette.white, windowSize={5}
fontFamily: FONT_FAMILY.InterMedium, onSnapToItem={onSnap}
}} renderItem={({ item, index }) => (
> <PlaybackItem
Suivre key={item?.id || index}
</Text> item={item}
</BlurView> isActive={index === activeIndex}
</Pressable> />
</View>
<Pressable>
<Image
source={icons.heartOutline}
style={size({ size: 26 })}
resizeMode="contain"
/>
</Pressable>
<Pressable>
<Image source={icons.share} style={size({ size: 26 })} />
</Pressable>
</View>
<View style={{ paddingHorizontal: 28 }}>
<BlurView
tint="dark"
intensity={20}
style={{
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 20,
backgroundColor: Palette.glass,
overflow: "hidden",
}}
experimentalBlurMethod={
Platform.OS !== "ios" ? "dimezisBlurView" : "none"
}
>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
}}
>
Description chanson. Viverra enim risus enim enim placerat.
Integer pulvinar tristique suscipit risus. Id hendrerit in
odio phasellus interdum
</Text>
</BlurView>
</View>
</View>
</View>
)} )}
/> />
</View> </View>
+4
View File
@@ -31,11 +31,14 @@ const DownloadSongs = ({ route }) => {
const handleDownloadUri = async () => { const handleDownloadUri = async () => {
if (action === "playback" && project?.id) { if (action === "playback" && project?.id) {
// Publication du playback // Publication du playback
console.log("project id : ", project.id);
try { try {
setIsLoading(true); setIsLoading(true);
const { resultURI = null } = await uploadFileToFirebase({ const { resultURI = null } = await uploadFileToFirebase({
uri: uri, uri: uri,
path: `musics/${project.id}/playback.mp4`, path: `musics/${project.id}/playback.mp4`,
shouldCompress: true,
fileType: "VIDEO",
}); });
if (!resultURI) throw new Error("Téléversement de l'image impossible"); if (!resultURI) throw new Error("Téléversement de l'image impossible");
@@ -59,6 +62,7 @@ const DownloadSongs = ({ route }) => {
}); });
} }
} catch (error) { } catch (error) {
console.log("error upload playback", error);
setTooltip({ setTooltip({
type: "error", type: "error",
text: "Erreur lors de la publication du playback", text: "Erreur lors de la publication du playback",
+1 -1
View File
@@ -8665,7 +8665,7 @@ react-native-calendars@^1.1300.0:
optionalDependencies: optionalDependencies:
moment "^2.29.4" moment "^2.29.4"
react-native-compressor@^1.8.24: react-native-compressor@^1.12.0:
version "1.12.0" version "1.12.0"
resolved "https://registry.yarnpkg.com/react-native-compressor/-/react-native-compressor-1.12.0.tgz#4c387f100ec6d98adbee10c22496d0e42397d8bf" resolved "https://registry.yarnpkg.com/react-native-compressor/-/react-native-compressor-1.12.0.tgz#4c387f100ec6d98adbee10c22496d0e42397d8bf"
integrity sha512-NMAYpXnTLwx/KecwlLF+9Dnrn/tKV5UVfta8Lk9eROsb05JYnUoKLprRzSSpHcyLjIdQAVaTtx2lPYsappMezw== integrity sha512-NMAYpXnTLwx/KecwlLF+9Dnrn/tKV5UVfta8Lk9eROsb05JYnUoKLprRzSSpHcyLjIdQAVaTtx2lPYsappMezw==