PlaybackControls
Review history
Play, pause, scrub, step between event times, select a playback speed, or return to the supplied live reference. The example owns a simulated clock and stops at the end of its fixed training window.
Training clock · Fixed live reference: 2026-10-01 12:01:00 UTC.
Controlled usage
All timestamps use Unix epoch milliseconds. The component displays the date, time, milliseconds and UTC zone. It requests changes; your application supplies the accepted cursor and playback state.
<PlaybackControls
start={windowStart}
end={windowEnd}
value={cursor}
playing={playing}
onValueChange={setCursor}
onPlayingChange={setPlaying}
eventTimes={events.map((event) => event.time)}
playbackRate={rate}
onPlaybackRateChange={setRate}
liveTime={liveTime}
followingLive={followingLive}
onReturnToLive={returnToLive}
/>
Scrubbing and event stepping call onPlayingChange(false) before
onValueChange(nextTime). Event times are sorted and deduplicated; invalid dates,
times outside the range and the current event time are skipped. Buttons disable
when there is no earlier or later event.
The application owns its clock, media seeks, playback completion, live reference,
range updates and synchronization. onReturnToLive can replace the review window
when the supplied live reference lies outside it. Changes are never accepted
optimistically by the component.
Optional controls
- Supply
eventTimesto show event stepping, including disabled steps for an empty list. - Supply
onPlaybackRateChangeto show speed selection. Defaults are 0.5×, 1×, 2× and 4×. Speeds are listed in ascending order, and a suppliedplaybackRatethat is not offered is shown in order among them. - Supply
onReturnToLiveand a validliveTimeto show Return to live. - Set
followingLiveto show the supplied follow state and disable Return to live.
Unavailable range
Invalid or equal bounds disable the transport and explain the problem. An invalid cursor displays “Unknown playback position.” A cursor outside the range clamps for display and disables transport until the caller supplies an in-range value. Epoch zero is valid.
A single timestamp is available. Playback requires a time range.
Disabled
Training clock · Fixed live reference: 2026-10-01 12:01:00 UTC.
Props
| PROP | TYPE | DEFAULT | REQUIRED |
|---|---|---|---|
startInclusive range start in Unix epoch milliseconds. Must be a valid date before end; zero is valid. | number | — | yes |
endInclusive range end in Unix epoch milliseconds. Invalid or single-point ranges disable transport with explanatory text. | number | — | yes |
valueControlled cursor in Unix epoch milliseconds. Out-of-range values clamp for display and disable transport; invalid dates display an unknown position. | number | — | yes |
playingCaller-owned playback state. The component owns no clock and makes no optimistic state changes. | boolean | — | yes |
onValueChange(value: number) => void. Requests a valid in-range timestamp after requesting pause. | function | — | yes |
onPlayingChange(playing: boolean) => void. Requests play/pause. Scrubbing and event stepping call this with false before onValueChange. | function | — | yes |
playbackRateControlled positive finite speed multiplier. A valid supplied rate remains visible, in order, even when absent from playbackRates; invalid rates display Unknown rate. | number | 1 | no |
playbackRatesReadonly number array of offered speed multipliers. Defaults to [0.5, 1, 2, 4]. Non-positive/non-finite rates and duplicates are omitted. Listed in ascending order. | array | — | no |
onPlaybackRateChange(rate: number) => void. Supplying this callback shows the controlled speed selector. | function | — | no |
eventTimesReadonly epoch-millisecond array. Supplying it shows previous/next event buttons. Times are sorted, deduplicated and filtered to valid dates within the range; stepping skips the current time. | array | — | no |
liveTimeApplication-supplied live reference in epoch milliseconds. May lie outside the review range; the application owns any range change. | number | — | no |
followingLiveShows Following live and disables Return to live while the caller follows its reference. | boolean | false | no |
onReturnToLive() => void. Shows Return to live only when liveTime is a valid date. Requests no cursor, range or playing change itself. | function | — | no |
disabledDisables all transport actions and removes them from sequential keyboard navigation. | boolean | false | no |
labelAccessible root-group name. Also names the position slider and playback-speed selector. | string | "Playback" | no |
className | string | — | no |
Accessibility
- The forwarded ref points to the named root group. Native div attributes (
id,data-*,style) reach it, and a suppliedaria-describedbyis kept alongside the unavailable message. - Buttons use native keyboard activation; the speed selector uses the shared Select primitive.
- The Slider thumb announces a formatted UTC timestamp. Each arrow key moves it by a readable step of about a two-hundredth of the window (10 seconds in a 30-minute window, 50 ms in a 10-second clip), stopping exactly at either end. Page Up and Page Down move a tenth of the window. Home and End request the exact range endpoints, and event stepping keeps exact milliseconds.
- Scrubbing preserves keyboard focus as accepted timestamps change. When an accepted request makes the focused button unavailable (stepping onto the first or last event, Return to live once following), focus moves to the other event step, else Play or Pause, else the group.
- Invalid inputs are described on the group and slider; disabled actions leave the tab order.
- Shared controls provide minimum 24px targets and the OPERATOR focus indicator.
Manifest entry
<PlaybackControls start={start} end={end} value={cursor} playing={playing} onValueChange={setCursor} onPlayingChange={setPlaying} eventTimes={eventTimes} playbackRate={rate} onPlaybackRateChange={setRate} liveTime={liveTime} followingLive={followingLive} onReturnToLive={returnToLive} />View agent manifest
{
"description": "Controlled UTC timestamp transport with a bounded scrubber, play/pause, optional event stepping, playback speed and return to a supplied live reference.",
"props": {
"start": {
"type": "number",
"required": true,
"description": "Inclusive range start in Unix epoch milliseconds. Must be a valid date before end; zero is valid."
},
"end": {
"type": "number",
"required": true,
"description": "Inclusive range end in Unix epoch milliseconds. Invalid or single-point ranges disable transport with explanatory text."
},
"value": {
"type": "number",
"required": true,
"description": "Controlled cursor in Unix epoch milliseconds. Out-of-range values clamp for display and disable transport; invalid dates display an unknown position."
},
"playing": {
"type": "boolean",
"required": true,
"description": "Caller-owned playback state. The component owns no clock and makes no optimistic state changes."
},
"onValueChange": {
"type": "function",
"required": true,
"description": "(value: number) => void. Requests a valid in-range timestamp after requesting pause."
},
"onPlayingChange": {
"type": "function",
"required": true,
"description": "(playing: boolean) => void. Requests play/pause. Scrubbing and event stepping call this with false before onValueChange."
},
"playbackRate": {
"type": "number",
"default": 1,
"description": "Controlled positive finite speed multiplier. A valid supplied rate remains visible, in order, even when absent from playbackRates; invalid rates display Unknown rate."
},
"playbackRates": {
"type": "array",
"description": "Readonly number array of offered speed multipliers. Defaults to [0.5, 1, 2, 4]. Non-positive/non-finite rates and duplicates are omitted. Listed in ascending order."
},
"onPlaybackRateChange": {
"type": "function",
"description": "(rate: number) => void. Supplying this callback shows the controlled speed selector."
},
"eventTimes": {
"type": "array",
"description": "Readonly epoch-millisecond array. Supplying it shows previous/next event buttons. Times are sorted, deduplicated and filtered to valid dates within the range; stepping skips the current time."
},
"liveTime": {
"type": "number",
"description": "Application-supplied live reference in epoch milliseconds. May lie outside the review range; the application owns any range change."
},
"followingLive": {
"type": "boolean",
"default": false,
"description": "Shows Following live and disables Return to live while the caller follows its reference."
},
"onReturnToLive": {
"type": "function",
"description": "() => void. Shows Return to live only when liveTime is a valid date. Requests no cursor, range or playing change itself."
},
"disabled": {
"type": "boolean",
"default": false,
"description": "Disables all transport actions and removes them from sequential keyboard navigation."
},
"label": {
"type": "string",
"default": "Playback",
"description": "Accessible root-group name. Also names the position slider and playback-speed selector."
},
"className": {
"type": "string"
}
},
"composable_with": [
"MediaFrame",
"TimeRange",
"EventTimeline",
"Chart"
],
"never_inside": [
"Button"
],
"example": "<PlaybackControls start={start} end={end} value={cursor} playing={playing} onValueChange={setCursor} onPlayingChange={setPlaying} eventTimes={eventTimes} playbackRate={rate} onPlaybackRateChange={setRate} liveTime={liveTime} followingLive={followingLive} onReturnToLive={returnToLive} />"
}