cameraInput
Physical cameras as live textures and pixel sources: list the capture devices (a webcam, a phone's front and back cameras), open one as a feed, show its live picture anywhere a texture goes (a material, a UI image, a recording), and read its pixels.
require("@builtin::modules.api.engine.cameraInput") -- cameraInput (also available as global 'cameraInput')
The cameraInput namespace: 19 functions.
globals/cameraInput/devices
cameraInput.devices() -> { CameraDevice }
Every capture device the platform offers: id is what
cameraInput.open takes as device, name the label a person
recognises, facing which way it points. Until camera access has been
granted, id and name read as empty strings and facing as
"unknown": the list is part of what the permission protects, so a
first open takes the default camera or a facing, and the list names the
devices after that.
Returns { CameraDevice } — Array of { id, name, facing, default }.
for _, d in ipairs(cameraInput.devices()) do print(d.name, d.facing) end
globals/cameraInput/feeds
cameraInput.feeds() -> { CameraFeedEntry }
Every open feed, with the owning context that opened it (scope,
the same tag scopes.list() shows), whose end stops the feed.
Returns { CameraFeedEntry } — Array of { id, scope, state, device, texture }.
for _, f in ipairs(cameraInput.feeds()) do print(f.id, f.state, f.scope) end
globals/cameraInput/onDeviceChange
cameraInput.onDeviceChange(callback: (devices: { CameraDevice }) -> ()) -> () -> ()
Call callback with the new device list every time a camera is
added or removed, or the platform names its devices after access is
granted. Each listener is watched by a task of the context that
registered it, so a listener a component registered ends when that
component is removed, and every other listener goes on hearing changes.
Parameters
callback(devices: { CameraDevice }) -> ()— Receives the device list, ascameraInput.devices()returns it.
Returns () -> () — A function that removes this listener.
local off = cameraInput.onDeviceChange(function(devices) print(#devices) end)
globals/cameraInput/open
cameraInput.open(opts: CameraOpenOpts?) -> (CameraFeed?, string?)
Open a camera as a feed. Returns the feed at once; it reads
permissionPending while the platform asks for access, then running
(or denied / failed with a reason). Wait with feed:awaitRunning().
feed.texture is a texture handle that shows the camera's current frame:
put it on a surface as a session material's base_color_texture
(renderer.material.create, worn with Model:applySessionMaterial) or in
a slot of a material that exists (renderer.material.setTexture(key, "base_color_texture", feed.texture)), in UI as
{ type = "image", props = { src = feed.texture } },
and av.record films it like any other scene content.
device names a camera by the id or name cameraInput.devices() lists;
facing asks for the "front" or "back" camera instead; with neither,
the platform's default camera opens. width, height and fps are hints:
the device delivers the nearest it can, and feed:status() reports what it
delivers. A second feed on a camera already open shares it.
Returns nil and the reason for a request refused before any camera is touched: a hint outside its range, a facing other than front or back, a device the platform does not offer.
Parameters
optsCameraOpenOpts(optional) —{ device, facing, width, height, fps }, every field optional.
Returns (CameraFeed?, string?) — The feed, or nil and the reason.
local feed = cameraInput.open({ facing = "front", width = 1280, height = 720 })
local screen = feed and renderer.material.create({ shader = "unlit", textures = { base_color_texture = feed.texture } }, "tv_screen")
globals/cameraInput/stopAll
cameraInput.stopAll() -> number
Stop every open feed and release every camera, whatever opened them.
Returns number — How many feeds stopped.
cameraInput.stopAll()
modules/cameraInput/awaitRunning
awaitRunning(self: CameraFeed, timeoutSeconds: number?): (boolean, string?)
Wait until the feed is running, and report whether it got there. Returns
as soon as the feed leaves starting and permissionPending, or when
timeoutSeconds have passed, whichever comes first: a permission prompt
nobody answers never settles, so the wait is always bounded.
Parameters
selfCameraFeedtimeoutSecondsnumber?(optional) — Seconds to wait at most. Defaults to 10.
local ok, why = feed:awaitRunning(15); if not ok then print(why) end
modules/cameraInput/devices
devices(): { CameraDevice }
Every capture device the platform offers: id is what
cameraInput.open takes as device, name the label a person
recognises, facing which way it points. Until camera access has been
granted, id and name read as empty strings and facing as
"unknown": the list is part of what the permission protects, so a
first open takes the default camera or a facing, and the list names the
devices after that.
for _, d in ipairs(cameraInput.devices()) do print(d.name, d.facing) end
modules/cameraInput/feeds
feeds(): { CameraFeedEntry }
Every open feed, with the owning context that opened it (scope,
the same tag scopes.list() shows), whose end stops the feed.
for _, f in ipairs(cameraInput.feeds()) do print(f.id, f.state, f.scope) end
modules/cameraInput/onDeviceChange
onDeviceChange(callback: (devices: { CameraDevice }) -> ()): () -> ()
Call callback with the new device list every time a camera is
added or removed, or the platform names its devices after access is
granted. Each listener is watched by a task of the context that
registered it, so a listener a component registered ends when that
component is removed, and every other listener goes on hearing changes.
Parameters
callback(devices: { CameraDevice }) -> ()— Receives the device list, ascameraInput.devices()returns it.
Returns () — A function that removes this listener.
local off = cameraInput.onDeviceChange(function(devices) print(#devices) end)
modules/cameraInput/open
open(opts: CameraOpenOpts?): (CameraFeed?, string?)
Open a camera as a feed. Returns the feed at once; it reads
permissionPending while the platform asks for access, then running
(or denied / failed with a reason). Wait with feed:awaitRunning().
feed.texture is a texture handle that shows the camera's current frame:
put it on a surface as a session material's base_color_texture
(renderer.material.create, worn with Model:applySessionMaterial) or in
a slot of a material that exists (renderer.material.setTexture(key, "base_color_texture", feed.texture)), in UI as
{ type = "image", props = { src = feed.texture } },
and av.record films it like any other scene content.
device names a camera by the id or name cameraInput.devices() lists;
facing asks for the "front" or "back" camera instead; with neither,
the platform's default camera opens. width, height and fps are hints:
the device delivers the nearest it can, and feed:status() reports what it
delivers. A second feed on a camera already open shares it.
Returns nil and the reason for a request refused before any camera is touched: a hint outside its range, a facing other than front or back, a device the platform does not offer.
Parameters
optsCameraOpenOpts?(optional) —{ device, facing, width, height, fps }, every field optional.
local feed = cameraInput.open({ facing = "front", width = 1280, height = 720 })
local screen = feed and renderer.material.create({ shader = "unlit", textures = { base_color_texture = feed.texture } }, "tv_screen")
modules/cameraInput/readPixels
readPixels(self: CameraFeed, rect: CameraRect?): (any, string?)
Read the pixels of the frame the feed is showing now, without stopping
it: the whole frame, or the rectangle { x, y, w, h } of it in pixels from
the top-left corner. The answer is a TextureCpuHandle holding RGBA8
pixels (:readPixel(x, y), :rgba(), :encodePng()); :unload() it once
done. Returns nil and the reason while no frame has arrived, after the feed
stopped, or for a rectangle outside the frame.
Parameters
selfCameraFeedrectCameraRect?(optional) — Optional{ x, y, w, h }.
local cpu = feed:readPixels(); if cpu then print(cpu:readPixel(0, 0)); cpu:unload() end
modules/cameraInput/status
status(self: CameraFeed): CameraStatus
Where the feed stands. width, height and fps are what the device
delivers, whatever the open asked for. frames advances once per new
frame, so two reads that show the same count saw the same picture.
rotation is the degrees clockwise the picture turns to stand upright on
the display, and follows a phone's screen as it turns; mirrored says the
picture, once turned, reads as the person expects only when shown
mirrored, which is how a front camera is shown. reason carries the
platform's own message for denied and failed, and what is being waited
on for permissionPending.
Parameters
selfCameraFeed
local s = feed:status(); print(s.state, s.width, s.height, s.frames)
modules/cameraInput/stop
stop(self: CameraFeed): boolean
Stop the feed. The camera is released once no other feed is reading it, which is what turns the platform's camera indicator off. True when the feed was open.
Parameters
selfCameraFeed
feed:stop()
modules/cameraInput/stopAll
stopAll(): number
Stop every open feed and release every camera, whatever opened them.
cameraInput.stopAll()
typed/builtin//modules/api/engine/cameraInput/cameraInput/devices
cameraInput.devices() -> { CameraDevice }
Every capture device the platform offers: id is what
cameraInput.open takes as device, name the label a person
recognises, facing which way it points. Until camera access has been
granted, id and name read as empty strings and facing as
"unknown": the list is part of what the permission protects, so a
first open takes the default camera or a facing, and the list names the
devices after that.
Returns { CameraDevice } — Array of { id, name, facing, default }.
for _, d in ipairs(cameraInput.devices()) do print(d.name, d.facing) end
typed/builtin//modules/api/engine/cameraInput/cameraInput/feeds
cameraInput.feeds() -> { CameraFeedEntry }
Every open feed, with the owning context that opened it (scope,
the same tag scopes.list() shows), whose end stops the feed.
Returns { CameraFeedEntry } — Array of { id, scope, state, device, texture }.
for _, f in ipairs(cameraInput.feeds()) do print(f.id, f.state, f.scope) end
typed/builtin//modules/api/engine/cameraInput/cameraInput/onDeviceChange
cameraInput.onDeviceChange(callback: (devices: { CameraDevice }) -> ()) -> () -> ()
Call callback with the new device list every time a camera is
added or removed, or the platform names its devices after access is
granted. Each listener is watched by a task of the context that
registered it, so a listener a component registered ends when that
component is removed, and every other listener goes on hearing changes.
Parameters
callback(devices: { CameraDevice }) -> ()— Receives the device list, ascameraInput.devices()returns it.
Returns () -> () — A function that removes this listener.
local off = cameraInput.onDeviceChange(function(devices) print(#devices) end)
typed/builtin//modules/api/engine/cameraInput/cameraInput/open
cameraInput.open(opts: CameraOpenOpts?) -> (CameraFeed?, string?)
Open a camera as a feed. Returns the feed at once; it reads
permissionPending while the platform asks for access, then running
(or denied / failed with a reason). Wait with feed:awaitRunning().
feed.texture is a texture handle that shows the camera's current frame:
put it on a surface as a session material's base_color_texture
(renderer.material.create, worn with Model:applySessionMaterial) or in
a slot of a material that exists (renderer.material.setTexture(key, "base_color_texture", feed.texture)), in UI as
{ type = "image", props = { src = feed.texture } },
and av.record films it like any other scene content.
device names a camera by the id or name cameraInput.devices() lists;
facing asks for the "front" or "back" camera instead; with neither,
the platform's default camera opens. width, height and fps are hints:
the device delivers the nearest it can, and feed:status() reports what it
delivers. A second feed on a camera already open shares it.
Returns nil and the reason for a request refused before any camera is touched: a hint outside its range, a facing other than front or back, a device the platform does not offer.
Parameters
optsCameraOpenOpts(optional) —{ device, facing, width, height, fps }, every field optional.
Returns (CameraFeed?, string?) — The feed, or nil and the reason.
local feed = cameraInput.open({ facing = "front", width = 1280, height = 720 })
local screen = feed and renderer.material.create({ shader = "unlit", textures = { base_color_texture = feed.texture } }, "tv_screen")
typed/builtin//modules/api/engine/cameraInput/cameraInput/stopAll
cameraInput.stopAll() -> number
Stop every open feed and release every camera, whatever opened them.
Returns number — How many feeds stopped.
cameraInput.stopAll()