index.tsx raw
1 import { cn, isInViewport } from '@/lib/utils'
2 import { useContentPolicy } from '@/providers/ContentPolicyProvider'
3 import { useUserPreferences } from '@/providers/UserPreferencesProvider'
4 import mediaManager from '@/services/media-manager.service'
5 import { useEffect, useRef, useState } from 'react'
6 import ExternalLink from '../ExternalLink'
7
8 export default function VideoPlayer({ src, className }: { src: string; className?: string }) {
9 const { autoplay } = useContentPolicy()
10 const { muteMedia, updateMuteMedia } = useUserPreferences()
11 const [error, setError] = useState(false)
12 const videoRef = useRef<HTMLVideoElement>(null)
13 const containerRef = useRef<HTMLDivElement>(null)
14
15 useEffect(() => {
16 const video = videoRef.current
17 const container = containerRef.current
18
19 if (!video || !container || error) return
20
21 const observer = new IntersectionObserver(
22 ([entry]) => {
23 if (entry.isIntersecting && autoplay) {
24 setTimeout(() => {
25 if (isInViewport(container)) {
26 mediaManager.autoPlay(video)
27 }
28 }, 200)
29 }
30
31 if (!entry.isIntersecting) {
32 mediaManager.pause(video)
33 }
34 },
35 { threshold: 1 }
36 )
37
38 observer.observe(container)
39
40 return () => {
41 observer.unobserve(container)
42 }
43 }, [autoplay, error])
44
45 useEffect(() => {
46 if (!videoRef.current) return
47
48 const video = videoRef.current
49
50 const handleVolumeChange = () => {
51 updateMuteMedia(video.muted)
52 }
53
54 video.addEventListener('volumechange', handleVolumeChange)
55
56 return () => {
57 video.removeEventListener('volumechange', handleVolumeChange)
58 }
59 }, [])
60
61 useEffect(() => {
62 const video = videoRef.current
63 if (!video || video.muted === muteMedia) return
64
65 if (muteMedia) {
66 video.muted = true
67 } else {
68 video.muted = false
69 }
70 }, [muteMedia])
71
72 if (error) {
73 return <ExternalLink url={src} />
74 }
75
76 return (
77 <div ref={containerRef}>
78 <video
79 ref={videoRef}
80 controls
81 playsInline
82 className={cn('rounded-xl max-h-[80vh] sm:max-h-[60vh] border', className)}
83 src={src}
84 onClick={(e) => e.stopPropagation()}
85 onPlay={(event) => {
86 mediaManager.play(event.currentTarget)
87 }}
88 muted={muteMedia}
89 onError={() => setError(true)}
90 />
91 </div>
92 )
93 }
94