aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/RecordingApi.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util/RecordingApi.ts')
-rw-r--r--src/client/util/RecordingApi.ts506
1 files changed, 251 insertions, 255 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)
+ // }
}