playback web
This commit is contained in:
@@ -361,6 +361,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
padding: 10,
|
padding: 10,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: Palette.transparentWhite,
|
borderColor: Palette.transparentWhite,
|
||||||
|
maxWidth: "50%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View>
|
<View>
|
||||||
@@ -500,6 +501,8 @@ const MusicDetails = ({ route }) => {
|
|||||||
</View>
|
</View>
|
||||||
</BlurView>
|
</BlurView>
|
||||||
{/* Right column: lyrics */}
|
{/* Right column: lyrics */}
|
||||||
|
|
||||||
|
{lines.length > 0 && (
|
||||||
<BlurView
|
<BlurView
|
||||||
intensity={1}
|
intensity={1}
|
||||||
style={{
|
style={{
|
||||||
@@ -510,6 +513,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
borderColor: Palette.transparentWhite,
|
borderColor: Palette.transparentWhite,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
height: 400,
|
height: 400,
|
||||||
|
maxWidth: "50%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{lines?.length ? (
|
{lines?.length ? (
|
||||||
@@ -563,6 +567,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
</ScrollView>
|
</ScrollView>
|
||||||
) : null}
|
) : null}
|
||||||
</BlurView>
|
</BlurView>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ import { gutters } from "../../styles";
|
|||||||
const RecordedPlayback = ({ route }) => {
|
const RecordedPlayback = ({ route }) => {
|
||||||
const { videoUri, project } = route.params || {};
|
const { videoUri, project } = route.params || {};
|
||||||
const songUrl = project?.songUrl || null;
|
const songUrl = project?.songUrl || null;
|
||||||
|
console.log("video uri is : ", videoUri);
|
||||||
const audioPlayer = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
|
const audioPlayer = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
|
||||||
const videoPlayer = useVideoPlayer(videoUri || null, (p) => {
|
const videoPlayer = useVideoPlayer(videoUri || null, (p) => {
|
||||||
p.loop = false;
|
p.loop = false;
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import { useAudioPlayer } from "expo-audio";
|
||||||
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { Text, View } from "react-native";
|
||||||
|
import { background } 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, Palette } from "../../styles";
|
||||||
|
|
||||||
|
const fmtSeconds = (s) => {
|
||||||
|
const total = Math.max(0, Math.floor(Number(s || 0)));
|
||||||
|
const m = Math.floor(total / 60).toString();
|
||||||
|
const sec = (total % 60).toString().padStart(2, "0");
|
||||||
|
return `${m}:${sec}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toSeconds = (value) => {
|
||||||
|
const n = Number(value ?? 0);
|
||||||
|
if (!Number.isFinite(n) || n < 0) return 0;
|
||||||
|
return n > 10000 ? n / 1000 : n; // heuristique ms → s
|
||||||
|
};
|
||||||
|
|
||||||
|
const RecordedPlayback = ({ route }) => {
|
||||||
|
const { videoUri, project } = route.params || {};
|
||||||
|
const songUrl = project?.songUrl || null;
|
||||||
|
console.log("video uri is : ", videoUri);
|
||||||
|
// AUDIO PLAYER (expo-audio → seconds)
|
||||||
|
const audioPlayer = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
|
||||||
|
|
||||||
|
// Optionnel: si tu veux quand même un rendu vidéo sur web si tu as une source
|
||||||
|
const videoElRef = useRef(null);
|
||||||
|
|
||||||
|
const [progress, setProgress] = useState({
|
||||||
|
posS: 0, // secondes
|
||||||
|
durS: 0, // secondes
|
||||||
|
playing: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Démarrage / arrêt
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
const start = async () => {
|
||||||
|
try {
|
||||||
|
if (audioPlayer && songUrl) {
|
||||||
|
await audioPlayer.play?.();
|
||||||
|
}
|
||||||
|
if (videoElRef.current && videoUri) {
|
||||||
|
// Lecture vidéo HTML5 (muet pour éviter les policies)
|
||||||
|
videoElRef.current.muted = true;
|
||||||
|
videoElRef.current.play().catch(() => {});
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
start();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
try {
|
||||||
|
if (audioPlayer?.playing) audioPlayer.pause?.();
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
if (videoElRef.current) {
|
||||||
|
videoElRef.current.pause();
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
}, [audioPlayer, songUrl, videoUri]);
|
||||||
|
|
||||||
|
// Boucle de progression + éventuelle sync de la vidéo si fournie
|
||||||
|
useEffect(() => {
|
||||||
|
const id = setInterval(() => {
|
||||||
|
try {
|
||||||
|
const durS = toSeconds(audioPlayer?.duration); // secondes
|
||||||
|
const posS = toSeconds(audioPlayer?.currentTime); // secondes
|
||||||
|
|
||||||
|
setProgress({
|
||||||
|
posS,
|
||||||
|
durS,
|
||||||
|
playing: !!audioPlayer?.playing,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sync vidéo si on a une source vidéo
|
||||||
|
if (
|
||||||
|
videoElRef.current &&
|
||||||
|
videoUri &&
|
||||||
|
!Number.isNaN(videoElRef.current.currentTime)
|
||||||
|
) {
|
||||||
|
const v = Number(videoElRef.current.currentTime || 0);
|
||||||
|
const drift = Math.abs(v - posS);
|
||||||
|
if (drift > 0.35) {
|
||||||
|
videoElRef.current.currentTime = Math.max(0, posS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}, 250);
|
||||||
|
return () => clearInterval(id);
|
||||||
|
}, [audioPlayer, videoUri]);
|
||||||
|
|
||||||
|
// Slider: ratio 0..1
|
||||||
|
const sliderProgress = useMemo(() => {
|
||||||
|
return progress.durS > 0 ? Math.min(1, progress.posS / progress.durS) : 0;
|
||||||
|
}, [progress]);
|
||||||
|
|
||||||
|
const onSeek = async (ratio) => {
|
||||||
|
try {
|
||||||
|
const durS = Number(progress.durS || 0);
|
||||||
|
const target = durS * ratio; // secondes
|
||||||
|
if (audioPlayer && durS > 0) {
|
||||||
|
await audioPlayer.seekTo?.(Math.floor(target)); // seek en secondes
|
||||||
|
}
|
||||||
|
if (videoElRef.current && videoUri) {
|
||||||
|
videoElRef.current.currentTime = Math.max(0, target);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wasPlayingRef = useRef(false);
|
||||||
|
const onSeekStart = async () => {
|
||||||
|
try {
|
||||||
|
wasPlayingRef.current = !!audioPlayer?.playing;
|
||||||
|
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||||
|
if (videoElRef.current && !videoElRef.current.paused) {
|
||||||
|
videoElRef.current.pause();
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
const onSeekEnd = async () => {
|
||||||
|
try {
|
||||||
|
if (wasPlayingRef.current) {
|
||||||
|
if (audioPlayer) await audioPlayer.play?.();
|
||||||
|
if (videoElRef.current && videoUri)
|
||||||
|
videoElRef.current.play().catch(() => {});
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page backgroundImg={background.playbackBG2} headerType="NONE">
|
||||||
|
<MusicLandHeader progress={19} onPressBack={goBack} />
|
||||||
|
<View
|
||||||
|
style={{ flex: 1, paddingTop: 12, gap: 14, paddingBottom: gutters * 2 }}
|
||||||
|
>
|
||||||
|
<View style={{ flex: 1, gap: 22, alignItems: "center" }}>
|
||||||
|
{/* Bloc vidéo optionnel si jamais tu as un videoUri sur web */}
|
||||||
|
{videoUri ? (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: "80%",
|
||||||
|
aspectRatio: 9 / 16,
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: "#00000066",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
ref={videoElRef}
|
||||||
|
src={videoUri}
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "contain",
|
||||||
|
backgroundColor: "black",
|
||||||
|
}}
|
||||||
|
controls={false}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
// Placeholder quand pas de vidéo sur web
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: "80%",
|
||||||
|
aspectRatio: 9 / 16,
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: "#00000066",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: Palette.white, opacity: 0.8 }}>
|
||||||
|
Aperçu vidéo non disponible sur le web
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Slider
|
||||||
|
value={fmtSeconds(progress.posS)} // mm:ss (seconds)
|
||||||
|
maxValue={fmtSeconds(progress.durS)} // mm:ss (seconds)
|
||||||
|
progress={sliderProgress}
|
||||||
|
seekEnabled={!!songUrl}
|
||||||
|
onSeek={onSeek}
|
||||||
|
onSeekStart={onSeekStart}
|
||||||
|
onSeekEnd={onSeekEnd}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={{ width: "80%", alignSelf: "center", marginTop: 4, gap: 12 }}
|
||||||
|
>
|
||||||
|
<GradientButton
|
||||||
|
title="Je valide"
|
||||||
|
onPress={() => {
|
||||||
|
navigate(Routes.DownloadSongs, {
|
||||||
|
action: "playback",
|
||||||
|
uri: videoUri || null, // peut être null sur web
|
||||||
|
project,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<BorderGradientButton
|
||||||
|
title="Recommencer"
|
||||||
|
onPress={() => {
|
||||||
|
try {
|
||||||
|
if (audioPlayer?.playing) audioPlayer.pause?.();
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
if (videoElRef.current && !videoElRef.current.paused)
|
||||||
|
videoElRef.current.pause();
|
||||||
|
} catch {}
|
||||||
|
navigate(Routes.RecordPlayback, { project });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RecordedPlayback;
|
||||||
@@ -17,11 +17,11 @@ import { uploadFileToFirebase } from "../../helpers/uploadToFirebase";
|
|||||||
import Page from "../../layouts/Page";
|
import Page from "../../layouts/Page";
|
||||||
import { Routes } from "../../navigation";
|
import { Routes } from "../../navigation";
|
||||||
import { goBack, navigate } from "../../navigation/NavigationService";
|
import { goBack, navigate } from "../../navigation/NavigationService";
|
||||||
|
import { useUserData } from "../../providers/UserDataProvider";
|
||||||
import { Palette } from "../../styles";
|
import { Palette } from "../../styles";
|
||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
import { size } from "../../styles/Style";
|
import { size } from "../../styles/Style";
|
||||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||||
import { useUserData } from "../../providers/UserDataProvider";
|
|
||||||
|
|
||||||
const DownloadSongs = ({ route }) => {
|
const DownloadSongs = ({ route }) => {
|
||||||
const { currentUID } = useUserData();
|
const { currentUID } = useUserData();
|
||||||
@@ -50,11 +50,11 @@ const DownloadSongs = ({ route }) => {
|
|||||||
playbackUrl: resultURI,
|
playbackUrl: resultURI,
|
||||||
updatedAt: serverTimestamp(),
|
updatedAt: serverTimestamp(),
|
||||||
},
|
},
|
||||||
{ merge: true },
|
{ merge: true }
|
||||||
);
|
);
|
||||||
setTooltip({
|
setTooltip({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Vidéo uploadée, conversion HLS en cours…",
|
text: "Vidéo uploadée",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTooltip({
|
setTooltip({
|
||||||
|
|||||||
Reference in New Issue
Block a user