Created
September 1, 2021 21:58
-
-
Save jasonLaster/5ee94b990ae2d952dbaf158fd7621eda to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/protocol/graphics.ts b/src/protocol/graphics.ts | |
index 1fc24c23..7159fdcf 100644 | |
--- a/src/protocol/graphics.ts | |
+++ b/src/protocol/graphics.ts | |
@@ -29,66 +29,6 @@ interface Timed { | |
time: number; | |
} | |
-// Given a sorted array of items with "time" properties, find the index of | |
-// the most recent item at or preceding a given time. | |
-function mostRecentIndex<T extends Timed>(array: T[], time: number): number | undefined { | |
- if (!array.length || time < array[0].time) { | |
- return undefined; | |
- } | |
- const index = binarySearch(0, array.length, (index: number) => { | |
- return time - array[index].time; | |
- }); | |
- assert(array[index].time <= time); | |
- if (index + 1 < array.length) { | |
- assert(array[index + 1].time >= time); | |
- } | |
- return index; | |
-} | |
- | |
-function mostRecentEntry<T extends Timed>(array: T[], time: number) { | |
- const index = mostRecentIndex(array, time); | |
- return index !== undefined ? array[index] : null; | |
-} | |
- | |
-function nextEntry<T extends Timed>(array: T[], time: number) { | |
- const index = mostRecentIndex(array, time); | |
- if (index === undefined) { | |
- return array.length ? array[0] : null; | |
- } | |
- return index + 1 < array.length ? array[index + 1] : null; | |
-} | |
- | |
-// Add an entry with a "time" property to an array that is sorted by time. | |
-function insertEntrySorted<T extends Timed>(array: T[], entry: T) { | |
- if (!array.length || array[array.length - 1].time <= entry.time) { | |
- array.push(entry); | |
- } else { | |
- const index = mostRecentIndex(array, entry.time); | |
- if (index !== undefined) { | |
- array.splice(index + 1, 0, entry); | |
- } else { | |
- array.unshift(entry); | |
- } | |
- } | |
-} | |
- | |
-function closerEntry<T1 extends Timed, T2 extends Timed>( | |
- time: number, | |
- entry1: T1 | null, | |
- entry2: T2 | null | |
-) { | |
- if (!entry1) { | |
- return entry2; | |
- } | |
- if (!entry2) { | |
- return entry1; | |
- } | |
- if (Math.abs(time - entry1.time) < Math.abs(time - entry2.time)) { | |
- return entry1; | |
- } | |
- return entry2; | |
-} | |
- | |
////////////////////////////// | |
// Paint / Mouse Event Points | |
////////////////////////////// | |
@@ -117,24 +57,6 @@ const gMouseClickEvents: MouseEvent[] = []; | |
// Device pixel ratio used by recording screen shots. | |
let gDevicePixelRatio: number; | |
-function onPaints({ paints }: paintPoints) { | |
- paints.forEach(({ point, time, screenShots }) => { | |
- const paintHash = screenShots.find(desc => desc.mimeType == "image/jpeg")!.hash; | |
- insertEntrySorted(gPaintPoints, { point, time, paintHash }); | |
- }); | |
-} | |
- | |
-function onMouseEvents(events: MouseEvent[], store: UIStore) { | |
- events.forEach(entry => { | |
- insertEntrySorted(gMouseEvents, entry); | |
- if (entry.kind == "mousedown") { | |
- insertEntrySorted(gMouseClickEvents, entry); | |
- } | |
- }); | |
- | |
- store.dispatch(actions.setEventsForType(gMouseClickEvents, "mousedown")); | |
-} | |
- | |
class VideoPlayer { | |
store: UIStore | null = null; | |
video: HTMLVideoElement | null = null; | |
@@ -211,7 +133,6 @@ export function setupGraphics(store: UIStore) { | |
ThreadFront.sessionWaiter.promise.then((sessionId: string) => { | |
paintPointsWaiter = client.Graphics.findPaints({}, sessionId); | |
- client.Graphics.addPaintPointsListener(onPaints); | |
client.Session.findMouseEvents({}, sessionId); | |
client.Session.addMouseEventsListener(({ events }) => onMouseEvents(events, store)); | |
@@ -262,40 +183,6 @@ export function addLastScreen(screen: ScreenShot | null, point: string, time: nu | |
} | |
} | |
-export function mostRecentPaintOrMouseEvent(time: number) { | |
- const paintEntry = mostRecentEntry(gPaintPoints, time); | |
- const mouseEntry = mostRecentEntry(gMouseEvents, time); | |
- return closerEntry(time, paintEntry, mouseEntry); | |
-} | |
- | |
-export function nextPaintOrMouseEvent(time: number) { | |
- const paintEntry = nextEntry(gPaintPoints, time); | |
- const mouseEntry = nextEntry(gMouseEvents, time); | |
- return closerEntry(time, paintEntry, mouseEntry); | |
-} | |
- | |
-export function nextPaintEvent(time: number) { | |
- return nextEntry(gPaintPoints, time); | |
-} | |
- | |
-export function previousPaintEvent(time: number) { | |
- const entry = mostRecentEntry(gPaintPoints, time); | |
- if (entry && entry.time == time) { | |
- return mostRecentEntry(gPaintPoints, time - 1); | |
- } | |
- return entry; | |
-} | |
- | |
-export function getMostRecentPaintPoint(time: number) { | |
- return mostRecentEntry(gPaintPoints, time); | |
-} | |
- | |
-export function getClosestPaintPoint(time: number) { | |
- const entryBefore = mostRecentEntry(gPaintPoints, time); | |
- const entryAfter = nextEntry(gPaintPoints, time); | |
- return closerEntry(time, entryBefore, entryAfter); | |
-} | |
- | |
export function getDevicePixelRatio() { | |
return gDevicePixelRatio; | |
} | |
@@ -318,43 +205,6 @@ export interface MouseAndClickPosition { | |
clickY?: number; | |
} | |
-export async function getGraphicsAtTime( | |
- time: number, | |
- forPlayback = false | |
-): Promise<{ screen?: ScreenShot; mouse?: MouseAndClickPosition }> { | |
- const paintIndex = mostRecentIndex(gPaintPoints, time); | |
- if (paintIndex === undefined) { | |
- // There are no graphics to paint here. | |
- return {}; | |
- } | |
- | |
- const { point, paintHash } = gPaintPoints[paintIndex]; | |
- if (!paintHash) { | |
- return {}; | |
- } | |
- | |
- const screenPromise = forPlayback | |
- ? features.videoPlayback | |
- ? Promise.resolve(undefined) | |
- : screenshotCache.getScreenshotForPlayback(point, paintHash) | |
- : screenshotCache.getScreenshotForPreview(point, paintHash); | |
- | |
- const screen = await screenPromise; | |
- | |
- let mouse: MouseAndClickPosition | undefined; | |
- const mouseEvent = mostRecentEntry(gMouseEvents, time); | |
- if (mouseEvent) { | |
- mouse = { x: mouseEvent.clientX, y: mouseEvent.clientY }; | |
- const clickEvent = mostRecentEntry(gMouseClickEvents, time); | |
- if (clickEvent && clickEvent.time + ClickThresholdMs >= time) { | |
- mouse.clickX = clickEvent.clientX; | |
- mouse.clickY = clickEvent.clientY; | |
- } | |
- } | |
- | |
- return { screen, mouse }; | |
-} | |
- | |
////////////////////////////// | |
// Rendering State | |
////////////////////////////// | |
diff --git a/src/protocol/paints-manager.ts b/src/protocol/paints-manager.ts | |
new file mode 100644 | |
index 00000000..f5cdfb96 | |
--- /dev/null | |
+++ b/src/protocol/paints-manager.ts | |
@@ -0,0 +1,159 @@ | |
+// Given a sorted array of items with "time" properties, find the index of | |
+// the most recent item at or preceding a given time. | |
+function mostRecentIndex<T extends Timed>(array: T[], time: number): number | undefined { | |
+ if (!array.length || time < array[0].time) { | |
+ return undefined; | |
+ } | |
+ const index = binarySearch(0, array.length, (index: number) => { | |
+ return time - array[index].time; | |
+ }); | |
+ assert(array[index].time <= time); | |
+ if (index + 1 < array.length) { | |
+ assert(array[index + 1].time >= time); | |
+ } | |
+ return index; | |
+} | |
+ | |
+function mostRecentEntry<T extends Timed>(array: T[], time: number) { | |
+ const index = this.mostRecentIndex(array, time); | |
+ return index !== undefined ? array[index] : null; | |
+} | |
+ | |
+function nextEntry<T extends Timed>(array: T[], time: number) { | |
+ const index = this.mostRecentIndex(array, time); | |
+ if (index === undefined) { | |
+ return array.length ? array[0] : null; | |
+ } | |
+ return index + 1 < array.length ? array[index + 1] : null; | |
+} | |
+ | |
+// Add an entry with a "time" property to an array that is sorted by time. | |
+function insertEntrySorted<T extends Timed>(array: T[], entry: T) { | |
+ if (!array.length || array[array.length - 1].time <= entry.time) { | |
+ array.push(entry); | |
+ } else { | |
+ const index = this.mostRecentIndex(array, entry.time); | |
+ if (index !== undefined) { | |
+ array.splice(index + 1, 0, entry); | |
+ } else { | |
+ array.unshift(entry); | |
+ } | |
+ } | |
+} | |
+ | |
+function closerEntry<T1 extends Timed, T2 extends Timed>( | |
+ time: number, | |
+ entry1: T1 | null, | |
+ entry2: T2 | null | |
+) { | |
+ if (!entry1) { | |
+ return entry2; | |
+ } | |
+ if (!entry2) { | |
+ return entry1; | |
+ } | |
+ if (Math.abs(time - entry1.time) < Math.abs(time - entry2.time)) { | |
+ return entry1; | |
+ } | |
+ return entry2; | |
+} | |
+ | |
+class PaintsManager { | |
+ paintPoints: TimeStampedPointWithPaintHash[] = [{ point: "0", time: 0, paintHash: "" }]; | |
+ | |
+ // All mouse events that have occurred in the recording, in order. | |
+ mouseEvents: MouseEvent[] = []; | |
+ | |
+ constructor(client) { | |
+ client.Graphics.addPaintPointsListener(this.onPaints); | |
+ } | |
+ | |
+ onPaints({ paints }: paintPoints) { | |
+ paints.forEach(({ point, time, screenShots }) => { | |
+ const paintHash = screenShots.find(desc => desc.mimeType == "image/jpeg")!.hash; | |
+ insertEntrySorted(this.paintPoints, { point, time, paintHash }); | |
+ }); | |
+ } | |
+ | |
+ onMouseEvents(events: MouseEvent[], store: UIStore) { | |
+ events.forEach(entry => { | |
+ insertEntrySorted(this.mouseEvents, entry); | |
+ if (entry.kind == "mousedown") { | |
+ insertEntrySorted(this.mouseClickEvents, entry); | |
+ } | |
+ }); | |
+ | |
+ // store.dispatch(actions.setEventsForType(this.mouseClickEvents, "mousedown")); | |
+ } | |
+ | |
+ mostRecentPaintOrMouseEvent(time: number) { | |
+ const paintEntry = mostRecentEntry(this.paintPoints, time); | |
+ const mouseEntry = mostRecentEntry(this.mouseEvents, time); | |
+ return closerEntry(time, paintEntry, mouseEntry); | |
+ } | |
+ | |
+ nextPaintOrMouseEvent(time: number) { | |
+ const paintEntry = nextEntry(this.paintPoints, time); | |
+ const mouseEntry = nextEntry(this.mouseEvents, time); | |
+ return closerEntry(time, paintEntry, mouseEntry); | |
+ } | |
+ | |
+ nextPaintEvent(time: number) { | |
+ return nextEntry(this.paintPoints, time); | |
+ } | |
+ | |
+ previousPaintEvent(time: number) { | |
+ const entry = mostRecentEntry(this.paintPoints, time); | |
+ if (entry && entry.time == time) { | |
+ return mostRecentEntry(this.paintPoints, time - 1); | |
+ } | |
+ return entry; | |
+ } | |
+ | |
+ getMostRecentPaintPoint(time: number) { | |
+ return mostRecentEntry(this.paintPoints, time); | |
+ } | |
+ | |
+ getClosestPaintPoint(time: number) { | |
+ const entryBefore = mostRecentEntry(this.paintPoints, time); | |
+ const entryAfter = nextEntry(this.paintPoints, time); | |
+ return closerEntry(time, entryBefore, entryAfter); | |
+ } | |
+ | |
+ async getGraphicsAtTime( | |
+ time: number, | |
+ forPlayback = false | |
+ ): Promise<{ screen?: ScreenShot; mouse?: MouseAndClickPosition }> { | |
+ const paintIndex = mostRecentIndex(this.paintPoints, time); | |
+ if (paintIndex === undefined) { | |
+ // There are no graphics to paint here. | |
+ return {}; | |
+ } | |
+ | |
+ const { point, paintHash } = this.paintPoints[paintIndex]; | |
+ if (!paintHash) { | |
+ return {}; | |
+ } | |
+ | |
+ const screenPromise = forPlayback | |
+ ? features.videoPlayback | |
+ ? Promise.resolve(undefined) | |
+ : screenshotCache.getScreenshotForPlayback(point, paintHash) | |
+ : screenshotCache.getScreenshotForPreview(point, paintHash); | |
+ | |
+ const screen = await screenPromise; | |
+ | |
+ let mouse: MouseAndClickPosition | undefined; | |
+ const mouseEvent = mostRecentEntry(this.mouseEvents, time); | |
+ if (mouseEvent) { | |
+ mouse = { x: mouseEvent.clientX, y: mouseEvent.clientY }; | |
+ const clickEvent = mostRecentEntry(gMouseClickEvents, time); | |
+ if (clickEvent && clickEvent.time + ClickThresholdMs >= time) { | |
+ mouse.clickX = clickEvent.clientX; | |
+ mouse.clickY = clickEvent.clientY; | |
+ } | |
+ } | |
+ | |
+ return { screen, mouse }; | |
+ } | |
+} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment