---
title: "av"
description: "The av namespace — the engine's Luau API reference for av."
section: "API Reference"
slug: "api-av"
canonical: "https://origozero.ai/docs/api-av"
updated: "2026-09-05T23:13:46.211159802+00:00"
tags: ["api", "reference"]
---

# av

The `av` namespace — 22 functions.

## globals/av/is_live {#globals-av-is-live}

```lua
av.is_live() -> boolean
```

True if a live-stream session is currently active.

**Returns** `boolean` — Whether the live encoder is running.

```lua
if av.is_live() then av.stop_live() end
```

## globals/av/is_recording {#globals-av-is-recording}

```lua
av.is_recording() -> boolean
```

True if a recording session is currently active.

**Returns** `boolean` — Whether a recording is in progress.

```lua
print("recording:", av.is_recording())
```

## globals/av/live {#globals-av-live}

```lua
av.live(opts: LiveOpts?) -> string?
```

Start the live-stream encoder. The stream is served at
`/engine/live.stream` and reverse-proxied at
`/stream/<instance>/live.stream` as a binary length-prefixed
protocol consumed by the multiviewer UI's WebCodecs decoder.
When `texture_handle` is set, the encoder reads from that GPU
texture's guid (a Camera pointed at it via `setTargetTexture`)
instead of the scene's viewport — that's how spectator cameras
work. Returns a stream URL, or nil when unsupported or a
session is already active.

**Parameters**

- `opts` `LiveOpts` _(optional)_ — Encoder options.

**Returns** `string?` — Stream URL or nil.

```lua
local url = av.live({ width = 1280, height = 720, fps = 60 })
```

## globals/av/record {#globals-av-record}

```lua
av.record(path: string, opts: RecordOpts?) -> (string?, string?)
```

Start recording the engine output to a VFS path. Default dir
is `/zero/runtime/recordings/` when `path` is not absolute. The
take runs until `av.stop_recording()` unless `opts` bounds it
with `max_duration_sec` (seconds of the take's own clock) or
`frames` (captured frames); `max_duration_sec` wins when both are
given, and the bound in force reads back as
`av.status().recordingBound`. With
no `chroma`/`range` opts the format defaults to full-range 4:4:4
HEVC where the GPU supports it, else 4:2:0. On the `"software"`
backend the take is H.264 encoded on the CPU, which costs the run
it records: read `av.status().recordingAchievedFps` against
`recordingRequestedFps` to see the rate it reached. What the take
did with the master mix reads back as
`av.status().recordingAudio`. `cadence` picks the clock the take
stamps its frames from. `"realtime"` (the default) stamps each
frame with the wall-clock slot it was captured in, so a recorded
session is watched back at the speed it happened and an engine
ticking under `fps` leaves slots empty. `"frame"` stamps every
rendered frame one fixed slot after the last, so a timeline whose
own clock advances a step per rendered frame — a cutscene, a
scripted demo, anything on a fixed timestep — is delivered at the
length that timeline runs to, however slowly the engine drew it: a
take of `frames = n` at `fps` is `n / fps` seconds of film, and a
`max_duration_sec` bound counts that film's seconds. A `"frame"`
take records silent, because the master mix plays in wall-clock
seconds and cannot share a file with a fixed-step picture;
`recordingAudio` says so, and an explicit `audio = true` beside it
is refused. Record the sound as a second `"realtime"` take.
`camera` names the camera the take draws its film from — an entity
proxy, an entity id, or an entity name. That camera holds the viewport
for as long as the take runs, above the priority contest and above
`camera.setEditorOverride`, so the film is its view and the frames
carry everything the presented frame carries. Only frames that camera
drew go into the film, and a camera that never draws the viewport ends
the take with the reason on `av.status().recordingError` — so a take is
the view it named or it is no take. The camera belongs to the take:
nothing is written to it, and the viewport is back under its own
contest the moment the take ends. It reads back as
`av.status().recordingCamera` while the take runs. Omitted, the take
records whichever camera holds the viewport, which in an engine on the
editor profile is the editor's own fly camera rather than the scene's.
`renderLayers` is the render-layer include spec the viewport is
drawn under for as long as the take runs — the same token string a
capture takes: `all` seeds every layer, `name` adds one and `!name`
drops one, so `"all !EditorUI !debug"` films the scene without the
editor's chrome or the authoring overlays (gizmos, light and probe
icons, frustums, collider wireframes) over it, and
`"all !ui !EditorUI !debug"` drops the authored HUD as well. The
viewport admits geometry and screens by that one spec, so it states
the whole picture. It belongs to the take: nothing is written to the
camera it is stated against, and the moment the take ends — its
bound reached, stopped, or refused — the viewport is back under the
camera's own spec. While a take states layers, the window shows what
the film holds, and `av.status().recordingLayers` reads the spec
back. Omitted, the take records the engine output as presented.
Returns the destination path of a session that is open and
recording, or nil
and the reason it is not — an adapter that cannot encode, a take
already running, an option the encoder rejects, a resolution the
device refuses. The engine opens the session, so the call waits
for it: run it where it can yield, wrapping it in `task.spawn`
from a callback that cannot. How a take finished reads back as
`av.status().recordingEnd`; a refused request puts its reason
there and on `recordingError` and leaves no take report behind,
while a request refused because a take is already running leaves
that take's report as it is.

**Parameters**

- `path` `string` — VFS destination path.
- `opts` `RecordOpts` _(optional)_ — Encoder options (optional).

**Returns** `(string?, string?)` — Destination VFS path of the open recording, or nil. Why the recording was refused, when it was.

```lua
local clip = av.record("intro.mp4", { fps = 60 })
local film = av.record("cut.mp4", { fps = 24, frames = 24 * 181, cadence = "frame" })
local clean = av.record("take.mp4", { fps = 24, renderLayers = "all !EditorUI !debug" })
local shot = av.record("film.mp4", { fps = 24, frames = 240, camera = "FilmCamera" })
```

## globals/av/status {#globals-av-status}

```lua
av.status() -> AvStatus
```

Report the encoder subsystem's state. Always available
regardless of GPU support. `backend` is the encode backend in use
— `"vulkan"` or `"vaapi"` on an adapter with a media engine,
`"software"` where encode runs on the CPU — and `hardware` is true
for the first two, so a caller that pays for the take in engine
time knows which it is getting. `codecs` lists what the backend
encodes with the recording default first. `live` is true while the
`av.live` stream is running. A take of its own reads back on the
recording fields: `recording` is the destination of the take in
flight, `recordingBound` what will end it, `recordingEnd` how the
most recent one ended, and `recordingError` why one produced no
file. `recordingAudio` is the codec the take is writing the master
mix with (`"opus"`), or the reason the file carries no audio track
— read it to tell a film with a soundtrack from a silent one.
`recordingLayers` is the render-layer include spec the armed take is
drawing the viewport under, in the words its caller wrote, and nil for
a take that stated none — the reading that answers what is in the
picture rather than how much of it there is. `recordingCamera` is the
entity id of the camera the take's most recent captured frame was drawn
from, and stands as the source of the most recent take once that take
has ended — it is read off the frame the engine drew, so it answers
which view a film holds whether or not the take named a camera.
`recordingCadence` is the clock the take stamps its frames from,
`"realtime"` or `"frame"`, and so what the tally below is a reading
against. What the take produced reads off `recordingFrames`,
`recordingBytes` (every byte the take has produced so far, climbing
while it runs and ending equal to the size of the file),
`recordingSeconds` (the timeline those frames cover),
`recordingAchievedFps` (the rate they arrived at) and
`recordingRequestedFps` (the rate asked for) — live while a take
runs, and its final tally once it ends. On `"realtime"` the
requested rate is a ceiling and an engine ticking under it reaches
less; on `"frame"` every rendered frame is a slot of the recorded
timeline, so `recordingSeconds` is that timeline's length and
`recordingAchievedFps` is the rate it plays back at.

**Returns** `AvStatus` — Encoder status table.

```lua
local s = av.status(); print(s.backend, s.hardware, s.recordingAudio)
```

## globals/av/stop_live {#globals-av-stop-live}

```lua
av.stop_live() -> boolean
```

Stop any active live-stream session.

**Returns** `boolean` — True if a session was stopped, false if none was active.

```lua
av.stop_live()
```

## globals/av/stop_recording {#globals-av-stop-recording}

```lua
av.stop_recording(handle: string?) -> (boolean, string?)
```

Stop the active recording (or the one for the given promise
handle). Returns true when a recording was armed at call time. A
false return carries a second value naming how the most recent
recording already ended — the bound it reached, or the failure
that cut it short — and nil when no recording has run at all. The
engine finalizes the take on its next tick: wait for
`av.is_recording()` to go false, then read what it produced off
`av.status()`.

**Parameters**

- `handle` `string` _(optional)_ — Promise handle of a specific recording (optional).

**Returns** `(boolean, string?)` — True if a recording was stopped. How the most recent recording ended, when nothing was armed.

```lua
local stopped, ended = av.stop_recording()
```

## modules/av/README {#modules-av-readme}

```lua
require("@builtin/modules/api/engine/av") -- av (also available as global 'av')
```

Audio/video encode + mux control — live streaming, recording, encoder/muxer primitives. Public Luau surface over the `__av` Internal FFI namespace.

Usage: local av = require("@builtin/modules/api/engine/av")
Also available as global: av

## modules/av/is_live {#modules-av-is-live}

```lua
is_live(): boolean
```

True if a live-stream session is currently active.

```lua
if av.is_live() then av.stop_live() end
```

## modules/av/is_recording {#modules-av-is-recording}

```lua
is_recording(): boolean
```

True if a recording session is currently active.

```lua
print("recording:", av.is_recording())
```

## modules/av/live {#modules-av-live}

```lua
live(opts: LiveOpts?): string?
```

Start the live-stream encoder. The stream is served at
`/engine/live.stream` and reverse-proxied at
`/stream/<instance>/live.stream` as a binary length-prefixed
protocol consumed by the multiviewer UI's WebCodecs decoder.
When `texture_handle` is set, the encoder reads from that GPU
texture's guid (a Camera pointed at it via `setTargetTexture`)
instead of the scene's viewport — that's how spectator cameras
work. Returns a stream URL, or nil when unsupported or a
session is already active.

**Parameters**

- `opts` `LiveOpts?` _(optional)_ — Encoder options.

```lua
local url = av.live({ width = 1280, height = 720, fps = 60 })
```

## modules/av/record {#modules-av-record}

```lua
record(path: string, opts: RecordOpts?): (string?, string?)
```

Start recording the engine output to a VFS path. Default dir
is `/zero/runtime/recordings/` when `path` is not absolute. The
take runs until `av.stop_recording()` unless `opts` bounds it
with `max_duration_sec` (seconds of the take's own clock) or
`frames` (captured frames); `max_duration_sec` wins when both are
given, and the bound in force reads back as
`av.status().recordingBound`. With
no `chroma`/`range` opts the format defaults to full-range 4:4:4
HEVC where the GPU supports it, else 4:2:0. On the `"software"`
backend the take is H.264 encoded on the CPU, which costs the run
it records: read `av.status().recordingAchievedFps` against
`recordingRequestedFps` to see the rate it reached. What the take
did with the master mix reads back as
`av.status().recordingAudio`. `cadence` picks the clock the take
stamps its frames from. `"realtime"` (the default) stamps each
frame with the wall-clock slot it was captured in, so a recorded
session is watched back at the speed it happened and an engine
ticking under `fps` leaves slots empty. `"frame"` stamps every
rendered frame one fixed slot after the last, so a timeline whose
own clock advances a step per rendered frame — a cutscene, a
scripted demo, anything on a fixed timestep — is delivered at the
length that timeline runs to, however slowly the engine drew it: a
take of `frames = n` at `fps` is `n / fps` seconds of film, and a
`max_duration_sec` bound counts that film's seconds. A `"frame"`
take records silent, because the master mix plays in wall-clock
seconds and cannot share a file with a fixed-step picture;
`recordingAudio` says so, and an explicit `audio = true` beside it
is refused. Record the sound as a second `"realtime"` take.
`camera` names the camera the take draws its film from — an entity
proxy, an entity id, or an entity name. That camera holds the viewport
for as long as the take runs, above the priority contest and above
`camera.setEditorOverride`, so the film is its view and the frames
carry everything the presented frame carries. Only frames that camera
drew go into the film, and a camera that never draws the viewport ends
the take with the reason on `av.status().recordingError` — so a take is
the view it named or it is no take. The camera belongs to the take:
nothing is written to it, and the viewport is back under its own
contest the moment the take ends. It reads back as
`av.status().recordingCamera` while the take runs. Omitted, the take
records whichever camera holds the viewport, which in an engine on the
editor profile is the editor's own fly camera rather than the scene's.
`renderLayers` is the render-layer include spec the viewport is
drawn under for as long as the take runs — the same token string a
capture takes: `all` seeds every layer, `name` adds one and `!name`
drops one, so `"all !EditorUI !debug"` films the scene without the
editor's chrome or the authoring overlays (gizmos, light and probe
icons, frustums, collider wireframes) over it, and
`"all !ui !EditorUI !debug"` drops the authored HUD as well. The
viewport admits geometry and screens by that one spec, so it states
the whole picture. It belongs to the take: nothing is written to the
camera it is stated against, and the moment the take ends — its
bound reached, stopped, or refused — the viewport is back under the
camera's own spec. While a take states layers, the window shows what
the film holds, and `av.status().recordingLayers` reads the spec
back. Omitted, the take records the engine output as presented.
Returns the destination path of a session that is open and
recording, or nil
and the reason it is not — an adapter that cannot encode, a take
already running, an option the encoder rejects, a resolution the
device refuses. The engine opens the session, so the call waits
for it: run it where it can yield, wrapping it in `task.spawn`
from a callback that cannot. How a take finished reads back as
`av.status().recordingEnd`; a refused request puts its reason
there and on `recordingError` and leaves no take report behind,
while a request refused because a take is already running leaves
that take's report as it is.

**Parameters**

- `path` `string` — VFS destination path.
- `opts` `RecordOpts?` _(optional)_ — Encoder options (optional).

```lua
local clip = av.record("intro.mp4", { fps = 60 })
local film = av.record("cut.mp4", { fps = 24, frames = 24 * 181, cadence = "frame" })
local clean = av.record("take.mp4", { fps = 24, renderLayers = "all !EditorUI !debug" })
local shot = av.record("film.mp4", { fps = 24, frames = 240, camera = "FilmCamera" })
```

## modules/av/status {#modules-av-status}

```lua
status(): AvStatus
```

Report the encoder subsystem's state. Always available
regardless of GPU support. `backend` is the encode backend in use
— `"vulkan"` or `"vaapi"` on an adapter with a media engine,
`"software"` where encode runs on the CPU — and `hardware` is true
for the first two, so a caller that pays for the take in engine
time knows which it is getting. `codecs` lists what the backend
encodes with the recording default first. `live` is true while the
`av.live` stream is running. A take of its own reads back on the
recording fields: `recording` is the destination of the take in
flight, `recordingBound` what will end it, `recordingEnd` how the
most recent one ended, and `recordingError` why one produced no
file. `recordingAudio` is the codec the take is writing the master
mix with (`"opus"`), or the reason the file carries no audio track
— read it to tell a film with a soundtrack from a silent one.
`recordingLayers` is the render-layer include spec the armed take is
drawing the viewport under, in the words its caller wrote, and nil for
a take that stated none — the reading that answers what is in the
picture rather than how much of it there is. `recordingCamera` is the
entity id of the camera the take's most recent captured frame was drawn
from, and stands as the source of the most recent take once that take
has ended — it is read off the frame the engine drew, so it answers
which view a film holds whether or not the take named a camera.
`recordingCadence` is the clock the take stamps its frames from,
`"realtime"` or `"frame"`, and so what the tally below is a reading
against. What the take produced reads off `recordingFrames`,
`recordingBytes` (every byte the take has produced so far, climbing
while it runs and ending equal to the size of the file),
`recordingSeconds` (the timeline those frames cover),
`recordingAchievedFps` (the rate they arrived at) and
`recordingRequestedFps` (the rate asked for) — live while a take
runs, and its final tally once it ends. On `"realtime"` the
requested rate is a ceiling and an engine ticking under it reaches
less; on `"frame"` every rendered frame is a slot of the recorded
timeline, so `recordingSeconds` is that timeline's length and
`recordingAchievedFps` is the rate it plays back at.

```lua
local s = av.status(); print(s.backend, s.hardware, s.recordingAudio)
```

## modules/av/stop_live {#modules-av-stop-live}

```lua
stop_live(): boolean
```

Stop any active live-stream session.

```lua
av.stop_live()
```

## modules/av/stop_recording {#modules-av-stop-recording}

```lua
stop_recording(handle: string?): (boolean, string?)
```

Stop the active recording (or the one for the given promise
handle). Returns true when a recording was armed at call time. A
false return carries a second value naming how the most recent
recording already ended — the bound it reached, or the failure
that cut it short — and nil when no recording has run at all. The
engine finalizes the take on its next tick: wait for
`av.is_recording()` to go false, then read what it produced off
`av.status()`.

**Parameters**

- `handle` `string?` _(optional)_ — Promise handle of a specific recording (optional).

```lua
local stopped, ended = av.stop_recording()
```

## typed/builtin//modules/api/engine/av/av/is_live {#typed-builtin-modules-api-engine-av-av-is-live}

```lua
av.is_live() -> boolean
```

True if a live-stream session is currently active.

**Returns** `boolean` — Whether the live encoder is running.

```lua
if av.is_live() then av.stop_live() end
```

## typed/builtin//modules/api/engine/av/av/is_recording {#typed-builtin-modules-api-engine-av-av-is-recording}

```lua
av.is_recording() -> boolean
```

True if a recording session is currently active.

**Returns** `boolean` — Whether a recording is in progress.

```lua
print("recording:", av.is_recording())
```

## typed/builtin//modules/api/engine/av/av/live {#typed-builtin-modules-api-engine-av-av-live}

```lua
av.live(opts: LiveOpts?) -> string?
```

Start the live-stream encoder. The stream is served at
`/engine/live.stream` and reverse-proxied at
`/stream/<instance>/live.stream` as a binary length-prefixed
protocol consumed by the multiviewer UI's WebCodecs decoder.
When `texture_handle` is set, the encoder reads from that GPU
texture's guid (a Camera pointed at it via `setTargetTexture`)
instead of the scene's viewport — that's how spectator cameras
work. Returns a stream URL, or nil when unsupported or a
session is already active.

**Parameters**

- `opts` `LiveOpts` _(optional)_ — Encoder options.

**Returns** `string?` — Stream URL or nil.

```lua
local url = av.live({ width = 1280, height = 720, fps = 60 })
```

## typed/builtin//modules/api/engine/av/av/record {#typed-builtin-modules-api-engine-av-av-record}

```lua
av.record(path: string, opts: RecordOpts?) -> (string?, string?)
```

Start recording the engine output to a VFS path. Default dir
is `/zero/runtime/recordings/` when `path` is not absolute. The
take runs until `av.stop_recording()` unless `opts` bounds it
with `max_duration_sec` (seconds of the take's own clock) or
`frames` (captured frames); `max_duration_sec` wins when both are
given, and the bound in force reads back as
`av.status().recordingBound`. With
no `chroma`/`range` opts the format defaults to full-range 4:4:4
HEVC where the GPU supports it, else 4:2:0. On the `"software"`
backend the take is H.264 encoded on the CPU, which costs the run
it records: read `av.status().recordingAchievedFps` against
`recordingRequestedFps` to see the rate it reached. What the take
did with the master mix reads back as
`av.status().recordingAudio`. `cadence` picks the clock the take
stamps its frames from. `"realtime"` (the default) stamps each
frame with the wall-clock slot it was captured in, so a recorded
session is watched back at the speed it happened and an engine
ticking under `fps` leaves slots empty. `"frame"` stamps every
rendered frame one fixed slot after the last, so a timeline whose
own clock advances a step per rendered frame — a cutscene, a
scripted demo, anything on a fixed timestep — is delivered at the
length that timeline runs to, however slowly the engine drew it: a
take of `frames = n` at `fps` is `n / fps` seconds of film, and a
`max_duration_sec` bound counts that film's seconds. A `"frame"`
take records silent, because the master mix plays in wall-clock
seconds and cannot share a file with a fixed-step picture;
`recordingAudio` says so, and an explicit `audio = true` beside it
is refused. Record the sound as a second `"realtime"` take.
`camera` names the camera the take draws its film from — an entity
proxy, an entity id, or an entity name. That camera holds the viewport
for as long as the take runs, above the priority contest and above
`camera.setEditorOverride`, so the film is its view and the frames
carry everything the presented frame carries. Only frames that camera
drew go into the film, and a camera that never draws the viewport ends
the take with the reason on `av.status().recordingError` — so a take is
the view it named or it is no take. The camera belongs to the take:
nothing is written to it, and the viewport is back under its own
contest the moment the take ends. It reads back as
`av.status().recordingCamera` while the take runs. Omitted, the take
records whichever camera holds the viewport, which in an engine on the
editor profile is the editor's own fly camera rather than the scene's.
`renderLayers` is the render-layer include spec the viewport is
drawn under for as long as the take runs — the same token string a
capture takes: `all` seeds every layer, `name` adds one and `!name`
drops one, so `"all !EditorUI !debug"` films the scene without the
editor's chrome or the authoring overlays (gizmos, light and probe
icons, frustums, collider wireframes) over it, and
`"all !ui !EditorUI !debug"` drops the authored HUD as well. The
viewport admits geometry and screens by that one spec, so it states
the whole picture. It belongs to the take: nothing is written to the
camera it is stated against, and the moment the take ends — its
bound reached, stopped, or refused — the viewport is back under the
camera's own spec. While a take states layers, the window shows what
the film holds, and `av.status().recordingLayers` reads the spec
back. Omitted, the take records the engine output as presented.
Returns the destination path of a session that is open and
recording, or nil
and the reason it is not — an adapter that cannot encode, a take
already running, an option the encoder rejects, a resolution the
device refuses. The engine opens the session, so the call waits
for it: run it where it can yield, wrapping it in `task.spawn`
from a callback that cannot. How a take finished reads back as
`av.status().recordingEnd`; a refused request puts its reason
there and on `recordingError` and leaves no take report behind,
while a request refused because a take is already running leaves
that take's report as it is.

**Parameters**

- `path` `string` — VFS destination path.
- `opts` `RecordOpts` _(optional)_ — Encoder options (optional).

**Returns** `(string?, string?)` — Destination VFS path of the open recording, or nil. Why the recording was refused, when it was.

```lua
local clip = av.record("intro.mp4", { fps = 60 })
local film = av.record("cut.mp4", { fps = 24, frames = 24 * 181, cadence = "frame" })
local clean = av.record("take.mp4", { fps = 24, renderLayers = "all !EditorUI !debug" })
local shot = av.record("film.mp4", { fps = 24, frames = 240, camera = "FilmCamera" })
```

## typed/builtin//modules/api/engine/av/av/status {#typed-builtin-modules-api-engine-av-av-status}

```lua
av.status() -> AvStatus
```

Report the encoder subsystem's state. Always available
regardless of GPU support. `backend` is the encode backend in use
— `"vulkan"` or `"vaapi"` on an adapter with a media engine,
`"software"` where encode runs on the CPU — and `hardware` is true
for the first two, so a caller that pays for the take in engine
time knows which it is getting. `codecs` lists what the backend
encodes with the recording default first. `live` is true while the
`av.live` stream is running. A take of its own reads back on the
recording fields: `recording` is the destination of the take in
flight, `recordingBound` what will end it, `recordingEnd` how the
most recent one ended, and `recordingError` why one produced no
file. `recordingAudio` is the codec the take is writing the master
mix with (`"opus"`), or the reason the file carries no audio track
— read it to tell a film with a soundtrack from a silent one.
`recordingLayers` is the render-layer include spec the armed take is
drawing the viewport under, in the words its caller wrote, and nil for
a take that stated none — the reading that answers what is in the
picture rather than how much of it there is. `recordingCamera` is the
entity id of the camera the take's most recent captured frame was drawn
from, and stands as the source of the most recent take once that take
has ended — it is read off the frame the engine drew, so it answers
which view a film holds whether or not the take named a camera.
`recordingCadence` is the clock the take stamps its frames from,
`"realtime"` or `"frame"`, and so what the tally below is a reading
against. What the take produced reads off `recordingFrames`,
`recordingBytes` (every byte the take has produced so far, climbing
while it runs and ending equal to the size of the file),
`recordingSeconds` (the timeline those frames cover),
`recordingAchievedFps` (the rate they arrived at) and
`recordingRequestedFps` (the rate asked for) — live while a take
runs, and its final tally once it ends. On `"realtime"` the
requested rate is a ceiling and an engine ticking under it reaches
less; on `"frame"` every rendered frame is a slot of the recorded
timeline, so `recordingSeconds` is that timeline's length and
`recordingAchievedFps` is the rate it plays back at.

**Returns** `AvStatus` — Encoder status table.

```lua
local s = av.status(); print(s.backend, s.hardware, s.recordingAudio)
```

## typed/builtin//modules/api/engine/av/av/stop_live {#typed-builtin-modules-api-engine-av-av-stop-live}

```lua
av.stop_live() -> boolean
```

Stop any active live-stream session.

**Returns** `boolean` — True if a session was stopped, false if none was active.

```lua
av.stop_live()
```

## typed/builtin//modules/api/engine/av/av/stop_recording {#typed-builtin-modules-api-engine-av-av-stop-recording}

```lua
av.stop_recording(handle: string?) -> (boolean, string?)
```

Stop the active recording (or the one for the given promise
handle). Returns true when a recording was armed at call time. A
false return carries a second value naming how the most recent
recording already ended — the bound it reached, or the failure
that cut it short — and nil when no recording has run at all. The
engine finalizes the take on its next tick: wait for
`av.is_recording()` to go false, then read what it produced off
`av.status()`.

**Parameters**

- `handle` `string` _(optional)_ — Promise handle of a specific recording (optional).

**Returns** `(boolean, string?)` — True if a recording was stopped. How the most recent recording ended, when nothing was armed.

```lua
local stopped, ended = av.stop_recording()
```
