Files
musicland/src/components/FullscreenIntroVideo.native.js
T
2026-09-08 14:20:23 +02:00

139 lines
3.6 KiB
JavaScript

import React, { useCallback, useEffect, useRef } from 'react'
import { Pressable, StyleSheet, Text, View } from 'react-native'
import { VideoView, useVideoPlayer } from 'expo-video'
import { videos } from '../assets'
import { Portal } from '@gorhom/portal'
// Fullscreen vertical video overlay without controls
// Props:
// - url?: string | number (require), source of the video. Defaults to videos.test
// - visible?: boolean, when false returns null
// - onClose: () => void, called when user skips or when video ends
const CLOSE_THRESHOLD_SECONDS = 0.35
const FullscreenIntroVideo = ({
url,
visible = true,
onClose,
contentFit = 'contain',
allowSkip = true,
skipLabel = 'Passer la vidéo',
}) => {
const source = url ? (typeof url === 'string' ? { uri: url } : url) : videos.test
const hasClosedRef = useRef(false)
const handleClose = useCallback(() => {
if (hasClosedRef.current) return
hasClosedRef.current = true
try {
onClose?.()
} catch (e) {}
}, [onClose])
const player = useVideoPlayer(source, (p) => {
p.loop = false
p.timeUpdateEventInterval = 0.25
})
useEffect(() => {
if (visible) {
hasClosedRef.current = false
} else {
hasClosedRef.current = true
try {
player?.pause?.()
} catch (e) {}
}
}, [player, visible])
useEffect(() => {
if (!visible) {
return
}
try {
player?.play?.()
} catch (e) {}
}, [player, visible])
useEffect(() => {
if (!player || !visible) return
const playToEndSub = player.addListener?.('playToEnd', handleClose)
const timeUpdateSub = player.addListener?.('timeUpdate', ({ currentTime } = {}) => {
if (!player?.duration || hasClosedRef.current) {
return
}
const remaining = player.duration - currentTime
if (Number.isFinite(remaining) && remaining <= CLOSE_THRESHOLD_SECONDS) {
handleClose()
}
})
const playingChangeSub = player.addListener?.('playingChange', ({ isPlaying } = {}) => {
if (isPlaying || !player?.duration || hasClosedRef.current) {
return
}
const remaining = player.duration - (player.currentTime ?? 0)
if (Number.isFinite(remaining) && remaining <= CLOSE_THRESHOLD_SECONDS) {
handleClose()
}
})
return () => {
try {
playToEndSub?.remove?.()
timeUpdateSub?.remove?.()
playingChangeSub?.remove?.()
} catch (e) {}
}
}, [handleClose, player, visible])
if (!visible) {
return null
}
return (
<Portal>
<View
pointerEvents="box-none"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'black',
justifyContent: 'center',
alignItems: 'center',
zIndex: 9999,
}}
>
<VideoView
player={player}
nativeControls={false}
contentFit={contentFit}
style={StyleSheet.absoluteFill}
/>
{allowSkip ? (
<Pressable
onPress={handleClose}
style={{
position: 'absolute',
top: 50,
right: 20,
backgroundColor: '#00000080',
paddingVertical: 10,
paddingHorizontal: 14,
borderRadius: 20,
borderWidth: 1,
borderColor: '#FFFFFF55',
}}
>
<Text style={{ color: '#FFF', fontSize: 14 }}>{skipLabel}</Text>
</Pressable>
) : null}
</View>
</Portal>
)
}
export default FullscreenIntroVideo