Builtin Library
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
| state | meaning |
|---|---|
idle | the feed was stopped |
starting | access was granted and the device is opening |
permissionPending | the platform is asking for camera access |
denied | camera access was refused; reason carries the platform's message |
running | frames are arriving |
failed | the 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.