feat: merge audio
This commit is contained in:
@@ -27,7 +27,7 @@ import Style, { gutters, size } from '../../styles/Style'
|
||||
import { getArtistDisplayName } from '../../utils/artistName'
|
||||
import { ensureAuthenticated } from '../../utils/authRedirect'
|
||||
import { createMusicSharePayload, openShareSheet } from '../../utils/shareSheet'
|
||||
// Import de la fonction utilitaire (à créer dans src/utils/lyricsUtils.js)
|
||||
// Assure-toi que le fichier lyricsUtils.js contient la fonction processKaraokeSections fournie précédemment
|
||||
import { processKaraokeSections } from './utils/lyricsUtils'
|
||||
|
||||
const timeBeforeIncrement = 20000
|
||||
@@ -50,7 +50,7 @@ const MusicDetails = ({ route }) => {
|
||||
|
||||
const { isLooping, setLooping } = usePlayer() || {}
|
||||
|
||||
// --- DATA ---
|
||||
// --- DATA FETCHING ---
|
||||
const { data: project } = useDataFromRef({
|
||||
ref: projectId ? projectsRef.doc(projectId) : null,
|
||||
simpleRef: true,
|
||||
@@ -65,7 +65,7 @@ const MusicDetails = ({ route }) => {
|
||||
condition: !!project?.userId,
|
||||
})
|
||||
|
||||
// --- MEMOS & STABILISATION ---
|
||||
// --- METADATA & PLAYER STABILIZATION ---
|
||||
const title = project?.title || 'Sans titre'
|
||||
const artist = useMemo(() => {
|
||||
const ownerName = getArtistDisplayName(owner, '')
|
||||
@@ -76,7 +76,7 @@ const MusicDetails = ({ route }) => {
|
||||
const coverUrl = project?.coverUrl || null
|
||||
const songUrl = project?.songUrl || null
|
||||
|
||||
// IMPORTANT: Stabiliser cet objet pour éviter les sauts iOS
|
||||
// On mémoïse le descriptor pour éviter les re-chargements intempestifs sur iOS
|
||||
const trackDescriptor = useMemo(() => {
|
||||
if (!songUrl) return null
|
||||
const id = projectId ? `project-${projectId}` : `song-${songUrl}`
|
||||
@@ -91,11 +91,10 @@ const MusicDetails = ({ route }) => {
|
||||
metadata: { projectId, screen: 'MusicDetails' },
|
||||
context: { projectId, screen: 'MusicDetails' },
|
||||
}
|
||||
console.log('[DEBUG PLAYER] Nouveau Track Descriptor:', desc.id)
|
||||
console.log('[PLAYER] Nouveau descriptor généré:', id)
|
||||
return desc
|
||||
}, [projectId, songUrl, title, artist, coverUrl])
|
||||
|
||||
// --- CONTROLLER ---
|
||||
const {
|
||||
isCurrent,
|
||||
isPlaying,
|
||||
@@ -108,9 +107,30 @@ const MusicDetails = ({ route }) => {
|
||||
seekBy,
|
||||
} = useTrackController(trackDescriptor)
|
||||
|
||||
const loopEnabled = isCurrent && !!isLooping
|
||||
// --- DURATION CALCULATIONS (Pour le clic sur le slider) ---
|
||||
const estimatedDurationMs = useMemo(() => {
|
||||
const toMs = (val) => {
|
||||
const n = Number(val); return (!n || n <= 0) ? 0 : (n > 1000 ? Math.round(n) : Math.round(n * 1000))
|
||||
}
|
||||
const idx = Number(project?.songIndex) || 0
|
||||
const ts = project?.musicTimestamps?.[idx]
|
||||
|
||||
// --- LOGIQUE LYRICS ---
|
||||
// Priorité 1: Metadata
|
||||
const fromMeta = toMs(ts?.durationMs || ts?.duration || ts?.audioDuration)
|
||||
if (fromMeta > 0) return fromMeta
|
||||
|
||||
// Priorité 2: Dernier mot synchronisé
|
||||
const words = ts?.alignedWords || []
|
||||
if (words.length > 0) {
|
||||
const last = words[words.length - 1]
|
||||
return Math.round((last.endS || last.startS || 0) * 1000)
|
||||
}
|
||||
return 0
|
||||
}, [project])
|
||||
|
||||
const sliderDurationMs = durationMs > 0 ? durationMs : estimatedDurationMs
|
||||
|
||||
// --- LYRICS LOGIC ---
|
||||
const sections = useMemo(() => {
|
||||
const idx = Number(project?.songIndex) || 0
|
||||
const ts = project?.musicTimestamps?.[idx]
|
||||
@@ -118,9 +138,7 @@ const MusicDetails = ({ route }) => {
|
||||
}, [project?.musicTimestamps, project?.songIndex])
|
||||
|
||||
const flatLines = useMemo(() => sections.flatMap((s) => s.lines), [sections])
|
||||
|
||||
const currentTimeS = (positionMs || 0) / 1000
|
||||
const visibleTimeS = currentTimeS + 0.3 // Offset pour fluidité iOS
|
||||
const visibleTimeS = (positionMs || 0) / 1000 + 0.3 // Offset de 300ms pour fluidité visuelle
|
||||
|
||||
const currentLineIdx = useMemo(() => {
|
||||
if (!flatLines.length) return -1
|
||||
@@ -128,31 +146,26 @@ const MusicDetails = ({ route }) => {
|
||||
}, [flatLines, visibleTimeS])
|
||||
|
||||
// --- EFFECTS ---
|
||||
|
||||
// Sync favoris
|
||||
useEffect(() => {
|
||||
if (project && currentUID) {
|
||||
setFav(Array.isArray(project.likedBy) ? project.likedBy.includes(currentUID) : false)
|
||||
}
|
||||
}, [project?.likedBy, currentUID])
|
||||
|
||||
// Timer pour les vues
|
||||
// Vues
|
||||
useEffect(() => {
|
||||
const clearTimer = () => { if (timerRef.current) clearInterval(timerRef.current) }
|
||||
const clear = () => timerRef.current && clearInterval(timerRef.current)
|
||||
if (isPlaying && projectId && !incrementDoneRef.current) {
|
||||
timerRef.current = setInterval(async () => {
|
||||
timerRef.current = setInterval(() => {
|
||||
listenedMsRef.current += 1000
|
||||
if (listenedMsRef.current >= timeBeforeIncrement) {
|
||||
console.log('[DEBUG] Incrémentation vue pour:', projectId)
|
||||
projectsRef.doc(projectId).update({ views: increment(1) }).catch(() => {})
|
||||
incrementDoneRef.current = true
|
||||
projectsRef.doc(projectId).set({ views: increment(1) }, { merge: true }).catch(() => {})
|
||||
clearTimer()
|
||||
clear()
|
||||
}
|
||||
}, 1000)
|
||||
} else {
|
||||
clearTimer()
|
||||
}
|
||||
return clearTimer
|
||||
} else { clear() }
|
||||
return clear
|
||||
}, [isPlaying, projectId])
|
||||
|
||||
// Auto-scroll
|
||||
@@ -163,56 +176,59 @@ const MusicDetails = ({ route }) => {
|
||||
}
|
||||
}, [currentLineIdx])
|
||||
|
||||
// Autoplay requested
|
||||
// Autoplay
|
||||
useEffect(() => {
|
||||
if (autoPlayRequested && !hasAutoPlayedRef.current && trackDescriptor) {
|
||||
const start = async () => {
|
||||
const run = async () => {
|
||||
try {
|
||||
if (!isCurrent) await ensureLoaded({ startPositionMs: 0, autoPlay: true })
|
||||
else if (!isPlaying) await resume()
|
||||
hasAutoPlayedRef.current = true
|
||||
} catch (e) { console.log('[PLAYER ERROR] Autoplay:', e.message) }
|
||||
} catch (e) { console.log('[PLAYER] Autoplay error:', e.message) }
|
||||
}
|
||||
start()
|
||||
run()
|
||||
}
|
||||
}, [autoPlayRequested, trackDescriptor, isCurrent, isPlaying, ensureLoaded, resume])
|
||||
|
||||
// --- ACTIONS ---
|
||||
// --- CALLBACKS ---
|
||||
const togglePlay = useCallback(async () => {
|
||||
if (!trackDescriptor) return
|
||||
try {
|
||||
if (!isCurrent) {
|
||||
console.log('[PLAYER] Chargement nouveau media:', trackDescriptor.title)
|
||||
console.log('[PLAYER] Chargement via togglePlay')
|
||||
await ensureLoaded({ startPositionMs: positionMs, autoPlay: true })
|
||||
} else {
|
||||
isPlaying ? await pause() : await resume()
|
||||
}
|
||||
} catch (e) { console.log('[PLAYER ERROR] Toggle:', e.message) }
|
||||
} catch (e) { console.log('[PLAYER] Toggle error:', e.message) }
|
||||
}, [trackDescriptor, isCurrent, isPlaying, ensureLoaded, positionMs, pause, resume])
|
||||
|
||||
const handleSliderSeek = useCallback(async (ms) => {
|
||||
if (!trackDescriptor) return
|
||||
const handleSliderSeek = useCallback(async (targetMs) => {
|
||||
if (!trackDescriptor || sliderDurationMs <= 0) return
|
||||
const ms = Math.max(0, Math.min(sliderDurationMs, Math.floor(targetMs)))
|
||||
console.log(`[PLAYER] Seek demandé vers: ${ms}ms`)
|
||||
try {
|
||||
if (!isCurrent) await ensureLoaded({ startPositionMs: ms, autoPlay: false })
|
||||
else await seekTo(Math.floor(ms))
|
||||
} catch (e) { console.log('[PLAYER ERROR] Seek:', e.message) }
|
||||
}, [trackDescriptor, isCurrent, ensureLoaded, seekTo])
|
||||
if (!isCurrent) {
|
||||
await ensureLoaded({ startPositionMs: ms, autoPlay: isPlaying })
|
||||
} else {
|
||||
await seekTo(ms)
|
||||
}
|
||||
} catch (e) { console.log('[PLAYER] Seek error:', e.message) }
|
||||
}, [trackDescriptor, sliderDurationMs, isCurrent, ensureLoaded, isPlaying, seekTo])
|
||||
|
||||
const handleLyricsSeek = async (timeS) => {
|
||||
const ms = timeS * 1000
|
||||
const ms = Math.max(0, timeS * 1000)
|
||||
try {
|
||||
if (!isCurrent) await ensureLoaded({ startPositionMs: ms, autoPlay: true })
|
||||
else {
|
||||
await seekTo(ms)
|
||||
if (!isPlaying) await resume()
|
||||
}
|
||||
} catch (e) { console.log('[PLAYER ERROR] LyricsSeek:', e.message) }
|
||||
} catch (e) { console.log('[PLAYER] Lyrics Seek error:', e.message) }
|
||||
}
|
||||
|
||||
// --- UI PARTS (Options / Share) ---
|
||||
const sharePayload = useMemo(() => createMusicSharePayload({ projectId, title, artist }), [artist, projectId, title])
|
||||
|
||||
const handleOpenOptions = () => {
|
||||
const sharePayload = createMusicSharePayload({ projectId, title, artist })
|
||||
SheetManager.show('MusicOptions', {
|
||||
payload: {
|
||||
projectId, title, ownerId: project?.userId || owner?.id || null,
|
||||
@@ -241,7 +257,7 @@ const MusicDetails = ({ route }) => {
|
||||
})}
|
||||
>
|
||||
<View style={{ flex: 1, paddingTop: 20, paddingBottom: gutters * 2 }}>
|
||||
{/* HEADER SECTION */}
|
||||
{/* INFOS MEDIA */}
|
||||
<View style={{ gap: 28 }}>
|
||||
{coverUrl && (
|
||||
<ExpoImage
|
||||
@@ -265,13 +281,12 @@ const MusicDetails = ({ route }) => {
|
||||
disabled={!isCurrent}
|
||||
style={{ ...size({ size: 32 }), alignItems: 'center', justifyContent: 'center', opacity: isCurrent ? 1 : 0.4 }}
|
||||
>
|
||||
<Feather name="repeat" size={24} color={loopEnabled ? Palette.primary : Palette.white} />
|
||||
<Feather name="repeat" size={24} color={isCurrent && isLooping ? Palette.primary : Palette.white} />
|
||||
</PressableScale>
|
||||
<PressableScale
|
||||
onPress={async () => {
|
||||
if (!projectId || !ensureAuthenticated(currentUID)) return
|
||||
const next = !fav
|
||||
setFav(next)
|
||||
const next = !fav; setFav(next)
|
||||
projectsRef.doc(projectId).update({
|
||||
likedBy: next ? arrayUnion(currentUID) : arrayRemove(currentUID),
|
||||
}).catch(() => setFav(!next))
|
||||
@@ -292,7 +307,7 @@ const MusicDetails = ({ route }) => {
|
||||
<View style={{ paddingTop: 22 }}>
|
||||
<ProgressSlider
|
||||
positionMs={positionMs}
|
||||
durationMs={durationMs || 0}
|
||||
durationMs={sliderDurationMs}
|
||||
isPlaying={isPlaying}
|
||||
onSeek={handleSliderSeek}
|
||||
onPause={pause}
|
||||
@@ -344,7 +359,9 @@ const MusicDetails = ({ route }) => {
|
||||
))}
|
||||
</ScrollView>
|
||||
) : (
|
||||
<Text style={{ color: 'gray', textAlign: 'center', marginTop: 20 }}>Chargement des paroles...</Text>
|
||||
<Text style={{ color: 'rgba(255,255,255,0.4)', textAlign: 'center', marginTop: 40, fontFamily: FONT_FAMILY.InterRegular }}>
|
||||
{songUrl ? "Chargement des paroles..." : "Aucune parole disponible"}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -199,8 +199,6 @@ const PlaybackDownload = ({ route }) => {
|
||||
}
|
||||
}, [projectForDownload?.playbackUrl])
|
||||
|
||||
|
||||
|
||||
const resolveAudioUrl = useCallback(() => {
|
||||
if (typeof projectForDownload?.songUrl === 'string' && projectForDownload.songUrl.trim()) {
|
||||
return projectForDownload.songUrl.trim()
|
||||
@@ -217,61 +215,32 @@ const PlaybackDownload = ({ route }) => {
|
||||
return null
|
||||
}, [projectForDownload])
|
||||
|
||||
const handleDownloadUri = useCallback(async () => {
|
||||
if (isPublishing || isDownloading) return
|
||||
const processMerge = useCallback(async () => {
|
||||
if (action !== 'playback') {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Téléchargement indisponible pour cette action',
|
||||
})
|
||||
return
|
||||
throw new Error('Téléchargement indisponible pour cette action')
|
||||
}
|
||||
|
||||
const playbackUrlToDownload = pendingPlaybackUrl || projectForDownload?.playbackUrl || null
|
||||
if (playbackUrlToDownload) {
|
||||
setIsDownloading(true)
|
||||
try {
|
||||
await triggerDownload(playbackUrlToDownload, projectForDownload?.title)
|
||||
} finally {
|
||||
setIsDownloading(false)
|
||||
}
|
||||
return
|
||||
}
|
||||
const cachedPlaybackUrl = pendingPlaybackUrl || projectForDownload?.playbackUrl || null
|
||||
if (cachedPlaybackUrl) return cachedPlaybackUrl
|
||||
|
||||
if (!projectForDownload?.id) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Projet introuvable pour ce playback',
|
||||
})
|
||||
return
|
||||
throw new Error('Projet introuvable pour ce playback')
|
||||
}
|
||||
|
||||
if (!uri) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Aucune vidéo trouvée pour ce playback',
|
||||
})
|
||||
return
|
||||
throw new Error('Aucune vidéo trouvée pour ce playback')
|
||||
}
|
||||
|
||||
if (!currentUID) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Utilisateur non authentifié',
|
||||
})
|
||||
return
|
||||
throw new Error('Utilisateur non authentifié')
|
||||
}
|
||||
|
||||
const audioUrl = resolveAudioUrl()
|
||||
if (!audioUrl) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Aucune piste audio disponible pour ce projet',
|
||||
})
|
||||
return
|
||||
throw new Error('Aucune piste audio disponible pour ce projet')
|
||||
}
|
||||
|
||||
console.log('[PlaybackDownload] handleDownloadUri playback', {
|
||||
console.log('[PlaybackDownload] processMerge playback', {
|
||||
projectId: projectForDownload.id,
|
||||
uri,
|
||||
currentUID,
|
||||
@@ -279,7 +248,6 @@ const PlaybackDownload = ({ route }) => {
|
||||
|
||||
let tempSourcePath = null
|
||||
try {
|
||||
setIsDownloading(true)
|
||||
setIsLoading(true)
|
||||
const { sourcePath, videoUrl } = await uploadSourceRecording({
|
||||
uri,
|
||||
@@ -289,7 +257,7 @@ const PlaybackDownload = ({ route }) => {
|
||||
tempSourcePath = sourcePath
|
||||
|
||||
const payload = {
|
||||
projectId: projectForDownload?.id,
|
||||
projectId: projectForDownload.id,
|
||||
videoUrl,
|
||||
audioUrl,
|
||||
storagePath: `users/${currentUID}/projects/${projectForDownload.id}/playback.mp4`,
|
||||
@@ -303,13 +271,21 @@ const PlaybackDownload = ({ route }) => {
|
||||
|
||||
const resultURI = result?.url || null
|
||||
|
||||
if (resultURI) {
|
||||
if (!resultURI) {
|
||||
throw new Error('Erreur lors de la publication du playback')
|
||||
}
|
||||
|
||||
setPendingPlaybackUrl(resultURI)
|
||||
setTooltip({
|
||||
type: 'success',
|
||||
text: 'Playback prêt à télécharger',
|
||||
return resultURI
|
||||
} catch (error) {
|
||||
console.log('[PlaybackDownload] error upload playback', {
|
||||
message: error?.message,
|
||||
code: error?.code,
|
||||
name: error?.name,
|
||||
details: error?.details,
|
||||
})
|
||||
await triggerDownload(resultURI, projectForDownload?.title)
|
||||
throw error
|
||||
} finally {
|
||||
if (tempSourcePath) {
|
||||
try {
|
||||
await firebase.storage().ref(tempSourcePath).delete()
|
||||
@@ -320,44 +296,35 @@ const PlaybackDownload = ({ route }) => {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Erreur lors de la publication du playback',
|
||||
})
|
||||
releaseBlobUrl(uri || null)
|
||||
setIsLoading(false)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[PlaybackDownload] error upload playback', {
|
||||
message: error?.message,
|
||||
code: error?.code,
|
||||
name: error?.name,
|
||||
details: error?.details,
|
||||
})
|
||||
if (tempSourcePath) {
|
||||
}, [action, currentUID, pendingPlaybackUrl, projectForDownload, resolveAudioUrl, setIsLoading, uri])
|
||||
|
||||
const handleDownloadUri = useCallback(async () => {
|
||||
if (isPublishing || isDownloading) return
|
||||
try {
|
||||
await firebase.storage().ref(tempSourcePath).delete()
|
||||
} catch { }
|
||||
}
|
||||
setIsDownloading(true)
|
||||
const resultURI = await processMerge()
|
||||
setTooltip({
|
||||
type: 'success',
|
||||
text: 'Playback prêt à télécharger',
|
||||
})
|
||||
await triggerDownload(resultURI, projectForDownload?.title)
|
||||
} catch (error) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: String(error?.message || 'Erreur lors de la publication du playback'),
|
||||
})
|
||||
} finally {
|
||||
releaseBlobUrl(uri || null)
|
||||
setIsDownloading(false)
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [
|
||||
action,
|
||||
currentUID,
|
||||
isDownloading,
|
||||
isPublishing,
|
||||
pendingPlaybackUrl,
|
||||
processMerge,
|
||||
projectForDownload,
|
||||
resolveAudioUrl,
|
||||
setIsLoading,
|
||||
setTooltip,
|
||||
uri,
|
||||
])
|
||||
|
||||
const startPlaybackDownloadCheckout = useCallback(
|
||||
@@ -397,34 +364,21 @@ const PlaybackDownload = ({ route }) => {
|
||||
]
|
||||
)
|
||||
|
||||
const handleDownloadPress = useCallback(() => {
|
||||
if (isDownloading || isCheckoutLaunching) {
|
||||
return
|
||||
const handleDownloadPress = async () => {
|
||||
if (!canDownloadPlayback) return startPlaybackDownloadCheckout()
|
||||
|
||||
if (isDownloading || isPublishing) return
|
||||
|
||||
try {
|
||||
setIsDownloading(true)
|
||||
const url = await processMerge()
|
||||
await triggerDownload(url, projectForDownload?.title)
|
||||
} catch (err) {
|
||||
setTooltip({ type: 'error', text: err.message })
|
||||
} finally {
|
||||
setIsDownloading(false)
|
||||
}
|
||||
if (canDownloadPlayback) {
|
||||
handleDownloadUri()
|
||||
return
|
||||
}
|
||||
if (downloadPaymentPending) {
|
||||
Alert.alert(
|
||||
'Paiement en attente',
|
||||
"Le paiement n'est pas encore confirmé. Si tu as déjà payé, patiente quelques instants.",
|
||||
[
|
||||
{ text: 'Attendre', style: 'cancel' },
|
||||
{ text: 'Relancer le paiement', onPress: () => startPlaybackDownloadCheckout({ force: true }) },
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
startPlaybackDownloadCheckout()
|
||||
}, [
|
||||
canDownloadPlayback,
|
||||
downloadPaymentPending,
|
||||
handleDownloadUri,
|
||||
isCheckoutLaunching,
|
||||
isDownloading,
|
||||
startPlaybackDownloadCheckout,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (!downloadPaymentPending || !hasPaidPlaybackDownload || isDownloading) {
|
||||
@@ -435,135 +389,29 @@ const PlaybackDownload = ({ route }) => {
|
||||
}, [downloadPaymentPending, handleDownloadUri, hasPaidPlaybackDownload, isDownloading])
|
||||
|
||||
const handlePublish = async () => {
|
||||
if (isPublishing) {
|
||||
console.log('[PlaybackDownload] publish blocked: already publishing')
|
||||
return
|
||||
}
|
||||
if (!hasAcceptedPublication) {
|
||||
console.log('[PlaybackDownload] publish blocked: publication not accepted')
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Confirme la diffusion de ton playback sur Musicland avant de publier',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!projectForDownload?.id) {
|
||||
console.log('[PlaybackDownload] publish: missing project id, navigating directly', {
|
||||
projectForDownload: projectForDownload?.id,
|
||||
})
|
||||
setTooltip({
|
||||
type: 'success',
|
||||
text: publishSuccessMessage,
|
||||
})
|
||||
navigate(Routes.PublishYoutube)
|
||||
return
|
||||
}
|
||||
let tempSourcePath = null
|
||||
let playbackUrlToSave = pendingPlaybackUrl || projectForDownload?.playbackUrl || null
|
||||
const audioUrl = resolveAudioUrl()
|
||||
if (!audioUrl && !playbackUrlToSave) {
|
||||
console.log('[PlaybackDownload] publish blocked: missing audioUrl and playbackUrl', {
|
||||
audioUrl,
|
||||
playbackUrlToSave,
|
||||
})
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Aucune piste audio disponible pour ce projet',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!currentUID) {
|
||||
console.log('[PlaybackDownload] publish blocked: missing currentUID')
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: 'Utilisateur non authentifié',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!hasAcceptedPublication) return
|
||||
|
||||
if (isPublishing) return
|
||||
|
||||
try {
|
||||
console.log('[PlaybackDownload] publish start', {
|
||||
projectId: projectForDownload?.id,
|
||||
hasPendingPlaybackUrl: Boolean(pendingPlaybackUrl),
|
||||
hasPlaybackUrl: Boolean(projectForDownload?.playbackUrl),
|
||||
hasAudioUrl: Boolean(audioUrl),
|
||||
hasUri: Boolean(uri),
|
||||
})
|
||||
setIsPublishing(true)
|
||||
setIsLoading(true)
|
||||
|
||||
if (!playbackUrlToSave) {
|
||||
console.log('[PlaybackDownload] publish: no playback url, uploading source')
|
||||
const { sourcePath, videoUrl } = await uploadSourceRecording({
|
||||
uri,
|
||||
uid: currentUID,
|
||||
projectId: projectForDownload.id,
|
||||
})
|
||||
tempSourcePath = sourcePath
|
||||
|
||||
const payload = {
|
||||
projectId: projectForDownload?.id,
|
||||
videoUrl,
|
||||
audioUrl,
|
||||
storagePath: `users/${currentUID}/projects/${projectForDownload.id}/playback.mp4`,
|
||||
}
|
||||
|
||||
console.log('[PlaybackDownload] publish: calling merge', payload)
|
||||
|
||||
const { data: result } = await callMergeVideoAndAudio(payload)
|
||||
|
||||
playbackUrlToSave = result?.url || null
|
||||
if (!playbackUrlToSave) {
|
||||
console.log('[PlaybackDownload] publish: merge result missing url', result)
|
||||
throw new Error('merge_failed')
|
||||
}
|
||||
setPendingPlaybackUrl(playbackUrlToSave)
|
||||
}
|
||||
|
||||
console.log('[PlaybackDownload] publish: saving playback url', {
|
||||
projectId: projectForDownload.id,
|
||||
playbackUrlToSave,
|
||||
})
|
||||
const url = await processMerge()
|
||||
await projectsRef.doc(projectForDownload.id).set(
|
||||
{
|
||||
playbackUrl: playbackUrlToSave,
|
||||
playbackUrl: url,
|
||||
updatedAt: serverTimestamp(),
|
||||
},
|
||||
{ merge: true }
|
||||
)
|
||||
|
||||
setTooltip({
|
||||
type: 'success',
|
||||
text: publishSuccessMessage,
|
||||
})
|
||||
console.log('[PlaybackDownload] publish: navigate to PublishYoutube', {
|
||||
projectId: projectForDownload.id,
|
||||
})
|
||||
navigate(Routes.PublishYoutube, { projectId: projectForDownload.id })
|
||||
} catch (error) {
|
||||
console.log('[PlaybackDownload] publish error', {
|
||||
message: error?.message,
|
||||
code: error?.code,
|
||||
name: error?.name,
|
||||
details: error?.details,
|
||||
})
|
||||
} catch (err) {
|
||||
setTooltip({
|
||||
type: 'error',
|
||||
text: String(error?.message || 'Publication impossible'),
|
||||
text: String(err?.message || 'Erreur de publication'),
|
||||
})
|
||||
} finally {
|
||||
if (tempSourcePath) {
|
||||
try {
|
||||
await firebase.storage().ref(tempSourcePath).delete()
|
||||
} catch (cleanupError) {
|
||||
console.log('[PlaybackDownload] unable to delete temp source', {
|
||||
message: cleanupError?.message,
|
||||
code: cleanupError?.code,
|
||||
})
|
||||
}
|
||||
}
|
||||
setIsPublishing(false)
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user