aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/nodes/RecordingBox/ProgressBar.scss7
-rw-r--r--src/client/views/nodes/RecordingBox/ProgressBar.tsx425
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.tsx443
3 files changed, 433 insertions, 442 deletions
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.scss b/src/client/views/nodes/RecordingBox/ProgressBar.scss
index 7768a2f03..28ad25ffa 100644
--- a/src/client/views/nodes/RecordingBox/ProgressBar.scss
+++ b/src/client/views/nodes/RecordingBox/ProgressBar.scss
@@ -37,6 +37,9 @@
cursor: not-allowed;
}
+.progressbar-dragging {
+ cursor: grabbing;
+}
// citation: https://codepen.io/_Master_/pen/PRdjmQ
@keyframes blinker {
@@ -113,6 +116,8 @@ border-color: red;
}
.segment-selected {
- border: 4px solid grey;
+ border: 4px solid #202020;
background-color: red;
+ opacity: .75;
+ cursor: grabbing;
}
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
index 7da3f45be..81f753e44 100644
--- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx
+++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
@@ -1,51 +1,47 @@
-import { disable } from 'colors';
-import { resolveTxt } from 'dns';
-import { videointelligence } from 'googleapis/build/src/apis/videointelligence';
-import { isInteger } from 'lodash';
import * as React from 'react';
import { useEffect, useState, useCallback, useRef } from "react"
import "./ProgressBar.scss"
import { MediaSegment } from './RecordingView';
interface ProgressBarProps {
- videos: MediaSegment[],
- setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>,
- orderVideos: boolean,
- progress: number,
+ videos: MediaSegment[],
+ setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>,
+ orderVideos: boolean,
+ progress: number,
recording: boolean,
doUndo: boolean,
}
interface SegmentBox {
- endTime: number,
- startTime: number,
- order: number,
+ endTime: number,
+ startTime: number,
+ order: number,
}
interface CurrentHover {
- index: number,
- minX: number,
- maxX: number
+ index: number,
+ minX: number,
+ maxX: number
}
export function ProgressBar(props: ProgressBarProps) {
- const progressBarRef = useRef<HTMLDivElement | null>(null)
+ const progressBarRef = useRef<HTMLDivElement | null>(null)
- // array for the order of video segments
- const [segments, setSegments] = useState<JSX.Element[]>([]);
+ // array for the order of video segments
+ const [segments, setSegments] = useState<JSX.Element[]>([]);
const [ordered, setOrdered] = useState<SegmentBox[]>([]);
-
+
const [undoStack, setUndoStack] = useState<SegmentBox[]>([]);
- const [dragged, setDragged] = useState<number>(-1);
+ const [dragged, setDragged] = useState<number>(-1);
+
+ // length of the time removed from the video, in seconds*100
+ const [totalRemovedTime, setTotalRemovedTime] = useState<number>(0);
+
+ // this holds the index of the videoc segment to be removed
+ const [removed, setRemoved] = useState<number>(-1);
- // length of the time removed from the video, in seconds*100
- const [totalRemovedTime, setTotalRemovedTime] = useState<number>(0);
- // this holds the index of the videoc segment to be removed
- const [removed, setRemoved] = useState<number>(-1);
-
-
// abstracted for other uses - brings back the most recently deleted segment
const handleUndo = () => {
// get the last element from the undo if it exists
@@ -60,27 +56,32 @@ export function ProgressBar(props: ProgressBarProps) {
}
useEffect(() => handleUndo(), [props.doUndo])
- useEffect(() => {
- // get segments segment's html using it's id -> make them appeared disabled (or enabled)
- segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
- progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording);
-
- if (props.recording)
- setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'min-content' }}>{props.videos.length + 1}</div>]);
- }, [props.recording])
-
-
- useEffect(() => {
- const totalTime = props.progress * 1000 - totalRemovedTime;
- const segmentsJSX = ordered.map((seg, i) =>
- <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>);
-
- setSegments(segmentsJSX)
- }, [dragged, ordered]);
-
- // to imporve performance, we only want to update the width, not re-render the whole thing
useEffect(() => {
- if (!props.recording) return
+ // get segments segment's html using it's id -> make them appeared disabled (or enabled)
+ segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
+ progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording);
+
+ if (props.recording)
+ setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'min-content' }}>{props.videos.length + 1}</div>]);
+ }, [props.recording])
+
+
+ useEffect(() => {
+ const totalTime = props.progress * 1000 - totalRemovedTime;
+ const segmentsJSX = ordered.map((seg, i) =>
+ <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>);
+
+ setSegments(segmentsJSX)
+ }, [dragged, ordered]);
+
+ // update the cursor to be dragging while moving the floating segment
+ useEffect(() => {
+ progressBarRef.current?.classList.toggle('progressbar-dragging', dragged !== -1);
+ }, [dragged]);
+
+ // to imporve performance, we only want to update the width, not re-render the whole thing
+ useEffect(() => {
+ if (!props.recording) return
const totalTime = props.progress * 1000 - totalRemovedTime;
let remainingTime = totalTime;
segments.forEach((seg, i) => {
@@ -89,197 +90,195 @@ export function ProgressBar(props: ProgressBarProps) {
// update remaining time
remainingTime -= (ordered[i].endTime - ordered[i].startTime);
- // update the width for this segment
+ // update the width for this segment
const htmlId = seg.props.id;
const segmentHtml = document.getElementById(htmlId);
if (segmentHtml) segmentHtml.style.width = `${((ordered[i].endTime - ordered[i].startTime) / totalTime) * 100}%`;
- });
+ });
// update the width of the expanding segment using the remaining time
const segExapandHtml = document.getElementById('segment-expanding');
if (segExapandHtml)
segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
-
- }, [props.progress]);
-
-
-
- useEffect(() => {
- // this useEffect fired when the videos are being rearragned to the order
- // in this case, do nothing.
- if (props.orderVideos) return;
-
- const order = props.videos.length - 1;
- // in this case, a new video is added -> push it onto ordered
- if (order >= ordered.length) {
- const { endTime, startTime } = props.videos.lastElement();
- setOrdered(prevOrdered => {
- return [...prevOrdered, { endTime, startTime , order }];
- });
- }
- // in this case, a video is removed
- else if (order < ordered.length) {
- console.error('warning: video removed from parent');
- }
- }, [props.videos]);
-
- useEffect(() => {
- props.setVideos(vids => ordered.map((seg) => vids[seg.order]));
- }, [props.orderVideos]);
-
- useEffect(() => {
- if (removed === -1) return;
- // update total removed time
- setTotalRemovedTime(prevRemoved => prevRemoved + (ordered[removed].endTime - ordered[removed].startTime));
-
- // put the element on the undo stack
- setUndoStack(prevUndo => [...prevUndo, ordered[removed]]);
- // remove the segment from the array
- setOrdered(prevOrdered => prevOrdered.filter((seg, i) => i !== removed));
- // reset to default/nullish state
- setRemoved(-1);
- }, [removed]);
-
- const updateLastHover = (segId: number): CurrentHover | null => {
- // get the segId of the segment that will become the new bounding area
- const rect = progressBarRef.current?.children[segId].getBoundingClientRect()
- if (rect == null) return null
- return {
- index: segId,
- minX: rect.x,
- maxX: rect.x + rect.width,
- }
- }
-
- const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
- // don't move the videobox element
- e.stopPropagation()
-
- // if recording, do nothing
- if (props.recording) return;
+ }, [props.progress]);
- // get the segment the user clicked on to be dragged
- const clickedSegment = e.target as HTMLDivElement & EventTarget
- // get the profess bar ro add event listeners
- // don't do anything if null
- const progressBar = progressBarRef.current
- if (progressBar == null || clickedSegment.id === progressBar.id) return
- // if holding shift key, let's remove that segment
- // TODO: think of a way to accomodate touch -> maybe if pointerup isn't fired after x secs? or if dragged into a trash bin or smthn
- if (e.shiftKey) {
- const segId = parseInt(clickedSegment.id.split('-')[1]);
- setRemoved(segId);
- return
- }
-
- if (e.ctrlKey) {
- handleUndo();
- return;
- // todo: implement undo stack
- }
+ useEffect(() => {
+ // this useEffect fired when the videos are being rearragned to the order
+ // in this case, do nothing.
+ if (props.orderVideos) return;
+
+ const order = props.videos.length - 1;
+ // in this case, a new video is added -> push it onto ordered
+ if (order >= ordered.length) {
+ const { endTime, startTime } = props.videos.lastElement();
+ setOrdered(prevOrdered => {
+ return [...prevOrdered, { endTime, startTime, order }];
+ });
+ }
+
+ // in this case, a video is removed
+ else if (order < ordered.length) {
+ console.warn('warning: video removed from parent');
+ }
+ }, [props.videos]);
- const ptrId = e.pointerId;
- progressBar.setPointerCapture(ptrId)
-
- const rect = clickedSegment.getBoundingClientRect()
- // id for segment is like 'segment-1' or 'segment-10',
- // so this works to get the id
- const segId = parseInt(clickedSegment.id.split('-')[1])
- // set the selected segment to be the one dragged
- setDragged(segId)
-
- // this is the logic for storing the lower X bound and upper X bound
- // to know whether a swap is needed between two segments
- let lastHover: CurrentHover = {
- index: segId,
- minX: rect.x,
- maxX: rect.x + rect.width,
- }
+ useEffect(() => {
+ props.setVideos(vids => ordered.map((seg) => vids[seg.order]));
+ }, [props.orderVideos]);
- // create the div element that tracks the cursor
- const detchedSegment = document.createElement("div")
- initDeatchSegment(detchedSegment, rect);
-
- const updateSegmentOrder = (event: PointerEvent): void => {
- event.stopPropagation();
- event.preventDefault();
-
- // this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged
- if (!progressBar.hasPointerCapture(ptrId)) {
- placeSegmentandCleanup();
- return;
- }
-
- followCursor(event, detchedSegment, rect)
-
- const curX = event.clientX;
- if (curX < lastHover.minX && lastHover.index > 0) {
- swapSegments(lastHover.index, lastHover.index - 1)
- lastHover = updateLastHover(lastHover.index - 1) ?? lastHover
- }
- else if (curX > lastHover.maxX && lastHover.index < segments.length - 1) {
- swapSegments(lastHover.index, lastHover.index + 1)
- lastHover = updateLastHover(lastHover.index + 1) ?? lastHover
- }
- }
-
- const placeSegmentandCleanup = (event?: PointerEvent): void => {
- event?.stopPropagation();
- event?.preventDefault();
- // remove the update event listener for pointermove
- progressBar.removeEventListener('pointermove', updateSegmentOrder), { once: true }
- // remove the floating segment from the DOM
- detchedSegment.remove()
- // dragged is -1 is equiv to nothing being dragged, so the normal state
- // so this will place the segment in it's location and update the segment bar
- setDragged(-1);
+ useEffect(() => {
+ if (removed === -1) return;
+ // update total removed time
+ setTotalRemovedTime(prevRemoved => prevRemoved + (ordered[removed].endTime - ordered[removed].startTime));
+
+ // put the element on the undo stack
+ setUndoStack(prevUndo => [...prevUndo, ordered[removed]]);
+ // remove the segment from the array
+ setOrdered(prevOrdered => prevOrdered.filter((seg, i) => i !== removed));
+ // reset to default/nullish state
+ setRemoved(-1);
+ }, [removed]);
+
+ const updateLastHover = (segId: number): CurrentHover | null => {
+ // get the segId of the segment that will become the new bounding area
+ const rect = progressBarRef.current?.children[segId].getBoundingClientRect()
+ if (rect == null) return null
+ return {
+ index: segId,
+ minX: rect.x,
+ maxX: rect.x + rect.width,
+ }
}
+ const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+ // don't move the videobox element
+ e.stopPropagation()
- progressBar.addEventListener('pointermove', updateSegmentOrder)
- progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true })
- }
+ // if recording, do nothing
+ if (props.recording) return;
- const swapSegments = (oldIndex: number, newIndex: number) => {
- if (newIndex == null) return
- setOrdered(prevOrdered => {
- const cpy = [...prevOrdered]
- cpy[oldIndex] = cpy[newIndex]
- cpy[newIndex] = prevOrdered[oldIndex]
- return cpy
- })
- setDragged(newIndex)
+ // get the segment the user clicked on to be dragged
+ const clickedSegment = e.target as HTMLDivElement & EventTarget
+
+ // get the profess bar ro add event listeners
+ // don't do anything if null
+ const progressBar = progressBarRef.current
+ if (progressBar == null || clickedSegment.id === progressBar.id) return
+
+ // if holding shift key, let's remove that segment
+ // TODO: think of a way to accomodate touch -> maybe if pointerup isn't fired after x secs? or if dragged into a trash bin or smthn
+ if (e.shiftKey) {
+ const segId = parseInt(clickedSegment.id.split('-')[1]);
+ setRemoved(segId);
+ return
+ }
+
+ if (e.ctrlKey) {
+ handleUndo();
+ return;
+ }
+
+ const ptrId = e.pointerId;
+ progressBar.setPointerCapture(ptrId)
+
+ const rect = clickedSegment.getBoundingClientRect()
+ // id for segment is like 'segment-1' or 'segment-10',
+ // so this works to get the id
+ const segId = parseInt(clickedSegment.id.split('-')[1])
+ // set the selected segment to be the one dragged
+ setDragged(segId)
+
+ // this is the logic for storing the lower X bound and upper X bound
+ // to know whether a swap is needed between two segments
+ let lastHover: CurrentHover = {
+ index: segId,
+ minX: rect.x,
+ maxX: rect.x + rect.width,
+ }
+
+ // create the div element that tracks the cursor
+ const detchedSegment = document.createElement("div")
+ initDeatchSegment(detchedSegment, rect);
+
+ const updateSegmentOrder = (event: PointerEvent): void => {
+ event.stopPropagation();
+ event.preventDefault();
+
+ // this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged
+ if (!progressBar.hasPointerCapture(ptrId)) {
+ placeSegmentandCleanup();
+ return;
+ }
+
+ followCursor(event, detchedSegment);
+
+ const curX = event.clientX;
+ if (curX < lastHover.minX && lastHover.index > 0) {
+ swapSegments(lastHover.index, lastHover.index - 1)
+ lastHover = updateLastHover(lastHover.index - 1) ?? lastHover
+ }
+ else if (curX > lastHover.maxX && lastHover.index < segments.length - 1) {
+ swapSegments(lastHover.index, lastHover.index + 1)
+ lastHover = updateLastHover(lastHover.index + 1) ?? lastHover
+ }
+ }
+
+ const placeSegmentandCleanup = (event?: PointerEvent): void => {
+ event?.stopPropagation();
+ event?.preventDefault();
+ // remove the update event listener for pointermove
+ progressBar.removeEventListener('pointermove', updateSegmentOrder);
+ // remove the floating segment from the DOM
+ detchedSegment.remove();
+ // dragged is -1 is equiv to nothing being dragged, so the normal state
+ // so this will place the segment in it's location and update the segment bar
+ setDragged(-1);
+ }
+
+
+ progressBar.addEventListener('pointermove', updateSegmentOrder)
+ progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true })
}
+ const swapSegments = (oldIndex: number, newIndex: number) => {
+ if (newIndex == null) return
+ setOrdered(prevOrdered => {
+ const cpy = [...prevOrdered]
+ cpy[oldIndex] = cpy[newIndex]
+ cpy[newIndex] = prevOrdered[oldIndex]
+ return cpy
+ })
+ setDragged(newIndex)
+ }
- // functions for the floating segment that tracks the cursor while grabbing it
- const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => {
- dot.classList.add("segment-selected")
- dot.style.transitionDuration = '0s';
- dot.style.position = 'absolute';
- dot.style.zIndex = '999';
- dot.style.width = `${rect.width}px`;
- dot.style.height = `${rect.height}px`;
- dot.style.left = `${rect.x}px`;
- dot.style.top = `${rect.y}px`;
- dot.draggable = false;
- document.body.append(dot)
- }
- const followCursor = (event: PointerEvent, dot: HTMLDivElement, rect: DOMRect): void => {
- // event.stopPropagation()
- // const { width, height } = dot.getBoundingClientRect()
- const { width, height } = rect;
- dot.style.left = `${event.clientX - width/2}px`;
- dot.style.top = `${event.clientY - height/2}px`;
- }
+
+ // functions for the floating segment that tracks the cursor while grabbing it
+ const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => {
+ dot.classList.add("segment-selected");
+ dot.style.transitionDuration = '0s';
+ dot.style.position = 'absolute';
+ dot.style.zIndex = '999';
+ dot.style.width = `${rect.width}px`;
+ dot.style.height = `${rect.height}px`;
+ dot.style.left = `${rect.x}px`;
+ dot.style.top = `${rect.y}px`;
+ dot.draggable = false;
+ document.body.append(dot);
+ }
+ const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
+ // event.stopPropagation()
+ const { width, height } = dot.getBoundingClientRect();
+ dot.style.left = `${event.clientX - width / 2}px`;
+ dot.style.top = `${event.clientY - height / 2}px`;
+ }
return (
- <div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}>
- {segments}
- </div>
+ <div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}>
+ {segments}
+ </div>
)
} \ No newline at end of file
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 701277cbd..3f54a97ce 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -13,298 +13,285 @@ import { DashUploadUtils } from '../../../../server/DashUploadUtils';
export interface MediaSegment {
videoChunks: any[],
- endTime: number,
- startTime: number
+ endTime: number,
+ startTime: number
}
interface IRecordingViewProps {
- setResult: (info: Upload.AccessPathInfo, trackScreen: boolean) => void
- setDuration: (seconds: number) => void
- id: string
+ setResult: (info: Upload.AccessPathInfo, trackScreen: boolean) => void
+ setDuration: (seconds: number) => void
+ id: string
}
const MAXTIME = 100000;
export function RecordingView(props: IRecordingViewProps) {
- const [recording, setRecording] = useState(false);
- const recordingTimerRef = useRef<number>(0);
- const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
- const [playing, setPlaying] = useState(false);
+ const [recording, setRecording] = useState(false);
+ const recordingTimerRef = useRef<number>(0);
+ const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
+ const [playing, setPlaying] = useState(false);
const [progress, setProgress] = useState(0);
-
+
+ // acts as a "refresh state" to tell progressBar when to undo
const [doUndo, setDoUndo] = useState(false);
const [videos, setVideos] = useState<MediaSegment[]>([]);
const [orderVideos, setOrderVideos] = useState<boolean>(false);
- const videoRecorder = useRef<MediaRecorder | null>(null);
- const videoElementRef = useRef<HTMLVideoElement | null>(null);
+ const videoRecorder = useRef<MediaRecorder | null>(null);
+ const videoElementRef = useRef<HTMLVideoElement | null>(null);
- const [finished, setFinished] = useState<boolean>(false)
- const [trackScreen, setTrackScreen] = useState<boolean>(true)
+ const [finished, setFinished] = useState<boolean>(false)
+ const [trackScreen, setTrackScreen] = useState<boolean>(true)
- const DEFAULT_MEDIA_CONSTRAINTS = {
- video: {
- width: 1280,
- height: 720,
- },
- audio: {
- echoCancellation: true,
- noiseSuppression: true,
- sampleRate: 44100
- }
- }
+ const DEFAULT_MEDIA_CONSTRAINTS = {
+ video: {
+ width: 1280,
+ height: 720,
+ },
+ audio: {
+ echoCancellation: true,
+ noiseSuppression: true,
+ sampleRate: 44100
+ }
+ }
useEffect(() => {
-
- // console.log('in videos useEffect', finished)
-
- if (finished) {
- (async () => {
+ if (finished) {
+ (async () => {
const inputPaths: string[] = [];
const videoFiles: File[] = []
videos.forEach(async (vid, i) => {
const videoFile = new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() });
videoFiles.push(videoFile);
-
+
const { name } = videoFile;
inputPaths.push(name)
- })
-
+ })
+
+ // upload the segments to the server and get their server access paths
const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles))
- .map(res => (res.result instanceof Error) ? '' : res.result.accessPaths.agnostic.server)
-
+ .map(res => (res.result instanceof Error) ? '' : res.result.accessPaths.agnostic.server)
+
+ // concat the segments together using post call
const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths)
- if (!(result instanceof Error)) { // convert this screenshotBox into normal videoBox
+ if (!(result instanceof Error)) {
props.setResult(result, trackScreen)
} else {
alert("video conversion failed");
}
-
- })();
-
- }
+ })();
+ }
+ }, [videos])
+ // this will call upon the progress bar to edit videos to be in the correct order
+ useEffect(() => {
+ if (finished) setOrderVideos(true);
+ }, [finished])
- }, [videos])
-
- useEffect(() => {
+ useEffect(() => {
+ // check if the browser supports media devices on first load
+ if (!navigator.mediaDevices) {
+ console.log('This browser does not support getUserMedia.')
+ }
+ // console.log('This device has the correct media devices.')
+ }, [])
- if (finished) {
- setOrderVideos(true);
- }
+ useEffect(() => {
+ // get access to the video element on every render
+ videoElementRef.current = document.getElementById(`video-${props.id}`) as HTMLVideoElement;
+ });
+ useEffect(() => {
+ let interval: any = null;
+ if (recording) {
+ interval = setInterval(() => {
+ setRecordingTimer(unit => unit + 1);
+ }, 10);
+ } else if (!recording && recordingTimer !== 0) {
+ clearInterval(interval);
+ }
+ return () => clearInterval(interval);
+ }, [recording])
- }, [finished])
+ useEffect(() => {
+ setVideoProgressHelper(recordingTimer)
+ recordingTimerRef.current = recordingTimer;
+ }, [recordingTimer])
- useEffect(() => {
- // check if the browser supports media devices on first load
- if (!navigator.mediaDevices) {
- console.log('This browser does not support getUserMedia.')
- }
- // console.log('This device has the correct media devices.')
- }, [])
-
- useEffect(() => {
- // get access to the video element on every render
- videoElementRef.current = document.getElementById(`video-${props.id}`) as HTMLVideoElement;
- })
-
- useEffect(() => {
- let interval: any = null;
- if (recording) {
- interval = setInterval(() => {
- setRecordingTimer(unit => unit + 1);
- }, 10);
- } else if (!recording && recordingTimer !== 0) {
- clearInterval(interval);
- }
- return () => clearInterval(interval);
- }, [recording])
-
- useEffect(() => {
- setVideoProgressHelper(recordingTimer)
- recordingTimerRef.current = recordingTimer;
- }, [recordingTimer])
-
- const setVideoProgressHelper = (progress: number) => {
- const newProgress = (progress / MAXTIME) * 100;
- setProgress(newProgress)
- }
- const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
- const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints)
-
- videoElementRef.current!.src = ""
- videoElementRef.current!.srcObject = stream
- videoElementRef.current!.muted = true
-
- return stream
- }
-
- const record = async () => {
- const stream = await startShowingStream();
- videoRecorder.current = new MediaRecorder(stream)
-
- // temporary chunks of video
- let videoChunks: any = []
-
- videoRecorder.current.ondataavailable = (event: any) => {
- if (event.data.size > 0) {
- videoChunks.push(event.data)
- }
- }
+ const setVideoProgressHelper = (progress: number) => {
+ const newProgress = (progress / MAXTIME) * 100;
+ setProgress(newProgress)
+ }
+ const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
+ const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints)
- videoRecorder.current.onstart = (event: any) => {
- setRecording(true);
- trackScreen && RecordingApi.Instance.start();
- }
+ videoElementRef.current!.src = ""
+ videoElementRef.current!.srcObject = stream
+ videoElementRef.current!.muted = true
- videoRecorder.current.onstop = () => {
- // if we have a last portion
- if (videoChunks.length > 1) {
- // append the current portion to the video pieces
- setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }])
- }
-
- // reset the temporary chunks
- videoChunks = []
- setRecording(false);
- trackScreen && RecordingApi.Instance.pause();
- }
+ return stream
+ }
- // recording paused
- videoRecorder.current.onpause = (event: any) => {
- // append the current portion to the video pieces
- setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }])
+ const record = async () => {
+ const stream = await startShowingStream();
+ videoRecorder.current = new MediaRecorder(stream)
- // reset the temporary chunks
- videoChunks = []
- setRecording(false);
- trackScreen && RecordingApi.Instance.pause();
- }
+ // temporary chunks of video
+ let videoChunks: any = []
- videoRecorder.current.onresume = async (event: any) => {
- await startShowingStream();
- setRecording(true);
- trackScreen && RecordingApi.Instance.resume();
+ videoRecorder.current.ondataavailable = (event: any) => {
+ if (event.data.size > 0) {
+ videoChunks.push(event.data)
+ }
+ }
+
+ videoRecorder.current.onstart = (event: any) => {
+ setRecording(true);
+ trackScreen && RecordingApi.Instance.start();
+ }
+
+ videoRecorder.current.onstop = () => {
+ // if we have a last portion
+ if (videoChunks.length > 1) {
+ // append the current portion to the video pieces
+ setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }])
}
- videoRecorder.current.start(200)
- }
+ // reset the temporary chunks
+ videoChunks = []
+ setRecording(false);
+ trackScreen && RecordingApi.Instance.pause();
+ }
+
+ // recording paused
+ videoRecorder.current.onpause = (event: any) => {
+ // append the current portion to the video pieces
+ setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }])
+
+ // reset the temporary chunks
+ videoChunks = []
+ setRecording(false);
+ trackScreen && RecordingApi.Instance.pause();
+ }
+
+ videoRecorder.current.onresume = async (event: any) => {
+ await startShowingStream();
+ setRecording(true);
+ trackScreen && RecordingApi.Instance.resume();
+ }
+
+ videoRecorder.current.start(200)
+ }
- const stop = (e: React.MouseEvent) => {
+ const stop = (e: React.MouseEvent) => {
e.stopPropagation()
if (videoRecorder.current) {
- setFinished(true);
- if (videoRecorder.current.state !== "inactive") {
- videoRecorder.current.stop();
- // recorder.current.stream.getTracks().forEach((track: any) => track.stop())
- }
+ setFinished(true);
+ if (videoRecorder.current.state !== "inactive") {
+ videoRecorder.current.stop();
+ // recorder.current.stream.getTracks().forEach((track: any) => track.stop())
}
- }
+ }
+ }
- const pause = (e: React.MouseEvent) => {
+ const pause = (e: React.MouseEvent) => {
e.stopPropagation()
- if (videoRecorder.current) {
- if (videoRecorder.current.state === "recording") {
- videoRecorder.current.stop();
- }
+ if (videoRecorder.current) {
+ if (videoRecorder.current.state === "recording") {
+ videoRecorder.current.stop();
}
- }
+ }
+ }
- const startOrResume = (e: React.MouseEvent) => {
+ const startOrResume = (e: React.MouseEvent) => {
e.stopPropagation()
- if (!videoRecorder.current || videoRecorder.current.state === "inactive") {
- record();
- } else if (videoRecorder.current.state === "paused") {
- videoRecorder.current.start();
- }
- }
+ if (!videoRecorder.current || videoRecorder.current.state === "inactive") {
+ record();
+ } else if (videoRecorder.current.state === "paused") {
+ videoRecorder.current.start();
+ }
+ }
const undoPrevious = (e: React.MouseEvent) => {
e.stopPropagation();
setDoUndo(prev => !prev);
- // const numVideos = videos.length
- // setRecordingTimer(numVideos == 1 ? 0 : videos[numVideos - 2].endTime)
- // setVideoProgressHelper(numVideos == 1 ? 0 : videos[numVideos - 2].endTime)
- // setVideos(videos.filter((_, idx) => idx !== numVideos - 1));
-
- }
-
- const handleOnTimeUpdate = () => {
- if (playing) {
- setVideoProgressHelper(videoElementRef.current!.currentTime)
- }
- };
+ }
- const millisecondToMinuteSecond = (milliseconds: number) => {
- const toTwoDigit = (digit: number) => {
- return String(digit).length == 1 ? "0" + digit : digit
- }
- const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
- const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
- return toTwoDigit(minutes) + " : " + toTwoDigit(seconds);
- }
+ const handleOnTimeUpdate = () => {
+ if (playing) {
+ setVideoProgressHelper(videoElementRef.current!.currentTime)
+ }
+ };
+
+ const millisecondToMinuteSecond = (milliseconds: number) => {
+ const toTwoDigit = (digit: number) => {
+ return String(digit).length == 1 ? "0" + digit : digit
+ }
+ const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
+ const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
+ return toTwoDigit(minutes) + " : " + toTwoDigit(seconds);
+ }
// TODO: have the undo button only appear if there is something to undo
return (
- <div className="recording-container">
- <div className="video-wrapper">
- <video id={`video-${props.id}`}
- autoPlay
- muted
- onTimeUpdate={handleOnTimeUpdate}
- />
- <div className="recording-sign">
- <span className="dot" />
- <p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
+ <div className="recording-container">
+ <div className="video-wrapper">
+ <video id={`video-${props.id}`}
+ autoPlay
+ muted
+ onTimeUpdate={handleOnTimeUpdate}
+ />
+ <div className="recording-sign">
+ <span className="dot" />
+ <p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
+ </div>
+ <div className="controls">
+
+ <div className="controls-inner-container">
+ <div className="record-button-wrapper">
+ {recording ?
+ <button className="stop-button" onClick={pause} /> :
+ <button className="record-button" onClick={startOrResume} />
+ }
</div>
- <div className="controls">
-
- <div className="controls-inner-container">
- <div className="record-button-wrapper">
- {recording ?
- <button className="stop-button" onClick={pause} /> :
- <button className="record-button" onClick={startOrResume} />
- }
- </div>
-
- {!recording && (videos.length > 0 ?
-
- <div className="options-wrapper video-edit-wrapper">
- <IconContext.Provider value={{ color: "grey", className: "video-edit-buttons" }}>
- <MdBackspace onClick={undoPrevious} />
- </IconContext.Provider>
- <IconContext.Provider value={{ color: "#cc1c08", className: "video-edit-buttons" }}>
- <FaCheckCircle onClick={stop} />
- </IconContext.Provider>
- </div>
-
- : <div className="options-wrapper track-screen-wrapper">
- <label className="track-screen">
- <input type="checkbox" checked={trackScreen} onChange={(e) => { setTrackScreen(e.target.checked) }} />
- <span className="checkmark"></span>
- Track Screen
- </label>
- </div>)}
-
- </div>
-
- </div>
-
- <ProgressBar
- videos={videos}
- setVideos={setVideos}
- orderVideos={orderVideos}
- progress={progress}
- recording={recording}
- doUndo={doUndo}
- // playSegment={playSegment}
- />
- </div>
- </div>)
+
+ {!recording && (videos.length > 0 ?
+
+ <div className="options-wrapper video-edit-wrapper">
+ <IconContext.Provider value={{ color: "grey", className: "video-edit-buttons" }}>
+ <MdBackspace onClick={undoPrevious} />
+ </IconContext.Provider>
+ <IconContext.Provider value={{ color: "#cc1c08", className: "video-edit-buttons" }}>
+ <FaCheckCircle onClick={stop} />
+ </IconContext.Provider>
+ </div>
+
+ : <div className="options-wrapper track-screen-wrapper">
+ <label className="track-screen">
+ <input type="checkbox" checked={trackScreen} onChange={(e) => { setTrackScreen(e.target.checked) }} />
+ <span className="checkmark"></span>
+ Track Screen
+ </label>
+ </div>)}
+
+ </div>
+
+ </div>
+
+ <ProgressBar
+ videos={videos}
+ setVideos={setVideos}
+ orderVideos={orderVideos}
+ progress={progress}
+ recording={recording}
+ doUndo={doUndo}
+ // playSegment={playSegment}
+ />
+ </div>
+ </div>)
} \ No newline at end of file