aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Foiani <sotech117@michaels-mbp-5.devices.brown.edu>2022-06-13 15:10:22 -0400
committerMichael Foiani <sotech117@michaels-mbp-5.devices.brown.edu>2022-06-13 15:10:22 -0400
commitd3bf3086d95ff6f895614d0942b84752662c1373 (patch)
treea820098bc7c690dd77faf0540f23b5beef0d0824 /src
parentc7e4bf8c4e19669666cce610020969cf0d865e49 (diff)
formatting and removing logs
Diffstat (limited to 'src')
-rw-r--r--src/client/util/RecordingApi.ts506
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.tsx443
2 files changed, 472 insertions, 477 deletions
diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts
index d56093cee..12ca07d67 100644
--- a/src/client/util/RecordingApi.ts
+++ b/src/client/util/RecordingApi.ts
@@ -3,289 +3,285 @@ import { IReactionDisposer, observable, reaction } from "mobx";
import { NumCast } from "../../fields/Types";
import { Doc } from "../../fields/Doc";
import { VideoBox } from "../views/nodes/VideoBox";
+import { isArray } from "lodash";
type Movement = {
- time: number,
- panX: number,
- panY: number,
- scale: number,
+ time: number,
+ panX: number,
+ panY: number,
+ scale: number,
}
export type Presentation = {
- movements: Movement[] | null,
- totalTime: number,
- meta: any,
+ movements: Movement[] | null,
+ totalTime: number,
+ meta: Object | Object[],
}
export class RecordingApi {
- private static get NULL_PRESENTATION(): Presentation {
- return { movements: null, meta: {}, totalTime: -1, }
- }
-
- // instance variables
- private currentPresentation: Presentation;
- private tracking: boolean;
- private absoluteStart: number;
-
-
- // create static instance and getter for global use
- @observable static _instance: RecordingApi;
- public static get Instance(): RecordingApi { return RecordingApi._instance }
- public constructor() {
- // init the global instance
- RecordingApi._instance = this;
-
- // init the instance variables
- this.currentPresentation = RecordingApi.NULL_PRESENTATION
- this.tracking = false;
- this.absoluteStart = -1;
-
- // used for tracking movements in the view frame
- this.disposeFunc = null;
- this.recordingFFView = null;
-
- // for now, set playFFView
- this.playFFView = null;
- this.timers = null;
-
- // put a pointerdown event on the doucment to see what the target
- }
-
- // little helper :)
- private get nullPresentation(): boolean {
- return this.currentPresentation.movements === null
- }
-
- public start = (meta?: Object) => {
- // update the presentation mode
- Doc.UserDoc().presentationMode = 'recording';
-
- // (1a) get start date for presenation
- const startDate = new Date();
- // (1b) set start timestamp to absolute timestamp
- this.absoluteStart = startDate.getTime();
-
- // (2) assign meta content if it exists
- this.currentPresentation.meta = meta || {}
- // (3) assign start date to currentPresenation
- this.currentPresentation.movements = []
- // (4) set tracking true to allow trackMovements
- this.tracking = true
- }
-
- /* stops the video and returns the presentatation; if no presentation, returns undefined */
- public yieldPresentation(clearData: boolean = true): Presentation | null {
- // if no presentation or done tracking, return null
- if (this.nullPresentation || !this.tracking) return null;
-
- // set the previus recording view to the play view
- this.playFFView = this.recordingFFView;
-
- // ensure we add the endTime now that they are done recording
- const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart };
-
- // reset the current presentation
- clearData && this.clear();
-
- return cpy;
- }
-
- public finish = (): void => {
- // make is tracking false
- this.tracking = false
- // reset the RecordingApi instance
- this.clear();
- }
-
- public clear = (): void => {
- // clear the disposeFunc if we are done (not tracking)
- if (!this.tracking) {
- this.removeRecordingFFView();
- // update the presentation mode now that we are done tracking
- Doc.UserDoc().presentationMode = 'none';
+ private static get NULL_PRESENTATION(): Presentation {
+ return { movements: null, meta: {}, totalTime: -1, }
}
- // clear presenation data
- this.currentPresentation = RecordingApi.NULL_PRESENTATION
- // clear isRecording
- // this.tracking = false
- // clear absoluteStart
- this.absoluteStart = -1
- }
-
- // call on dispose function to stop tracking movements
- public removeRecordingFFView = (): void => {
- this.disposeFunc?.();
- this.disposeFunc = null;
- }
-
- private trackMovements = (panX: number, panY: number, scale: number = 0) => {
- // ensure we are recording
- if (!this.tracking) {
- console.error('[recordingApi.ts] trackMovements(): tracking is false')
- return;
+
+ // instance variables
+ private currentPresentation: Presentation;
+ private tracking: boolean;
+ private absoluteStart: number;
+
+
+ // create static instance and getter for global use
+ @observable static _instance: RecordingApi;
+ public static get Instance(): RecordingApi { return RecordingApi._instance }
+ public constructor() {
+ // init the global instance
+ RecordingApi._instance = this;
+
+ // init the instance variables
+ this.currentPresentation = RecordingApi.NULL_PRESENTATION
+ this.tracking = false;
+ this.absoluteStart = -1;
+
+ // used for tracking movements in the view frame
+ this.disposeFunc = null;
+ this.recordingFFView = null;
+
+ // for now, set playFFView
+ this.playFFView = null;
+ this.timers = null;
+ }
+
+ // little helper :)
+ private get nullPresentation(): boolean {
+ return this.currentPresentation.movements === null
+ }
+
+ public start = (meta?: Object) => {
+ // update the presentation mode
+ Doc.UserDoc().presentationMode = 'recording';
+
+ // (1a) get start date for presenation
+ const startDate = new Date();
+ // (1b) set start timestamp to absolute timestamp
+ this.absoluteStart = startDate.getTime();
+
+ // (2) assign meta content if it exists
+ this.currentPresentation.meta = meta || {}
+ // (3) assign start date to currentPresenation
+ this.currentPresentation.movements = []
+ // (4) set tracking true to allow trackMovements
+ this.tracking = true
+ }
+
+ /* stops the video and returns the presentatation; if no presentation, returns undefined */
+ public yieldPresentation(clearData: boolean = true): Presentation | null {
+ // if no presentation or done tracking, return null
+ if (this.nullPresentation || !this.tracking) return null;
+
+ // set the previus recording view to the play view
+ this.playFFView = this.recordingFFView;
+
+ // ensure we add the endTime now that they are done recording
+ const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart };
+
+ // reset the current presentation
+ clearData && this.clear();
+
+ return cpy;
+ }
+
+ public finish = (): void => {
+ // make is tracking false
+ this.tracking = false
+ // reset the RecordingApi instance
+ this.clear();
}
- // check to see if the presetation is init
- if (this.nullPresentation) {
- console.error('[recordingApi.ts] trackMovements(): no presentation')
- return;
+
+ public clear = (): void => {
+ // clear the disposeFunc if we are done (not tracking)
+ if (!this.tracking) {
+ this.removeRecordingFFView();
+ // update the presentation mode now that we are done tracking
+ Doc.UserDoc().presentationMode = 'none';
+ }
+ // clear presenation data
+ this.currentPresentation = RecordingApi.NULL_PRESENTATION
+ // clear isRecording
+ // this.tracking = false
+ // clear absoluteStart
+ this.absoluteStart = -1
}
- // TO FIX: bob
- // console.debug('track movment')
-
- // get the time
- const time = new Date().getTime() - this.absoluteStart
- // make new movement object
- const movement: Movement = { time, panX, panY, scale }
-
- // add that movement to the current presentation data's movement array
- this.currentPresentation.movements && this.currentPresentation.movements.push(movement)
- }
-
- // instance variable for the FFView
- private disposeFunc: IReactionDisposer | null;
- private recordingFFView: CollectionFreeFormView | null;
-
- // set the FFView that will be used in a reaction to track the movements
- public setRecordingFFView = (view: CollectionFreeFormView): void => {
- // set the view to the current view
- if (view === this.recordingFFView || view == null) return;
-
- // this.recordingFFView = view;
- // set the reaction to track the movements
- this.disposeFunc = reaction(
- () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }),
- (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale)
- )
-
- // for now, set the most recent recordingFFView to the playFFView
- this.recordingFFView = view;
- }
-
- // TODO: extract this into different class with pause and resume recording
- // TODO: store the FFview with the movements
- private playFFView: CollectionFreeFormView | null;
- private timers: NodeJS.Timeout[] | null;
-
- public setPlayFFView = (view: CollectionFreeFormView): void => {
- this.playFFView = view
- }
-
- // pausing movements will dispose all timers that are planned to replay the movements
- // play movemvents will recreate them when the user resumes the presentation
- public pauseMovements = (): undefined | Error => {
- if (this.playFFView === null) {
- return new Error('[recordingApi.ts] pauseMovements() failed: no view')
+ // call on dispose function to stop tracking movements
+ public removeRecordingFFView = (): void => {
+ this.disposeFunc?.();
+ this.disposeFunc = null;
}
- if (!this._isPlaying) {
- //return new Error('[recordingApi.ts] pauseMovements() failed: not playing')
- return
+ private trackMovements = (panX: number, panY: number, scale: number = 0) => {
+ // ensure we are recording
+ if (!this.tracking) {
+ console.error('[recordingApi.ts] trackMovements(): tracking is false')
+ return;
+ }
+ // check to see if the presetation is init
+ if (this.nullPresentation) {
+ console.error('[recordingApi.ts] trackMovements(): no presentation')
+ return;
+ }
+
+ // get the time
+ const time = new Date().getTime() - this.absoluteStart
+ // make new movement object
+ const movement: Movement = { time, panX, panY, scale }
+
+ // add that movement to the current presentation data's movement array
+ this.currentPresentation.movements && this.currentPresentation.movements.push(movement)
}
- this._isPlaying = false
- // TODO: set userdoc presentMode to browsing
- this.timers?.map(timer => clearTimeout(timer))
- // this.videoBox = null;
- }
+ // instance variable for the FFView
+ private disposeFunc: IReactionDisposer | null;
+ private recordingFFView: CollectionFreeFormView | null;
+
+ // set the FFView that will be used in a reaction to track the movements
+ public setRecordingFFView = (view: CollectionFreeFormView): void => {
+ // set the view to the current view
+ if (view === this.recordingFFView || view == null) return;
- private videoBox: VideoBox | null = null;
+ // this.recordingFFView = view;
+ // set the reaction to track the movements
+ this.disposeFunc = reaction(
+ () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }),
+ (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale)
+ )
- // by calling pause on the VideoBox, the pauseMovements will be called
- public pauseVideoAndMovements = (): boolean => {
- this.videoBox?.Pause()
+ // for now, set the most recent recordingFFView to the playFFView
+ this.recordingFFView = view;
+ }
- this.pauseMovements()
- return this.videoBox == null
- }
+ // TODO: extract this into different class with pause and resume recording
+ // TODO: store the FFview with the movements
+ private playFFView: CollectionFreeFormView | null;
+ private timers: NodeJS.Timeout[] | null;
- public _isPlaying = false;
+ public setPlayFFView = (view: CollectionFreeFormView): void => {
+ this.playFFView = view
+ }
- public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => {
- if (presentation.movements === null || this.playFFView === null) {
- return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view')
+ // pausing movements will dispose all timers that are planned to replay the movements
+ // play movemvents will recreate them when the user resumes the presentation
+ public pauseMovements = (): undefined | Error => {
+ if (this.playFFView === null) {
+ return new Error('[recordingApi.ts] pauseMovements() failed: no view')
+ }
+
+ if (!this._isPlaying) {
+ //return new Error('[recordingApi.ts] pauseMovements() failed: not playing')
+ return
+ }
+ this._isPlaying = false
+ // TODO: set userdoc presentMode to browsing
+ this.timers?.map(timer => clearTimeout(timer))
+
+ // this.videoBox = null;
}
- if (this._isPlaying) return;
- this._isPlaying = true;
- Doc.UserDoc().presentationMode = 'watching';
+ private videoBox: VideoBox | null = null;
- // TODO: consider this bug at the end of the clip on seek
- this.videoBox = videoBox || null;
+ // by calling pause on the VideoBox, the pauseMovements will be called
+ public pauseVideoAndMovements = (): boolean => {
+ this.videoBox?.Pause()
- // only get the movements that are remaining in the video time left
- const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000)
+ this.pauseMovements()
+ return this.videoBox == null
+ }
- // helper to replay a movement
- const document = this.playFFView
- let preScale = -1;
- const zoomAndPan = (movement: Movement) => {
- const { panX, panY, scale } = movement;
- (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0);
- document.Document._panX = panX;
- document.Document._panY = panY;
+ public _isPlaying = false;
+
+ public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => {
+ if (presentation.movements === null || this.playFFView === null) {
+ return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view')
+ }
+ if (this._isPlaying) return;
+
+ this._isPlaying = true;
+ Doc.UserDoc().presentationMode = 'watching';
+
+ // TODO: consider this bug at the end of the clip on seek
+ this.videoBox = videoBox || null;
+
+ // only get the movements that are remaining in the video time left
+ const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000)
+
+ // helper to replay a movement
+ const document = this.playFFView
+ let preScale = -1;
+ const zoomAndPan = (movement: Movement) => {
+ const { panX, panY, scale } = movement;
+ (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0);
+ document.Document._panX = panX;
+ document.Document._panY = panY;
+
+ preScale = scale;
+ }
+
+ // set the first frame to be at the start of the pres
+ zoomAndPan(filteredMovements[0]);
+
+ // make timers that will execute each movement at the correct replay time
+ this.timers = filteredMovements.map(movement => {
+ const timeDiff = movement.time - timeViewed * 1000
+ return setTimeout(() => {
+ // replay the movement
+ zoomAndPan(movement)
+ // if last movement, presentation is done -> set the instance var
+ if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false;
+ }, timeDiff)
+ })
+ }
- preScale = scale;
+ // method that concatenates an array of presentatations into one
+ public concatPresentations = (presentations: Presentation[]): Presentation => {
+ // these three will lead to the combined presentation
+ let combinedMovements: Movement[] = [];
+ let sumTime = 0;
+ let combinedMetas: any[] = [];
+
+ presentations.forEach((presentation) => {
+ const { movements, totalTime, meta } = presentation;
+
+ // update movements if they had one
+ if (movements) {
+ // add the summed time to the movements
+ const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } });
+ // concat the movements already in the combined presentation with these new ones
+ combinedMovements.push(...addedTimeMovements);
+ }
+
+ // update the totalTime
+ sumTime += totalTime;
+
+ // concatenate the metas
+ combinedMetas.push(meta);
+ });
+
+ // return the combined presentation with the updated total summed time
+ return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
}
- // set the first frame to be at the start of the pres
- zoomAndPan(filteredMovements[0]);
-
- // make timers that will execute each movement at the correct replay time
- this.timers = filteredMovements.map(movement => {
- const timeDiff = movement.time - timeViewed * 1000
- return setTimeout(() => {
- // replay the movement
- zoomAndPan(movement)
- // if last movement, presentation is done -> set the instance var
- if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false;
- }, timeDiff)
- })
- }
-
- // method that concatenates an array of presentatations into one
- public concatPresentations = (presentations: Presentation[]): Presentation => {
- // these three will lead to the combined presentation
- let combinedMovements: Movement[] = [];
- let sumTime = 0;
- let combinedMetas: any[] = [];
-
- presentations.forEach((presentation) => {
- const { movements, totalTime, meta } = presentation;
-
- // update movements if they had one
- if (movements) {
- // add the summed time to the movements
- const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } });
- // concat the movements already in the combined presentation with these new ones
- combinedMovements.push(...addedTimeMovements);
- }
-
- // update the totalTime
- sumTime += totalTime;
-
- // concatenate the metas
- combinedMetas.push(...meta);
- });
-
- // return the combined presentation with the updated total summed time
- return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
- }
-
- // Unfinished code for tracing multiple free form views
- // export let pres: Map<CollectionFreeFormView, IReactionDisposer> = new Map()
-
- // export function AddRecordingFFView(ffView: CollectionFreeFormView): void {
- // pres.set(ffView,
- // reaction(() => ({ x: ffView.panX, y: ffView.panY }),
- // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y)))
- // )
- // }
-
- // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void {
- // const disposer = pres.get(ffView);
- // disposer?.();
- // pres.delete(ffView)
- // }
+ // Unfinished code for tracing multiple free form views
+ // export let pres: Map<CollectionFreeFormView, IReactionDisposer> = new Map()
+
+ // export function AddRecordingFFView(ffView: CollectionFreeFormView): void {
+ // pres.set(ffView,
+ // reaction(() => ({ x: ffView.panX, y: ffView.panY }),
+ // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y)))
+ // )
+ // }
+
+ // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void {
+ // const disposer = pres.get(ffView);
+ // disposer?.();
+ // pres.delete(ffView)
+ // }
}
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 15883632a..ec9838bdd 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -11,262 +11,261 @@ import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
import { Presentation, RecordingApi } from '../../../util/RecordingApi';
export interface MediaSegment {
- videoChunks: any[],
- endTime: number,
- startTime: number,
- presentation?: Presentation,
+ videoChunks: any[],
+ endTime: number,
+ startTime: number,
+ presentation?: Presentation,
}
interface IRecordingViewProps {
- setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void
- setDuration: (seconds: number) => void
- id: string
+ setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => 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 [progress, setProgress] = useState(0);
-
- // acts as a "refresh state" to tell progressBar when to undo
- const [doUndo, setDoUndo] = useState(false);
- // whether an undo can occur or not
- const [canUndo, setCanUndo] = 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 [finished, setFinished] = useState<boolean>(false)
- const [trackScreen, setTrackScreen] = useState<boolean>(true)
+ 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);
+ // whether an undo can occur or not
+ const [canUndo, setCanUndo] = 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 [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
+ }
+ };
+
+ useEffect(() => {
+ if (finished) {
+ // make the total presentation that'll match the concatted video
+ let concatPres = trackScreen && RecordingApi.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
+
+ // this async function uses the server to create the concatted video and then sets the result to it's accessPaths
+ (async () => {
+ const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() }));
+
+ // 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)
+
+ // concat the segments together using post call
+ const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths);
+ !(result instanceof Error) ? props.setResult(result, concatPres || undefined) : console.error("video conversion failed");
+ })();
+ }
+ }, [videos]);
+
+ // this will call upon the progress bar to edit videos to be in the correct order
+ useEffect(() => {
+ finished && setOrderVideos(true);
+ }, [finished]);
+
+ // check if the browser supports media devices on first load
+ useEffect(() => { if (!navigator.mediaDevices) alert('This browser does not support getUserMedia.'); }, []);
+
+ 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;
- const DEFAULT_MEDIA_CONSTRAINTS = {
- video: {
- width: 1280,
- height: 720,
- },
- audio: {
- echoCancellation: true,
- noiseSuppression: true,
- sampleRate: 44100
+ return stream;
}
- }
- // useEffect(() => console.debug('progress', progress), [progress])
+ const record = async () => {
+ // don't need to start a new stream every time we start recording a new segment
+ if (!videoRecorder.current) videoRecorder.current = new MediaRecorder(await startShowingStream());
- useEffect(() => {
- if (finished) {
- // make the total presentation that'll match the concatted video
- let concatPres = trackScreen && RecordingApi.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
+ // temporary chunks of video
+ let videoChunks: any = [];
- // this async function uses the server to create the concatted video and then sets the result to it's accessPaths
- (async () => {
- const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() }));
+ videoRecorder.current.ondataavailable = (event: any) => {
+ if (event.data.size > 0) videoChunks.push(event.data);
+ };
- // 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)
+ videoRecorder.current.onstart = (event: any) => {
+ setRecording(true);
+ // start the recording api when the video recorder starts
+ trackScreen && RecordingApi.Instance.start();
+ };
- // concat the segments together using post call
- const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths);
- !(result instanceof Error) ? props.setResult(result, concatPres || undefined) : console.error("video conversion failed");
- })();
+ videoRecorder.current.onstop = () => {
+ // if we have a last portion
+ if (videoChunks.length > 1) {
+ // append the current portion to the video pieces
+ const nextVideo = {
+ videoChunks,
+ endTime: recordingTimerRef.current,
+ startTime: videos?.lastElement()?.endTime || 0
+ };
+
+ // depending on if a presenation exists, add it to the video
+ const presentation = RecordingApi.Instance.yieldPresentation();
+ setVideos(videos => [...videos, (presentation != null && trackScreen) ? { ...nextVideo, presentation } : nextVideo]);
+ }
+
+ // reset the temporary chunks
+ videoChunks = [];
+ setRecording(false);
+ }
+
+ videoRecorder.current.start(200);
}
- }, [videos])
-
- // this will call upon the progress bar to edit videos to be in the correct order
- useEffect(() => {
- finished && setOrderVideos(true);
- }, [finished])
-
- // check if the browser supports media devices on first load
- useEffect(() => { if (!navigator.mediaDevices) alert('This browser does not support getUserMedia.'); }, [])
-
- 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
+ // if this is called, then we're done recording all the segments
+ const finish = (e: React.PointerEvent) => {
+ e.stopPropagation();
- return stream
- }
+ // call stop on the video recorder if active
+ videoRecorder.current?.state !== "inactive" && videoRecorder.current?.stop();
- const record = async () => {
- // don't need to start a new stream every time we start recording a new segment
- if (!videoRecorder.current) videoRecorder.current = new MediaRecorder(await startShowingStream())
+ // end the streams (audio/video) to remove recording icon
+ const stream = videoElementRef.current!.srcObject;
+ stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop());
- // temporary chunks of video
- let videoChunks: any = []
+ // finish/clear the recoringApi
+ RecordingApi.Instance.finish();
- videoRecorder.current.ondataavailable = (event: any) => {
- if (event.data.size > 0) videoChunks.push(event.data)
+ // this will call upon progessbar to update videos to be in the correct order
+ setFinished(true);
}
- videoRecorder.current.onstart = (event: any) => {
- setRecording(true);
- // start the recording api when the video recorder starts
- trackScreen && RecordingApi.Instance.start();
+ const pause = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ // if recording, then this is just a new segment
+ videoRecorder.current?.state === "recording" && videoRecorder.current.stop();
}
- videoRecorder.current.onstop = () => {
- // if we have a last portion
- if (videoChunks.length > 1) {
- // append the current portion to the video pieces
- const nextVideo = {
- videoChunks,
- endTime: recordingTimerRef.current,
- startTime: videos?.lastElement()?.endTime || 0
- };
-
- // depending on if a presenation exists, add it to the video
- const presentation = RecordingApi.Instance.yieldPresentation();
- setVideos(videos => [...videos, (presentation != null && trackScreen) ? { ...nextVideo, presentation } : nextVideo]);
- }
+ const start = (e: React.PointerEvent) => {
+ setupMoveUpEvents({}, e, returnTrue, returnFalse, e => {
+ // start recording if not already recording
+ if (!videoRecorder.current || videoRecorder.current.state === "inactive") record();
- // reset the temporary chunks
- videoChunks = []
- setRecording(false);
+ return true; // cancels propagation to documentView to avoid selecting it.
+ }, false, false);
}
- videoRecorder.current.start(200)
- }
-
-
- // if this is called, then we're done recording all the segments
- const finish = (e: React.PointerEvent) => {
- e.stopPropagation();
-
- // call stop on the video recorder if active
- videoRecorder.current?.state !== "inactive" && videoRecorder.current?.stop();
-
- // end the streams (audio/video) to remove recording icon
- const stream = videoElementRef.current!.srcObject;
- stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop());
-
- // finish/clear the recoringApi
- RecordingApi.Instance.finish();
-
- // this will call upon progessbar to update videos to be in the correct order
- setFinished(true);
- }
-
- const pause = (e: React.PointerEvent) => {
- e.stopPropagation()
- // if recording, then this is just a new segment
- videoRecorder.current?.state === "recording" && videoRecorder.current.stop();
- }
-
- const start = (e: React.PointerEvent) => {
- setupMoveUpEvents({}, e, returnTrue, returnFalse, e => {
- // start recording if not already recording
- if (!videoRecorder.current || videoRecorder.current.state === "inactive") record();
-
- return true; // cancels propagation to documentView to avoid selecting it.
- }, false, false);
- }
-
- const undoPrevious = (e: React.PointerEvent) => {
- e.stopPropagation();
- setDoUndo(prev => !prev);
- }
+ const undoPrevious = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ setDoUndo(prev => !prev);
+ }
- const handleOnTimeUpdate = () => { playing && setVideoProgressHelper(videoElementRef.current!.currentTime); };
+ const handleOnTimeUpdate = () => { playing && setVideoProgressHelper(videoElementRef.current!.currentTime); };
- const millisecondToMinuteSecond = (milliseconds: number) => {
- const toTwoDigit = (digit: number) => {
- return String(digit).length == 1 ? "0" + digit : digit
+ 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 minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
- const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
- return toTwoDigit(minutes) + " : " + toTwoDigit(seconds);
- }
-
- return (
- <div className="recording-container">
- <div className="video-wrapper">
- <video id={`video-${props.id}`}
- autoPlay
- muted
- onTimeUpdate={() => handleOnTimeUpdate()}
- ref={videoElementRef}
- />
- <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" onPointerDown={pause} /> :
- <button className="record-button" onPointerDown={start} />
- }
- </div>
- {!recording && (videos.length > 0 ?
-
- <div className="options-wrapper video-edit-wrapper">
- <IconContext.Provider value={{ color: "grey", className: "video-edit-buttons", style: { display: canUndo ? 'inherit' : 'none' } }}>
- <MdBackspace onPointerDown={undoPrevious} />
- </IconContext.Provider>
- <IconContext.Provider value={{ color: "#cc1c08", className: "video-edit-buttons" }}>
- <FaCheckCircle onPointerDown={finish} />
- </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}
- setCanUndo={setCanUndo}
- />
- </div>
- </div>)
+ return (
+ <div className="recording-container">
+ <div className="video-wrapper">
+ <video id={`video-${props.id}`}
+ autoPlay
+ muted
+ onTimeUpdate={() => handleOnTimeUpdate()}
+ ref={videoElementRef}
+ />
+ <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" onPointerDown={pause} /> :
+ <button className="record-button" onPointerDown={start} />
+ }
+ </div>
+
+ {!recording && (videos.length > 0 ?
+
+ <div className="options-wrapper video-edit-wrapper">
+ <IconContext.Provider value={{ color: "grey", className: "video-edit-buttons", style: { display: canUndo ? 'inherit' : 'none' } }}>
+ <MdBackspace onPointerDown={undoPrevious} />
+ </IconContext.Provider>
+ <IconContext.Provider value={{ color: "#cc1c08", className: "video-edit-buttons" }}>
+ <FaCheckCircle onPointerDown={finish} />
+ </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}
+ setCanUndo={setCanUndo}
+ />
+ </div>
+ </div>)
} \ No newline at end of file