1+ import { useState , useEffect , useRef , useContext } from 'react' ;
2+ import { SegmentContext } from './SegmentContext' ;
3+
4+ let apiLoaded = false ;
5+ let apiCallbacks = [ ] ;
6+
7+ function ensureYouTubeAPI ( ) {
8+ if ( window . YT && window . YT . Player ) {
9+ apiLoaded = true ;
10+ return ;
11+ }
12+ if ( document . querySelector ( 'script[src="https://www.youtube.com/iframe_api"]' ) ) return ;
13+ let script = document . createElement ( 'script' ) ;
14+ script . src = 'https://www.youtube.com/iframe_api' ;
15+ document . head . appendChild ( script ) ;
16+ window . onYouTubeIframeAPIReady = ( ) => {
17+ apiLoaded = true ;
18+ apiCallbacks . forEach ( cb => cb ( ) ) ;
19+ apiCallbacks = [ ] ;
20+ } ;
21+ }
22+
23+ function onAPIReady ( cb ) {
24+ if ( apiLoaded ) {
25+ cb ( ) ;
26+ } else {
27+ apiCallbacks . push ( cb ) ;
28+ }
29+ }
30+
31+ function formatTimecode ( seconds ) {
32+ let h = Math . floor ( seconds / 3600 ) ;
33+ let m = Math . floor ( ( seconds % 3600 ) / 60 ) ;
34+ let s = Math . floor ( seconds % 60 ) ;
35+ let ms = Math . round ( ( seconds % 1 ) * 1000 ) ;
36+ return `${ String ( h ) . padStart ( 2 , '0' ) } :${ String ( m ) . padStart ( 2 , '0' ) } :${ String ( s ) . padStart ( 2 , '0' ) } .${ String ( ms ) . padStart ( 3 , '0' ) } ` ;
37+ }
38+
39+ function VideoPlayer ( { videoId, showSegmentControls } ) {
40+ let containerRef = useRef ( null ) ;
41+ let playerRef = useRef ( null ) ;
42+ let [ ready , setReady ] = useState ( false ) ;
43+ let [ state , setState ] = useState ( 'idle' ) ;
44+ let [ startTimecode , setStartTimecode ] = useState ( null ) ;
45+ let { showSegmentButton, setSegmentTimecode } = useContext ( SegmentContext ) ;
46+
47+ useEffect ( ( ) => {
48+ let mounted = true ;
49+ let container = containerRef . current ;
50+
51+ let playerDiv = document . createElement ( 'div' ) ;
52+ container . appendChild ( playerDiv ) ;
53+
54+ ensureYouTubeAPI ( ) ;
55+ onAPIReady ( ( ) => {
56+ if ( ! mounted ) return ;
57+ playerRef . current = new window . YT . Player ( playerDiv , {
58+ videoId,
59+ width : '100%' ,
60+ height : '300' ,
61+ events : {
62+ onReady : ( ) => {
63+ if ( ! mounted ) return ;
64+ let iframe = playerRef . current . getIframe ( ) ;
65+ iframe . setAttribute ( 'data-video-id' , videoId ) ;
66+ setReady ( true ) ;
67+ }
68+ }
69+ } ) ;
70+ } ) ;
71+
72+ return ( ) => {
73+ mounted = false ;
74+ if ( playerRef . current && playerRef . current . destroy ) {
75+ playerRef . current . destroy ( ) ;
76+ playerRef . current = null ;
77+ }
78+ while ( container . firstChild ) {
79+ container . removeChild ( container . firstChild ) ;
80+ }
81+ setReady ( false ) ;
82+ } ;
83+ } , [ videoId ] ) ;
84+
85+ let handleClick = ( ) => {
86+ if ( ! ready || ! playerRef . current ) return ;
87+ let current = formatTimecode ( playerRef . current . getCurrentTime ( ) ) ;
88+
89+ if ( state === 'idle' ) {
90+ setStartTimecode ( current ) ;
91+ setState ( 'start_captured' ) ;
92+ } else {
93+ setSegmentTimecode ( `${ startTimecode } --> ${ current } @${ videoId } ` ) ;
94+ setState ( 'idle' ) ;
95+ setStartTimecode ( null ) ;
96+ }
97+ } ;
98+
99+ let handleCancel = ( ) => {
100+ setState ( 'idle' ) ;
101+ setStartTimecode ( null ) ;
102+ } ;
103+
104+ let shouldShowButton = showSegmentButton && showSegmentControls ;
105+
106+ return (
107+ < div className = "video-player-container" >
108+ < div ref = { containerRef } />
109+ { shouldShowButton && (
110+ < div className = "segment-selector justify-content-end" >
111+ < button
112+ className = { `btn btn-sm ${ state === 'idle' ? 'btn-outline-danger' : 'btn-warning' } ` }
113+ onClick = { handleClick }
114+ disabled = { ! ready }
115+ >
116+ { ! ready
117+ ? 'Loading the player…'
118+ : state === 'idle'
119+ ? 'Define segment start'
120+ : `Define segment end (start: ${ startTimecode } )`
121+ }
122+ </ button >
123+ { state === 'start_captured' && (
124+ < button
125+ className = "btn btn-sm btn-outline-danger ms-2"
126+ onClick = { handleCancel }
127+ >
128+ Cancel
129+ </ button >
130+ ) }
131+ </ div >
132+ ) }
133+ </ div >
134+ ) ;
135+ }
136+
137+ export default VideoPlayer ;
0 commit comments