Video review

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.

0:00 / 0:00

A comment now would land at 0:00

Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/video-review.json
Usage
Markers in, seconds out. The block owns no thread, composer or approval.
TSImport
import { VideoReview } from "@/components/blocks/video-review";
TSExample
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"
    />
  );
}
Composition
The SDK VideoPlayer with the review parts in its transport and timelineOverlay slots.
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, pulsing
Preview
Markers on the seek bar, one per comment; clicking one pauses on its moment, and a comment with a spot is pinned on the frame. Click the picture to place one. Frame steps sit beside play.
0:00 / 0:00

A comment now would land at 0:00

Read only
No onTimeChange: an approved or older version you watch rather than annotate. Markers still select and seek.
0:00 / 0:00
API Reference
Props for the block plus the shape of each entry it takes.

VideoReview

PropTypeDefaultDescription
srcstring | null—The video. Null draws `emptyLabel` in a box of the same shape.
posterstring—
aspectnumber16 / 9Width / height, reserved before the video loads.
markersVideoReviewMarker[]—One per comment, on the seek bar.
selectedId / onSelectstring | 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.
fpsnumber30Nominal frame rate for the frame steps.
maxHeightstring"58svh"Height budget, spent on max-width so the box keeps the picture's shape.
volumeStorageKeystring—Remember the volume per browser under this localStorage key.
emptyLabel / backLabel / forwardLabelstring—
labelstring—Names the surface to a screen reader.
classNamestring—

VideoReviewMarker

PropTypeDefaultDescription
idstring—
labelReact.ReactNode—Usually its number in the thread.
secondsnumber—Seconds into the video.
descriptionstring—Read to a screen reader.
mutedboolean—Settled feedback, drawn quiet.
x / ynumber—A spot on the frame, 0–1. Pinned on the picture while the playhead is within half a second of it, or while selected.