Files
musicland/src/components/FullscreenIntroVideo.web.js
T

252 lines
5.5 KiB
JavaScript

import { Portal } from '@gorhom/portal'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import { resolveMediaUri } from '../utils/resolveMediaUri'
const CLOSE_THRESHOLD_SECONDS = 0.35
const CLOSE_POLL_INTERVAL_MS = 500
const overlayStyle = {
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0,
backgroundColor: 'black',
justifyContent: 'center',
alignItems: 'center',
zIndex: 9999,
}
const videoStyle = {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
}
const closeButtonStyle = {
position: 'absolute',
top: 50,
right: 20,
backgroundColor: '#00000080',
paddingVertical: 10,
paddingHorizontal: 14,
borderRadius: 20,
borderWidth: 1,
borderColor: '#FFFFFF55',
cursor: 'pointer',
}
const closeTextStyle = {
color: '#FFF',
fontSize: 14,
}
const playButtonStyle = {
backgroundColor: '#000000CC',
paddingVertical: 14,
paddingHorizontal: 20,
borderRadius: 24,
borderWidth: 1,
borderColor: '#FFFFFF88',
cursor: 'pointer',
zIndex: 1,
}
const FullscreenIntroVideo = ({
url,
visible = true,
onClose,
contentFit = 'cover',
allowSkip = true,
requireAudio = false,
offerSoundActivation = false,
skipLabel = 'Passer la vidéo',
playLabel = 'Lire la vidéo avec le son',
}) => {
const videoRef = useRef(null)
const [uri, setUri] = useState(null)
const [muted, setMuted] = useState(false)
const [awaitingInteraction, setAwaitingInteraction] = useState(false)
const hasClosedRef = useRef(false)
const handleClose = useCallback(() => {
if (hasClosedRef.current) return
hasClosedRef.current = true
onClose?.()
}, [onClose])
const evaluateShouldClose = useCallback(() => {
const video = videoRef.current
if (!video || hasClosedRef.current) {
return
}
if (video.ended) {
handleClose()
return
}
const remaining = video.duration - video.currentTime
if (Number.isFinite(remaining) && remaining <= CLOSE_THRESHOLD_SECONDS) {
handleClose()
}
}, [handleClose])
useEffect(() => {
let isMounted = true
const assignUri = (nextUri) => {
if (isMounted) {
setMuted(false)
setAwaitingInteraction(false)
setUri(nextUri)
}
}
const load = async () => {
try {
const nextUri = await resolveMediaUri(url)
if (!nextUri) {
if (isMounted) {
setUri(null)
handleClose()
}
return
}
assignUri(nextUri)
} catch {
if (isMounted) {
setUri(null)
handleClose()
}
}
}
load()
return () => {
isMounted = false
}
}, [handleClose, url])
useEffect(() => {
if (!visible || !uri) {
return undefined
}
hasClosedRef.current = false
let rafId
const attemptPlay = () => {
const video = videoRef.current
if (!video) {
rafId = requestAnimationFrame(attemptPlay)
return
}
video.currentTime = 0
const result = video.play()
if (result?.catch) {
result.catch((error) => {
if (error?.name === 'NotAllowedError' && requireAudio) {
setAwaitingInteraction(true)
} else if (error?.name === 'NotAllowedError' && !muted) {
video.muted = true
setMuted(true)
setAwaitingInteraction(offerSoundActivation)
const mutedResult = video.play()
mutedResult?.catch?.(() => {})
}
})
}
}
attemptPlay()
const pollId = setInterval(evaluateShouldClose, CLOSE_POLL_INTERVAL_MS)
return () => {
if (rafId) {
cancelAnimationFrame(rafId)
}
clearInterval(pollId)
const video = videoRef.current
video?.pause()
}
}, [evaluateShouldClose, muted, offerSoundActivation, requireAudio, uri, visible])
const handleManualPlay = useCallback(() => {
const video = videoRef.current
if (!video) return
setMuted(false)
video.muted = false
video.currentTime = 0
const result = video.play()
if (result?.then) {
result.then(() => setAwaitingInteraction(false)).catch(() => {})
}
}, [])
useEffect(() => {
const video = videoRef.current
if (!video || !muted) {
return
}
const result = video.play()
if (result?.catch) {
result.catch(() => {})
}
}, [muted])
if (!visible) {
return null
}
return (
<Portal>
<View pointerEvents="box-none" style={overlayStyle}>
{uri ? (
<video
ref={videoRef}
src={uri}
style={{ ...videoStyle, objectFit: contentFit }}
playsInline
autoPlay
loop={false}
muted={muted}
controls={false}
onEnded={handleClose}
onPause={evaluateShouldClose}
onTimeUpdate={evaluateShouldClose}
onError={handleClose}
/>
) : null}
{awaitingInteraction ? (
<Pressable onPress={handleManualPlay} style={playButtonStyle}>
<Text style={closeTextStyle}>{playLabel}</Text>
</Pressable>
) : null}
{allowSkip ? (
<Pressable onPress={handleClose} style={closeButtonStyle}>
<Text style={closeTextStyle}>{skipLabel}</Text>
</Pressable>
) : null}
</View>
</Portal>
)
}
export default FullscreenIntroVideo