Reference
The video module. Every function takes the opaque VideoPlayer? handle returned
by video_open (except video_backend_ready). Null handles are
tolerated — getters return zero, actions are no-ops.
Lifecycle
video_open(filename : string) : VideoPlayer?
Open a video file. Three container formats are auto-detected from the file’s magic bytes:
MPEG-1
.mpg— pl_mpeg, always available (MPEG-1 video + MP2 audio).AV1 IVF
.ivf— dav1d, video only.WebM
.webm— nestegg demux of AV1 video (dav1d) + Opus audio (libopus).
The two AV1 paths require the module built with dav1d (the default; DAS_VIDEO_DAV1D).
Returns a player handle, or null if the file can’t be opened or has no decodable
stream. Audio is disabled until you call video_enable_audio — the video-only
paths carry no audio cost.
video_open(data : uint8 const?; size : int) : VideoPlayer?
Open from a borrowed in-memory buffer instead of a file, with the same format
auto-detection. The bytes are referenced, not copied — they must stay alive and
unchanged until video_close (the player reads from them throughout playback). This
is the path for embedded or preloaded clips you render-to-texture and replay, or to
open several players over one buffer. From a das array, pass the address
explicitly: video_open(unsafe(addr(buf[0])), length(buf)) — the unsafe marks
the buffer-lifetime contract you’re accepting.
video_close(player : VideoPlayer?)
Destroy the player and its decoder. Pair every video_open with a video_close
(defer() { video_close(player) } is the idiom).
Decoding
video_decode(player : VideoPlayer?) : bool
Decode the next video frame. Returns true if a frame was produced, false at end
of stream. The frame is held by the player until the next video_decode — borrow it
with get_data before calling again.
video_rewind(player : VideoPlayer?)
Seek back to the start (both video and audio). Clears the held frame/audio batch. Use it to loop.
Stream info
All cheap getters on the open stream:
video_width(player : VideoPlayer?) : int // display width, pixels
video_height(player : VideoPlayer?) : int // display height, pixels
video_framerate(player : VideoPlayer?) : double // frames per second
video_samplerate(player : VideoPlayer?) : int // audio sample rate (Hz)
video_duration(player : VideoPlayer?) : double // stream length, seconds
video_frame_time(player : VideoPlayer?) : double // pts of the current frame, seconds
video_has_ended(player : VideoPlayer?) : bool // true past end of stream
video_y_stride / video_uv_stride give the plane strides for the YUV route —
the tightly-packed plane widths, which are macroblock-rounded and so may exceed the
display video_width. The display size is always video_width × video_height.
video_y_stride(player : VideoPlayer?) : int // Y plane stride (== plane width)
video_uv_stride(player : VideoPlayer?) : int // U/V plane stride
Frame access (borrow)
The current frame’s pixels are borrowed inside a block as array<uint8># views —
valid only inside the block and only until the next video_decode. Two overloads of
get_data:
// packed RGBA8: width*height*4 bytes, top-left origin, always opaque (alpha 255)
player |> get_data() $(rgb : array<uint8>#) { ... }
// native YUV420p planes: Y full-res, U (cb) and V (cr) quarter-res, zero-copy.
// Each plane is packed at its stride (video_y_stride / video_uv_stride).
player |> get_data() $(y, u, v : array<uint8>#) { ... }
The RGBA route runs pl_mpeg’s YUV→RGB on the CPU into a player-owned buffer. The YUV route hands you the decoder’s planes directly (no conversion, no copy) — convert on the GPU for the cheaper path.
Audio (opt-in)
video_has_audio(player : VideoPlayer?) : bool // does the file carry an audio stream?
video_enable_audio(player : VideoPlayer?) : bool // enable it; false if there is none
video_audio_channels(player : VideoPlayer?) : int // interleaved channel count
video_enable_audio must be called (once, after open) before decoding audio. It only
enables a stream that exists, so the video-only paths never buffer audio packets. The
channel count is 2 for MPEG-1 (pl_mpeg is always interleaved stereo) and the Opus
channel count (1 or 2) for WebM. video_samplerate is the MP2 rate for MPEG-1 and
48000 Hz for WebM (Opus always decodes at 48 kHz).
video_decode_audio(player : VideoPlayer?) : bool // decode the next batch; false at end
video_audio_time(player : VideoPlayer?) : double // pts of the current batch, seconds
video_audio_count(player : VideoPlayer?) : int // frames per channel in the batch
The batch size is fixed for MPEG-1 (1152 stereo frames) and varies for Opus (per the
packet duration); always size the borrow against video_audio_count × the channel
count.
// interleaved floats (count*channels), normalized -1..1, borrowed for the block only
player |> get_audio_data() $(s : array<float>#) { ... }
append_to_pcm (dasAudio) takes ownership of the array it’s given, so clone the
borrowed batch (chunk := s) before pushing it. See Usage for the full
A/V-synced loop.
Backend probe
video_backend_ready() : int // 1 once the native module has loaded + linked
A trivial smoke check used by the headless test to confirm the .shared_module is in.