From 1eb2c362b020b3cbe446bbc1585108129fda6977 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 01:11:54 -0400 Subject: Get storing pres data working, but is choppy due to mobx usage. --- src/client/util/RecordingApi.ts | 303 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 src/client/util/RecordingApi.ts (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts new file mode 100644 index 000000000..c4f76282c --- /dev/null +++ b/src/client/util/RecordingApi.ts @@ -0,0 +1,303 @@ +import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; +import { IReactionDisposer, observable, reaction } from "mobx"; +import { NumCast } from "../../fields/Types"; + +type Movement = { + time: number, + panX: number, + panY: number, +} + +type Presentation = { + movements: Array + meta: Object, + startDate: Date | null, +} + +export class RecordingApi { + + private static NULL_PRESENTATION: Presentation = { + movements: [], + meta: {}, + startDate: null, + } + + // 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; + + // for now, set playFFView + this.playFFView = null; + this.timers = null; + } + + // little helper :) + private get isInitPresenation(): boolean { + return this.currentPresentation.startDate === 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()') + } + + // (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.startDate = startDate + // (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()') + } + + 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.disposeFunc = null + + return presCopy; + } + + public pause = (): Error | undefined => { + if (this.currentPresentation.startDate === null) { + console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') + return new Error('[recordingApi.ts] pause(): no presenation') + } + // don't allow track movments + this.isRecording = false + + // set relativeStart to the pausedTimestamp + const timestamp = new Date().getTime() + this.absoluteStart = timestamp + } + + public resume = () => { + const timestamp = new Date().getTime() + const startTimestamp = this.currentPresentation.startDate?.getTime() + if (!startTimestamp) { + console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') + return new Error('[recordingApi.ts] pause()') + } + + // update absoluteStart to bridge the paused time + const absoluteTimePaused = timestamp - this.absoluteStart + this.absoluteStart = absoluteTimePaused + } + + // public finish = (): Error | Presentation => { + // if (this.isInitPresenation) { + // console.error('[recordingApi.ts] finish() failed: no presentation data. try calling start() first') + // return new Error('[recordingApi.ts] finish()') + // } + + // // return a copy of the the presentation data + // return { ...this.currentPresentation } + // } + + private trackMovements = (panX: number, panY: number): Error | undefined => { + // ensure we are recording + if (!this.isRecording) { + console.error('[recordingApi.ts] pause() failed: recording is paused()') + return new Error('[recordingApi.ts] pause()') + } + + // get the relative time + const timestamp = new Date().getTime() + const relativeTime = timestamp - this.absoluteStart + + // make new movement struct + const movement: Movement = { time: relativeTime, panX, panY } + + // add that movement to the current presentation data's movement array + this.currentPresentation.movements.push(movement) + } + + // instance variable for the FFView + private disposeFunc: IReactionDisposer | 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 === null || view === this.playFFView) 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.playFFView = 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 + private playFFView: CollectionFreeFormView | null; + private timers: Timer[] | null; + + // public followMovements = (presentation: Presentation): undefined | Error => { + // console.log(presentation) + // if (presentation.startDate === null || this.playFFView === null) { + // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + // } + + // const document = this.playFFView.Document + // const { movements } = presentation + // this.timers = movements.map(movement => { + // const { panX, panY, time } = movement + // return new Timer(() => { + // document._panX = panX; + // document._panY = panY; + // // TODO: consider cleaning this array to null or some state + // }, time) + // }) + // } + + public pauseMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] pauseMovements() failed: no view') + } + // TODO: set userdoc presentMode to browsing + this.timers?.forEach(timer => timer.pause()) + } + + public resumeMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] resumeMovements() failed: no view') + } + this.timers?.forEach(timer => timer.resume()) + } + + public followMovements = (presentation: Presentation): undefined | Error => { + if (presentation.startDate === null || this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + } + + const document = this.playFFView.Document + const { movements } = presentation + movements.forEach(movement => { + const { panX, panY, time } = movement + // set the pan to what was stored + setTimeout(() => { + document._panX = panX; + document._panY = panY; + }, time) + }) + } + // Unfinished code for tracing multiple free form views + // export let pres: Map = 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) + // } +} + +/** Represents the `setTimeout` with an ability to perform pause/resume actions + * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout + */ +export class Timer { + private _start: Date; + private _remaining: number; + private _durationTimeoutId?: NodeJS.Timeout; + private _callback: (...args: any[]) => void; + private _done = false; + get done () { + return this._done; + } + + public constructor(callback: (...args: any[]) => void, ms = 0) { + this._callback = () => { + callback(); + this._done = true; + }; + this._remaining = ms; + this.resume(); + } + + /** pauses the timer */ + public pause(): Timer { + if (this._durationTimeoutId && !this._done) { + this._clearTimeoutRef(); + this._remaining -= new Date().getTime() - this._start.getTime(); + } + return this; + } + + /** resumes the timer */ + public resume(): Timer { + if (!this._durationTimeoutId && !this._done) { + this._start = new Date; + this._durationTimeoutId = setTimeout(this._callback, this._remaining); + } + return this; + } + + /** + * clears the timeout and marks it as done. + * + * After called, the timeout will not resume + */ + public clearTimeout() { + this._clearTimeoutRef(); + this._done = true; + } + + private _clearTimeoutRef() { + if (this._durationTimeoutId) { + clearTimeout(this._durationTimeoutId); + this._durationTimeoutId = undefined; + } + } + +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 91746b4e9c570a7579e3396e2e31c73d8fa9915e Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 01:32:27 -0400 Subject: updated to subtracting timestamps for better smoothness --- src/client/util/RecordingApi.ts | 27 +++++++++++----------- .../views/nodes/RecordingBox/RecordingView.tsx | 2 ++ 2 files changed, 15 insertions(+), 14 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index c4f76282c..ab7642a90 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -11,7 +11,7 @@ type Movement = { type Presentation = { movements: Array meta: Object, - startDate: Date | null, + startTime: number | null, } export class RecordingApi { @@ -19,7 +19,7 @@ export class RecordingApi { private static NULL_PRESENTATION: Presentation = { movements: [], meta: {}, - startDate: null, + startTime: null, } // instance variables @@ -50,7 +50,7 @@ export class RecordingApi { // little helper :) private get isInitPresenation(): boolean { - return this.currentPresentation.startDate === null + return this.currentPresentation.startTime === null } public start = (meta?: Object): Error | undefined => { @@ -63,12 +63,12 @@ export class RecordingApi { // (1a) get start date for presenation const startDate = new Date() // (1b) set start timestamp to absolute timestamp - this.absoluteStart = startDate.getTime() + // this.absoluteStart = startDate.getTime() // (2) assign meta content if it exists this.currentPresentation.meta = meta || {} // (3) assign start date to currentPresenation - this.currentPresentation.startDate = startDate + this.currentPresentation.startTime = startDate.getTime() // (4) set isRecording true to allow trackMovements this.isRecording = true } @@ -89,15 +89,15 @@ export class RecordingApi { // clear absoluteStart this.absoluteStart = -1 // clear the disposeFunc - this.disposeFunc = null + this.removeRecordingFFView() return presCopy; } public pause = (): Error | undefined => { - if (this.currentPresentation.startDate === null) { + if (this.isInitPresenation) { console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') - return new Error('[recordingApi.ts] pause(): no presenation') + return new Error('[recordingApi.ts] pause(): no presentation') } // don't allow track movments this.isRecording = false @@ -109,7 +109,7 @@ export class RecordingApi { public resume = () => { const timestamp = new Date().getTime() - const startTimestamp = this.currentPresentation.startDate?.getTime() + const startTimestamp = this.currentPresentation.startTime if (!startTimestamp) { console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') return new Error('[recordingApi.ts] pause()') @@ -137,12 +137,11 @@ export class RecordingApi { return new Error('[recordingApi.ts] pause()') } - // get the relative time + // get the time const timestamp = new Date().getTime() - const relativeTime = timestamp - this.absoluteStart // make new movement struct - const movement: Movement = { time: relativeTime, panX, panY } + const movement: Movement = { time: timestamp, panX, panY } // add that movement to the current presentation data's movement array this.currentPresentation.movements.push(movement) @@ -211,7 +210,7 @@ export class RecordingApi { } public followMovements = (presentation: Presentation): undefined | Error => { - if (presentation.startDate === null || this.playFFView === null) { + if (presentation.startTime === null || this.playFFView === null) { return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } @@ -223,7 +222,7 @@ export class RecordingApi { setTimeout(() => { document._panX = panX; document._panY = panY; - }, time) + }, time - presentation.startTime) }) } // Unfinished code for tracing multiple free form views diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 870ef87d7..51cc44941 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -176,6 +176,7 @@ export function RecordingView(props: IRecordingViewProps) { videoChunks = [] setRecording(false); setFinished(true); + console.log("finished recording") RecordingApi.Instance.pause(); } @@ -187,6 +188,7 @@ export function RecordingView(props: IRecordingViewProps) { // reset the temporary chunks videoChunks = [] setRecording(false); + console.log("paused recording") RecordingApi.Instance.pause(); } -- cgit v1.2.3-70-g09d2 From 4dc4b0939d4e4afbc9f6db999ff80d434ef4ccc6 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 02:00:55 -0400 Subject: Added the presentation mode back in. However, pres isn't being recreated smoothly after being stored in the db. --- src/client/util/RecordingApi.ts | 21 +++++++++++++++++---- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 ++++-- .../views/nodes/RecordingBox/RecordingView.tsx | 3 --- 3 files changed, 21 insertions(+), 9 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index ab7642a90..b53cba79d 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -1,6 +1,7 @@ import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; import { IReactionDisposer, observable, reaction } from "mobx"; import { NumCast } from "../../fields/Types"; +import { Doc } from "../../fields/Doc"; type Movement = { time: number, @@ -42,6 +43,7 @@ export class RecordingApi { // used for tracking movements in the view frame this.disposeFunc = null; + this.recordingFFView = null; // for now, set playFFView this.playFFView = null; @@ -60,6 +62,9 @@ export class RecordingApi { 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 @@ -80,6 +85,9 @@ export class RecordingApi { return new Error('[recordingApi.ts] clear()') } + // update the presentation mode + Doc.UserDoc().presentationMode = 'none' + const presCopy = { ...this.currentPresentation } // clear presenation data @@ -133,8 +141,8 @@ export class RecordingApi { private trackMovements = (panX: number, panY: number): Error | undefined => { // ensure we are recording if (!this.isRecording) { - console.error('[recordingApi.ts] pause() failed: recording is paused()') - return new Error('[recordingApi.ts] pause()') + console.error('[recordingApi.ts] trackMovements() failed: recording is paused()') + return new Error('[recordingApi.ts] trackMovements()') } // get the time @@ -149,11 +157,12 @@ export class RecordingApi { // 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 === null || view === this.playFFView) return; + if (view === this.recordingFFView || view === null) return; //this.recordingFFView = view; // set the reaction to track the movements @@ -163,7 +172,7 @@ export class RecordingApi { ) // for now, set the most recent recordingFFView to the playFFView - this.playFFView = view; + this.recordingFFView = view; } // call on dispose function to stop tracking movements @@ -194,6 +203,10 @@ export class RecordingApi { // }) // } + 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') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f74e526b6..8bcf6f46f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -965,8 +965,10 @@ export class CollectionFreeFormView extends CollectionSubView { setRecording(true); - // TODO: update names // RecordingApi.Instance.clear(); RecordingApi.Instance.start(); } @@ -176,7 +175,6 @@ export function RecordingView(props: IRecordingViewProps) { videoChunks = [] setRecording(false); setFinished(true); - console.log("finished recording") RecordingApi.Instance.pause(); } @@ -188,7 +186,6 @@ export function RecordingView(props: IRecordingViewProps) { // reset the temporary chunks videoChunks = [] setRecording(false); - console.log("paused recording") RecordingApi.Instance.pause(); } -- cgit v1.2.3-70-g09d2 From a2173243c7a447527e2e86ec0a00998ed8b9cc40 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 03:30:07 -0400 Subject: Get play and pause to work. There is a bug with getting double called that was causing choppiness. --- src/client/util/RecordingApi.ts | 207 ++++++++++++++------- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 10 +- 3 files changed, 148 insertions(+), 71 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index b53cba79d..dfe8e98ca 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -183,11 +183,10 @@ export class RecordingApi { // TODO: extract this into different class with pause and resume recording private playFFView: CollectionFreeFormView | null; - private timers: Timer[] | null; + private timers: NodeJS.Timeout[] | null; - // public followMovements = (presentation: Presentation): undefined | Error => { - // console.log(presentation) - // if (presentation.startDate === null || this.playFFView === null) { + // public loadPresentation = (presentation: Presentation): undefined | Error => { + // if (presentation.startTime === null || this.playFFView === null) { // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') // } @@ -195,48 +194,85 @@ export class RecordingApi { // const { movements } = presentation // this.timers = movements.map(movement => { // const { panX, panY, time } = movement - // return new Timer(() => { + + // const t = new Timer(() => { // document._panX = panX; // document._panY = panY; // // TODO: consider cleaning this array to null or some state - // }, time) + // }, time - presentation.startTime) + // t.pause() + // return t // }) + // console.log(this.timers) // } public setPlayFFView = (view: CollectionFreeFormView): void => { this.playFFView = view } + // public pauseMovements = (): undefined | Error => { + // console.log('[recordingApi.ts] pauseMovements()') + // if (this.playFFView === null) { + // return new Error('[recordingApi.ts] pauseMovements() failed: no view') + // } + // // TODO: set userdoc presentMode to browsing + // this.timers?.forEach(timer => timer.pause()) + // } + public pauseMovements = (): undefined | Error => { if (this.playFFView === null) { return new Error('[recordingApi.ts] pauseMovements() failed: no view') } - // TODO: set userdoc presentMode to browsing - this.timers?.forEach(timer => timer.pause()) - } - public resumeMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] resumeMovements() failed: no view') + if (!this._isPlaying) { + return new Error('[recordingApi.ts] pauseMovements() failed: not playing') } - this.timers?.forEach(timer => timer.resume()) + // TODO: set userdoc presentMode to browsing + //console.log('cleared timers', this.timers) + console.log(this.timers?.map(timer => clearTimeout(timer))) + console.log('[recordingApi.ts] pauseMovements()') + + this._isPlaying = false } - public followMovements = (presentation: Presentation): undefined | Error => { + // public resumeMovements = (): undefined | Error => { + // if (this.playFFView === null) { + // return new Error('[recordingApi.ts] resumeMovements() failed: no view') + // } + // console.log('resume') + // this.timers?.forEach(timer => timer.resume()) + // } + + private _isPlaying = false; + + public playMovements = (presentation: Presentation, timeViewed: number = 0): undefined | Error => { if (presentation.startTime === 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') + } + this._isPlaying = true; + + console.log(timeViewed) + const document = this.playFFView.Document const { movements } = presentation - movements.forEach(movement => { + this.timers = movements.reduce((arr: NodeJS.Timeout[], movement) => { const { panX, panY, time } = movement + const absoluteTime = time - presentation.startTime - timeViewed*1000 + if (absoluteTime < 0) return arr; + // set the pan to what was stored - setTimeout(() => { + arr.push(setTimeout(() => { document._panX = panX; document._panY = panY; - }, time - presentation.startTime) - }) + }, absoluteTime)) + return arr; + }, []) + + console.log(this.timers.length) } // Unfinished code for tracing multiple free form views // export let pres: Map = new Map() @@ -258,58 +294,95 @@ export class RecordingApi { /** Represents the `setTimeout` with an ability to perform pause/resume actions * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout */ -export class Timer { - private _start: Date; - private _remaining: number; - private _durationTimeoutId?: NodeJS.Timeout; - private _callback: (...args: any[]) => void; - private _done = false; - get done () { - return this._done; +class Timer { + private timerId: NodeJS.Timeout | null; + private callback: (...args: any[]) => void + private start: number + private remaining: number; + + constructor(callback: (...args: any[]) => void, delay: number) { + this.callback = callback; + this.remaining = delay; + + this.start = Date.now(); + this.timerId = setTimeout(this.callback, this.remaining); } - public constructor(callback: (...args: any[]) => void, ms = 0) { - this._callback = () => { - callback(); - this._done = true; - }; - this._remaining = ms; - this.resume(); - } - - /** pauses the timer */ - public pause(): Timer { - if (this._durationTimeoutId && !this._done) { - this._clearTimeoutRef(); - this._remaining -= new Date().getTime() - this._start.getTime(); - } - return this; - } + public pause = () => { + console.log('[timer.ts] pause()') + this.timerId !== null && clearTimeout(this.timerId); + this.timerId = null; + this.remaining -= (Date.now() - this.start); + }; - /** resumes the timer */ - public resume(): Timer { - if (!this._durationTimeoutId && !this._done) { - this._start = new Date; - this._durationTimeoutId = setTimeout(this._callback, this._remaining); + public resume = () => { + if (this.timerId) { + return; } - return this; - } - /** - * clears the timeout and marks it as done. - * - * After called, the timeout will not resume - */ - public clearTimeout() { - this._clearTimeoutRef(); - this._done = true; - } + this.start = Date.now(); + this.timerId = setTimeout(this.callback, this.remaining); + }; - private _clearTimeoutRef() { - if (this._durationTimeoutId) { - clearTimeout(this._durationTimeoutId); - this._durationTimeoutId = undefined; - } - } - -} \ No newline at end of file + public clear = () => { + console.log('[timer.ts] clear()') + this.timerId !== null && clearTimeout(this.timerId); + // this.timerId = null; + // this.remaining -= (Date.now() - this.start); + }; +} +// class Timer { +// private _start: Date; +// private _remaining: number; +// private _durationTimeoutId?: NodeJS.Timeout; +// private _callback: (...args: any[]) => void; +// private _done = false; +// get done () { +// return this._done; +// } + +// public constructor(callback: (...args: any[]) => void, ms = 0) { +// this._callback = () => { +// callback(); +// this._done = true; +// }; +// this._remaining = ms; +// this.resume(); +// } + +// /** pauses the timer */ +// public pause(): Timer { +// if (this._durationTimeoutId && !this._done) { +// this._clearTimeoutRef(); +// this._remaining -= new Date().getTime() - this._start.getTime(); +// } +// return this; +// } + +// /** resumes the timer */ +// public resume(): Timer { +// if (!this._durationTimeoutId && !this._done) { +// this._start = new Date; +// this._durationTimeoutId = setTimeout(this._callback, this._remaining); +// } +// return this; +// } + +// /** +// * clears the timeout and marks it as done. +// * +// * After called, the timeout will not resume +// */ +// public clearTimeout() { +// this._clearTimeoutRef(); +// this._done = true; +// } + +// private _clearTimeoutRef() { +// if (this._durationTimeoutId) { +// clearTimeout(this._durationTimeoutId); +// this._durationTimeoutId = undefined; +// } +// } + +// } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8bcf6f46f..214d4bbdc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -966,7 +966,7 @@ export class CollectionFreeFormView extends CollectionSubView { // if presentation isn't null, call followmovements on the recording api if (this.presentation) { - const err = RecordingApi.Instance.followMovements(this.presentation); - if (err) console.log(err); + const err = RecordingApi.Instance.playMovements(this.presentation, this.player?.currentTime || 0); + err && console.log(err) } - this._playing = true; const eleTime = this.player?.currentTime || 0; if (this.timeline) { @@ -198,6 +197,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + if (this.presentation) { + const err = RecordingApi.Instance.pauseMovements(); + err && console.log(err); + } + this._playing = false; this.removeCurrentlyPlaying(); try { -- cgit v1.2.3-70-g09d2 From 4ee1c1f87b311f54e1b928b417017c3a0429d1a4 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 03:31:46 -0400 Subject: Condense code. --- src/client/util/RecordingApi.ts | 147 +--------------------------------------- 1 file changed, 1 insertion(+), 146 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index dfe8e98ca..e779a4ab1 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -128,16 +128,6 @@ export class RecordingApi { this.absoluteStart = absoluteTimePaused } - // public finish = (): Error | Presentation => { - // if (this.isInitPresenation) { - // console.error('[recordingApi.ts] finish() failed: no presentation data. try calling start() first') - // return new Error('[recordingApi.ts] finish()') - // } - - // // return a copy of the the presentation data - // return { ...this.currentPresentation } - // } - private trackMovements = (panX: number, panY: number): Error | undefined => { // ensure we are recording if (!this.isRecording) { @@ -185,40 +175,10 @@ export class RecordingApi { private playFFView: CollectionFreeFormView | null; private timers: NodeJS.Timeout[] | null; - // public loadPresentation = (presentation: Presentation): undefined | Error => { - // if (presentation.startTime === null || this.playFFView === null) { - // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') - // } - - // const document = this.playFFView.Document - // const { movements } = presentation - // this.timers = movements.map(movement => { - // const { panX, panY, time } = movement - - // const t = new Timer(() => { - // document._panX = panX; - // document._panY = panY; - // // TODO: consider cleaning this array to null or some state - // }, time - presentation.startTime) - // t.pause() - // return t - // }) - // console.log(this.timers) - // } - public setPlayFFView = (view: CollectionFreeFormView): void => { this.playFFView = view } - // public pauseMovements = (): undefined | Error => { - // console.log('[recordingApi.ts] pauseMovements()') - // if (this.playFFView === null) { - // return new Error('[recordingApi.ts] pauseMovements() failed: no view') - // } - // // TODO: set userdoc presentMode to browsing - // this.timers?.forEach(timer => timer.pause()) - // } - public pauseMovements = (): undefined | Error => { if (this.playFFView === null) { return new Error('[recordingApi.ts] pauseMovements() failed: no view') @@ -235,14 +195,6 @@ export class RecordingApi { this._isPlaying = false } - // public resumeMovements = (): undefined | Error => { - // if (this.playFFView === null) { - // return new Error('[recordingApi.ts] resumeMovements() failed: no view') - // } - // console.log('resume') - // this.timers?.forEach(timer => timer.resume()) - // } - private _isPlaying = false; public playMovements = (presentation: Presentation, timeViewed: number = 0): undefined | Error => { @@ -271,9 +223,8 @@ export class RecordingApi { }, absoluteTime)) return arr; }, []) - - console.log(this.timers.length) } + // Unfinished code for tracing multiple free form views // export let pres: Map = new Map() @@ -290,99 +241,3 @@ export class RecordingApi { // pres.delete(ffView) // } } - -/** Represents the `setTimeout` with an ability to perform pause/resume actions - * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout - */ -class Timer { - private timerId: NodeJS.Timeout | null; - private callback: (...args: any[]) => void - private start: number - private remaining: number; - - constructor(callback: (...args: any[]) => void, delay: number) { - this.callback = callback; - this.remaining = delay; - - this.start = Date.now(); - this.timerId = setTimeout(this.callback, this.remaining); - } - - public pause = () => { - console.log('[timer.ts] pause()') - this.timerId !== null && clearTimeout(this.timerId); - this.timerId = null; - this.remaining -= (Date.now() - this.start); - }; - - public resume = () => { - if (this.timerId) { - return; - } - - this.start = Date.now(); - this.timerId = setTimeout(this.callback, this.remaining); - }; - - public clear = () => { - console.log('[timer.ts] clear()') - this.timerId !== null && clearTimeout(this.timerId); - // this.timerId = null; - // this.remaining -= (Date.now() - this.start); - }; -} -// class Timer { -// private _start: Date; -// private _remaining: number; -// private _durationTimeoutId?: NodeJS.Timeout; -// private _callback: (...args: any[]) => void; -// private _done = false; -// get done () { -// return this._done; -// } - -// public constructor(callback: (...args: any[]) => void, ms = 0) { -// this._callback = () => { -// callback(); -// this._done = true; -// }; -// this._remaining = ms; -// this.resume(); -// } - -// /** pauses the timer */ -// public pause(): Timer { -// if (this._durationTimeoutId && !this._done) { -// this._clearTimeoutRef(); -// this._remaining -= new Date().getTime() - this._start.getTime(); -// } -// return this; -// } - -// /** resumes the timer */ -// public resume(): Timer { -// if (!this._durationTimeoutId && !this._done) { -// this._start = new Date; -// this._durationTimeoutId = setTimeout(this._callback, this._remaining); -// } -// return this; -// } - -// /** -// * clears the timeout and marks it as done. -// * -// * After called, the timeout will not resume -// */ -// public clearTimeout() { -// this._clearTimeoutRef(); -// this._done = true; -// } - -// private _clearTimeoutRef() { -// if (this._durationTimeoutId) { -// clearTimeout(this._durationTimeoutId); -// this._durationTimeoutId = undefined; -// } -// } - -// } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 32f1fb8df81b5dfc12539090c3fe589099bf165b Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 04:43:22 -0400 Subject: Pause works with presentation now. --- src/client/util/RecordingApi.ts | 86 ++++++++++++---------- .../collectionFreeForm/CollectionFreeFormView.tsx | 7 +- src/client/views/nodes/VideoBox.tsx | 2 +- 3 files changed, 56 insertions(+), 39 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index e779a4ab1..ee427e5df 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -2,6 +2,7 @@ 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, @@ -10,17 +11,15 @@ type Movement = { } type Presentation = { - movements: Array + movements: Array | null meta: Object, - startTime: number | null, } export class RecordingApi { private static NULL_PRESENTATION: Presentation = { - movements: [], + movements: null, meta: {}, - startTime: null, } // instance variables @@ -52,7 +51,7 @@ export class RecordingApi { // little helper :) private get isInitPresenation(): boolean { - return this.currentPresentation.startTime === null + return this.currentPresentation.movements === null } public start = (meta?: Object): Error | undefined => { @@ -68,12 +67,12 @@ export class RecordingApi { // (1a) get start date for presenation const startDate = new Date() // (1b) set start timestamp to absolute timestamp - // this.absoluteStart = startDate.getTime() + this.absoluteStart = startDate.getTime() // (2) assign meta content if it exists this.currentPresentation.meta = meta || {} // (3) assign start date to currentPresenation - this.currentPresentation.startTime = startDate.getTime() + this.currentPresentation.movements = [] // (4) set isRecording true to allow trackMovements this.isRecording = true } @@ -110,39 +109,35 @@ export class RecordingApi { // don't allow track movments this.isRecording = false - // set relativeStart to the pausedTimestamp + // set adjust absoluteStart to add the time difference const timestamp = new Date().getTime() - this.absoluteStart = timestamp + this.absoluteStart = timestamp - this.absoluteStart } public resume = () => { - const timestamp = new Date().getTime() - const startTimestamp = this.currentPresentation.startTime - if (!startTimestamp) { - console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') - return new Error('[recordingApi.ts] pause()') - } - - // update absoluteStart to bridge the paused time - const absoluteTimePaused = timestamp - this.absoluteStart - this.absoluteStart = absoluteTimePaused + 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) { - console.error('[recordingApi.ts] trackMovements() failed: recording is paused()') 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 timestamp = new Date().getTime() - - // make new movement struct - const movement: Movement = { time: timestamp, panX, panY } + 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.push(movement) + this.currentPresentation.movements && this.currentPresentation.movements.push(movement) } // instance variable for the FFView @@ -172,6 +167,7 @@ export class RecordingApi { } // 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; @@ -185,42 +181,58 @@ export class RecordingApi { } if (!this._isPlaying) { - return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + return } + this._isPlaying = false // TODO: set userdoc presentMode to browsing - //console.log('cleared timers', this.timers) - console.log(this.timers?.map(timer => clearTimeout(timer))) - console.log('[recordingApi.ts] pauseMovements()') + this.timers?.map(timer => clearTimeout(timer)) - this._isPlaying = false + 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): undefined | Error => { - if (presentation.startTime === null || this.playFFView === null) { + 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 new Error('[recordingApi.ts] playMovements() failed: already playing') + return } this._isPlaying = true; + // Doc.UserDoc().presentationMode = 'watching'; - console.log(timeViewed) + // TODO: consider this bug at the end of the clip on seek + console.log(timeViewed, videoBox?.player?.currentTime) + 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 absoluteTime = time - presentation.startTime - timeViewed*1000 - if (absoluteTime < 0) return arr; + + 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; - }, absoluteTime)) + // if (movement === movements[movements.length - 1]) { + // this._isPlaying = false; + // } + }, timeDiff)) return arr; }, []) } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 214d4bbdc..9e57bca4f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -968,7 +968,12 @@ export class CollectionFreeFormView extends CollectionSubView { // if presentation isn't null, call followmovements on the recording api if (this.presentation) { - const err = RecordingApi.Instance.playMovements(this.presentation, this.player?.currentTime || 0); + const err = RecordingApi.Instance.playMovements(this.presentation, this.player?.currentTime || 0, this); err && console.log(err) } -- cgit v1.2.3-70-g09d2 From a20320fc54f2cddffeabce79b22451a6da551aa5 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 04:48:35 -0400 Subject: Stopping on interaction will pause the presenation and the video. --- src/client/util/RecordingApi.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index ee427e5df..8fb503b00 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -211,10 +211,10 @@ export class RecordingApi { return } this._isPlaying = true; - // Doc.UserDoc().presentationMode = 'watching'; + Doc.UserDoc().presentationMode = 'watching'; // TODO: consider this bug at the end of the clip on seek - console.log(timeViewed, videoBox?.player?.currentTime) + // console.log(timeViewed) this.videoBox = videoBox || null; const document = this.playFFView.Document @@ -229,9 +229,10 @@ export class RecordingApi { arr.push(setTimeout(() => { document._panX = panX; document._panY = panY; - // if (movement === movements[movements.length - 1]) { - // this._isPlaying = false; - // } + if (movement === movements[movements.length - 1]) { + this._isPlaying = false; + Doc.UserDoc().presentationMode = 'none'; + } }, timeDiff)) return arr; }, []) -- cgit v1.2.3-70-g09d2 From a9ff0c90656de71e37edafba68e946807d41403f Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 13:36:13 -0400 Subject: fix infinite pause/play bug with a stopper, but still very buggy --- src/client/util/RecordingApi.ts | 3 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- .../views/nodes/RecordingBox/RecordingView.scss | 20 ++++++++++++-------- .../views/nodes/RecordingBox/RecordingView.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 5 +++++ 5 files changed, 22 insertions(+), 12 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 8fb503b00..fec579486 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -188,7 +188,7 @@ export class RecordingApi { // TODO: set userdoc presentMode to browsing this.timers?.map(timer => clearTimeout(timer)) - this.videoBox = null; + // this.videoBox = null; } private videoBox: VideoBox | null = null; @@ -236,6 +236,7 @@ export class RecordingApi { }, timeDiff)) return arr; }, []) + } // Unfinished code for tracing multiple free form views diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9e57bca4f..aa2e0c417 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -970,9 +970,9 @@ export class CollectionFreeFormView extends CollectionSubView