diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/RecordingBox/ProgressBar.scss | 7 | ||||
-rw-r--r-- | src/client/views/nodes/RecordingBox/ProgressBar.tsx | 425 | ||||
-rw-r--r-- | src/client/views/nodes/RecordingBox/RecordingView.tsx | 443 |
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 |