139 lines
3.6 KiB
JavaScript
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
|