Video

Animated frame sequences with optional audio. Loaded via Asset.GetVideo(path), which always returns two values, the Video userdata and a companion SoundData (or nil for sources that don't carry audio). Hand the SoundData to SFX.LoadSound if you want it to play; the Video drives texture updates on linked BaseParts.

local Asset = import("Asset")
local SFX = import("SFX")
local Renderable = import("Renderable")

local video, audio = Asset.GetVideo("cutscenes.intro")

-- Show the video on a flat panel in the world.
local screen = Renderable.BasePart("Cube")
screen.Size = Vector.new(4, 2.25, 0.05)
video:LinkPart(screen)
video.Looped = true
video:Play()

-- Audio is non-nil for any source that carries an audio track.
-- GIF doesn't, MP4/MOV typically do.
if audio then
    local snd = SFX.LoadSound(audio)
    snd.Looped = true
    snd:Play()
end

Supported formats

ExtensionAudioDecoder
.gif none Pure-Rust via the image crate. Always available, no system deps.
.mp4 extracted as 16-bit PCM WAV -> SoundData Delegated to ffmpeg on PATH. Whatever codec ffmpeg reads (H.264, H.265, AV1, etc.).
.mov same Same ffmpeg path; QuickTime container.
MP4 / MOV require ffmpeg on PATH. Real video codecs (H.264 / HEVC / AV1) are not bundled with the runtime — adding them would balloon the binary by tens of MB and introduces real licensing complications. Instead, the engine shells out to a system-installed ffmpeg at Asset.GetVideo time and reads back raw RGBA frames + WAV audio. Loading is therefore O(file length): a 30-second 720p clip can take a couple of seconds the first time. Cache the resulting VideoAsset in a Lua local if you need to play it repeatedly.
For shipped games: include ffmpeg next to the launcher EXE (Windows) or document it as a runtime dependency. If you can't ship ffmpeg with your game, pre-convert the source MP4 / MOV to an animated GIF at build time — the GIF path needs no system deps and works everywhere.

Video API

.Looped boolean

When true, advances back to frame 0 at the end. Toggle live, setting false mid-play lets the current playthrough finish then stops.

.CurrentFrame number

Frame index currently being shown. Reading is free; writing seeks the playhead immediately and resets the elapsed-ms accumulator.

video:Width()-> number
video:Height()-> number
video:FrameCount()-> number
video:FrameDelayMs()-> number
video:Length()-> number

Total length in seconds (FrameCount × FrameDelayMs ÷ 1000).

video:Source()-> string
video:Play()method
video:Pause()method
video:Stop()method

Pause and reset to frame 0.

video:CurrentImage() -> ImageAsset method

Build an ImageAsset from the current frame. Cached per frame index, repeated calls during the same displayed frame return the same cheap asset. Useful if you want to feed a still snapshot into a GUI image or a part Texture without LinkPart.

video:LinkPart(part) method

Bind a BasePart so its Texture auto-updates each tick the video advances. The engine swaps the part's Texture with fresh frame pixels every video tick. Each BasePart can only be linked to one Video at a time, calling LinkPart again on a different video transparently re-routes the binding.

video:UnlinkPart(part) method

Stop driving the part's Texture; the part keeps the last frame that was uploaded.

Audio is decoupled by design. A Video doesn't auto-play its companion audio, you decide when. For lip-sync, start the SFX sound and the video on the same frame and let both advance independently. For UI ambient loops just play the video and ignore the sound.