diff --git a/src/components/VideoPlayer/index.tsx b/src/components/VideoPlayer/index.tsx index 4e95be2f..4a0277c5 100644 --- a/src/components/VideoPlayer/index.tsx +++ b/src/components/VideoPlayer/index.tsx @@ -1,5 +1,7 @@ import { cn } from '@/lib/utils' import NsfwOverlay from '../NsfwOverlay' +import { useEffect, useRef } from 'react' +import VideoManager from '@/services/videomanager' export default function VideoPlayer({ src, @@ -12,15 +14,63 @@ export default function VideoPlayer({ isNsfw?: boolean size?: 'normal' | 'small' }) { + const videoRef = useRef(null) + const containerRef = useRef(null) + + useEffect(() => { + const video = videoRef.current + const container = containerRef.current + + if (!video || !container) return + + const observer = new IntersectionObserver( + async ([entry]) => { + const isVisible = entry.isIntersecting + + if (!isVisible && !video.paused) { + await VideoManager.enterPiP(video) + } + + if (isVisible) { + if ( + document.pictureInPictureElement === video || + (video as any).webkitPresentationMode === 'picture-in-picture' + ) { + await VideoManager.exitPiP(video) + } + } + }, + { + threshold: 0.5 + } + ) + + observer.observe(container) + + return () => { + observer.unobserve(container) + } + }, []) + + const handlePlay = async () => { + const video = videoRef.current + if (!video) return + + await VideoManager.playVideo(video) + } return ( -
-
+ <> +
+
+ ) } diff --git a/src/services/videomanager.ts b/src/services/videomanager.ts new file mode 100644 index 00000000..5f20efe4 --- /dev/null +++ b/src/services/videomanager.ts @@ -0,0 +1,38 @@ +class VideoManager { + private static currentVideo: HTMLVideoElement | null = null + + static async enterPiP(video: HTMLVideoElement) { + if (VideoManager.currentVideo && VideoManager.currentVideo !== video) { + await VideoManager.exitPiP(VideoManager.currentVideo) + } + + if ('requestPictureInPicture' in video) { + await video.requestPictureInPicture() + } else if ('webkitSetPresentationMode' in video) { + ;(video as any).webkitSetPresentationMode('picture-in-picture') + } + } + + static async exitPiP(video: HTMLVideoElement) { + video.pause() + if (document.pictureInPictureElement === video) { + await document.exitPictureInPicture() + } else if ('webkitSetPresentationMode' in video) { + ;(video as any).webkitSetPresentationMode('inline') + } + + if (VideoManager.currentVideo === video) { + VideoManager.currentVideo = null + } + } + + static async playVideo(video: HTMLVideoElement) { + if (VideoManager.currentVideo && VideoManager.currentVideo !== video) { + await VideoManager.exitPiP(VideoManager.currentVideo) + } + VideoManager.currentVideo = video + video.play() + } +} + +export default VideoManager