Skip to content

Commit 61658c9

Browse files
authored
Merge pull request #102 from nebulabroadcast/75-preview-selecting-region-should-seek-to-selection-start
Preview: Improved accuracy, marking and looping
2 parents aeefa2a + c74a33a commit 61658c9

File tree

7 files changed

+211
-51
lines changed

7 files changed

+211
-51
lines changed

backend/api/browse.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,13 @@ def build_query(
217217
if request.query:
218218
for elm in slugify(request.query, make_set=True, min_length=3):
219219
# no need to sanitize this. slugified strings are safe
220-
cond_list.append(f"id IN (SELECT id FROM ft WHERE value LIKE '{elm}%')")
220+
q = f"""
221+
id IN (
222+
SELECT id FROM ft
223+
WHERE object_type = 0 AND value LIKE '{elm}%'
224+
)
225+
"""
226+
cond_list.append(q)
221227

222228
# Access control
223229

frontend/src/components/InputTimecode.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const InputTimecode = ({
2121
setText('')
2222
return
2323
}
24-
const tc = new Timecode(value * fps, fps)
24+
const tc = new Timecode(Math.floor(value * fps), fps)
2525
let str = tc.toString()
2626
str = str.replace(/;/g, ':')
2727
str = str.substring(0, 11)

frontend/src/containers/VideoPlayer/Trackbar.jsx

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
import { useRef, useEffect, useState, useCallback } from 'react'
1+
import { useRef, useEffect, useState, useCallback, useMemo } from 'react'
22
import { Canvas, Navbar } from '/src/components'
33

44
const Trackbar = ({
5-
videoDuration,
5+
duration,
66
currentTime,
7+
isPlaying,
78
onScrub,
89
markIn,
910
markOut,
1011
bufferedRanges,
12+
frameRate,
13+
marks,
1114
}) => {
1215
const canvasRef = useRef(null)
16+
const resizeObserverRef = useRef(null)
1317
const [isDragging, setIsDragging] = useState(false)
1418

19+
const auxMarks = marks || {}
20+
21+
const numFrames = useMemo(
22+
() => Math.floor(duration * frameRate),
23+
[frameRate, duration]
24+
)
1525
// DRAW
1626

1727
const drawSlider = useCallback(() => {
@@ -29,10 +39,24 @@ const Trackbar = ({
2939
ctx.fillStyle = '#19161f'
3040
ctx.fillRect(0, 0, width, height)
3141

42+
const frameWidth = numFrames >= width ? 2 : width / numFrames
43+
const handleWidth = Math.max(frameWidth, 2)
44+
45+
if (numFrames < width / 4) {
46+
for (let i = 1; i < numFrames; i++) {
47+
const x = (i / numFrames) * width
48+
ctx.strokeStyle = '#303030'
49+
ctx.beginPath()
50+
ctx.moveTo(x, 0)
51+
ctx.lineTo(x, height)
52+
ctx.stroke()
53+
}
54+
}
55+
3256
// Draw the buffered ranges
3357
for (const range of bufferedRanges) {
34-
const start = (range.start / videoDuration) * width
35-
const end = (range.end / videoDuration) * width
58+
const start = (range.start / duration) * width
59+
const end = (range.end / duration) * width
3660
ctx.strokeStyle = '#885bff'
3761
ctx.beginPath()
3862
ctx.moveTo(start, 0)
@@ -42,7 +66,7 @@ const Trackbar = ({
4266

4367
let markInX = 0
4468
if (markIn) {
45-
markInX = (markIn / videoDuration) * width
69+
markInX = (markIn / duration) * width
4670
ctx.strokeStyle = 'green'
4771
ctx.beginPath()
4872
ctx.moveTo(markInX, 0)
@@ -52,7 +76,7 @@ const Trackbar = ({
5276

5377
let markOutX = width
5478
if (markOut) {
55-
markOutX = (markOut / videoDuration) * width
79+
markOutX = (markOut / duration) * width
5680
ctx.strokeStyle = 'red'
5781
ctx.beginPath()
5882
ctx.moveTo(markOutX, 0)
@@ -66,27 +90,55 @@ const Trackbar = ({
6690
ctx.lineTo(markOutX, height - 1)
6791
ctx.stroke()
6892

93+
// Draw the poster frame
94+
95+
if (auxMarks.poster_frame) {
96+
const posterFrameX = (auxMarks.poster_frame / duration) * width
97+
ctx.fillStyle = '#ff00ff'
98+
ctx.beginPath()
99+
ctx.moveTo(posterFrameX - 4, 0)
100+
ctx.lineTo(posterFrameX + 4, 0)
101+
ctx.lineTo(posterFrameX, 4)
102+
ctx.closePath()
103+
ctx.fill()
104+
}
105+
106+
//
69107
// Draw the handle
70-
const progressWidth = (currentTime / videoDuration) * width
108+
//
109+
110+
let currentFrame
111+
if (isPlaying) {
112+
currentFrame = Math.floor(currentTime * frameRate)
113+
if (currentFrame >= numFrames) {
114+
currentFrame = numFrames - 1
115+
}
116+
} else {
117+
currentFrame = Math.floor(currentTime * frameRate)
118+
}
119+
120+
const progressX =
121+
currentFrame >= numFrames ? width : (currentFrame / numFrames) * width
122+
71123
ctx.fillStyle = '#0ed3fe'
72124
ctx.beginPath()
73-
ctx.fillRect(progressWidth - 1, 0, 2, height)
125+
ctx.fillRect(progressX - 1, 0, handleWidth, height)
74126
ctx.fill()
75-
}, [currentTime, videoDuration, markIn, markOut])
127+
}, [currentTime, duration, markIn, markOut])
76128

77129
// Events
78130

79131
useEffect(() => {
80132
drawSlider()
81-
}, [currentTime, videoDuration, markIn, markOut])
133+
}, [currentTime, duration, markIn, markOut])
82134

83135
// Dragging
84136

85137
const handleMouseMove = (e) => {
86138
if (!isDragging) return
87139
const rect = canvasRef.current.getBoundingClientRect()
88140
const x = e.clientX - rect.left
89-
const newTime = (x / rect.width) * videoDuration
141+
const newTime = (x / rect.width) * duration
90142
onScrub(newTime)
91143
}
92144

@@ -116,13 +168,27 @@ const Trackbar = ({
116168

117169
const handleClick = (e) => {
118170
const rect = canvasRef.current.getBoundingClientRect()
119-
console.log(rect)
120-
console.log(e.clientX, rect.left)
121171
const x = e.clientX - rect.left
122-
const newTime = (x / rect.width) * videoDuration
172+
const newTime = (x / rect.width) * duration
123173
onScrub(newTime)
124174
}
125175

176+
useEffect(() => {
177+
if (!canvasRef.current) return
178+
179+
resizeObserverRef.current = new ResizeObserver(() => {
180+
drawSlider()
181+
})
182+
183+
resizeObserverRef.current.observe(canvasRef.current)
184+
185+
return () => {
186+
if (resizeObserverRef.current) {
187+
resizeObserverRef.current.disconnect()
188+
}
189+
}
190+
}, [drawSlider])
191+
126192
return (
127193
<Navbar>
128194
<Canvas

frontend/src/containers/VideoPlayer/VideoPlayerBody.jsx

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import styled from 'styled-components'
22

33
import { useAudioContext } from './AudioContext'
4-
import { useState, useEffect } from 'react'
4+
import { useState, useEffect, useRef } from 'react'
55

66
import VUMeter from './VUMeter'
77
import ChannelSelect from './ChannelSelect'
@@ -36,6 +36,7 @@ const VideoPlayerBody = ({ ...props }) => {
3636
const [currentTime, setCurrentTime] = useState(0)
3737
const [duration, setDuration] = useState(0)
3838
const [isPlaying, setIsPlaying] = useState(false)
39+
const [loop, setLoop] = useState(false)
3940
const [videoDimensions, setVideoDimensions] = useState({
4041
width: 600,
4142
height: 400,
@@ -46,6 +47,14 @@ const VideoPlayerBody = ({ ...props }) => {
4647
const [markIn, setMarkIn] = useState()
4748
const [markOut, setMarkOut] = useState()
4849

50+
const desiredFrame = useRef(0)
51+
52+
useEffect(() => {
53+
if (props.setPosition) {
54+
props.setPosition(currentTime)
55+
}
56+
}, [currentTime])
57+
4958
// Propagating markIn and markOut to parent component
5059

5160
useEffect(() => {
@@ -61,6 +70,14 @@ const VideoPlayerBody = ({ ...props }) => {
6170
console.log('markIn', props.markIn, markIn)
6271
if (props.markIn || null !== markIn) {
6372
setMarkIn(props.markIn)
73+
if (
74+
!isPlaying &&
75+
videoRef.current &&
76+
props.markIn !== undefined &&
77+
currentTime !== props.markOut
78+
) {
79+
videoRef.current.currentTime = props.markIn
80+
}
6481
}
6582
if (props.markOut || null !== markOut) {
6683
setMarkOut(props.markOut)
@@ -90,8 +107,7 @@ const VideoPlayerBody = ({ ...props }) => {
90107

91108
useEffect(() => {
92109
if (!videoRef.current) return
93-
// TODO:
94-
const frameLength = 0.04
110+
const frameLength = 1 / props.frameRate
95111
const updateTime = () => {
96112
const actualDuration = videoRef.current.duration
97113
if (actualDuration !== duration) {
@@ -107,10 +123,19 @@ const VideoPlayerBody = ({ ...props }) => {
107123
} else {
108124
setCurrentTime(actualTime)
109125
}
126+
desiredFrame.current = Math.floor(actualTime * props.frameRate)
110127
}
111128
updateTime()
112129
}, [videoRef, isPlaying, duration])
113130

131+
const seekToTime = (newTime) => {
132+
const videoElement = videoRef.current
133+
if (!videoElement) return
134+
if (videoElement.currentTime === newTime) return
135+
videoElement.currentTime = newTime
136+
setCurrentTime(newTime)
137+
}
138+
114139
const handleLoad = () => {
115140
setIsPlaying(false)
116141
setCurrentTime(0)
@@ -126,6 +151,32 @@ const VideoPlayerBody = ({ ...props }) => {
126151
setBufferedRanges([])
127152
}
128153

154+
const handlePlay = () => {
155+
setIsPlaying(true)
156+
}
157+
158+
const handlePause = () => {
159+
seekToTime(desiredFrame.current / props.frameRate)
160+
setTimeout(() => {
161+
if (videoRef.current?.paused) {
162+
seekToTime(desiredFrame.current / props.frameRate)
163+
setIsPlaying(false)
164+
}
165+
}, 20)
166+
}
167+
168+
const handleEnded = () => {
169+
if (!isPlaying) {
170+
return
171+
}
172+
if (loop) {
173+
videoRef.current.currentTime = 0
174+
videoRef.current.play()
175+
} else {
176+
setIsPlaying(false)
177+
}
178+
}
179+
129180
const handleProgress = (e) => {
130181
// create a list of buffered time ranges
131182
const buffered = e.target.buffered
@@ -139,9 +190,11 @@ const VideoPlayerBody = ({ ...props }) => {
139190
}
140191

141192
const handleScrub = (newTime) => {
142-
videoRef.current.pause()
143-
videoRef.current.currentTime = newTime
144-
setCurrentTime(newTime)
193+
desiredFrame.current = Math.floor(newTime * props.frameRate)
194+
const desiredTime = desiredFrame.current / props.frameRate
195+
if (desiredTime === videoRef.current?.currentTime) return
196+
videoRef.current?.pause()
197+
seekToTime(desiredTime)
145198
}
146199

147200
// half of the nodes will be on the left, the other half on the right
@@ -156,6 +209,12 @@ const VideoPlayerBody = ({ ...props }) => {
156209
<InputTimecode value={currentTime} tooltip="Current position" />
157210
<ChannelSelect gainNodes={gainNodes} />
158211
<div style={{ flex: 1 }} />
212+
<Button
213+
icon="loop"
214+
tooltip={loop ? 'Disable loop' : 'Enable loop'}
215+
onClick={() => setLoop(!loop)}
216+
active={loop}
217+
/>
159218
<Button
160219
icon="crop_free"
161220
tooltip={showOverlay ? 'Hide guides' : 'Show guides'}
@@ -186,9 +245,9 @@ const VideoPlayerBody = ({ ...props }) => {
186245
controls={false}
187246
onLoad={handleLoad}
188247
onLoadedMetadata={handleLoadedMetadata}
189-
onPlaying={() => setIsPlaying(true)}
190-
onPause={() => setIsPlaying(false)}
191-
onTimeUpdate={() => setCurrentTime(videoRef.current.currentTime)}
248+
onEnded={handleEnded}
249+
onPlay={handlePlay}
250+
onPause={handlePause}
192251
onProgress={handleProgress}
193252
src={props.src}
194253
style={{ outline: showOverlay ? '1px solid silver' : 'none' }}
@@ -204,16 +263,20 @@ const VideoPlayerBody = ({ ...props }) => {
204263
</section>
205264

206265
<Trackbar
207-
videoDuration={duration}
266+
duration={duration}
267+
frameRate={props.frameRate}
268+
isPlaying={isPlaying}
208269
currentTime={currentTime}
209270
onScrub={handleScrub}
210271
markIn={markIn}
211272
markOut={markOut}
212273
bufferedRanges={bufferedRanges}
274+
marks={props.marks}
213275
/>
214276

215277
<VideoPlayerControls
216278
markIn={markIn}
279+
frameRate={props.frameRate}
217280
setMarkIn={setMarkIn}
218281
markOut={markOut}
219282
setMarkOut={setMarkOut}

frontend/src/containers/VideoPlayer/VideoPlayerControls.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const VideoPlayerControls = ({
99
setMarkIn,
1010
setMarkOut,
1111
isPlaying,
12+
frameRate,
1213
}) => {
1314
const { videoRef } = useAudioContext()
1415

@@ -20,7 +21,7 @@ const VideoPlayerControls = ({
2021
markOutRef.current = markOut
2122
}, [markIn, markOut])
2223

23-
const frameLength = 0.04 // TODO
24+
const frameLength = 1 / frameRate
2425

2526
const handlePlayPause = () => {
2627
if (videoRef.current.paused) {
@@ -115,7 +116,7 @@ const VideoPlayerControls = ({
115116
break
116117
case 'ArrowLeft':
117118
handleGoBack1()
118-
e.preventDevault()
119+
e.preventDefault()
119120
break
120121
case 'ArrowRight':
121122
handleGoForward1()

0 commit comments

Comments
 (0)