Files
musicland/src/screens/Playback/PlaybackAiStatus.web.js
T
Thomas Demirdjian c8656d6273 heygen
2026-08-04 10:59:27 +02:00

284 lines
8.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import useMinuit from 'react-native-minuit/src/hooks/useMinuit.js'
import React, { useMemo, useState } from 'react'
import { Image, Text, View } from 'react-native'
import { background } from '../../assets'
import useDataFromRef from '../../hooks/useDataFromRef'
import ActivityLoader from '../../components/ActivityLoader'
import BorderGradientButton from '../../components/BorderGradientButton'
import GradientButton from '../../components/GradientButton'
import MusicLandHeader from '../../components/MusicLandHeader'
import { getFunctionsClient, projectsRef } from '../../config/firebase'
import Page from '../../layouts/Page'
import { Routes } from '../../navigation'
import { goBack, navigate, push } from '../../navigation/NavigationService'
import { useUserData } from '../../providers/UserDataProvider'
import { gutters, Palette } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts'
import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader'
import { PLAYBACK_AI_STATUS } from '../../utils/playbackAi'
const FUNCTIONS_REGION = 'europe-west1'
const resolveProject = ({ routeProject, selectedProject, liveProject }) => {
if (liveProject?.id) return liveProject
if (routeProject?.id && selectedProject?.id === routeProject.id) {
return selectedProject
}
return routeProject || null
}
const callValidatePlaybackDraft = (payload) => {
const callable = getFunctionsClient(FUNCTIONS_REGION).httpsCallable('heygen-validatePlaybackDraft')
return callable(payload)
}
const PlaybackAiStatus = ({ route }) => {
const { project: routeProject } = route.params || {}
const { selectedProject } = useUserData() || {}
const { setTooltip } = useMinuit()
const [isValidating, setIsValidating] = useState(false)
const projectId = routeProject?.id || selectedProject?.id || null
const { data: liveProject = null } = useDataFromRef({
ref: projectId ? projectsRef.doc(projectId) : null,
simpleRef: true,
listener: true,
condition: !!projectId,
refreshArray: [projectId],
})
const project = useMemo(
() => resolveProject({ routeProject, selectedProject, liveProject }),
[liveProject, routeProject, selectedProject]
)
const status =
project?.playbackStatus ||
(routeProject?.playbackGenerating ? PLAYBACK_AI_STATUS.GENERATING : null)
const draftUrl = project?.playbackDraftUrl || routeProject?.playbackDraftUrl || null
const handleValidate = async () => {
try {
if (!project?.id) {
throw new Error('Projet introuvable')
}
setIsValidating(true)
await callValidatePlaybackDraft({ projectId: project.id })
const latestProjectSnap = await projectsRef.doc(project.id).get()
const latestProject = latestProjectSnap.exists
? { ...latestProjectSnap.data(), id: latestProjectSnap.id }
: project
navigate(Routes.PlaybackDownload, {
action: 'playback',
project: latestProject,
})
} catch (error) {
setTooltip({
type: 'error',
text: error?.message || 'Impossible de valider le playback IA',
})
} finally {
setIsValidating(false)
}
}
const handleRetry = () => {
push(Routes.PlaybackAi, {
project,
initialPhotoUrl: project?.playbackPhotoUrl || null,
})
}
return (
<Page
backgroundImg={background.playbackBG2}
backgroundColor="#07111B"
headerType="NONE"
>
<MusicLandHeader progress={38} onPressBack={goBack} />
<View style={{ flex: 1, paddingBottom: gutters * 2 }}>
<CreateLyricsHeader title="Playback IA" />
<View style={styles.content}>
<View style={styles.headerCard}>
{project?.coverUrl ? (
<Image source={{ uri: project.coverUrl }} style={styles.cover} resizeMode="cover" />
) : null}
<View style={{ flex: 1, gap: 4 }}>
<Text style={styles.title}>{project?.title || 'Sans titre'}</Text>
<Text style={styles.subtitle}>Format vertical 9:16 généré avec HeyGen</Text>
</View>
</View>
{status === PLAYBACK_AI_STATUS.GENERATING ? (
<View style={styles.stateCard}>
<ActivityLoader defaultMessage="HeyGen génère ton playback IA..." />
<Text style={styles.helperText}>
Tu peux fermer cet écran et revenir plus tard. Une notification te sera envoyée dès
que ta vidéo sera prête.
</Text>
</View>
) : null}
{status === PLAYBACK_AI_STATUS.FAILED ? (
<View style={styles.stateCard}>
<Text style={styles.errorTitle}>La génération a échoué</Text>
<Text style={styles.helperText}>
{project?.playbackError || 'HeyGen na pas réussi à générer la vidéo.'}
</Text>
<BorderGradientButton title="Réessayer" onPress={handleRetry} />
</View>
) : null}
{status === PLAYBACK_AI_STATUS.DRAFT_READY ? (
<>
<View style={styles.videoCard}>
{draftUrl ? (
<video
src={draftUrl}
controls
playsInline
style={styles.video}
/>
) : (
<View style={[styles.videoFallback, styles.emptyVideo]}>
<Text style={styles.helperText}>La vidéo HeyGen est introuvable.</Text>
</View>
)}
</View>
<View style={styles.actions}>
<GradientButton
title="Valider ce playback"
onPress={handleValidate}
disabled={isValidating || !draftUrl}
/>
<BorderGradientButton
title="Recommencer"
onPress={handleRetry}
disabled={isValidating}
/>
{isValidating ? (
<ActivityLoader defaultMessage="Validation du playback IA..." />
) : null}
</View>
</>
) : null}
{status === PLAYBACK_AI_STATUS.READY && project?.playbackUrl ? (
<View style={styles.stateCard}>
<Text style={styles.successTitle}>Le playback IA est déjà validé</Text>
<GradientButton
title="Continuer"
onPress={() =>
navigate(Routes.PlaybackDownload, {
action: 'playback',
project,
})
}
/>
</View>
) : null}
</View>
</View>
</Page>
)
}
const styles = {
content: {
flex: 1,
width: '92%',
alignSelf: 'center',
gap: 16,
},
headerCard: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
padding: 16,
borderRadius: 18,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.14)',
backgroundColor: 'rgba(5,12,24,0.76)',
},
cover: {
width: 68,
height: 68,
borderRadius: 14,
},
title: {
color: Palette.white,
fontSize: 18,
fontFamily: FONT_FAMILY.InterSemiBold,
},
subtitle: {
color: Palette.white,
opacity: 0.7,
fontFamily: FONT_FAMILY.InterRegular,
},
stateCard: {
flex: 1,
minHeight: 240,
borderRadius: 20,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.14)',
backgroundColor: 'rgba(5,12,24,0.8)',
alignItems: 'center',
justifyContent: 'center',
gap: 14,
paddingHorizontal: 20,
},
videoCard: {
width: '60%',
maxWidth: 360,
aspectRatio: 9 / 16,
alignSelf: 'center',
borderRadius: 22,
overflow: 'hidden',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.14)',
backgroundColor: 'rgba(5,12,24,0.8)',
padding: 12,
},
video: {
width: '100%',
height: '100%',
borderRadius: 18,
backgroundColor: '#0A0A0A',
objectFit: 'contain',
},
videoFallback: {
width: '100%',
height: '100%',
borderRadius: 18,
backgroundColor: '#0A0A0A',
},
emptyVideo: {
alignItems: 'center',
justifyContent: 'center',
},
helperText: {
color: Palette.white,
opacity: 0.72,
fontFamily: FONT_FAMILY.InterRegular,
lineHeight: 20,
textAlign: 'center',
},
errorTitle: {
color: Palette.white,
fontSize: 18,
fontFamily: FONT_FAMILY.InterSemiBold,
},
successTitle: {
color: Palette.white,
fontSize: 18,
fontFamily: FONT_FAMILY.InterSemiBold,
},
actions: {
gap: 12,
},
}
export default PlaybackAiStatus