fix music url structure

This commit is contained in:
Thomas Demirdjian
2025-09-02 13:17:19 +02:00
parent 98367af0b7
commit d634c55aaa
8 changed files with 141 additions and 168 deletions
-4
View File
@@ -210,10 +210,6 @@ export const tutorial = {
tasks: tutorialTasks, tasks: tutorialTasks,
}; };
export const mockups = {
loginAppDashboard,
};
export const background = { export const background = {
writingBG, writingBG,
studioBG, studioBG,
+3 -6
View File
@@ -55,9 +55,6 @@ export default function useDataFromRef({
if (!_.isEqual(data, initialState)) { if (!_.isEqual(data, initialState)) {
onUpdate(initialState); onUpdate(initialState);
} }
console.log("Reset state", initialState);
setEndReached(false); setEndReached(false);
setLastVisible(null); setLastVisible(null);
setData(initialState); setData(initialState);
@@ -113,7 +110,7 @@ export default function useDataFromRef({
if (e.code === "firestore/permission-denied") { if (e.code === "firestore/permission-denied") {
console.warn( console.warn(
"Permission denied for ref: ", "Permission denied for ref: ",
ref?._collectionPath?.relativeName ref?._collectionPath?.relativeName,
); );
} else { } else {
console.log(e); console.log(e);
@@ -143,14 +140,14 @@ export default function useDataFromRef({
if (e.code === "firestore/permission-denied") { if (e.code === "firestore/permission-denied") {
console.warn( console.warn(
"Permission denied for ref: ", "Permission denied for ref: ",
ref?._collectionPath?.relativeName ref?._collectionPath?.relativeName,
); );
} else { } else {
console.log(e); console.log(e);
} }
await updateData([]); await updateData([]);
setLoading(false); setLoading(false);
} },
); );
}; };
+2 -2
View File
@@ -40,10 +40,10 @@ const Library = () => {
<MyMusic /> <MyMusic />
<BackTracks /> <BackTracks />
<LikedMusic /> <LikedMusic />
<MyClips /> {/*<MyClips />*/}
<MyPlaylist /> <MyPlaylist />
<LikedPlayback /> <LikedPlayback />
<LikedClips /> {/*<LikedClips />*/}
</ScrollView> </ScrollView>
</View> </View>
</Page> </Page>
+51 -79
View File
@@ -1,7 +1,14 @@
import { useRoute } from "@react-navigation/core"; import { useRoute } from "@react-navigation/core";
import { Audio } from "expo-audio"; import { useAudioPlayer } from "expo-audio";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { Pressable, ScrollView, StyleSheet, Text, View, Image as RNImage } from "react-native"; import {
Pressable,
ScrollView,
StyleSheet,
Text,
View,
Image as RNImage,
} from "react-native";
import { Image as ExpoImage } from "expo-image"; import { Image as ExpoImage } from "expo-image";
import { SheetManager } from "react-native-actions-sheet"; import { SheetManager } from "react-native-actions-sheet";
import { useGlobal } from "reactn"; import { useGlobal } from "reactn";
@@ -19,6 +26,10 @@ import Page from "../../layouts/Page";
import { Palette } from "../../styles"; import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts"; import { FONT_FAMILY } from "../../styles/Fonts";
import Style, { gutters, size } from "../../styles/Style"; import Style, { gutters, size } from "../../styles/Style";
import {
responsiveHeight,
responsiveWidth,
} from "react-native-responsive-dimensions";
// 20 secondes // 20 secondes
const timeBeforeIncrement = 20000; const timeBeforeIncrement = 20000;
@@ -37,9 +48,7 @@ const MusicDetails = () => {
const timerRef = React.useRef(null); const timerRef = React.useRef(null);
const { data: project } = useDataFromRef({ const { data: project } = useDataFromRef({
ref: projectId ref: projectId ? projectsRef.doc(projectId) : null,
? firebase.firestore().collection("projects").doc(projectId)
: null,
simpleRef: true, simpleRef: true,
listener: true, listener: true,
condition: !!projectId, condition: !!projectId,
@@ -64,61 +73,27 @@ const MusicDetails = () => {
const title = project?.title || "Sans titre"; const title = project?.title || "Sans titre";
const artist = owner?.userName || "MusicLand"; const artist = owner?.userName || "MusicLand";
const coverUrl = project?.coverUrl || null; const coverUrl = project?.coverUrl || null;
const songUrl = useMemo(() => { const songUrl = project?.songUrl || null;
if (project?.song?.url) return project.song.url;
const arr = Array.isArray(project?.musicUrls) ? project.musicUrls : [];
return arr[0] || null;
}, [project]);
const soundRef = React.useRef(null); const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
// Load/unload audio with expo-audio for reliable status updates on iOS/Android // Reset counters when the track changes
useEffect(() => { useEffect(() => {
let isMounted = true;
const load = async () => {
try {
// Unload previous
if (soundRef.current) {
await soundRef.current.unloadAsync();
soundRef.current.setOnPlaybackStatusUpdate(null);
soundRef.current = null;
}
// Reset listen tracking per song load
listenedMsRef.current = 0; listenedMsRef.current = 0;
incrementDoneRef.current = false; incrementDoneRef.current = false;
if (!songUrl) return;
const { sound } = await Audio.Sound.createAsync(
{ uri: songUrl },
{ shouldPlay: false }
);
sound.setOnPlaybackStatusUpdate((status) => {
if (!isMounted) return;
if (!status || !status.isLoaded) return;
const pos = status.positionMillis || 0;
const dur = status.durationMillis || 0;
setProgressInfo({ pos, dur });
setIsPlaying(!!status.isPlaying);
});
soundRef.current = sound;
} catch (e) {
console.log("Audio load error", e?.message);
}
};
load();
return () => {
isMounted = false;
(async () => {
try {
if (soundRef.current) {
await soundRef.current.unloadAsync();
soundRef.current.setOnPlaybackStatusUpdate(null);
soundRef.current = null;
}
} catch (_) {}
})();
};
}, [songUrl]); }, [songUrl]);
// Poll player state to update progress and play state
useEffect(() => {
const id = setInterval(() => {
const dur = (player?.duration || 0) * 1000;
const pos = (player?.currentTime || 0) * 1000;
setProgressInfo({ pos, dur });
setIsPlaying(!!player?.playing);
}, 300);
return () => clearInterval(id);
}, [player]);
// Start/stop a timer to accumulate listened milliseconds while playing // Start/stop a timer to accumulate listened milliseconds while playing
useEffect(() => { useEffect(() => {
const clearTimer = () => { const clearTimer = () => {
@@ -163,15 +138,13 @@ const MusicDetails = () => {
}; };
const togglePlay = async () => { const togglePlay = async () => {
const sound = soundRef.current; if (!player || !songUrl) return;
if (!sound || !songUrl) return;
try { try {
const status = await sound.getStatusAsync(); if (player.playing) {
if (status?.isLoaded && status.isPlaying) { await player.pause?.();
await sound.pauseAsync();
setIsPlaying(false); setIsPlaying(false);
} else { } else {
await sound.playAsync(); await player.play?.();
setIsPlaying(true); setIsPlaying(true);
} }
} catch (e) { } catch (e) {
@@ -183,9 +156,8 @@ const MusicDetails = () => {
try { try {
const dur = progressInfo.dur || 0; const dur = progressInfo.dur || 0;
const pos = Math.floor(dur * ratio); const pos = Math.floor(dur * ratio);
const sound = soundRef.current; if (player && dur > 0) {
if (sound && dur > 0) { await player.seekTo?.(Math.floor((pos || 0) / 1000));
await sound.setPositionAsync(pos);
} }
} catch (e) { } catch (e) {
console.log("MusicDetails seek error", e?.message); console.log("MusicDetails seek error", e?.message);
@@ -196,8 +168,7 @@ const MusicDetails = () => {
try { try {
const cur = Math.floor((progressInfo.pos || 0) / 1000); const cur = Math.floor((progressInfo.pos || 0) / 1000);
const next = Math.max(0, cur + deltaSeconds); const next = Math.max(0, cur + deltaSeconds);
const sound = soundRef.current; if (player) await player.seekTo?.(next);
if (sound) await sound.setPositionAsync(next * 1000);
} catch (e) { } catch (e) {
console.log("MusicDetails seekBy error", e?.message); console.log("MusicDetails seekBy error", e?.message);
} }
@@ -241,10 +212,9 @@ const MusicDetails = () => {
paddingTop: 20, paddingTop: 20,
paddingBottom: gutters * 2, paddingBottom: gutters * 2,
}} }}
// stickyHeaderIndices={[1]}
> >
<View style={{ gap: 28 }}> <View style={{ gap: 28 }}>
{coverUrl ? ( {coverUrl && (
<ExpoImage <ExpoImage
source={{ uri: coverUrl }} source={{ uri: coverUrl }}
cachePolicy="memory-disk" cachePolicy="memory-disk"
@@ -253,8 +223,6 @@ const MusicDetails = () => {
transition={150} transition={150}
style={styles.img} style={styles.img}
/> />
) : (
<RNImage source={img.placeholder4} style={styles.img} />
)} )}
<View style={{ ...Style.containerSpaceBetween }}> <View style={{ ...Style.containerSpaceBetween }}>
<View> <View>
@@ -298,22 +266,22 @@ const MusicDetails = () => {
</View> </View>
</View> </View>
</View> </View>
{songUrl && (
<View style={{ paddingTop: 22 }}> <View style={{ paddingTop: 22 }}>
<Slider <Slider
value={fmt(progressInfo.pos)} value={fmt(progressInfo.pos)}
maxValue={fmt(progressInfo.dur)} maxValue={fmt(progressInfo.dur)}
progress={ progress={
progressInfo.dur ? (progressInfo.pos || 0) / progressInfo.dur : 0 progressInfo.dur
? (progressInfo.pos || 0) / progressInfo.dur
: 0
} }
seekEnabled={!!songUrl} seekEnabled={!!songUrl}
onSeekStart={async () => { onSeekStart={async () => {
try { try {
const sound = soundRef.current; wasPlayingBeforeSeek.current = !!player?.playing;
const status = await sound?.getStatusAsync?.(); if (player?.playing) {
wasPlayingBeforeSeek.current = await player.pause?.();
!!status?.isLoaded && !!status?.isPlaying;
if (status?.isLoaded && status?.isPlaying) {
await sound.pauseAsync();
setIsPlaying(false); setIsPlaying(false);
} }
} catch (e) { } catch (e) {
@@ -323,9 +291,8 @@ const MusicDetails = () => {
onSeek={onSeek} onSeek={onSeek}
onSeekEnd={async () => { onSeekEnd={async () => {
try { try {
const sound = soundRef.current; if (player && wasPlayingBeforeSeek.current) {
if (sound && wasPlayingBeforeSeek.current) { await player.play?.();
await sound.playAsync();
setIsPlaying(true); setIsPlaying(true);
} }
wasPlayingBeforeSeek.current = false; wasPlayingBeforeSeek.current = false;
@@ -334,7 +301,9 @@ const MusicDetails = () => {
} }
}} }}
/> />
<View style={{ ...Style.containerRow, gap: 24, alignSelf: "center" }}> <View
style={{ ...Style.containerRow, gap: 24, alignSelf: "center" }}
>
{/* Previous (rewind 10s) */} {/* Previous (rewind 10s) */}
<Pressable onPress={() => seekBy(-10)}> <Pressable onPress={() => seekBy(-10)}>
<RNImage <RNImage
@@ -371,6 +340,7 @@ const MusicDetails = () => {
</Pressable> </Pressable>
</View> </View>
</View> </View>
)}
{description?.length > 0 && ( {description?.length > 0 && (
<View style={{ marginTop: 30, gap: 20 }}> <View style={{ marginTop: 30, gap: 20 }}>
<Text <Text
@@ -402,6 +372,8 @@ const styles = StyleSheet.create({
fontSize: 20, fontSize: 20,
color: Palette.white, color: Palette.white,
fontFamily: FONT_FAMILY.HelveticaNeueMedium, fontFamily: FONT_FAMILY.HelveticaNeueMedium,
width: responsiveWidth(70),
marginBottom: responsiveHeight(1),
}, },
name: { name: {
fontSize: 16, fontSize: 16,
-7
View File
@@ -239,13 +239,6 @@ const Research = () => {
/> />
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<ScrollView contentContainerStyle={{ paddingTop: 2 }}> <ScrollView contentContainerStyle={{ paddingTop: 2 }}>
{(!selected &&
!projectsLoading &&
!usersLoading &&
filteredProjects.length === 0 &&
filteredUsers.length === 0) && (
<EmptyText text={"Aucun résultat"} />
)}
{((!selected && (filteredProjects.length > 0 || projectsLoading)) || {((!selected && (filteredProjects.length > 0 || projectsLoading)) ||
selected === "Musiques") && ( selected === "Musiques") && (
<View style={{ paddingHorizontal: 2 }}> <View style={{ paddingHorizontal: 2 }}>
+15 -3
View File
@@ -1,6 +1,13 @@
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { Platform, Pressable, StyleSheet, Text, View, Image as RNImage } from "react-native"; import {
Platform,
Pressable,
StyleSheet,
Text,
View,
Image as RNImage,
} from "react-native";
import { Image as ExpoImage } from "expo-image"; import { Image as ExpoImage } from "expo-image";
import { useGlobal } from "reactn"; import { useGlobal } from "reactn";
import { icons, img } from "../../../assets"; import { icons, img } from "../../../assets";
@@ -8,6 +15,7 @@ import { arrayRemove, arrayUnion, projectsRef } from "../../../config/firebase";
import { Palette, Style } from "../../../styles"; import { Palette, Style } 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 { responsiveWidth } from "react-native-responsive-dimensions";
const MusicCard = ({ const MusicCard = ({
onPress, onPress,
@@ -90,7 +98,7 @@ const MusicCard = ({
/> />
) : ( ) : (
<RNImage <RNImage
source={img.placeholder2} source={img.placeholder}
style={{ ...size({ size: 60 }), borderRadius: 12 }} style={{ ...size({ size: 60 }), borderRadius: 12 }}
/> />
)} )}
@@ -107,7 +115,9 @@ const MusicCard = ({
} }
> >
<View> <View>
<Text style={styles.title}>{title || "Sans titre"}</Text> <Text numberOfLines={2} style={styles.title}>
{title || "Sans titre"}
</Text>
<Text style={styles.subTitle}>{subtitle || "MusicLand"}</Text> <Text style={styles.subTitle}>{subtitle || "MusicLand"}</Text>
</View> </View>
<View <View
@@ -160,6 +170,8 @@ const styles = StyleSheet.create({
fontSize: 16, fontSize: 16,
color: Palette.white, color: Palette.white,
fontFamily: FONT_FAMILY.OwnersRegular, fontFamily: FONT_FAMILY.OwnersRegular,
lineHeight: 18,
width: responsiveWidth(45),
}, },
subTitle: { subTitle: {
fontSize: 12, fontSize: 12,
+8 -5
View File
@@ -30,8 +30,8 @@ const CREATE_DATA = [
img: ai.bena, img: ai.bena,
bg: background.productionBG, bg: background.productionBG,
label: "Bena", label: "Bena",
desc: "Come back when you want\nto publish the song!", desc: "Come back when you want\nto generate cover!",
type: "Producer", type: "Designer",
}, },
{ {
img: ai.john, img: ai.john,
@@ -72,15 +72,18 @@ const NewMusicOptions = ({ route }) => {
if (isLocked(index)) return; if (isLocked(index)) return;
switch (index) { switch (index) {
case 0: case 0:
navigate(Routes.WritingLyrics, {}); navigate(Routes.WritingLyrics, {
projectId: currentProjet?.id || null,
});
break; break;
case 1: case 1:
navigate(Routes.Studio, { action: item.type }); navigate(Routes.Compose, { projectId: currentProjet.id });
break; break;
case 2: case 2:
navigate(Routes.Production, { action: item.type }); navigate(Routes.PouchReady, { projectId: currentProjet.id });
break; break;
default: default:
navigate(Routes.PouchReady, { projectId: currentProjet.id });
break; break;
} }
}; };
+1 -1
View File
@@ -124,7 +124,7 @@ const SongReady = () => {
.doc(projectId) .doc(projectId)
.set( .set(
{ {
song: { index: selectedIndex, url }, songUrl: url,
updatedAt: firebase.firestore.FieldValue.serverTimestamp(), updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
}, },
{ merge: true }, { merge: true },