Comment on a moment of a video, or a spot on its frame — the SDK video player with comment markers on its seek bar, pins on the picture, frame steps, and the playhead reported as you watch.
A comment now would land at 0:00
npx shadcn@latest add https://sdk-components.thesqd.com/r/video-review.jsonimport { VideoReview } from "@/components/blocks/video-review";import { VideoReview } from "@/components/blocks/video-review";
// The anchor follows the playhead; freeze it once the reviewer starts typing,
// and let only explicit gestures (scrub, frame step, marker) move it after.
export function ConceptVideo({ comments, draft, setDraft, frozen }) {
return (
<VideoReview
src={concept.src}
aspect={concept.aspect}
markers={comments.map((c, i) => ({ id: c.id, label: i + 1, seconds: c.seconds }))}
onTimeChange={(seconds, explicit) => {
if (!frozen || explicit) setDraft(seconds);
}}
draft={frozen ? draft : null}
volumeStorageKey="app.media.volume"
/>
);
}VideoReview — focusable group: ← → frame step (Shift: 1s), Space plays
└── VideoPlayer — the SDK primitive, controlsLayout="below": speed, PiP, fullscreen, volume
├── frameOverlay slot — over the picture
│ ├── click layer — onPlace: pause, hand up { seconds, x, y }
│ ├── pins — markers with x/y, near the playhead or selected
│ └── draft spot — pulsing
├── transport slot — Back / Forward one frame (ghost icon Buttons + Tooltip)
└── timelineOverlay slot — over the seek bar, in its own box
├── markers[] — one button per comment; click pauses on its moment
└── draft — the frozen moment, pulsingA comment now would land at 0:00
VideoReview
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | null | — | The video. Null draws `emptyLabel` in a box of the same shape. |
poster | string | — | |
aspect | number | 16 / 9 | Width / height, reserved before the video loads. |
markers | VideoReviewMarker[] | — | One per comment, on the seek bar. |
selectedId / onSelect | string | null / (id) => void | — | Selecting a marker pauses on its moment; selecting it again clears. |
onTimeChange | (seconds, explicit) => void | — | The playhead as you watch. `explicit` is a scrub, frame step or marker. Absent means read-only. |
onPlace | ({ seconds, x, y }) => void | — | Clicking the picture pauses and places a comment on that spot (fractions of the frame). Absent, a click plays and pauses. |
draft | { seconds, x?, y? } | null | — | The frozen moment, and spot, a comment is being written about. |
fps | number | 30 | Nominal frame rate for the frame steps. |
maxHeight | string | "58svh" | Height budget, spent on max-width so the box keeps the picture's shape. |
volumeStorageKey | string | — | Remember the volume per browser under this localStorage key. |
emptyLabel / backLabel / forwardLabel | string | — | |
label | string | — | Names the surface to a screen reader. |
className | string | — |
VideoReviewMarker
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | — | |
label | React.ReactNode | — | Usually its number in the thread. |
seconds | number | — | Seconds into the video. |
description | string | — | Read to a screen reader. |
muted | boolean | — | Settled feedback, drawn quiet. |
x / y | number | — | A spot on the frame, 0–1. Pinned on the picture while the playhead is within half a second of it, or while selected. |