11import {
2- forwardRef , useState , useRef , useEffect , useImperativeHandle , SyntheticEvent ,
2+ forwardRef , useState , useRef , useImperativeHandle , SyntheticEvent ,
33} from "react" ;
44import { useTranslation } from "react-i18next" ;
5- import { match , notNullish , useColorScheme } from "@opencast/appkit" ;
5+ import { notNullish , useColorScheme } from "@opencast/appkit" ;
66
77import { useStudioState } from "../../studio-state" ;
88import CutOutIcon from "./cut-out-icon.svg" ;
99import { VideoBox } from "../../ui/VideoBox" ;
10- import { ALMOST_ZERO } from "." ;
1110import { SHORTCUTS , useShortcut } from "../../shortcuts" ;
1211
1312
@@ -37,12 +36,6 @@ export const Preview = forwardRef<PreviewHandle, PreviewProps>((
3736 const videoRefs = [ useRef < HTMLVideoElement > ( null ) , useRef < HTMLVideoElement > ( null ) ] ;
3837 const allVideos = videoRefs . slice ( 0 , recordings . length ) ;
3938
40- const desktopIndex = recordings . length === 2
41- ? ( recordings [ 0 ] . deviceType === "desktop" ? 0 : 1 )
42- : null ;
43-
44- // The index of the last video ref that received an event (0 or 1).
45- const lastOrigin = useRef < 0 | 1 > ( ) ;
4639
4740 // When updating the currenTime, i.e. the play position, we want to throttle
4841 // this somehow. Just always setting `currentTime` is not ideal: consider
@@ -72,16 +65,16 @@ export const Preview = forwardRef<PreviewHandle, PreviewProps>((
7265
7366 useImperativeHandle ( ref , ( ) => ( {
7467 get currentTime ( ) {
75- return notNullish ( videoRefs [ lastOrigin . current ?? 0 ] . current ?. currentTime ) ;
68+ return notNullish ( videoRefs [ 0 ] . current ?. currentTime ) ;
7669 } ,
7770 set currentTime ( newTime ) {
7871 setTime ( newTime ) ;
7972 } ,
8073 get duration ( ) {
81- return notNullish ( videoRefs [ lastOrigin . current ?? 0 ] . current ?. duration ) ;
74+ return notNullish ( videoRefs [ 0 ] . current ?. duration ) ;
8275 } ,
8376 get isPlaying ( ) {
84- const v = videoRefs [ lastOrigin . current ?? 0 ] . current ;
77+ const v = videoRefs [ 0 ] . current ;
8578 return v != null && v . currentTime > 0 && ! v . paused && ! v . ended ;
8679 } ,
8780 get isReadyToPlay ( ) {
@@ -98,79 +91,19 @@ export const Preview = forwardRef<PreviewHandle, PreviewProps>((
9891 } ,
9992 } ) ) ;
10093
101- // Some browsers don't calculate the duration for the recorded videos
102- // preventing us from seeking in the video. We force it below
103- // in the event handlers of the video elements, but we want to hold off
104- // on some effects until that calculation is done.
105- type DurationCalcState = "done" | "started" ;
106- const durationCalculationProgress = [
107- useRef < DurationCalcState > ( ) ,
108- useRef < DurationCalcState > ( ) ,
109- ] ;
110- const [ durationsCalculated , setDurationsCalculated ] = useState < boolean > ( false ) ;
111-
11294 // Some logic to decide whether we currently are in a part of the video that
11395 // will be removed. The state will be updated in `onTimeUpdate` below and is
11496 // only here to trigger a rerender: the condition for rendering the overlay is
11597 // below.
11698 const isInCutRegion = ( time : number ) =>
11799 ( start !== null && time < start ) || ( end !== null && time > end ) ;
118- const currentTime = videoRefs [ lastOrigin . current ?? 0 ] . current ?. currentTime || 0 ;
100+ const currentTime = videoRefs [ 0 ] . current ?. currentTime || 0 ;
119101 const overlayVisible = isInCutRegion ( currentTime ) ;
120102 const [ , setOverlayVisible ] = useState ( overlayVisible ) ;
121103
122- useEffect ( ( ) => {
123- if ( durationsCalculated ) {
124- onReady ( ) ;
125- }
126- } , [ onReady , durationsCalculated ] ) ;
127-
128- // Setup backup synchronization between both video elements
129- useEffect ( ( ) => {
130- if ( ! durationsCalculated ) {
131- return ;
132- }
133-
134- if ( desktopIndex != null ) {
135- // If we have two recordings, both will have audio. But the user doesn't
136- // want to hear audio twice, so we mute one video element. Particularly,
137- // we mute the desktop video, as there the audio/video synchronization is
138- // not as critical.
139- notNullish ( videoRefs [ desktopIndex ] . current ) . volume = 0 ;
140-
141- const va = notNullish ( videoRefs [ 0 ] . current ) ;
142- const vb = notNullish ( videoRefs [ 1 ] . current ) ;
143-
144- // We regularly check if both video elements diverge too much from one
145- // another.
146- let frameCounter = 0 ;
147- let fixRequest : number ;
148- const fixTime = ( ) => {
149- // Only run every 60 frames.
150- if ( frameCounter % 60 === 0 ) {
151- // We want the difference to be below 150ms. Usually, even without
152- // this backup solution, it should be below 50ms at all time. That's
153- // what testing showed.
154- const diff = Math . abs ( va . currentTime - vb . currentTime ) ;
155- if ( diff > 0.15 && lastOrigin . current != null ) {
156- const origin = videoRefs [ lastOrigin . current ] . current ;
157- const target = videoRefs [ lastOrigin . current === 0 ? 1 : 0 ] . current ;
158- notNullish ( target ) . currentTime = notNullish ( origin ) . currentTime ;
159- }
160- }
161-
162- frameCounter ++ ;
163- fixRequest = window . requestAnimationFrame ( fixTime ) ;
164- } ;
165- fixRequest = window . requestAnimationFrame ( fixTime ) ;
166-
167- return ( ) => window . cancelAnimationFrame ( fixRequest ) ;
168- }
169- } ) ;
170-
171104
172105 const jumpInTime = ( diff : number ) =>
173- setTime ( notNullish ( videoRefs [ lastOrigin . current ?? 0 ] . current ?. currentTime ) + diff ) ;
106+ setTime ( notNullish ( videoRefs [ 0 ] . current ?. currentTime ) + diff ) ;
174107
175108 // TODO: This is obviously not always correct. Finding out the FPS of the
176109 // recording is surprisingly tricky. And actually, browsers seem to record
@@ -210,49 +143,22 @@ export const Preview = forwardRef<PreviewHandle, PreviewProps>((
210143 ref = { videoRefs [ index ] }
211144 key = { index }
212145 src = { recording . url }
213- onLoadedData = { event => {
214- // Force the browser to calculate the duration of the stream
215- // by seeking way past its end. *fingers crossed*
216- // We reset this later in an effect. (See above.)
217- // Also without setting the current time once initially,
218- // some browsers show a black video element instead of the first frame.
219- event . currentTarget . currentTime = Number . MAX_VALUE ;
220- durationCalculationProgress [ index ] . current = "started" ;
221- } }
146+ onLoadedData = { ( ) => onReady ( ) }
222147 onSeeked = { ( ) => {
223- if ( durationsCalculated ) {
224- const isOtherSeeking = videoRefs [ index == 0 ? 1 : 0 ] . current ?. seeking ;
225- const queued = queuedSeek . current ;
226- if ( ! isOtherSeeking && queued != null ) {
227- allVideos . forEach ( r => {
228- if ( r . current ) {
229- r . current . currentTime = queued ;
230- }
231- } ) ;
232- queuedSeek . current = null ;
233- }
148+ const isOtherSeeking = videoRefs [ index == 0 ? 1 : 0 ] . current ?. seeking ;
149+ const queued = queuedSeek . current ;
150+ if ( ! isOtherSeeking && queued != null ) {
151+ allVideos . forEach ( r => {
152+ if ( r . current ) {
153+ r . current . currentTime = queued ;
154+ }
155+ } ) ;
156+ queuedSeek . current = null ;
234157 }
235158 } }
236159 onTimeUpdate = { event => {
237- if ( durationsCalculated ) {
238- setOverlayVisible ( isInCutRegion ( event . currentTarget . currentTime ) ) ;
239- onTimeUpdate ( event ) ;
240- } else {
241- match ( notNullish ( durationCalculationProgress [ index ] . current ) , {
242- "started" : ( ) => {
243- event . currentTarget . currentTime = ALMOST_ZERO ;
244- durationCalculationProgress [ index ] . current = "done" ;
245- } ,
246- "done" : ( ) => {
247- const finishedCalculations = durationCalculationProgress
248- . filter ( p => p . current === "done" )
249- . length ;
250- if ( finishedCalculations === recordings . length ) {
251- setDurationsCalculated ( true ) ;
252- }
253- } ,
254- } ) ;
255- }
160+ setOverlayVisible ( isInCutRegion ( event . currentTarget . currentTime ) ) ;
161+ onTimeUpdate ( event ) ;
256162 } }
257163
258164 // For iOS: without the autoplay attribute, the `loadeddata` event is
0 commit comments