Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

cameraInput

Physical cameras (a webcam, a phone's front and back cameras) as live textures and pixel sources. The camera global is the scene's own cameras; this module is the machine's.

local feed, why = cameraInput.open({ facing = "front", width = 1280, height = 720 })
if feed == nil then
    print("no feed:", why)
    return
end

local screen = renderer.material.create(
    { shader = "unlit", textures = { base_color_texture = feed.texture } }, "tv_screen")
local tv = entity.spawn("TV", { position = { 0, 1.5, -3 }, scale = { 1.6, 0.9, 0.02 } })
tv.component.add("Model", { model = "cube" })
tv.component.get("Model"):applySessionMaterial(screen)

local ok, reason = feed:awaitRunning(15)
if not ok then
    print("the camera did not start:", reason)
    return
end
print(feed:status().width, feed:status().height, feed:status().frames)

A feed is a state, not a call

statemeaning
idlethe feed was stopped
startingaccess was granted and the device is opening
permissionPendingthe platform is asking for camera access
deniedcamera access was refused; reason carries the platform's message
runningframes are arriving
failedthe device did not open or stopped; reason says why (no camera, the camera is busy, a constraint it cannot meet, a platform with no capture backend)

feed:awaitRunning(timeout) waits out starting and permissionPending and answers true, or false with the state and reason it settled in.

The picture

feed.texture is a texture handle, valid from the moment the feed opens: a session material (renderer.material.create({ shader = "unlit", textures = { base_color_texture = feed.texture } }, key), worn with Model:applySessionMaterial), a slot of a material that exists (renderer.material.setTexture(key, "base_color_texture", feed.texture)), a UI image ({ type = "image", props = { src = feed.texture } }) and av.record all take it. feed:status() reports the size and frame rate the device delivers, frames (advances once per new frame), and rotation and mirrored (a front camera reads as the person expects when shown mirrored).

feed:readPixels(rect?) answers a TextureCpuHandle of the current frame, or of the rectangle { x, y, w, h } of it, without stopping the feed.

Devices and access

cameraInput.devices() lists { id, name, facing, default }. Until camera access is granted, id and name are empty: open by facing or take the default first, then list again. cameraInput.onDeviceChange(fn) hears cameras being added and removed.

Ending a feed

feed:stop() releases the camera once no other feed is reading it, which turns the platform's camera indicator off. A feed also ends when the content that opened it unloads: the component that opened it is removed, the module that opened it reloads, or the world unloads. cameraInput.feeds() lists every open feed with the context that holds it.

Platforms

The browser, Linux and Android builds capture from cameras. The Linux build reads USB webcams through their /dev/video* device nodes, adds the cameras PipeWire offers that no device node does (a laptop's MIPI camera), and inside a Flatpak sandbox asks the desktop's camera portal, where a refused request reads denied with the portal's answer. The Android build lists the phone's front, back and external cameras with their facing, asks for the Camera permission the first time a feed opens (permissionPending until the person answers, denied when they refuse or the privacy toggle switches the camera off), reports a rotation that follows the screen as it turns, and lets the camera go while the app is in the background. A build with no capture backend for its platform (native Windows) lists no devices and opens every feed as failed, with a reason naming the platform.