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.ts257
1 files changed, 257 insertions, 0 deletions
diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts
new file mode 100644
index 000000000..fec579486
--- /dev/null
+++ b/src/client/util/RecordingApi.ts
@@ -0,0 +1,257 @@
+import { CollectionFreeFormView } from "../views/collections/collectionFreeForm";
+import { IReactionDisposer, observable, reaction } from "mobx";
+import { NumCast } from "../../fields/Types";
+import { Doc } from "../../fields/Doc";
+import { VideoBox } from "../views/nodes/VideoBox";
+
+type Movement = {
+ time: number,
+ panX: number,
+ panY: number,
+}
+
+type Presentation = {
+ movements: Array<Movement> | null
+ meta: Object,
+}
+
+export class RecordingApi {
+
+ private static NULL_PRESENTATION: Presentation = {
+ movements: null,
+ meta: {},
+ }
+
+ // instance variables
+ private currentPresentation: Presentation;
+ private isRecording: 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.isRecording = 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 isInitPresenation(): boolean {
+ return this.currentPresentation.movements === null
+ }
+
+ public start = (meta?: Object): Error | undefined => {
+ // check if already init a presentation
+ if (!this.isInitPresenation) {
+ console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.')
+ return new Error('[recordingApi.ts] start()')
+ }
+
+ // 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 isRecording true to allow trackMovements
+ this.isRecording = true
+ }
+
+ public clear = (): Error | Presentation => {
+ // TODO: maybe archive the data?
+ if (this.isRecording) {
+ console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() first')
+ return new Error('[recordingApi.ts] clear()')
+ }
+
+ // update the presentation mode
+ Doc.UserDoc().presentationMode = 'none'
+
+ const presCopy = { ...this.currentPresentation }
+
+ // clear presenation data
+ this.currentPresentation = RecordingApi.NULL_PRESENTATION
+ // clear isRecording
+ this.isRecording = false
+ // clear absoluteStart
+ this.absoluteStart = -1
+ // clear the disposeFunc
+ this.removeRecordingFFView()
+
+ return presCopy;
+ }
+
+ public pause = (): Error | undefined => {
+ if (this.isInitPresenation) {
+ console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first')
+ return new Error('[recordingApi.ts] pause(): no presentation')
+ }
+ // don't allow track movments
+ this.isRecording = false
+
+ // set adjust absoluteStart to add the time difference
+ const timestamp = new Date().getTime()
+ this.absoluteStart = timestamp - this.absoluteStart
+ }
+
+ public resume = () => {
+ this.isRecording = true
+ // set absoluteStart to the pausedTimestamp
+ this.absoluteStart = new Date().getTime() - this.absoluteStart
+ }
+
+ private trackMovements = (panX: number, panY: number): Error | undefined => {
+ // ensure we are recording
+ if (!this.isRecording) {
+ return new Error('[recordingApi.ts] trackMovements()')
+ }
+ // check to see if the presetation is init
+ if (this.isInitPresenation) {
+ return new Error('[recordingApi.ts] trackMovements(): no presentation')
+ }
+
+ // get the time
+ const time = new Date().getTime() - this.absoluteStart
+ // make new movement object
+ console.log(time)
+ const movement: Movement = { time, panX, panY }
+
+ // 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) }),
+ (res) => (res.x !== -1 && res.y !== -1) && this.trackMovements(res.x, res.y)
+ )
+
+ // for now, set the most recent recordingFFView to the playFFView
+ this.recordingFFView = view;
+ }
+
+ // call on dispose function to stop tracking movements
+ public removeRecordingFFView = (): void => {
+ this.disposeFunc?.();
+ this.disposeFunc = 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 setPlayFFView = (view: CollectionFreeFormView): void => {
+ this.playFFView = view
+ }
+
+ 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;
+ }
+
+ private videoBox: VideoBox | null = null;
+
+ // by calling pause on the VideoBox, the pauseMovements will be called
+ public pauseVideoAndMovements = (): boolean => {
+ this.videoBox?.Pause()
+ return this.videoBox === null
+ }
+
+ private _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 new Error('[recordingApi.ts] playMovements() failed: already playing')
+ return
+ }
+ this._isPlaying = true;
+ Doc.UserDoc().presentationMode = 'watching';
+
+ // TODO: consider this bug at the end of the clip on seek
+ // console.log(timeViewed)
+ this.videoBox = videoBox || null;
+
+ const document = this.playFFView.Document
+ const { movements } = presentation
+ this.timers = movements.reduce((arr: NodeJS.Timeout[], movement) => {
+ const { panX, panY, time } = movement
+
+ const timeDiff = time - timeViewed*1000
+ if (timeDiff < 0) return arr;
+
+ // set the pan to what was stored
+ arr.push(setTimeout(() => {
+ document._panX = panX;
+ document._panY = panY;
+ if (movement === movements[movements.length - 1]) {
+ this._isPlaying = false;
+ Doc.UserDoc().presentationMode = 'none';
+ }
+ }, timeDiff))
+ return arr;
+ }, [])
+
+ }
+
+ // 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)
+ // }
+}