playback web
This commit is contained in:
@@ -361,6 +361,7 @@ const MusicDetails = ({ route }) => {
|
||||
padding: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: Palette.transparentWhite,
|
||||
maxWidth: "50%",
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
@@ -500,6 +501,8 @@ const MusicDetails = ({ route }) => {
|
||||
</View>
|
||||
</BlurView>
|
||||
{/* Right column: lyrics */}
|
||||
|
||||
{lines.length > 0 && (
|
||||
<BlurView
|
||||
intensity={1}
|
||||
style={{
|
||||
@@ -510,6 +513,7 @@ const MusicDetails = ({ route }) => {
|
||||
borderColor: Palette.transparentWhite,
|
||||
borderRadius: 12,
|
||||
height: 400,
|
||||
maxWidth: "50%",
|
||||
}}
|
||||
>
|
||||
{lines?.length ? (
|
||||
@@ -563,6 +567,7 @@ const MusicDetails = ({ route }) => {
|
||||
</ScrollView>
|
||||
) : null}
|
||||
</BlurView>
|
||||
)}
|
||||
</View>
|
||||
</Page>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ import { gutters } from "../../styles";
|
||||
const RecordedPlayback = ({ route }) => {
|
||||
const { videoUri, project } = route.params || {};
|
||||
const songUrl = project?.songUrl || null;
|
||||
|
||||
console.log("video uri is : ", videoUri);
|
||||
const audioPlayer = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
|
||||
const videoPlayer = useVideoPlayer(videoUri || null, (p) => {
|
||||
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 { Routes } from "../../navigation";
|
||||
import { goBack, navigate } from "../../navigation/NavigationService";
|
||||
import { useUserData } from "../../providers/UserDataProvider";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { size } from "../../styles/Style";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
import { useUserData } from "../../providers/UserDataProvider";
|
||||
|
||||
const DownloadSongs = ({ route }) => {
|
||||
const { currentUID } = useUserData();
|
||||
@@ -50,11 +50,11 @@ const DownloadSongs = ({ route }) => {
|
||||
playbackUrl: resultURI,
|
||||
updatedAt: serverTimestamp(),
|
||||
},
|
||||
{ merge: true },
|
||||
{ merge: true }
|
||||
);
|
||||
setTooltip({
|
||||
type: "success",
|
||||
text: "Vidéo uploadée, conversion HLS en cours…",
|
||||
text: "Vidéo uploadée",
|
||||
});
|
||||
} else {
|
||||
setTooltip({
|
||||
|
||||
Reference in New Issue
Block a user