Audio review
ProA recording split into one lane per speaker under a single playhead, with the stretches where people talk over each other marked, and play and skip controls.
npx prodular-ui add audio-reviewPart of Prodular UI Pro. Preview and read the docs freely, install with a license. See pricing
Three voices
Play, skip, or click and drag across the lanes. The name of whoever is talking at the playhead lights up.
Call with Tidewater Freight
October 14, three voices. Hatched where two people talk at once.
- Maya and Daniel at once from 0:17 to 0:18
- Priya and Daniel at once from 0:32 to 0:33
"use client"
import * as React from "react"
import { AudioReview, AudioReviewControls, AudioReviewLane } from "@/components/ui/audio-review"
const DURATION = 43const BARS = 140const people = [ { id: "maya", name: "Maya Okafor", org: "Halcyon Labs", color: "var(--chart-1)", seed: 1.3 }, { id: "daniel", name: "Daniel Reyes", org: "Tidewater Freight", color: "var(--chart-2)", seed: 2.1 }, { id: "priya", name: "Priya Natarajan", org: "Tidewater Freight", color: "var(--chart-3)", seed: 0.7 },]// Who talks when, in seconds.const turns: [string, number, number][] = [ ["maya", 0, 8.6], ["daniel", 9.2, 18.9], ["maya", 17.6, 28.2], ["priya", 28.8, 33.4], ["daniel", 32.6, 36.4], ["maya", 37, 42.6],]const overlaps = [ { from: 17.6, to: 18.9, label: "Maya and Daniel at once" }, { from: 32.6, to: 33.4, label: "Priya and Daniel at once" },]
function lane(id: string, seed: number) { return Array.from({ length: BARS }, (_, i) => { const t = ((i + 0.5) / BARS) * DURATION const on = turns.some(([who, from, to]) => who === id && t >= from && t < to) return on ? 0.3 + 0.6 * Math.abs(Math.sin(i * seed) * Math.cos(i * 0.29)) : 0 })}const lanes = Object.fromEntries(people.map((p) => [p.id, lane(p.id, p.seed)]))
export default function AudioReviewCall() { const [time, setTime] = React.useState(17.9) const [playing, setPlaying] = React.useState(false)
React.useEffect(() => { if (!playing) return let last = performance.now() let frame = requestAnimationFrame(function tick(now) { setTime((t) => Math.min(t + (now - last) / 1000, DURATION)) last = now frame = requestAnimationFrame(tick) }) return () => cancelAnimationFrame(frame) }, [playing])
if (playing && time >= DURATION) setPlaying(false)
return ( <div className="flex w-full max-w-2xl flex-col gap-4 rounded-xl border bg-card p-4 shadow-xs"> <div className="flex flex-col gap-0.5"> <p className="m-0 text-sm font-medium">Call with Tidewater Freight</p> <p className="m-0 text-[13px] text-muted-foreground">October 14, three voices. Hatched where two people talk at once.</p> </div> <AudioReview duration={DURATION} time={time} onSeek={setTime} overlaps={overlaps} label="Call with Tidewater Freight"> {people.map((p) => ( <AudioReviewLane key={p.id} label={p.name} description={p.org} color={p.color} values={lanes[p.id]} speaking={turns.some(([who, from, to]) => who === p.id && time >= from && time < to)} /> ))} </AudioReview> <AudioReviewControls time={time} duration={DURATION} playing={playing} onPlayingChange={(next) => { if (next && time >= DURATION) setTime(0) setPlaying(next) }} onSeek={setTime} className="border-t pt-3" /> </div> )}Anatomy
<AudioReview duration={duration} time={time} onSeek={seek} overlaps={overlaps}> <AudioReviewLane label="Maya Okafor" color="var(--chart-1)" values={maya} speaking /> <AudioReviewLane label="Daniel Reyes" color="var(--chart-2)" values={daniel} /></AudioReview><AudioReviewControls time={time} duration={duration} playing={playing} onPlayingChange={setPlaying} onSeek={seek} />Guidance
Use it when
- Use it where people check who said what and when: call reviews, disputed answers, interview notes.
- Mark overlaps where crosstalk matters, for example when a transcript may have mixed two people up.
- Pair it with Transcript below, in the same speaker colors.
Avoid
- One speaker. Use Waveform with a play button.
- More than four or five lanes. Group the rest or let people pick whom to show.
- Live calls. The lanes show a finished recording.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Separate the voices before drawing them: pass each AudioReviewLane the loudness of that speaker only, from 0 to 1, with 0 where they are silent, and the same number of values in every lane.
- Keep time in one place, the audio element's currentTime, and pass it to both AudioReview and AudioReviewControls. Seek the audio in onSeek.
- Set speaking on the lane whose speaker is talking at the current time, from the diarization segments.
- Pass overlaps from the diarization: every stretch where two speakers' segments overlap, with a label that names them. The label is what screen readers hear.
- Use the same chart token per speaker in AudioReviewLane, TranscriptTurn and Avatar --avatar-speaking.
API
AudioReview
| Prop | Type | Default | Description |
|---|---|---|---|
| duration / time | number | Length and seconds played. | |
| onSeek | (seconds: number) => void | Called on click, drag, arrows (5 s), Page Up and Down (30 s), Home and End. | |
| overlaps | { from, to, label? }[] | Stretches where people talk at once, hatched across every lane. |
AudioReviewLane
| Prop | Type | Default | Description |
|---|---|---|---|
| label / description / color | string | Who, a second line, and their color. | |
| values | number[] | This speaker's loudness, 0 where they are silent. | |
| speaking | boolean | Highlights the name while they talk at the playhead. |
AudioReviewControls
| Prop | Type | Default | Description |
|---|---|---|---|
| time / duration / playing / onPlayingChange / onSeek | number, boolean, callbacks | Play and pause, skip back and forward by skip seconds (5), and the time. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add audio-review# or, with the registry in components.jsonnpx shadcn@latest add @prodular/audio-review