feat: merge audio

This commit is contained in:
2026-03-03 15:24:38 +01:00
parent 4755c0d8e2
commit 372b59660f
2 changed files with 125 additions and 260 deletions
+61 -213
View File
@@ -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,29 +271,12 @@ const PlaybackDownload = ({ route }) => {
const resultURI = result?.url || null
if (resultURI) {
setPendingPlaybackUrl(resultURI)
setTooltip({
type: 'success',
text: 'Playback prêt à télécharger',
})
await triggerDownload(resultURI, projectForDownload?.title)
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,
})
}
}
} else {
setTooltip({
type: 'error',
text: 'Erreur lors de la publication du playback',
})
if (!resultURI) {
throw new Error('Erreur lors de la publication du playback')
}
setPendingPlaybackUrl(resultURI)
return resultURI
} catch (error) {
console.log('[PlaybackDownload] error upload playback', {
message: error?.message,
@@ -333,31 +284,47 @@ const PlaybackDownload = ({ route }) => {
name: error?.name,
details: error?.details,
})
throw error
} finally {
if (tempSourcePath) {
try {
await firebase.storage().ref(tempSourcePath).delete()
} catch { }
} catch (cleanupError) {
console.log('[PlaybackDownload] unable to delete temp source', {
message: cleanupError?.message,
code: cleanupError?.code,
})
}
}
releaseBlobUrl(uri || null)
setIsLoading(false)
}
}, [action, currentUID, pendingPlaybackUrl, projectForDownload, resolveAudioUrl, setIsLoading, uri])
const handleDownloadUri = useCallback(async () => {
if (isPublishing || isDownloading) return
try {
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 (