Download src/components/simple-videos-player.tsx from DynamicIntelligence/dynamic_intelligence_sample_data: direct link, hf CLI and curl.
- Browser
- Download file 9.08 kB
-
https://huggingface.co/spaces/DynamicIntelligence/dynamic_intelligence_sample_data/resolve/08eabcff2e38dc7d9251a2d92489cf16d9f752b4/src/components/simple-videos-player.tsx
- Command line
-
hf download hf://spaces/DynamicIntelligence/dynamic_intelligence_sample_data@08eabcff2e38dc7d9251a2d92489cf16d9f752b4/src/components/simple-videos-player.tsx
-
curl -L -o simple-videos-player.tsx https://huggingface.co/spaces/DynamicIntelligence/dynamic_intelligence_sample_data/resolve/08eabcff2e38dc7d9251a2d92489cf16d9f752b4/src/components/simple-videos-player.tsx
9.08 kB
| "use client"; | |
| import React, { useEffect, useRef } from "react"; | |
| import { useTime } from "../context/time-context"; | |
| import { FaExpand, FaCompress, FaTimes, FaEye } from "react-icons/fa"; | |
| type VideoInfo = { | |
| filename: string; | |
| url: string; | |
| isSegmented?: boolean; | |
| segmentStart?: number; | |
| segmentEnd?: number; | |
| segmentDuration?: number; | |
| }; | |
| type VideoPlayerProps = { | |
| videosInfo: VideoInfo[]; | |
| onVideosReady?: () => void; | |
| }; | |
| export const SimpleVideosPlayer = ({ | |
| videosInfo, | |
| onVideosReady, | |
| }: VideoPlayerProps) => { | |
| const { currentTime, setCurrentTime, isPlaying, setIsPlaying } = useTime(); | |
| const videoRefs = useRef<(HTMLVideoElement | null)[]>([]); | |
| const [hiddenVideos, setHiddenVideos] = React.useState<string[]>([]); | |
| const [enlargedVideo, setEnlargedVideo] = React.useState<string | null>(null); | |
| const [showHiddenMenu, setShowHiddenMenu] = React.useState(false); | |
| const [videosReady, setVideosReady] = React.useState(false); | |
| const firstVisibleIdx = videosInfo.findIndex( | |
| (video) => !hiddenVideos.includes(video.filename) | |
| ); | |
| // Initialize video refs array | |
| useEffect(() => { | |
| videoRefs.current = videoRefs.current.slice(0, videosInfo.length); | |
| }, [videosInfo.length]); | |
| // Handle videos ready | |
| useEffect(() => { | |
| let readyCount = 0; | |
| const checkReady = () => { | |
| readyCount++; | |
| if (readyCount === videosInfo.length && onVideosReady) { | |
| setVideosReady(true); | |
| onVideosReady(); | |
| setIsPlaying(true); | |
| } | |
| }; | |
| videoRefs.current.forEach((video, index) => { | |
| if (video) { | |
| const info = videosInfo[index]; | |
| // Setup segment boundaries | |
| if (info.isSegmented) { | |
| const handleTimeUpdate = () => { | |
| const segmentEnd = info.segmentEnd || video.duration; | |
| const segmentStart = info.segmentStart || 0; | |
| if (video.currentTime >= segmentEnd - 0.05) { | |
| video.currentTime = segmentStart; | |
| // Also update the global time to reset to start | |
| if (index === firstVisibleIdx) { | |
| setCurrentTime(0); | |
| } | |
| } | |
| }; | |
| const handleLoadedData = () => { | |
| video.currentTime = info.segmentStart || 0; | |
| checkReady(); | |
| }; | |
| video.addEventListener('timeupdate', handleTimeUpdate); | |
| video.addEventListener('loadeddata', handleLoadedData); | |
| // Store cleanup | |
| (video as any)._segmentHandlers = () => { | |
| video.removeEventListener('timeupdate', handleTimeUpdate); | |
| video.removeEventListener('loadeddata', handleLoadedData); | |
| }; | |
| } else { | |
| // For non-segmented videos, handle end of video | |
| const handleEnded = () => { | |
| video.currentTime = 0; | |
| if (index === firstVisibleIdx) { | |
| setCurrentTime(0); | |
| } | |
| }; | |
| video.addEventListener('ended', handleEnded); | |
| video.addEventListener('canplaythrough', checkReady, { once: true }); | |
| // Store cleanup | |
| (video as any)._segmentHandlers = () => { | |
| video.removeEventListener('ended', handleEnded); | |
| }; | |
| } | |
| } | |
| }); | |
| return () => { | |
| videoRefs.current.forEach((video) => { | |
| if (video && (video as any)._segmentHandlers) { | |
| (video as any)._segmentHandlers(); | |
| } | |
| }); | |
| }; | |
| }, [videosInfo, onVideosReady, setIsPlaying, firstVisibleIdx, setCurrentTime]); | |
| // Handle play/pause | |
| useEffect(() => { | |
| if (!videosReady) return; | |
| videoRefs.current.forEach((video, idx) => { | |
| if (video && !hiddenVideos.includes(videosInfo[idx].filename)) { | |
| if (isPlaying) { | |
| video.play().catch(e => { | |
| if (e.name !== 'AbortError') { | |
| console.error("Error playing video"); | |
| } | |
| }); | |
| } else { | |
| video.pause(); | |
| } | |
| } | |
| }); | |
| }, [isPlaying, videosReady, hiddenVideos, videosInfo]); | |
| // Sync video times | |
| useEffect(() => { | |
| if (!videosReady) return; | |
| videoRefs.current.forEach((video, index) => { | |
| if (video && !hiddenVideos.includes(videosInfo[index].filename)) { | |
| const info = videosInfo[index]; | |
| let targetTime = currentTime; | |
| if (info.isSegmented) { | |
| targetTime = (info.segmentStart || 0) + currentTime; | |
| } | |
| if (Math.abs(video.currentTime - targetTime) > 0.2) { | |
| video.currentTime = targetTime; | |
| } | |
| } | |
| }); | |
| }, [currentTime, videosInfo, videosReady, hiddenVideos]); | |
| // Handle time update from first visible video | |
| const handleTimeUpdate = (e: React.SyntheticEvent<HTMLVideoElement>) => { | |
| const video = e.target as HTMLVideoElement; | |
| const videoIndex = videoRefs.current.findIndex(ref => ref === video); | |
| const info = videosInfo[videoIndex]; | |
| if (info) { | |
| let globalTime = video.currentTime; | |
| if (info.isSegmented) { | |
| globalTime = video.currentTime - (info.segmentStart || 0); | |
| } | |
| setCurrentTime(globalTime); | |
| } | |
| }; | |
| // Handle play click for segmented videos | |
| const handlePlay = (video: HTMLVideoElement, info: VideoInfo) => { | |
| if (info.isSegmented) { | |
| const segmentStart = info.segmentStart || 0; | |
| const segmentEnd = info.segmentEnd || video.duration; | |
| if (video.currentTime < segmentStart || video.currentTime >= segmentEnd) { | |
| video.currentTime = segmentStart; | |
| } | |
| } | |
| video.play(); | |
| }; | |
| return ( | |
| <> | |
| {/* Hidden videos menu */} | |
| {hiddenVideos.length > 0 && ( | |
| <div className="relative mb-4"> | |
| <button | |
| className="flex items-center gap-2 rounded bg-slate-800 px-3 py-2 text-sm text-slate-100 hover:bg-slate-700 border border-slate-500" | |
| onClick={() => setShowHiddenMenu(!showHiddenMenu)} | |
| > | |
| <FaEye /> Show Hidden Videos ({hiddenVideos.length}) | |
| </button> | |
| {showHiddenMenu && ( | |
| <div className="absolute left-0 mt-2 w-max rounded border border-slate-500 bg-slate-900 shadow-lg p-2 z-50"> | |
| <div className="mb-2 text-xs text-slate-300"> | |
| Restore hidden videos: | |
| </div> | |
| {hiddenVideos.map((filename) => ( | |
| <button | |
| key={filename} | |
| className="block w-full text-left px-2 py-1 rounded hover:bg-slate-700 text-slate-100" | |
| onClick={() => setHiddenVideos(prev => prev.filter(v => v !== filename))} | |
| > | |
| {filename} | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {/* Videos */} | |
| <div className="flex flex-wrap gap-x-2 gap-y-6"> | |
| {videosInfo.map((info, idx) => { | |
| if (hiddenVideos.includes(info.filename)) return null; | |
| const isEnlarged = enlargedVideo === info.filename; | |
| const isFirstVisible = idx === firstVisibleIdx; | |
| return ( | |
| <div | |
| key={info.filename} | |
| className={`${ | |
| isEnlarged | |
| ? "z-40 fixed inset-0 bg-black bg-opacity-90 flex flex-col items-center justify-center" | |
| : "max-w-96" | |
| }`} | |
| > | |
| <p className="truncate w-full rounded-t-xl bg-gray-800 px-2 text-sm text-gray-300 flex items-center justify-between"> | |
| <span>{info.filename}</span> | |
| <span className="flex gap-1"> | |
| <button | |
| title={isEnlarged ? "Minimize" : "Enlarge"} | |
| className="ml-2 p-1 hover:bg-slate-700 rounded" | |
| onClick={() => setEnlargedVideo(isEnlarged ? null : info.filename)} | |
| > | |
| {isEnlarged ? <FaCompress /> : <FaExpand />} | |
| </button> | |
| <button | |
| title="Hide Video" | |
| className="ml-1 p-1 hover:bg-slate-700 rounded" | |
| onClick={() => setHiddenVideos(prev => [...prev, info.filename])} | |
| disabled={videosInfo.filter(v => !hiddenVideos.includes(v.filename)).length === 1} | |
| > | |
| <FaTimes /> | |
| </button> | |
| </span> | |
| </p> | |
| <video | |
| ref={el => videoRefs.current[idx] = el} | |
| className={`w-full object-contain ${ | |
| isEnlarged ? "max-h-[90vh] max-w-[90vw]" : "" | |
| }`} | |
| muted | |
| preload="auto" | |
| onPlay={(e) => handlePlay(e.currentTarget, info)} | |
| onTimeUpdate={isFirstVisible ? handleTimeUpdate : undefined} | |
| > | |
| <source src={info.url} type="video/mp4" /> | |
| Your browser does not support the video tag. | |
| </video> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| </> | |
| ); | |
| }; | |
| export default SimpleVideosPlayer; | |