feat: fixes and formatter
This commit is contained in:
@@ -1,173 +1,167 @@
|
||||
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import { VideoView, useVideoPlayer } from "expo-video";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Image, Pressable, View } from "react-native";
|
||||
import { background, icons } from "../../assets";
|
||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
import ProgressSlider from "../../components/player/ProgressSlider";
|
||||
import Page from "../../layouts/Page";
|
||||
import { Routes } from "../../navigation";
|
||||
import { goBack, navigate } from "../../navigation/NavigationService";
|
||||
import { gutters, Palette } from "../../styles";
|
||||
import useSharedAudioPlayer from '../../hooks/useSharedAudioPlayer'
|
||||
import * as FileSystem from 'expo-file-system'
|
||||
import { VideoView, useVideoPlayer } from 'expo-video'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Image, Pressable, View } from 'react-native'
|
||||
import { background, icons } from '../../assets'
|
||||
import BorderGradientButton from '../../components/BorderGradientButton'
|
||||
import GradientButton from '../../components/GradientButton'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
import ProgressSlider from '../../components/player/ProgressSlider'
|
||||
import Page from '../../layouts/Page'
|
||||
import { Routes } from '../../navigation'
|
||||
import { goBack, navigate } from '../../navigation/NavigationService'
|
||||
import { gutters, Palette } from '../../styles'
|
||||
const RecordedPlayback = ({ route }) => {
|
||||
const { videoUri, project } = route.params || {};
|
||||
const songUrl = project?.songUrl || null;
|
||||
const { videoUri, project } = route.params || {}
|
||||
const songUrl = project?.songUrl || null
|
||||
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",
|
||||
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" },
|
||||
});
|
||||
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
|
||||
p.timeUpdateEventInterval = 0.2;
|
||||
});
|
||||
p.loop = false
|
||||
p.muted = true // recorded video has no audio; keep muted anyway
|
||||
p.timeUpdateEventInterval = 0.2
|
||||
})
|
||||
|
||||
const [progressInfo, setProgressInfo] = useState({
|
||||
pos: 0,
|
||||
dur: 0,
|
||||
isPlaying: false,
|
||||
});
|
||||
const playbackEndedRef = useRef(false);
|
||||
})
|
||||
const playbackEndedRef = useRef(false)
|
||||
|
||||
// Start both players on mount
|
||||
const stopPlayback = useCallback(async () => {
|
||||
try {
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||
} catch (e) { }
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.()
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (videoPlayer?.playing) videoPlayer.pause();
|
||||
} catch (e) { }
|
||||
}, [audioPlayer, videoPlayer]);
|
||||
if (videoPlayer?.playing) videoPlayer.pause()
|
||||
} catch (e) {}
|
||||
}, [audioPlayer, videoPlayer])
|
||||
|
||||
useEffect(() => {
|
||||
playbackEndedRef.current = false;
|
||||
playbackEndedRef.current = false
|
||||
const start = async () => {
|
||||
try {
|
||||
if (audioPlayer && songUrl) await audioPlayer.play?.();
|
||||
if (videoPlayer) videoPlayer.play();
|
||||
} catch (e) { }
|
||||
};
|
||||
start();
|
||||
if (audioPlayer && songUrl) await audioPlayer.play?.()
|
||||
if (videoPlayer) videoPlayer.play()
|
||||
} catch (e) {}
|
||||
}
|
||||
start()
|
||||
return () => {
|
||||
playbackEndedRef.current = false;
|
||||
void stopPlayback();
|
||||
};
|
||||
}, [audioPlayer, songUrl, stopPlayback, videoPlayer]);
|
||||
playbackEndedRef.current = false
|
||||
void stopPlayback()
|
||||
}
|
||||
}, [audioPlayer, songUrl, stopPlayback, videoPlayer])
|
||||
|
||||
// Poll from audio player for progress display; keep video in sync if drifting
|
||||
useEffect(() => {
|
||||
const id = global.setInterval(() => {
|
||||
try {
|
||||
const dur = (audioPlayer?.duration || 0) * 1000;
|
||||
const pos = (audioPlayer?.currentTime || 0) * 1000;
|
||||
const playing = !!audioPlayer?.playing || !!videoPlayer?.playing;
|
||||
setProgressInfo({ pos, dur, isPlaying: playing });
|
||||
const dur = (audioPlayer?.duration || 0) * 1000
|
||||
const pos = (audioPlayer?.currentTime || 0) * 1000
|
||||
const playing = !!audioPlayer?.playing || !!videoPlayer?.playing
|
||||
setProgressInfo({ pos, dur, isPlaying: playing })
|
||||
|
||||
// basic drift correction: if desync > 300ms, align video
|
||||
if (videoPlayer && !Number.isNaN(videoPlayer.currentTime)) {
|
||||
const v = (videoPlayer.currentTime || 0) * 1000;
|
||||
const drift = Math.abs(v - pos);
|
||||
const v = (videoPlayer.currentTime || 0) * 1000
|
||||
const drift = Math.abs(v - pos)
|
||||
if (drift > 350) {
|
||||
videoPlayer.currentTime = Math.max(0, (pos || 0) / 1000);
|
||||
videoPlayer.currentTime = Math.max(0, (pos || 0) / 1000)
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
}, 250);
|
||||
return () => global.clearInterval(id);
|
||||
}, [audioPlayer, videoPlayer]);
|
||||
} catch (e) {}
|
||||
}, 250)
|
||||
return () => global.clearInterval(id)
|
||||
}, [audioPlayer, videoPlayer])
|
||||
|
||||
const onSeek = async (targetMs) => {
|
||||
try {
|
||||
const dur = progressInfo.dur || 0;
|
||||
const pos = Math.max(0, Math.min(dur, Math.floor(targetMs)));
|
||||
if (audioPlayer && dur > 0)
|
||||
await audioPlayer.seekTo?.(Math.floor(pos / 1000));
|
||||
if (videoPlayer) videoPlayer.currentTime = Math.max(0, pos / 1000);
|
||||
} catch (e) { }
|
||||
};
|
||||
const dur = progressInfo.dur || 0
|
||||
const pos = Math.max(0, Math.min(dur, Math.floor(targetMs)))
|
||||
if (audioPlayer && dur > 0) await audioPlayer.seekTo?.(Math.floor(pos / 1000))
|
||||
if (videoPlayer) videoPlayer.currentTime = Math.max(0, pos / 1000)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const onSeekStart = useCallback(() => {
|
||||
playbackEndedRef.current = false;
|
||||
}, []);
|
||||
playbackEndedRef.current = false
|
||||
}, [])
|
||||
|
||||
const pauseDuringSeek = useCallback(async () => {
|
||||
try {
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||
} catch (e) { }
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.()
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (videoPlayer?.playing) videoPlayer.pause();
|
||||
} catch (e) { }
|
||||
}, [audioPlayer, videoPlayer]);
|
||||
if (videoPlayer?.playing) videoPlayer.pause()
|
||||
} catch (e) {}
|
||||
}, [audioPlayer, videoPlayer])
|
||||
|
||||
const resumeAfterSeek = useCallback(async () => {
|
||||
try {
|
||||
if (audioPlayer) await audioPlayer.play?.();
|
||||
} catch (e) { }
|
||||
if (audioPlayer) await audioPlayer.play?.()
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (videoPlayer) videoPlayer.play();
|
||||
} catch (e) { }
|
||||
}, [audioPlayer, videoPlayer]);
|
||||
if (videoPlayer) videoPlayer.play()
|
||||
} catch (e) {}
|
||||
}, [audioPlayer, videoPlayer])
|
||||
|
||||
const sliderProgress = useMemo(() => {
|
||||
return progressInfo.dur
|
||||
? Math.min(1, (progressInfo.pos || 0) / progressInfo.dur)
|
||||
: 0;
|
||||
}, [progressInfo]);
|
||||
return progressInfo.dur ? Math.min(1, (progressInfo.pos || 0) / progressInfo.dur) : 0
|
||||
}, [progressInfo])
|
||||
|
||||
useEffect(() => {
|
||||
const duration = progressInfo?.dur || 0;
|
||||
if (!duration) return;
|
||||
const position = progressInfo?.pos || 0;
|
||||
const duration = progressInfo?.dur || 0
|
||||
if (!duration) return
|
||||
const position = progressInfo?.pos || 0
|
||||
if (playbackEndedRef.current && duration - position > 1000) {
|
||||
playbackEndedRef.current = false;
|
||||
return;
|
||||
playbackEndedRef.current = false
|
||||
return
|
||||
}
|
||||
const remaining = Math.max(0, duration - position);
|
||||
const remaining = Math.max(0, duration - position)
|
||||
if (remaining <= 400 && !playbackEndedRef.current) {
|
||||
playbackEndedRef.current = true;
|
||||
void stopPlayback();
|
||||
playbackEndedRef.current = true
|
||||
void stopPlayback()
|
||||
}
|
||||
}, [progressInfo, stopPlayback]);
|
||||
}, [progressInfo, stopPlayback])
|
||||
|
||||
const handleTogglePlayback = async () => {
|
||||
try {
|
||||
const duration = progressInfo?.dur || 0;
|
||||
const position = progressInfo?.pos || 0;
|
||||
const isAtEnd = duration > 0 && duration - position < 1000;
|
||||
const isCurrentlyPlaying =
|
||||
!!audioPlayer?.playing || !!videoPlayer?.playing;
|
||||
const duration = progressInfo?.dur || 0
|
||||
const position = progressInfo?.pos || 0
|
||||
const isAtEnd = duration > 0 && duration - position < 1000
|
||||
const isCurrentlyPlaying = !!audioPlayer?.playing || !!videoPlayer?.playing
|
||||
|
||||
if (isCurrentlyPlaying) {
|
||||
playbackEndedRef.current = false;
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||
if (videoPlayer?.playing) videoPlayer.pause();
|
||||
return;
|
||||
playbackEndedRef.current = false
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.()
|
||||
if (videoPlayer?.playing) videoPlayer.pause()
|
||||
return
|
||||
}
|
||||
|
||||
if (isAtEnd) {
|
||||
if (audioPlayer) await audioPlayer.seekTo?.(0);
|
||||
if (videoPlayer) videoPlayer.currentTime = 0;
|
||||
if (audioPlayer) await audioPlayer.seekTo?.(0)
|
||||
if (videoPlayer) videoPlayer.currentTime = 0
|
||||
}
|
||||
|
||||
playbackEndedRef.current = false;
|
||||
if (songUrl && audioPlayer) await audioPlayer.play?.();
|
||||
if (videoPlayer) videoPlayer.play();
|
||||
} catch (e) { }
|
||||
};
|
||||
playbackEndedRef.current = false
|
||||
if (songUrl && audioPlayer) await audioPlayer.play?.()
|
||||
if (videoPlayer) videoPlayer.play()
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return (
|
||||
<Page backgroundColor={Palette.grayMid} headerType="NONE">
|
||||
<MusicLandHeader progress={19} onPressBack={goBack} />
|
||||
<View
|
||||
style={{ flex: 1, paddingTop: 12, gap: 14, paddingBottom: gutters * 2 }}
|
||||
>
|
||||
<View style={{ flex: 1, paddingTop: 12, gap: 14, paddingBottom: gutters * 2 }}>
|
||||
<View style={{ flex: 1, gap: 22 }}>
|
||||
{!!videoUri && (
|
||||
<VideoView
|
||||
@@ -175,12 +169,12 @@ const RecordedPlayback = ({ route }) => {
|
||||
nativeControls={false}
|
||||
contentFit="contain"
|
||||
style={{
|
||||
width: "80%",
|
||||
width: '80%',
|
||||
flex: 1,
|
||||
alignSelf: "center",
|
||||
alignSelf: 'center',
|
||||
borderRadius: 16,
|
||||
backgroundColor: "#00000066",
|
||||
overflow: "hidden",
|
||||
backgroundColor: '#00000066',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -200,13 +194,13 @@ const RecordedPlayback = ({ route }) => {
|
||||
width: 72,
|
||||
height: 72,
|
||||
borderRadius: 36,
|
||||
alignSelf: "center",
|
||||
alignSelf: 'center',
|
||||
marginTop: 4,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "rgba(10, 5, 24, 0.65)",
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgba(10, 5, 24, 0.65)',
|
||||
borderWidth: 1,
|
||||
borderColor: "#F94697",
|
||||
borderColor: '#F94697',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
@@ -215,41 +209,39 @@ const RecordedPlayback = ({ route }) => {
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
<View
|
||||
style={{ width: "80%", alignSelf: "center", marginTop: 4, gap: 12 }}
|
||||
>
|
||||
<View style={{ width: '80%', alignSelf: 'center', marginTop: 4, gap: 12 }}>
|
||||
<GradientButton
|
||||
title="Je valide"
|
||||
onPress={() => {
|
||||
navigate(Routes.PlaybackDownload, {
|
||||
action: "playback",
|
||||
action: 'playback',
|
||||
uri: videoUri,
|
||||
project,
|
||||
});
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<BorderGradientButton
|
||||
title="Recommencer"
|
||||
onPress={async () => {
|
||||
try {
|
||||
await stopPlayback();
|
||||
} catch (e) { }
|
||||
await stopPlayback()
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (videoUri) {
|
||||
const info = await FileSystem.getInfoAsync(videoUri);
|
||||
const info = await FileSystem.getInfoAsync(videoUri)
|
||||
if (info?.exists)
|
||||
await FileSystem.deleteAsync(videoUri, {
|
||||
idempotent: true,
|
||||
});
|
||||
})
|
||||
}
|
||||
} catch (e) { }
|
||||
navigate(Routes.RecordPlayback, { project });
|
||||
} catch (e) {}
|
||||
navigate(Routes.RecordPlayback, { project })
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default RecordedPlayback;
|
||||
export default RecordedPlayback
|
||||
|
||||
Reference in New Issue
Block a user