Skip to content

Commit

Permalink
Replace declarative Video component with imperative controller
Browse files Browse the repository at this point in the history
  • Loading branch information
bvaughn committed Mar 5, 2024
1 parent ce97fc7 commit cbf72d5
Show file tree
Hide file tree
Showing 30 changed files with 765 additions and 592 deletions.
16 changes: 8 additions & 8 deletions packages/e2e-tests/helpers/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,23 +96,21 @@ export async function getGraphicsTime(page: Page): Promise<number | null> {
export async function getGraphicsPixelColor(page: Page, x: number, y: number) {
return await page.evaluate(
([x, y]) => {
const element = document.querySelector("#graphics");
if (element == null) {
const element = document.querySelector("#graphics") as HTMLImageElement;
if (!element?.getAttribute("src")) {
return null;
}

const imageElement = element as HTMLImageElement;

const canvas = document.createElement("canvas");
canvas.width = imageElement.width;
canvas.height = imageElement.height;
canvas.width = element.width;
canvas.height = element.height;

const context = canvas.getContext("2d");
if (context == null) {
return null;
}

context.drawImage(imageElement, 0, 0);
context.drawImage(element, 0, 0);

const { data } = context.getImageData(x, y, 1, 1);

Expand All @@ -127,7 +125,9 @@ export async function getGraphicsPixelColor(page: Page, x: number, y: number) {
}

export async function waitForGraphicsToLoad(page: Page) {
await debugPrint(page, `Waiting for graphics to load...`, "waitForGraphicsToLoad");

await waitFor(async () => {
await expect(await getGraphicsStatus(page)).not.toBe("fetching-cached-paint");
await expect(await getGraphicsStatus(page)).toBe("loaded");
});
}
4 changes: 2 additions & 2 deletions packages/e2e-tests/tests/repaint-05.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import test, { Page, expect } from "../testFixtureCloneRecording";

test.use({ exampleKey: "paint_at_intervals.html" });

async function seekToTimePercentAndWaitForPaint(page: Page, time: number) {
await seekToTimePercent(page, time);
async function seekToTimePercentAndWaitForPaint(page: Page, percent: number) {
await seekToTimePercent(page, percent);
await waitForGraphicsToLoad(page);
}

Expand Down
15 changes: 8 additions & 7 deletions packages/protocol/PaintsCache.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSingleEntryCache } from "suspense";

import { StreamingScreenShotCache } from "protocol/StreamingScreenShotCache";
import { recordingTargetCache } from "replay-next/src/suspense/BuildIdCache";
import { screenshotCache } from "replay-next/src/suspense/ScreenshotCache";
import { find, findIndexGTE, findIndexLTE } from "replay-next/src/utils/array";
import { getDimensions } from "replay-next/src/utils/image";
import { replayClient } from "shared/client/ReplayClientContext";
Expand Down Expand Up @@ -41,17 +41,18 @@ export async function findFirstMeaningfulPaint() {
const paint = paints[index];

try {
const { value } = await StreamingScreenShotCache.readAsync(
const screenShot = await screenshotCache.readAsync(
replayClient,
paint.time,
paint.point
paint.point,
paint.paintHash
);
if (value && value.hash) {
const { width, height } = await getDimensions(value.hash, value.mimeType);

if (screenShot && screenShot.hash) {
const { width, height } = await getDimensions(screenShot.hash, screenShot.mimeType);

// Estimate how "interesting" the screen is based on what % of the image is different pixels.
// This is done to avoid showing something like a blank page or a mostly empty loading screen.
if (value.data.length > (width * height) / 40) {
if (screenShot.data.length > (width * height) / 40) {
return paint;
}
}
Expand Down
16 changes: 16 additions & 0 deletions packages/protocol/RepaintGraphicsCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { PauseId, repaintGraphicsResult } from "@replayio/protocol";
import { Cache, createCache } from "suspense";

import { ReplayClientInterface } from "shared/client/types";

export const RepaintGraphicsCache: Cache<
[replayClient: ReplayClientInterface, pauseId: PauseId],
repaintGraphicsResult | null
> = createCache({
config: { immutable: true },
debugLabel: "RepaintGraphicsCache",
getKey: ([replayClient, pauseId]) => pauseId,
load: async ([replayClient, pauseId]) => {
return replayClient.repaintGraphics(pauseId);
},
});
109 changes: 0 additions & 109 deletions packages/protocol/StreamingScreenShotCache.ts

This file was deleted.

22 changes: 13 additions & 9 deletions src/ui/actions/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ export function seek({
location?: Location;
}): UIThunkAction<Promise<void>> {
return async (dispatch, getState, { replayClient }) => {
const isPlaying = getPlayback(getState()) !== null;
if (isPlaying) {
dispatch(stopPlayback());
}

const seekLock = new Object();
dispatch(pauseRequestedAt({ seekLock, executionPoint, time, location }));
dispatch(setTimelineState({ currentTime: time, playback: null }));
Expand Down Expand Up @@ -378,7 +383,7 @@ export function togglePlayback(): UIThunkAction {
}

export function startPlayback(
{ beginTime: optBeginTime, endTime: optEndTime }: PlaybackOptions = {
{ beginPoint = null, beginTime, endPoint = null, endTime }: PlaybackOptions = {
beginTime: null,
endTime: null,
}
Expand All @@ -387,21 +392,20 @@ export function startPlayback(
const state = getState();
const currentTime = getCurrentTime(state);

const endTime =
optEndTime ||
endTime =
endTime ||
(getPlaybackFocusWindow(state) && replayClient.getCurrentFocusWindow()?.end.time) ||
getZoomRegion(state).endTime;

const beginDate = Date.now();
const beginTime =
optBeginTime ||
beginTime =
beginTime ||
(currentTime >= endTime
? (getPlaybackFocusWindow(state) && replayClient.getCurrentFocusWindow()?.begin.time) || 0
: currentTime);

dispatch(
setTimelineState({
playback: { beginTime, beginDate, endTime, time: beginTime },
playback: { beginPoint, beginTime, endPoint, endTime, time: beginTime },
currentTime: beginTime,
})
);
Expand All @@ -412,15 +416,15 @@ export function startPlayback(

export function stopPlayback(updateTime: boolean = true): UIThunkAction {
return async (dispatch, getState) => {
dispatch(setTimelineState({ playback: null }));

if (updateTime) {
const playback = getPlayback(getState());

if (playback) {
dispatch(seek({ time: playback.time }));
}
}

dispatch(setTimelineState({ playback: null }));
};
}

Expand Down
6 changes: 3 additions & 3 deletions src/ui/components/Comments/VideoComments/VideoComment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useLayoutEffect, useRef } from "react";
import { isVisualCommentTypeData } from "replay-next/components/sources/utils/comments";
import { Comment } from "shared/graphql/types";
import { setHoveredCommentId, setSelectedCommentId } from "ui/actions/app";
import { subscribe } from "ui/components/Video/MutableGraphicsState";
import { state } from "ui/components/Video/imperative/MutableGraphicsState";
import { useAppDispatch } from "ui/setup/hooks";

import styles from "./VideoComment.module.css";
Expand All @@ -26,8 +26,8 @@ export default function VideoComment({
const element = elementRef.current;
if (element) {
// Imperatively position and scale these graphics to avoid "render lag" when resizes occur
return subscribe(state => {
const { height, width } = state;
return state.listen(state => {
const { height, width } = state.graphicsRect;
const { scaledX, scaledY } = typeData;

element.style.left = `${scaledX * width}px`;
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/NodePickerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { highlightNode, unhighlightNode } from "devtools/client/inspector/markup/actions/markup";
import { useMostRecentLoadedPause } from "replay-next/src/hooks/useMostRecentLoadedPause";
import { ReplayClientContext } from "shared/client/ReplayClientContext";
import { getMouseEventPosition } from "ui/components/Video/getMouseEventPosition";
import { getMouseEventPosition } from "ui/components/Video/imperative/getMouseEventPosition";
import { useAppDispatch } from "ui/setup/hooks";
import { boundingRectsCache, getMouseTarget } from "ui/suspense/nodeCaches";

Expand Down
14 changes: 3 additions & 11 deletions src/ui/components/Timeline/ProgressBars.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,27 @@
import clamp from "lodash/clamp";
import React from "react";

import {
getCurrentTime,
getHoverTime,
getPlaybackPrecachedTime,
getZoomRegion,
} from "ui/reducers/timeline";
import { getCurrentTime, getHoverTime, getZoomRegion } from "ui/reducers/timeline";
import { useAppSelector } from "ui/setup/hooks";
import { getVisiblePosition } from "ui/utils/timeline";

export default function ProgressBars() {
const currentTime = useAppSelector(getCurrentTime);
const hoverTime = useAppSelector(getHoverTime);
const precachedTime = useAppSelector(getPlaybackPrecachedTime);
const zoomRegion = useAppSelector(getZoomRegion);

const percent = getVisiblePosition({ time: currentTime, zoom: zoomRegion }) * 100;
const hoverPercent = getVisiblePosition({ time: hoverTime, zoom: zoomRegion }) * 100;
const precachedPercent = getVisiblePosition({ time: precachedTime, zoom: zoomRegion }) * 100;

return (
<>
<div className="progress-line full" />
<div
className="progress-line preview-max"
style={{ width: `${clamp(Math.max(hoverPercent, precachedPercent), 0, 100)}%` }}
style={{ width: `${clamp(hoverPercent, 0, 100)}%` }}
/>
<div
className="progress-line preview-min"
style={{ width: `${clamp(Math.min(hoverPercent, precachedPercent), 0, 100)}%` }}
style={{ width: `${clamp(hoverPercent, 0, 100)}%` }}
data-hover-value={hoverPercent}
/>
<div
Expand Down
Loading

0 comments on commit cbf72d5

Please sign in to comment.