Knit UI
GitHub

Playground

Loading playground…

true
Playground
<Video
  source="https://media.w3.org/2010/05/sintel/trailer.mp4"
  contentFit="contain"
  size="md"
  controls
  autoHide
  loop={false}
  muted={false}
  aspectRatio={1.7777777777777777}
/>

Examples

Basic

Loading example…

BasicSourceStorybook

Auto Play Muted

Loading example…

Auto Play MutedSourceStorybook

Content Fit Cover

Loading example…

Content Fit CoverSourceStorybook

Sizes

Control sizing across the shared kit scale — the chrome's buttons, icons, and scrubber all step together off the canonical controlMetrics/icon ladders (the same scale Button/ActionIcon use), not a media-local copy.

Loading example…

SizesSourceStorybook

Native Platform Controls

Loading example…

Native Platform ControlsSourceStorybook

Custom Control Bar

Compose a custom control bar from the exported parts.

Loading example…

Custom Control BarSourceStorybook

Captions

Loading example…

CaptionsSourceStorybook

Custom Styled Captions

Because captions ride the kit overlay (not the platform's built-in subtitle renderer), their typography is fully stylable via the captions / captionText style slots — font, size, color, weight, background.

Loading example…

Custom Styled CaptionsSourceStorybook

Keyboard Shortcuts

Keyboard shortcuts (focus the player first): Space/k play·pause, ←/→ seek, ↑/↓ volume, m mute, f fullscreen, p PiP, 0–9 jump, </> speed.

Loading example…

Keyboard ShortcutsSourceStorybook

Persistent Controls

Always-visible controls (no auto-hide).

Loading example…

Persistent ControlsSourceStorybook

Styles

Per-slot styles overrides (Pillar B).

Loading example…

StylesSourceStorybook

Swap Source

Two players sharing nothing but the contract — swap the source at runtime.

Loading example…

Swap SourceSourceStorybook

Two Players

Two players sharing ONE shared video engine (the default singleton). The single <video> teleports into whichever player you press play on; the other shows its poster and pauses. Returning to the first resumes from where you left off — and there is only ever ONE <video> element in the DOM.

Loading example…

Two PlayersSourceStorybook

Now Playing

Now-playing / lock-screen controls with a cover preview. nowPlaying folds the title/artist/artwork into the source metadata and flips expo-video's showNowPlayingNotification + staysActiveInBackground, so the OS shows a media notification with the artwork as the cover and playback keeps going in the background. Native-only — surfacing it on a device build also needs the expo-video config plugin's supportsBackgroundPlayback: true. (No-op on web; the story still renders a normal player there.)

Loading example…

Now PlayingSourceStorybook

Props

PropTypeDefaultDescription
source requiredstring | number | VideoSourceObject | nullhttps://media.w3.org/2010/05/sintel/trailer.mp4The media to play.
allowsPictureInPictureboolean | undefinedAllow Picture-in-Picture. Default `true`.
artiststring | undefinedNow-playing artist / secondary text under the title.
artworkstring | undefinedNow-playing artwork (cover) image URL.
aspectRationumber | undefinedFixed aspect ratio for the frame (e.g. `16 / 9`).
autoHidenumber | boolean | undefinedtrueAuto-hide the chrome during playback. `true` uses the default delay, `false` keeps it always visible, a number sets the delay in ms.
autoPlayboolean | undefinedBegin playback as soon as the source is ready.
contentFitVideoContentFit | undefined
contain · cover · fill
containHow the frame is scaled in the container. Default `'contain'`.
controlsReactNodetrueThe control chrome. `true` (default) renders the built-in control bar; `false` renders no chrome; a node renders custom chrome inside the context (compose `Video.*` parts).
controlsOffsetnumber | SpaceTokens | undefinedInset of the control bar from the frame edges.
doubleClickToFullscreenboolean | undefinedWeb: double-click/tap the video to toggle fullscreen. Default `true`.
getController((controller: VideoController) => void) | undefinedReceives the controller once ready.
heightnumber | Animated.AnimatedNode | GetThemeValueForKey<"height"> | null | undefined
idstring | undefinedStable identity for the shared-engine registry. Defaults to a generated id (each `<Video>` is its own player); pass an explicit `id` to make two mounts address the SAME shared-engine slot.
keyboardboolean | undefinedEnable keyboard shortcuts (space, arrows, m, f, …). Default `true`.
labelstring | undefinedAccessible label for the player region. Default `"Video player"`.
loopboolean | undefinedfalseReplay automatically on end.
mutedboolean | undefinedfalseStart muted (required by browsers for unattended autoplay).
nativeControlsboolean | undefinedUse the platform's own native controls instead of the kit chrome.
nowPlayingboolean | VideoMetadata | undefinedEnable the OS now-playing notification + lock-screen controls (native; no-op on web). `true` derives the displayed metadata from `title`/`artist`/`artwork`; an object supplies it explicitly. Surfacing it on a device build also requires the `expo-video` config plugin's `supportsBackgroundPlayback: true`.
onEnded(() => void) | undefined
onError((error: VideoError) => void) | undefined
onPlayingChange((playing: boolean) => void) | undefined
onReady(() => void) | undefinedFired once the player first becomes ready to play.
onStatusChange((status: VideoStatus, error: VideoError | null) => void) | undefined
onTimeUpdate((currentTime: number) => void) | undefined
playbackRatenumber | undefinedInitial playback rate.
posterstring | undefinedPoster image shown before playback (placeholder while the player is idle).
showBigPlayButtonboolean | undefinedShow the centered big play button while paused. Default `true`.
size system"xxs" | "xs" | "sm" | "md" | "lg" | "xl" | "xxl" | undefined
xxs · xs · sm · md · lg · xl · xxl
mdControl sizing scale. Default `'md'`.
styles systemSlotStyles<VideoStyles> | undefinedPer-slot style overrides.
testIDstring | undefined
textTracksVideoTextTrack[] | undefinedExternal (sidecar) caption files. The controller fetches, parses, and renders them with the kit's own overlay on every platform.
timeUpdateIntervalnumber | undefinedSeconds the controller waits between `timeUpdate` emissions (native).
titlestring | undefinedNow-playing title (used for the OS lock-screen / now-playing notification).
updateIntervalnumber | undefinedStatus / `timeUpdate` interval, ms (native). The unified name.
volumenumber | undefinedInitial volume, 0..1.
widthnumber | GetThemeValueForKey<"width"> | Animated.AnimatedNode | null | undefinedFrame style props (width/height/borderRadius…).

Style slots

Every part of Video can be styled through the styles prop. Explicit props on the component always win over slot styles.

SlotUsage
bigPlaystyles={{ bigPlay: { … } }}
bufferingstyles={{ buffering: { … } }}
captionTextstyles={{ captionText: { … } }}
captionsstyles={{ captions: { … } }}
controlBarstyles={{ controlBar: { … } }}
rootstyles={{ root: { … } }}
scrimstyles={{ scrim: { … } }}
surfacestyles={{ surface: { … } }}