Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HACK DO NOT SUBMIT. Integration example for replay videos. #10600

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/replay-next/src/suspense/ScreenshotCache.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExecutionPoint, ScreenShot } from "@replayio/protocol";
import { ExecutionPoint, ScreenShot, Video } from "@replayio/protocol";

Check failure on line 1 in packages/replay-next/src/suspense/ScreenshotCache.ts

View workflow job for this annotation

GitHub Actions / Trunk Check

prettier

Incorrect formatting, autoformat by running 'trunk fmt'
import { createCache } from "suspense";

import { paintHashCache } from "replay-next/src/suspense/PaintHashCache";
Expand All @@ -19,3 +19,16 @@
return screenShot;
},
});

export const videoCache = createCache<
[replayClient: ReplayClientInterface],
Video[]
>({
config: { immutable: true },
debugLabel: "ScreenshotCache",
getKey: ([client]) => client.getRecordingId() || "",
load: async ([client]) => {
const videos = await client.getVideos();
return videos;
},
});
10 changes: 10 additions & 0 deletions packages/shared/client/ReplayClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {

Check failure on line 1 in packages/shared/client/ReplayClient.ts

View workflow job for this annotation

GitHub Actions / Trunk Check

prettier

Incorrect formatting, autoformat by running 'trunk fmt'
BreakpointId,
Result as EvaluationResult,
ExecutionPoint,
Expand Down Expand Up @@ -45,6 +45,7 @@
TimeStampedPoint,
TimeStampedPointRange,
VariableMapping,
Video,
annotations,
createPauseResult,
findPointsResults,
Expand Down Expand Up @@ -810,6 +811,15 @@
return screen;
}

async getVideos(): Promise<Video[]> {
const sessionId = await this.waitForSession();
const { videos } = await client.Graphics.getVideos(
{ mimeType: "video/webm" },
sessionId
);
return videos;
}

async mapExpressionToGeneratedScope(expression: string, location: Location): Promise<string> {
const sessionId = await this.waitForSession();
const result = await client.Debugger.mapExpressionToGeneratedScope(
Expand Down
2 changes: 2 additions & 0 deletions pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const csp = (props: any) => {
// Required to inline images from the database and from external avaters
`img-src 'self' data: https:`,

`media-src 'self' data: https:`,

// Required for our logpoint analysis cache (which uses a Web worker)
`worker-src 'self' blob:`,
]
Expand Down
31 changes: 25 additions & 6 deletions src/ui/components/Video/Video.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext, useLayoutEffect, useState } from "react";

Check failure on line 1 in src/ui/components/Video/Video.tsx

View workflow job for this annotation

GitHub Actions / Trunk Check

prettier

Incorrect formatting, autoformat by running 'trunk fmt'

import Icon from "replay-next/components/Icon";
import { LoadingProgressBar } from "replay-next/components/LoadingProgressBar";
Expand Down Expand Up @@ -31,22 +31,38 @@
useLayoutEffect(() => {
const containerElement = document.getElementById("video") as HTMLDivElement;
const graphicsElement = document.getElementById("graphics") as HTMLImageElement;
const videoElement = document.getElementById("webmvideo") as HTMLVideoElement;
const videoSourceElement = document.getElementById("videoSrc") as HTMLSourceElement;
const graphicsOverlayElement = document.getElementById("overlay-graphics") as HTMLDivElement;

let prevState: Partial<State> = {};
let stalledTimeout: NodeJS.Timeout | null = null;

// Keep graphics in sync with the imperatively managed screenshot state
state.listen(nextState => {
if (nextState.screenShot != prevState.screenShot) {
const { screenShot } = nextState;
if (screenShot) {
graphicsElement.src = `data:${screenShot.mimeType};base64,${screenShot.data}`;
} else {
graphicsElement.src = "";
if (nextState.videos.length > 0 && !videoElement.src) {
graphicsElement.hidden = true;
videoElement.hidden = false;
videoElement.src = `data:video/webm;base64,${nextState.videos[0].data}`;
} else {
if (nextState.screenShot != prevState.screenShot) {
const { screenShot } = nextState;
if (screenShot) {
graphicsElement.src = `data:${screenShot.mimeType};base64,${screenShot.data}`;
graphicsElement.hidden = false;
videoElement.hidden = true;
} else {
graphicsElement.src = "";
videoSourceElement.src = "";
}
}
}

const { videos, paintIndex } = nextState;
if (videos.length > 0 && paintIndex !== null && paintIndex > 0) {
videoElement.currentTime = (paintIndex - 1) * (1.0 / videos[0].fps);
}

// Show loading progress bar if graphics stall for longer than 5s
const isLoading = nextState.status === "loading";
const wasLoading = prevState.status === "loading";
Expand Down Expand Up @@ -116,6 +132,9 @@
</div>

<img className={styles.Image} id="graphics" onClick={onClick} />
<video className={styles.Image} id="webmvideo" onClick={onClick}>
<source type="video/webm" id="videoSrc"/>
</video>

{/* Graphics that are relative to the rendered screenshot go here; this container is automatically positioned to align with the screenshot */}
<div className={styles.Graphics} id="overlay-graphics">
Expand Down
48 changes: 46 additions & 2 deletions src/ui/components/Video/imperative/MutableGraphicsState.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// The recording graphics (screenshots) are global state; there is only one rendered (in the DOM) at any point in time

Check failure on line 1 in src/ui/components/Video/imperative/MutableGraphicsState.tsx

View workflow job for this annotation

GitHub Actions / Trunk Check

prettier

Incorrect formatting, autoformat by running 'trunk fmt'
// Although declarative (React) components render the graphics elements (e.g. comments, DOM highlights, recorded cursor)
// it is better for performance if imperative code (re)positions these elements,
// otherwise there may be "render lag" when windows are resized.
Expand All @@ -11,7 +11,7 @@
//
// This approach is unusual, but it's arguably cleaner than sharing these values via the DOM.

import { ExecutionPoint, ScreenShot } from "@replayio/protocol";
import { ExecutionPoint, ScreenShot, Video } from "@replayio/protocol";

import { fitImageToContainer, getDimensions } from "replay-next/src/utils/image";
import { shallowEqual } from "shared/utils/compare";
Expand All @@ -34,8 +34,10 @@
localScale: number;
recordingScale: number;
screenShot: ScreenShot | undefined;
videos: Video[];
screenShotType: ScreenShotType | undefined;
status: Status;
paintIndex: number | null;
}

export const state = createState<State>({
Expand All @@ -52,6 +54,8 @@
screenShot: undefined,
screenShotType: undefined,
status: "loading",
paintIndex: null,
videos: [],
});

let lock: Object | null = null;
Expand All @@ -62,9 +66,11 @@
didResize?: boolean;
executionPoint: ExecutionPoint | null;
screenShot: ScreenShot | null;
videos: Video[];
screenShotType: ScreenShotType | null;
status: Status;
time: number;
paintIndex: number | null;
}> = {}
) {
const prevState = state.read();
Expand All @@ -76,6 +82,8 @@
screenShotType = prevState.screenShotType,
status = prevState.status,
time = prevState.currentTime,
paintIndex = prevState.paintIndex,
videos = prevState.videos,
} = options;

if (shallowEqual(options, { didResize })) {
Expand All @@ -92,7 +100,41 @@
let graphicsRect = prevState.graphicsRect;
let localScale = prevState.localScale;
let recordingScale = prevState.recordingScale;
if (screenShot && (screenShot != prevState.screenShot || didResize)) {
if (videos.length > 0) {
const naturalDimensions = {
aspectRatio: videos[0].width / videos[0].height,
height: videos[0].height,
width: videos[0].width,
}

if (lock !== localLock) {
return;
}

const naturalHeight = naturalDimensions.height;
const naturalWidth = naturalDimensions.width;

const containerRect = graphicsElement.getBoundingClientRect();
const scaledDimensions = fitImageToContainer({
containerHeight: containerRect.height,
containerWidth: containerRect.width,
imageHeight: naturalHeight,
imageWidth: naturalWidth,
});

const clientHeight = scaledDimensions.height;
const clientWidth = scaledDimensions.width;

localScale = clientWidth / naturalWidth;
recordingScale = 1.0;

graphicsRect = {
height: clientHeight,
left: containerRect.left + (containerRect.width - clientWidth) / 2,
top: containerRect.top + (containerRect.height - clientHeight) / 2,
width: clientWidth,
};
} else if (screenShot && (screenShot != prevState.screenShot || didResize)) {
const naturalDimensions = await getDimensions(screenShot.data, screenShot.mimeType);
if (lock !== localLock) {
return;
Expand Down Expand Up @@ -132,6 +174,8 @@
screenShot: screenShot || undefined,
screenShotType: screenShotType || undefined,
status,
paintIndex,
videos
};

if (!shallowEqual(prevState, nextState)) {
Expand Down
39 changes: 28 additions & 11 deletions src/ui/components/Video/imperative/updateGraphics.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ExecutionPoint, ScreenShot } from "@replayio/protocol";

Check failure on line 1 in src/ui/components/Video/imperative/updateGraphics.ts

View workflow job for this annotation

GitHub Actions / Trunk Check

prettier

Incorrect formatting, autoformat by running 'trunk fmt'

import { PaintsCache, findMostRecentPaint } from "protocol/PaintsCache";
import { PaintsCache, findMostRecentPaint, findMostRecentPaintIndex } from "protocol/PaintsCache";
import { RepaintGraphicsCache } from "protocol/RepaintGraphicsCache";
import { paintHashCache } from "replay-next/src/suspense/PaintHashCache";
import { screenshotCache } from "replay-next/src/suspense/ScreenshotCache";
import { screenshotCache, videoCache } from "replay-next/src/suspense/ScreenshotCache";
import { ReplayClientInterface } from "shared/client/types";
import { updateState } from "ui/components/Video/imperative/MutableGraphicsState";

Expand Down Expand Up @@ -32,9 +32,11 @@
}

const promises: Promise<ScreenShot | undefined>[] = [];
const videos = await videoCache.readAsync(replayClient);

// If the current time is before the first paint, we should show nothing
const paintPoint = findMostRecentPaint(time);
const paintIndex = findMostRecentPaintIndex(time);
const isBeforeFirstCachedPaint = !paintPoint || !paintPoint.paintHash;
if (isBeforeFirstCachedPaint) {
updateState(graphicsElement, {
Expand All @@ -44,7 +46,7 @@
status: executionPoint ? "loading" : "loaded",
time,
});
} else {
} else if (videos.length == 0) {
const cachedScreenShot = paintHashCache.getValueIfCached(paintPoint.paintHash);
if (cachedScreenShot) {
// If this screenshot has already been cached, skip fetching it again
Expand All @@ -63,20 +65,34 @@
}

let repaintGraphicsScreenShot: ScreenShot | undefined = undefined;
if (executionPoint) {
const promise = fetchRepaintGraphics({
// if (executionPoint) {
// const promise = fetchRepaintGraphics({
// executionPoint,
// replayClient,
// time,
// }).then(screenShot => {
// repaintGraphicsScreenShot = screenShot;

// return screenShot;
// });

// promises.push(promise);
// }

if (videos.length > 0) {
updateState(graphicsElement, {
executionPoint,
replayClient,
screenShotType: repaintGraphicsScreenShot != null ? "repaint" : "cached-paint",
status: "loaded",
time,
}).then(screenShot => {
repaintGraphicsScreenShot = screenShot;

return screenShot;
paintIndex,
videos
});

promises.push(promise);
return true;
}


if (promises.length === 0) {
// If we are before the first paint and have no execution point to request a repaint,
// then we should clear out the currently visible graphics and bail out
Expand All @@ -97,6 +113,7 @@
screenShotType: repaintGraphicsScreenShot != null ? "repaint" : "cached-paint",
status: "loaded",
time,
paintIndex,
});

if (repaintGraphicsScreenShot != null) {
Expand Down
Loading