Skip to content

Commit

Permalink
Merge pull request #102 from nebulabroadcast/75-preview-selecting-reg…
Browse files Browse the repository at this point in the history
…ion-should-seek-to-selection-start

Preview: Improved accuracy, marking and looping
  • Loading branch information
martastain authored Nov 30, 2024
2 parents aeefa2a + c74a33a commit 61658c9
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 51 deletions.
8 changes: 7 additions & 1 deletion backend/api/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,13 @@ def build_query(
if request.query:
for elm in slugify(request.query, make_set=True, min_length=3):
# no need to sanitize this. slugified strings are safe
cond_list.append(f"id IN (SELECT id FROM ft WHERE value LIKE '{elm}%')")
q = f"""
id IN (
SELECT id FROM ft
WHERE object_type = 0 AND value LIKE '{elm}%'
)
"""
cond_list.append(q)

# Access control

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/InputTimecode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const InputTimecode = ({
setText('')
return
}
const tc = new Timecode(value * fps, fps)
const tc = new Timecode(Math.floor(value * fps), fps)
let str = tc.toString()
str = str.replace(/;/g, ':')
str = str.substring(0, 11)
Expand Down
94 changes: 80 additions & 14 deletions frontend/src/containers/VideoPlayer/Trackbar.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { useRef, useEffect, useState, useCallback } from 'react'
import { useRef, useEffect, useState, useCallback, useMemo } from 'react'
import { Canvas, Navbar } from '/src/components'

const Trackbar = ({
videoDuration,
duration,
currentTime,
isPlaying,
onScrub,
markIn,
markOut,
bufferedRanges,
frameRate,
marks,
}) => {
const canvasRef = useRef(null)
const resizeObserverRef = useRef(null)
const [isDragging, setIsDragging] = useState(false)

const auxMarks = marks || {}

const numFrames = useMemo(
() => Math.floor(duration * frameRate),
[frameRate, duration]
)
// DRAW

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

const frameWidth = numFrames >= width ? 2 : width / numFrames
const handleWidth = Math.max(frameWidth, 2)

if (numFrames < width / 4) {
for (let i = 1; i < numFrames; i++) {
const x = (i / numFrames) * width
ctx.strokeStyle = '#303030'
ctx.beginPath()
ctx.moveTo(x, 0)
ctx.lineTo(x, height)
ctx.stroke()
}
}

// Draw the buffered ranges
for (const range of bufferedRanges) {
const start = (range.start / videoDuration) * width
const end = (range.end / videoDuration) * width
const start = (range.start / duration) * width
const end = (range.end / duration) * width
ctx.strokeStyle = '#885bff'
ctx.beginPath()
ctx.moveTo(start, 0)
Expand All @@ -42,7 +66,7 @@ const Trackbar = ({

let markInX = 0
if (markIn) {
markInX = (markIn / videoDuration) * width
markInX = (markIn / duration) * width
ctx.strokeStyle = 'green'
ctx.beginPath()
ctx.moveTo(markInX, 0)
Expand All @@ -52,7 +76,7 @@ const Trackbar = ({

let markOutX = width
if (markOut) {
markOutX = (markOut / videoDuration) * width
markOutX = (markOut / duration) * width
ctx.strokeStyle = 'red'
ctx.beginPath()
ctx.moveTo(markOutX, 0)
Expand All @@ -66,27 +90,55 @@ const Trackbar = ({
ctx.lineTo(markOutX, height - 1)
ctx.stroke()

// Draw the poster frame

if (auxMarks.poster_frame) {
const posterFrameX = (auxMarks.poster_frame / duration) * width
ctx.fillStyle = '#ff00ff'
ctx.beginPath()
ctx.moveTo(posterFrameX - 4, 0)
ctx.lineTo(posterFrameX + 4, 0)
ctx.lineTo(posterFrameX, 4)
ctx.closePath()
ctx.fill()
}

//
// Draw the handle
const progressWidth = (currentTime / videoDuration) * width
//

let currentFrame
if (isPlaying) {
currentFrame = Math.floor(currentTime * frameRate)
if (currentFrame >= numFrames) {
currentFrame = numFrames - 1
}
} else {
currentFrame = Math.floor(currentTime * frameRate)
}

const progressX =
currentFrame >= numFrames ? width : (currentFrame / numFrames) * width

ctx.fillStyle = '#0ed3fe'
ctx.beginPath()
ctx.fillRect(progressWidth - 1, 0, 2, height)
ctx.fillRect(progressX - 1, 0, handleWidth, height)
ctx.fill()
}, [currentTime, videoDuration, markIn, markOut])
}, [currentTime, duration, markIn, markOut])

// Events

useEffect(() => {
drawSlider()
}, [currentTime, videoDuration, markIn, markOut])
}, [currentTime, duration, markIn, markOut])

// Dragging

const handleMouseMove = (e) => {
if (!isDragging) return
const rect = canvasRef.current.getBoundingClientRect()
const x = e.clientX - rect.left
const newTime = (x / rect.width) * videoDuration
const newTime = (x / rect.width) * duration
onScrub(newTime)
}

Expand Down Expand Up @@ -116,13 +168,27 @@ const Trackbar = ({

const handleClick = (e) => {
const rect = canvasRef.current.getBoundingClientRect()
console.log(rect)
console.log(e.clientX, rect.left)
const x = e.clientX - rect.left
const newTime = (x / rect.width) * videoDuration
const newTime = (x / rect.width) * duration
onScrub(newTime)
}

useEffect(() => {
if (!canvasRef.current) return

resizeObserverRef.current = new ResizeObserver(() => {
drawSlider()
})

resizeObserverRef.current.observe(canvasRef.current)

return () => {
if (resizeObserverRef.current) {
resizeObserverRef.current.disconnect()
}
}
}, [drawSlider])

return (
<Navbar>
<Canvas
Expand Down
83 changes: 73 additions & 10 deletions frontend/src/containers/VideoPlayer/VideoPlayerBody.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from 'styled-components'

import { useAudioContext } from './AudioContext'
import { useState, useEffect } from 'react'
import { useState, useEffect, useRef } from 'react'

import VUMeter from './VUMeter'
import ChannelSelect from './ChannelSelect'
Expand Down Expand Up @@ -36,6 +36,7 @@ const VideoPlayerBody = ({ ...props }) => {
const [currentTime, setCurrentTime] = useState(0)
const [duration, setDuration] = useState(0)
const [isPlaying, setIsPlaying] = useState(false)
const [loop, setLoop] = useState(false)
const [videoDimensions, setVideoDimensions] = useState({
width: 600,
height: 400,
Expand All @@ -46,6 +47,14 @@ const VideoPlayerBody = ({ ...props }) => {
const [markIn, setMarkIn] = useState()
const [markOut, setMarkOut] = useState()

const desiredFrame = useRef(0)

useEffect(() => {
if (props.setPosition) {
props.setPosition(currentTime)
}
}, [currentTime])

// Propagating markIn and markOut to parent component

useEffect(() => {
Expand All @@ -61,6 +70,14 @@ const VideoPlayerBody = ({ ...props }) => {
console.log('markIn', props.markIn, markIn)
if (props.markIn || null !== markIn) {
setMarkIn(props.markIn)
if (
!isPlaying &&
videoRef.current &&
props.markIn !== undefined &&
currentTime !== props.markOut
) {
videoRef.current.currentTime = props.markIn
}
}
if (props.markOut || null !== markOut) {
setMarkOut(props.markOut)
Expand Down Expand Up @@ -90,8 +107,7 @@ const VideoPlayerBody = ({ ...props }) => {

useEffect(() => {
if (!videoRef.current) return
// TODO:
const frameLength = 0.04
const frameLength = 1 / props.frameRate
const updateTime = () => {
const actualDuration = videoRef.current.duration
if (actualDuration !== duration) {
Expand All @@ -107,10 +123,19 @@ const VideoPlayerBody = ({ ...props }) => {
} else {
setCurrentTime(actualTime)
}
desiredFrame.current = Math.floor(actualTime * props.frameRate)
}
updateTime()
}, [videoRef, isPlaying, duration])

const seekToTime = (newTime) => {
const videoElement = videoRef.current
if (!videoElement) return
if (videoElement.currentTime === newTime) return
videoElement.currentTime = newTime
setCurrentTime(newTime)
}

const handleLoad = () => {
setIsPlaying(false)
setCurrentTime(0)
Expand All @@ -126,6 +151,32 @@ const VideoPlayerBody = ({ ...props }) => {
setBufferedRanges([])
}

const handlePlay = () => {
setIsPlaying(true)
}

const handlePause = () => {
seekToTime(desiredFrame.current / props.frameRate)
setTimeout(() => {
if (videoRef.current?.paused) {
seekToTime(desiredFrame.current / props.frameRate)
setIsPlaying(false)
}
}, 20)
}

const handleEnded = () => {
if (!isPlaying) {
return
}
if (loop) {
videoRef.current.currentTime = 0
videoRef.current.play()
} else {
setIsPlaying(false)
}
}

const handleProgress = (e) => {
// create a list of buffered time ranges
const buffered = e.target.buffered
Expand All @@ -139,9 +190,11 @@ const VideoPlayerBody = ({ ...props }) => {
}

const handleScrub = (newTime) => {
videoRef.current.pause()
videoRef.current.currentTime = newTime
setCurrentTime(newTime)
desiredFrame.current = Math.floor(newTime * props.frameRate)
const desiredTime = desiredFrame.current / props.frameRate
if (desiredTime === videoRef.current?.currentTime) return
videoRef.current?.pause()
seekToTime(desiredTime)
}

// half of the nodes will be on the left, the other half on the right
Expand All @@ -156,6 +209,12 @@ const VideoPlayerBody = ({ ...props }) => {
<InputTimecode value={currentTime} tooltip="Current position" />
<ChannelSelect gainNodes={gainNodes} />
<div style={{ flex: 1 }} />
<Button
icon="loop"
tooltip={loop ? 'Disable loop' : 'Enable loop'}
onClick={() => setLoop(!loop)}
active={loop}
/>
<Button
icon="crop_free"
tooltip={showOverlay ? 'Hide guides' : 'Show guides'}
Expand Down Expand Up @@ -186,9 +245,9 @@ const VideoPlayerBody = ({ ...props }) => {
controls={false}
onLoad={handleLoad}
onLoadedMetadata={handleLoadedMetadata}
onPlaying={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
onTimeUpdate={() => setCurrentTime(videoRef.current.currentTime)}
onEnded={handleEnded}
onPlay={handlePlay}
onPause={handlePause}
onProgress={handleProgress}
src={props.src}
style={{ outline: showOverlay ? '1px solid silver' : 'none' }}
Expand All @@ -204,16 +263,20 @@ const VideoPlayerBody = ({ ...props }) => {
</section>

<Trackbar
videoDuration={duration}
duration={duration}
frameRate={props.frameRate}
isPlaying={isPlaying}
currentTime={currentTime}
onScrub={handleScrub}
markIn={markIn}
markOut={markOut}
bufferedRanges={bufferedRanges}
marks={props.marks}
/>

<VideoPlayerControls
markIn={markIn}
frameRate={props.frameRate}
setMarkIn={setMarkIn}
markOut={markOut}
setMarkOut={setMarkOut}
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/containers/VideoPlayer/VideoPlayerControls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const VideoPlayerControls = ({
setMarkIn,
setMarkOut,
isPlaying,
frameRate,
}) => {
const { videoRef } = useAudioContext()

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

const frameLength = 0.04 // TODO
const frameLength = 1 / frameRate

const handlePlayPause = () => {
if (videoRef.current.paused) {
Expand Down Expand Up @@ -115,7 +116,7 @@ const VideoPlayerControls = ({
break
case 'ArrowLeft':
handleGoBack1()
e.preventDevault()
e.preventDefault()
break
case 'ArrowRight':
handleGoForward1()
Expand Down
Loading

0 comments on commit 61658c9

Please sign in to comment.