global player

This commit is contained in:
2025-10-07 09:51:38 +02:00
parent 5cd6741ae6
commit f796d91267
15 changed files with 1031 additions and 136 deletions
+9 -2
View File
@@ -1,4 +1,4 @@
import { useAudioPlayer } from "expo-audio";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import { Image as ExpoImage } from "expo-image";
import React, { useEffect, useMemo, useRef, useState } from "react";
import {
@@ -75,7 +75,14 @@ const MusicDetails = ({ route }) => {
const coverUrl = project?.coverUrl || null;
const songUrl = project?.songUrl || null;
const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
const player = useSharedAudioPlayer(songUrl ? { uri: songUrl } : undefined, {
id: projectId ? `project-${projectId}` : songUrl ? `song-${songUrl}` : undefined,
title,
artist,
artwork: coverUrl,
coverUrl,
metadata: { projectId, screen: "MusicDetails" },
});
// Karaoke aligned words (from timestamps)
const alignedWords = useMemo(() => {
+25 -117
View File
@@ -1,6 +1,6 @@
import { BlurView } from "expo-blur";
import { Image as ExpoImage } from "expo-image";
import React, { useEffect, useMemo, useRef, useState } from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
Pressable,
Image as RNImage,
@@ -26,119 +26,12 @@ import {
usersRef,
} from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import Page from "../../layouts/Page";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import Style, { gutters, size } from "../../styles/Style";
const useHtmlAudioPlayer = (source) => {
const src = typeof source === "string" ? source : source?.uri || null;
const audioRef = useRef(null);
const [state, setState] = useState({
durationMs: 0,
positionMs: 0,
isPlaying: false,
});
useEffect(() => {
setState({ durationMs: 0, positionMs: 0, isPlaying: false });
if (!src) {
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.src = "";
audioRef.current.load();
audioRef.current = null;
}
return undefined;
}
const audio = new Audio(src);
audio.preload = "auto";
audio.crossOrigin = "anonymous";
audioRef.current = audio;
const syncDuration = () => {
const duration = Number.isFinite(audio.duration)
? audio.duration * 1000
: 0;
setState((prev) => ({ ...prev, durationMs: duration }));
};
const handleLoadedMetadata = () => syncDuration();
const handleDurationChange = () => syncDuration();
const handleTimeUpdate = () => {
setState((prev) => ({
...prev,
positionMs: audio.currentTime * 1000,
}));
};
const handlePlay = () => setState((prev) => ({ ...prev, isPlaying: true }));
const handlePause = () =>
setState((prev) => ({ ...prev, isPlaying: false }));
const handleEnded = () =>
setState((prev) => ({
...prev,
positionMs: Number.isFinite(audio.duration)
? audio.duration * 1000
: prev.positionMs,
isPlaying: false,
}));
audio.addEventListener("loadedmetadata", handleLoadedMetadata);
audio.addEventListener("durationchange", handleDurationChange);
audio.addEventListener("timeupdate", handleTimeUpdate);
audio.addEventListener("play", handlePlay);
audio.addEventListener("pause", handlePause);
audio.addEventListener("ended", handleEnded);
return () => {
audio.pause();
audio.removeEventListener("loadedmetadata", handleLoadedMetadata);
audio.removeEventListener("durationchange", handleDurationChange);
audio.removeEventListener("timeupdate", handleTimeUpdate);
audio.removeEventListener("play", handlePlay);
audio.removeEventListener("pause", handlePause);
audio.removeEventListener("ended", handleEnded);
audio.src = "";
audio.load();
audioRef.current = null;
};
}, [src]);
const play = React.useCallback(async () => {
if (!audioRef.current) return;
try {
await audioRef.current.play();
} catch (e) {
throw e;
}
}, []);
const pause = React.useCallback(() => {
if (!audioRef.current) return;
audioRef.current.pause();
}, []);
const seekTo = React.useCallback((seconds) => {
if (!audioRef.current) return;
const next = Math.max(0, Number(seconds) || 0);
const duration = audioRef.current.duration;
const bounded = Number.isFinite(duration) ? Math.min(next, duration) : next;
audioRef.current.currentTime = bounded;
setState((prev) => ({ ...prev, positionMs: bounded * 1000 }));
}, []);
return {
play,
pause,
seekTo,
playing: state.isPlaying,
positionMs: state.positionMs,
durationMs: state.durationMs,
};
};
// 20 secondes
const timeBeforeIncrement = 20000;
@@ -183,14 +76,29 @@ const MusicDetails = ({ route }) => {
const coverUrl = project?.coverUrl || null;
const songUrl = project?.songUrl || null;
const {
play: playAudio,
pause: pauseAudio,
seekTo: seekAudio,
playing: isPlaying,
positionMs,
durationMs,
} = useHtmlAudioPlayer(songUrl ? { uri: songUrl } : undefined);
const sharedPlayer = useSharedAudioPlayer(songUrl ? { uri: songUrl } : undefined, {
id: projectId ? `project-${projectId}` : songUrl ? `song-${songUrl}` : undefined,
title,
artist,
artwork: coverUrl,
coverUrl,
metadata: { projectId, screen: "MusicDetails" },
});
const playAudio = useCallback(async () => {
if (sharedPlayer?.play) await sharedPlayer.play();
}, [sharedPlayer]);
const pauseAudio = useCallback(async () => {
if (sharedPlayer?.pause) await sharedPlayer.pause();
}, [sharedPlayer]);
const seekAudio = useCallback(
(seconds) => {
if (sharedPlayer?.seekTo) sharedPlayer.seekTo(seconds);
},
[sharedPlayer]
);
const isPlaying = !!sharedPlayer?.playing;
const positionMs = Math.max(0, (sharedPlayer?.currentTime || 0) * 1000);
const durationMs = Math.max(0, (sharedPlayer?.duration || 0) * 1000);
// Karaoke aligned words (from timestamps)
const alignedWords = useMemo(() => {
+9 -2
View File
@@ -1,5 +1,5 @@
import { useFocusEffect } from "@react-navigation/native";
import { useAudioPlayer } from "expo-audio";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import { CameraView, useCameraPermissions } from "expo-camera";
import React, {
useCallback,
@@ -54,7 +54,14 @@ const RecordPlayback = ({ route }) => {
// Musique
const songUrl = project?.songUrl || null;
const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
const player = useSharedAudioPlayer(songUrl ? { uri: songUrl } : undefined, {
id: projectId ? `record-${projectId}` : songUrl ? `record-${songUrl}` : undefined,
title: typeof project?.title === "string" ? project.title : "Sans titre",
artist: typeof project?.userName === "string" ? project.userName : "MusicLand",
artwork: project?.coverUrl || null,
coverUrl: project?.coverUrl || null,
metadata: { projectId, screen: "RecordPlayback" },
});
const musicIndex = useMemo(() => {
const i = Number(songIndex);
+9 -2
View File
@@ -1,5 +1,5 @@
import { useFocusEffect } from "@react-navigation/native";
import { useAudioPlayer } from "expo-audio";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import { useCameraPermissions } from "expo-camera";
import React, {
useCallback,
@@ -61,7 +61,14 @@ const RecordPlayback = ({ route }) => {
// Player
const songUrl = project?.songUrl || null;
const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
const player = useSharedAudioPlayer(songUrl ? { uri: songUrl } : undefined, {
id: projectId ? `record-${projectId}` : songUrl ? `record-${songUrl}` : undefined,
title: typeof project?.title === "string" ? project.title : "Sans titre",
artist: typeof project?.userName === "string" ? project.userName : "MusicLand",
artwork: project?.coverUrl || null,
coverUrl: project?.coverUrl || null,
metadata: { projectId, screen: "RecordPlayback" },
});
// MediaStream / Recorder (web only)
const previewVideoRef = useRef(null);
+9 -2
View File
@@ -1,4 +1,4 @@
import { useAudioPlayer } from "expo-audio";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import * as FileSystem from "expo-file-system";
import { VideoView, useVideoPlayer } from "expo-video";
import React, { useEffect, useMemo, useRef, useState } from "react";
@@ -16,7 +16,14 @@ 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 audioPlayer = useSharedAudioPlayer(songUrl ? { uri: songUrl } : undefined, {
id: project?.id ? `recorded-${project.id}` : songUrl ? `recorded-${songUrl}` : undefined,
title: typeof project?.title === "string" ? project.title : "Sans titre",
artist: typeof project?.userName === "string" ? project.userName : "MusicLand",
artwork: project?.coverUrl || null,
coverUrl: project?.coverUrl || null,
metadata: { projectId: project?.id, screen: "RecordedPlayback" },
});
const videoPlayer = useVideoPlayer(videoUri || null, (p) => {
p.loop = false;
p.muted = true; // recorded video has no audio; keep muted anyway
+9 -2
View File
@@ -1,4 +1,4 @@
import { useAudioPlayer } from "expo-audio";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { Text, View } from "react-native";
import { background } from "../../assets";
@@ -29,7 +29,14 @@ const RecordedPlayback = ({ route }) => {
const songUrl = project?.songUrl || null;
console.log("video uri is : ", videoUri);
// AUDIO PLAYER (expo-audio → seconds)
const audioPlayer = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
const audioPlayer = useSharedAudioPlayer(songUrl ? { uri: songUrl } : undefined, {
id: project?.id ? `recorded-${project.id}` : songUrl ? `recorded-${songUrl}` : undefined,
title: typeof project?.title === "string" ? project.title : "Sans titre",
artist: typeof project?.userName === "string" ? project.userName : "MusicLand",
artwork: project?.coverUrl || null,
coverUrl: project?.coverUrl || null,
metadata: { projectId: project?.id, screen: "RecordedPlayback" },
});
// Optionnel: si tu veux quand même un rendu vidéo sur web si tu as une source
const videoElRef = useRef(null);
+9 -2
View File
@@ -1,5 +1,5 @@
import { useIsFocused, useRoute } from "@react-navigation/native";
import { useAudioPlayer } from "expo-audio";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import { BlurView } from "expo-blur";
import { VideoView, useVideoPlayer } from "expo-video";
import React, {
@@ -115,7 +115,14 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
const creatorName = item?.userName;
const audioPlayer = useAudioPlayer(audioSource || undefined);
const audioPlayer = useSharedAudioPlayer(audioSource || undefined, {
id: item?.id ? `playback-${item.id}` : audioSource?.uri ? `playback-${audioSource.uri}` : undefined,
title: typeof item?.title === "string" ? item.title : "",
artist: typeof item?.userName === "string" ? item.userName : "",
artwork: item?.coverUrl || null,
coverUrl: item?.coverUrl || null,
metadata: { playbackId: item?.id },
});
const videoPlayer = useVideoPlayer(videoUrl || null, (p) => {
p.loop = false;
p.muted = true;
@@ -1,4 +1,4 @@
import { useAudioPlayer } from "expo-audio";
import useSharedAudioPlayer from "../../../hooks/useSharedAudioPlayer";
import { BlurView } from "expo-blur";
import React, {
useCallback,
@@ -144,7 +144,14 @@ const PlaybackItem = ({
setIsLiked(currentUID ? lb.includes(currentUID) : false);
}, [item?.likedBy, currentUID]);
const audioPlayer = useAudioPlayer(audioSource || undefined);
const audioPlayer = useSharedAudioPlayer(audioSource || undefined, {
id: item?.id ? `playback-${item.id}` : audioSource?.uri ? `playback-${audioSource.uri}` : undefined,
title: typeof item?.title === "string" ? item.title : "",
artist: typeof item?.userName === "string" ? item.userName : "",
artwork: item?.coverUrl || null,
coverUrl: item?.coverUrl || null,
metadata: { playbackId: item?.id },
});
// Helpers to normalize and map clocks
const getAudioTimeSeconds = useCallback(() => {
+25 -5
View File
@@ -1,6 +1,6 @@
/* global setInterval, clearInterval */
import { useFocusEffect } from "@react-navigation/native";
import { useAudioPlayer } from "expo-audio";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import { BlurView } from "expo-blur";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Image, Platform, Pressable, Text, View } from "react-native";
@@ -34,11 +34,31 @@ const SongReady = () => {
0: { pos: 0, dur: 0 },
1: { pos: 0, dur: 0 },
});
const player0 = useAudioPlayer(
musicUrls[0] ? { uri: musicUrls[0] } : undefined
const player0 = useSharedAudioPlayer(
musicUrls[0] ? { uri: musicUrls[0] } : undefined,
{
id: musicUrls[0] ? `songready-${musicUrls[0]}` : undefined,
title:
(Array.isArray(selectedProject?.musicTitles)
? selectedProject?.musicTitles?.[0]
: null) || selectedProject?.title || "Option 1",
artwork: selectedProject?.coverUrl || null,
coverUrl: selectedProject?.coverUrl || null,
metadata: { index: 0, projectId },
}
);
const player1 = useAudioPlayer(
musicUrls[1] ? { uri: musicUrls[1] } : undefined
const player1 = useSharedAudioPlayer(
musicUrls[1] ? { uri: musicUrls[1] } : undefined,
{
id: musicUrls[1] ? `songready-${musicUrls[1]}` : undefined,
title:
(Array.isArray(selectedProject?.musicTitles)
? selectedProject?.musicTitles?.[1]
: null) || selectedProject?.title || "Option 2",
artwork: selectedProject?.coverUrl || null,
coverUrl: selectedProject?.coverUrl || null,
metadata: { index: 1, projectId },
}
);
const wasPlayingBeforeSeek = useRef({ 0: false, 1: false });