Skip to content

Commit befa494

Browse files
Fix and simplify the review/cut step (#1253)
See commits
2 parents 35ca67a + c4ae6c4 commit befa494

File tree

1 file changed

+19
-113
lines changed

1 file changed

+19
-113
lines changed

src/steps/review/preview.tsx

Lines changed: 19 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import {
2-
forwardRef, useState, useRef, useEffect, useImperativeHandle, SyntheticEvent,
2+
forwardRef, useState, useRef, useImperativeHandle, SyntheticEvent,
33
} from "react";
44
import { useTranslation } from "react-i18next";
5-
import { match, notNullish, useColorScheme } from "@opencast/appkit";
5+
import { notNullish, useColorScheme } from "@opencast/appkit";
66

77
import { useStudioState } from "../../studio-state";
88
import CutOutIcon from "./cut-out-icon.svg";
99
import { VideoBox } from "../../ui/VideoBox";
10-
import { ALMOST_ZERO } from ".";
1110
import { 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

Comments
 (0)