These docs describe the main branch, including unreleased changes. Read the 0.8.0 docs.

Video

VideoRenderable plays an MP4 file with H.264 video and optional AAC audio. Decoding, the media clock, audio, and frame drawing run in native code. Decoded frames never cross into JavaScript.

Availability

FieldAvailability
Package@opentui/core
Core renderableVideoRenderable
React<video> (automatic)
Solid<video> (automatic)
StatusBuilt in

Renderable API

import { VideoRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()
const video = new VideoRenderable(renderer, {
  id: "player",
  source: "./clip.mp4",
  width: 60,
  height: 20,
  autoplay: true,
  loop: true,
  onError: console.error,
})

renderer.root.add(video)

source is a file path. Opening is synchronous: ready and videoMetadata are set when the setter returns. onReady, onError, and the onPlay of an autoplay run in a microtask, so callbacks assigned after source still receive the result. Setting autoplay after source starts the open source if it has not started yet. Replacing source keeps playback running if it was. Setting it to undefined closes the player.

React

import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"

const renderer = await createCliRenderer()
createRoot(renderer).render(<video source="./clip.mp4" autoplay loop style={{ width: 60, height: 20 }} />)

Solid

import { createCliRenderer } from "@opentui/core"
import { render } from "@opentui/solid"

const renderer = await createCliRenderer()
await render(() => <video source="./clip.mp4" autoplay loop style={{ width: 60, height: 20 }} />, renderer)

Playback

  • While a video plays, it ticks on the renderer's animation clock at maxFps. Each tick renders a frame and reads playback state, drawn or not, so a hidden or scrolled-out video keeps time, ends, and loops. A native audio thread refills audio between ticks. Each paint draws the current frame.

  • With an audio track, audio output is the media clock. Without audio, or if the audio device fails, playback follows the wall clock.

  • Frames present at the source frame rate or maxFps, whichever is lower.

  • seek() clamps to [0, duration]. Seeking past the end does not wrap, even when loop is on.

  • Frames draw like an Image with protocol: "auto". fit works the same way.

  • With Kitty output, the decoder lowers its decode size while frames take longer than the frame interval, and raises it again after they recover. qualityTier reports the step; 0 is full size.

  • Set OPENTUI_EXPERIMENTAL_VIDEO_HWACCEL=1 to try VideoToolbox decoding on macOS. See Environment variables.

Options and state

MemberTypeDescription
sourcestring | undefinedMP4 file path. An empty string closes the player
fit"fit" | "cover" | "fill"Destination sizing. Defaults to "fit"
autoplaybooleanPlays each source once it opens
loopbooleanRestarts at the end
mutedbooleanSilences audio. The audio clock keeps running
volumenumberNon-negative and within the 32-bit float range. Defaults to 1
avSyncOffsetMsnumberFrom -60000 to 60000. Positive values show frames earlier relative to the audio
maxFpsnumberInteger presentation-rate cap from 1 to 120. Defaults to 30
readybooleanWhether a source is open
videoMetadataVideoMetadata | nullwidth, height, fps, duration, and hasAudio. Zero is unknown
playing, pausedbooleanPlayback state. Before a source opens, the requested state
endedbooleanWhether playback reached the end without looping
currentTimenumberMedia time in seconds. Assigning it seeks
durationnumberDuration in seconds
qualityTiernumberAdaptive decode-size step for Kitty output
play(), pause()() => voidStart or stop playback. toggle() switches
seek(time)(seconds: number) => voidSeek. seekBy(delta) and seekToMs(ms) are relative and millisecond
CallbacksonReady, onError, onEnd, ...onPlay, onPause, onSeek, and onTimeUpdate report changes

Options set before a source opens apply when it opens. An option value out of range throws a RangeError and keeps the previous value.

Open, seek, and decode failures call onError with the native detail. A failed decode stops playback at its position and keeps the last frame; play() and seek() retry, and play() after a failed open opens the source again. An audio failure keeps playback running silently on the wall clock.

Use Image for still images. See Audio for sound that is not tied to a video.