Bpistle/src/components/VideoPlayer/index.tsx
codytseng 481603d0e8 feat: add video loop playback setting
Add a toggle in General Settings to enable/disable video loop playback,
following the same pattern as the existing autoplay setting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 13:55:53 +08:00

94 lines
2.4 KiB
TypeScript

import { cn, isInViewport } from '@/lib/utils'
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
import mediaManager from '@/services/media-manager.service'
import { useEffect, useRef, useState } from 'react'
import ExternalLink from '../ExternalLink'
export default function VideoPlayer({ src, className }: { src: string; className?: string }) {
const { autoplay, videoLoop } = useContentPolicy()
const { muteMedia, updateMuteMedia } = useUserPreferences()
const [error, setError] = useState(false)
const videoRef = useRef<HTMLVideoElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const video = videoRef.current
const container = containerRef.current
if (!video || !container || error) return
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && autoplay) {
setTimeout(() => {
if (isInViewport(container)) {
mediaManager.autoPlay(video)
}
}, 200)
}
if (!entry.isIntersecting) {
mediaManager.pause(video)
}
},
{ threshold: 1 }
)
observer.observe(container)
return () => {
observer.unobserve(container)
}
}, [autoplay, error])
useEffect(() => {
if (!videoRef.current) return
const video = videoRef.current
const handleVolumeChange = () => {
updateMuteMedia(video.muted)
}
video.addEventListener('volumechange', handleVolumeChange)
return () => {
video.removeEventListener('volumechange', handleVolumeChange)
}
}, [])
useEffect(() => {
const video = videoRef.current
if (!video || video.muted === muteMedia) return
if (muteMedia) {
video.muted = true
} else {
video.muted = false
}
}, [muteMedia])
if (error) {
return <ExternalLink url={src} />
}
return (
<div ref={containerRef}>
<video
ref={videoRef}
controls
playsInline
loop={videoLoop}
className={cn('max-h-[80vh] rounded-xl border sm:max-h-[60vh]', className)}
src={src}
onClick={(e) => e.stopPropagation()}
onPlay={(event) => {
mediaManager.play(event.currentTarget)
}}
muted={muteMedia}
onError={() => setError(true)}
/>
</div>
)
}