Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ before format-specific metadata. Unrelated areas can proceed in parallel.
- [JS track end](/quest/m2/2318-js-net-remaining-capability-gaps-vs-rs-moq-net-setup-role.md) - js/net declares a track end ahead of the live edge and observes the publisher's SUBSCRIBE_END
- [E2EE](/quest/m2/e2ee/README.md) - TypeScript and Rust peers interoperate over encrypted broadcasts no relay can decrypt
- [Processor](/quest/m2/processor/README.md) - a customer-run worker publishes an on-demand contribution with scoped access
- [LiveKit Agents adapter (Python)](/quest/m2/livekit-agents-python.md) - a `livekit-agents` voice session runs against a MoQ relay through a MoQ audio input and output and an announce-prefix runner
- [LiveKit client shim](/quest/m2/livekit-shim.md) - a drop-in `livekit-client` facade over `@moq/room`, media and core events first
- [LiveKit shim data surfaces](/quest/m2/livekit-shim-data.md) - publishData, text streams, and RPC on the data convention
- [Data convention](/quest/m2/data-convention.md) - one request/response shape over the catalog's `json` and `binary` sections, plus the `window` mode, so rooms, teleop, and voice stop hand-rolling reply channels
- [Active speaker](/quest/m2/room-active-speaker.md) - audio level and speaking signals on every `@moq/room` member, and a `moq-audio` meter for native apps
- [LiveKit Agents adapter (Node)](/quest/m2/livekit-agents-node.md) - the Python adapter ported to `@livekit/agents`
- [#3056](/quest/m2/3056-watch-video-decoder-captures-the-rewind-generation-at.md) - watch: the video decoder resets on a declared discontinuity
- [#933](/quest/m2/933-video-rotation-metadata-not-propagated-from-mobile-camera.md) - the catalog rotation follows the live camera's orientation
- [#2075](/quest/m2/2075-mirror-catalog-reservation-gating-in-moq-hang-js-hang.md) - @moq/publish gates the first catalog snapshot until every reserved track is described
Expand Down
63 changes: 63 additions & 0 deletions quest/m2/data-convention.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# [M] Data convention

## Goal

One request/response convention over the catalog's existing `json` and
`binary` data sections (#3109), so rooms, teleoperation, voice agents, and
any app stop hand-rolling reply channels: a caller's `stream`-mode track of
`{id, method, params}` answered on the callee's `stream`-mode track of
`{id, ok, result}` or `{id, error}`, with the callee advertised in its
catalog and the caller discovered from an announce prefix. The `window`
mode moq-json already implements joins the catalog's known modes, every
in-tree data track declares itself, and the hang draft carries all of it.

Today three consumers each invented a reply channel: the Pipecat
transport's fixed-name `transcript.json.z` track, teleop's proposed `rpc`
tracks, and pronto's `status` echoing a command `sequence`. `@moq/room`'s
chat window is not in the catalog at all.

## Plan

- Modes: add `window` (the retained run moq-json's `js/json/src/window`
writes) as a JSON-only known mode. `KnownMode` in
`js/hang/src/catalog/mode.ts` and `Mode` in `rs/hang/src/catalog/mode.rs`
are shared by both sections today, and `@moq/binary` and `moq-binary`
implement only snapshot and stream, so support detection splits by
section (`jsonModeSupported` and `binaryModeSupported`, or per-section
known sets) and a binary `window` entry stays unreadable rather than
appearing supported. Datagram delivery is a track `Info` property, not a
mode, and stays out of the catalog.
- Request/response: `moq-json` gains typed `Request`/`Response` producers
and consumers over its stream mode in JS and Rust: the caller keeps the
correlation id and its own timeout, the callee answers each id once, and
a lagged reader surfaces the error rather than retrying. Discovery is the
direction teleop and Voice already use: the callee's response track is a
`json` entry with a `schema` naming the method set, and callers are found
under an announce prefix (operator publishes, robot subscribes;
`request/` and `response/` subtrees).
- Reliability is what `moq-net` gives (a group is one QUIC stream; a reader
that falls past `MAX_GROUP_CACHE` is told so). The docs say that instead
of promising delivery. Non-goals: byte-stream file transfer, delivery
receipts, per-participant addressing beyond a path and a scoped token,
and a cross-host timebase.
- Adopt in the same change: `@moq/room` `Chat` and its native twin
`moq_room::chat::Publisher` (`rs/moq-room/src/chat.rs`) declare their
`window` track in the `json` section, so Rust and JavaScript room
catalogs agree; `rs/moq-native/examples/chat.rs` and
`rs/moq-json/examples/telemetry.rs` declare theirs. The draft gains the
`window` mode and the request/response shape, and `doc/concept`'s
catalog page gains both, per the cross-package checklist for an `rs/hang`
catalog change. A catalog without data sections parses unchanged, which
the existing `deserialize_section` leniency already guarantees.
- Tests: `window` round trip in both languages, a request answered across a
local relay, a lagged caller surfacing the error, and an unknown mode
still passing through verbatim.

## Related

- [Robot teleoperation primitive](/quest/m3/teleop/robot.md) - the `rpc`,
`telemetry`, and `command` tracks are the first non-room consumer
- [LiveKit shim data surfaces](/quest/m2/livekit-shim-data.md) -
`publishData`, streams, and RPC map onto this
- [Catalog track identity](/quest/m3/catalog-tracks.md) - whatever it
decides about immutable definitions applies to data entries too
24 changes: 24 additions & 0 deletions quest/m2/livekit-agents-node.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# [M] LiveKit Agents adapter (Node)

## Goal

The Python adapter's design ported to `@livekit/agents`: an `AudioInput`
(`ReadableStream<AudioFrame>`) and `AudioOutput` (`captureFrame`, `flush`,
`clearBuffer`) over `@moq/net` and `@moq/hang`, plus the same announce-prefix
runner, published from `js/` as `@moq/livekit-agents`. Done when the stock
Node voice-agent example answers a browser publisher through a local relay.

## Plan

- `AgentSession.start({agent})` builds `RoomIO` only when a room is passed
(`agent_session.ts`), and their tests assign `session.input.audio`
directly, so the shape is the same as Python. `@livekit/rtc-node` stays a
dependency for `AudioFrame`.
- Reuse the Python adapter's track naming, barge-in replacement, RTVI turn
mapping, and tests one for one, so a browser publisher is agnostic to which
runtime answers.

## Required

- [LiveKit Agents adapter (Python)](/quest/m2/livekit-agents-python.md) -
settles the design this ports
48 changes: 48 additions & 0 deletions quest/m2/livekit-agents-python.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# [M] LiveKit Agents adapter (Python)

## Goal

A `livekit-agents` voice agent runs against a MoQ relay instead of a LiveKit
room, with its STT, LLM, TTS, turn detection, and plugins unchanged: a
`moq-livekit-agents` package in `py/` supplies an `AudioInput` and
`AudioOutput` over MoQ and a runner that starts one `AgentSession` per
announced request broadcast and publishes its response. Done when a stock
LiveKit voice-agent example answers a browser publisher through a local relay
with only the I/O wiring changed.

## Plan

- The framework already runs without a room. `AgentSession.start(agent)`
with `session.input.audio` and `session.output.audio` assigned is how its
own test harness (`tests/fake_io.py`) and `console` mode
(`cli/tcp_console.py`) work, and `get_job_context(required=False)` keeps a
session independent of a Worker. Subclass `voice/io.py`'s `AudioInput`
(async iterator of `rtc.AudioFrame`) and `AudioOutput` (`capture_frame`,
`flush`, `clear_buffer`, and `on_playback_finished` when the track drains).
- Media the way Pipecat's MoQ transport does it (`pipecat.transports.moq`):
the `moq` package encodes and decodes Opus inside the FFI, the agent's
audio is one hang audio track replaced by a fresh one on barge-in so
catalog-aware players follow the successor, and frames cross as int16 at
the session's sample rate. `livekit-rtc` stays a dependency for the frame
type; no WebRTC connection is ever opened.
- The runner replaces dispatch: consume an announce prefix, start a session
per `request/<x>` broadcast, publish at `response/<x>`, end the session on
unannounce. The disjoint subtrees are the loop guard. A scoped token
(subscribe `request/**`, publish `response/**`) is the whole credential;
there is no registration and no dispatch API.
- Client turn signals ride the same RTVI JSON track the Pipecat transport
uses, mapped onto `session.interrupt()` and `session.commit_user_turn()`,
so one browser publisher works with either framework's agent.
- The README states what `RoomIO` provides and this does not: participant
attributes, `lk.chat` text input, transcription forwarding, `SessionHost`,
and RPC. Text and transcript surfaces follow the
[data convention](/quest/m2/data-convention.md) when it lands.
- Tests: a fake `AgentSession` I/O round trip over a local relay, barge-in
replacing the output track, and unannounce ending the session. Pin
`livekit-agents` to the 1.8 line and record the `io.py` surface it relies
on, since that module is not covered by their stability promise.

## Related

- [LiveKit Agents adapter (Node)](/quest/m2/livekit-agents-node.md)
- [LiveKit client shim](/quest/m2/livekit-shim.md) - the client-side half
26 changes: 26 additions & 0 deletions quest/m2/livekit-shim-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# [M] LiveKit shim data surfaces

## Goal

`publishData`, text streams, and RPC in the LiveKit client shim work instead
of throwing: reliable `publishData` is a `stream`-mode `binary` track and
lossy a `snapshot`-mode one, text streams are `stream`-mode `json` tracks
keyed by topic, and RPC is the data convention's request/response pair
Comment on lines +6 to +8

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve publishData routing metadata

When a caller supplies LiveKit's topic or destinationIdentities options, mapping only the payload onto a binary snapshot/stream leaves nowhere to carry those fields. Receivers therefore cannot reproduce the original topic or filter addressed packets, so targeted data can be delivered to unintended room clients and compatible samples will observe incorrect events. Define an envelope or track-naming scheme that preserves this metadata, or explicitly reject these options.

AGENTS.md reference: AGENTS.md:L57-L60

Useful? React with 👍 / 👎.

addressed by the callee's identity path. Byte streams with progress stay
unimplemented and throw. Done when a LiveKit sample that uses each of the
three runs against a relay unchanged.

## Plan

- Every track the shim publishes is declared in the participant's
`camera.hang` catalog, so a plain `@moq/room` client can read a LiveKit
app's data too. The README maps each LiveKit method to its track and its
delivery guarantee.
- Tests: a reliable and a lossy `publishData` round trip, a topic stream
read by a late joiner (only what the stream retains), and an RPC with a
timeout and an error reply.

## Required

- [LiveKit client shim](/quest/m2/livekit-shim.md)
- [Data convention](/quest/m2/data-convention.md)
43 changes: 43 additions & 0 deletions quest/m2/livekit-shim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# [L] LiveKit client shim

## Goal

A drop-in `livekit-client`-compatible JS package (e.g. `@moq/livekit`) that
runs a multi-participant room entirely over MoQ. The v1 surface is media plus
core events: Room connect/disconnect, local and remote participants,
camera/mic/screenshare publish, auto-subscribe, and the TrackSubscribed event
family; data surfaces (publishData, streams, RPC) are stubbed. The token slot
takes an ordinary moq-auth token, no LiveKit JWT parsing. Done when an
off-the-shelf LiveKit JS sample runs against a MoQ relay with only the import
and the connect URL/token changed.

## Plan

- The shim is a LiveKit-API facade over `@moq/room` (`js/room`, landed in
#3634): the room is a path prefix in the connection URL and token root,
participants are discovered from the announce stream, identity is the path
before `camera.hang` and `screen.hang`, and a screenshare's
announce/unannounce is its lifecycle. The shim groups the two broadcasts
per identity into one RemoteParticipant and maps catalog entries to
TrackPublications.
- Build on `@moq/publish` and `@moq/watch`. LiveKit quality hints map to
the receiver-driven pixel target where they can (`setVideoQuality` picks
the rendition, `adaptiveStream` follows the rendered size); a hint the
target cannot express (`setVideoFPS`, per-layer bitrate caps) throws a
clear unsupported error rather than no-oping, so an app never believes a
limit is active.
- v1 is identity-only: `participant.identity` comes from the path and muted
state derives from catalog track presence. Names and coarse state come
from `@moq/room`'s `hang/*.json` metadata in a follow-up, not a rival
scheme; `ActiveSpeakersChanged` waits on
[active speaker](/quest/m2/room-active-speaker.md).
- Tokens come from `@moq/room` `claims()`: publish under `<identity>/**`
only, so participants cannot publish at each other's paths.
- `publishData`, text and byte streams, and RPC throw a clear
not-implemented error in v1; [shim data surfaces](/quest/m2/livekit-shim-data.md)
maps them onto the data convention afterwards.

## Related

- [LiveKit Agents adapter (Python)](/quest/m2/livekit-agents-python.md) -
the agent-side half of the same migration
27 changes: 27 additions & 0 deletions quest/m2/room-active-speaker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# [S] Active speaker

## Goal

`@moq/room` exposes who is talking: every `Remote` and the `Local` carry an
audio level signal and a debounced `speaking` boolean, and the `Room`
exposes the ordered active-speaker set, so a grid can highlight the speaker
and the LiveKit shim can emit `ActiveSpeakersChanged`. `moq-room` is
media-free (announce events and chat; decoded audio lives in `moq-audio`
and the application), so the native half is a level meter in `moq-audio`
over decoded PCM with the same thresholds, which a native app wires itself;
no room media layer is added.

## Plan

- Measure on decoded audio, independent of the member's playback mute:
`Member.muted` defaults to true and gates only the output today, so the
level analysis runs on the decoded frames before that gate (or keeps the
Comment on lines +16 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Meter the local capture path too

This plan only measures decoded frames and discusses Member.muted, but the promised Local signal has no decoder: js/room/src/local.ts sends microphone capture through cameraAudioCapture directly to the encoder. Executing this plan as written therefore leaves local audio level and speaking without an input; include metering of the captured PCM and regression coverage for the local participant.

Useful? React with 👍 / 👎.

decoder subscribed while the sink is silent), and a muted-for-me member
still reads as speaking. Measuring on the wire is not an option since the
relay never decodes.
- Level is a smoothed RMS in dBFS; `speaking` uses an attack and release
threshold with a short hold, exposed through the existing `@moq/signals`
idiom. The `moq-audio` meter uses the same numbers and a test vector
shared with the JS one.
- The demo at `demo/web/src/meet.html` highlights the loudest tile as proof,
with every remote left muted.
5 changes: 3 additions & 2 deletions quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ worth settling now. Deferral does not by itself abandon a feature.
- [iOS capture](/quest/m3/mobile-capture-ios.md) - camera and screen capture if the mobile ownership decision selects Rust
- [Android capture](/quest/m3/mobile-capture-android.md) - NDK/JNI capture and codec backends if the mobile ownership decision selects Rust
- [Mobile completion](/quest/m3/mobile-completion.md) - verify the selected native/mobile path before closing #700
- [C++ SDK](/quest/m3/cpp-sdk.md) - an idiomatic wrapper over libmoq's C ABI, with the OBS plugin as its first consumer
- [Linux OBS GPU input](/quest/m3/obs-linux-gpu.md) - publish OBS compositor frames without CPU readback on a validated Linux graphics/encoder combination
- [LiveKit client shim](/quest/m3/livekit-shim.md) - a media compatibility facade over the room SDK
- [QUIC capacity probing](/quest/m3/quic-probe.md) - compare useful early retransmissions with padding and no probing

- [Embedded video](/quest/m3/video-embedded.md) - EGL import in the renderer, so moq-video presents on a Pi
Expand All @@ -42,7 +42,8 @@ worth settling now. Deferral does not by itself abandon a feature.
- [#1838](/quest/m3/1838-tr-101-290-monitoring-requirements-broadcast-contribution.md) - TR 101 290 monitoring: requirements (broadcast/contribution health metrics)
- [Teleoperation](/quest/m3/teleop/README.md) - MoQ carries robot video down and control up on one session as a library capability
- [SIP media stack](/quest/m3/sip-stack.md) - terminate one inbound SIP audio call leg and expose it as Opus frames
- [SIP DTMF](/quest/m3/sip-dtmf.md) - RFC 4733 keypad events become a data track beside the caller's audio
- [SIP transfer](/quest/m3/sip-transfer.md) - blind transfer over REFER and warm transfer over a second leg, driven through the data convention
- [Carrier voice](/quest/m3/carrier-voice/README.md) - determine whether MoQ should be the call fabric for programmable carrier voice
- [LiveKit WebRTC bridge](/quest/m3/livekit-webrtc-bridge.md) - a go/no-go verdict, backed by a spike, on per-track LiveKit-to-MoQ bridging
- [Vision worker](/quest/m3/processor-vision.md) - a documented customer-run vision worker proves the processor contract
- [Common Access Tokens](/quest/m3/cat/README.md) - a moq-transport client presents a CAT in SETUP and `moq auth serve` admits it with the scope its `moqt` claim names
25 changes: 25 additions & 0 deletions quest/m3/cpp-sdk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# [M] C++ SDK

## Goal

C++ developers (robotics tooling, engines, native apps) get a thin,
idiomatic wrapper over `libmoq`'s C ABI: RAII handles, `std::span` payloads,
callbacks or futures for announce and subscribe, and hang audio, video, and
data tracks, installed as a CMake package beside `moq.pc`. The OBS plugin
(`cpp/obs`) moves onto it as the first consumer, and the wrapper's surface
tracks the Go, Swift, and Kotlin bindings one for one.

## Plan

- Header-first in `cpp/moq`, generated where the C ABI already carries the
shape (cbindgen output in `rs/libmoq`), handwritten where ownership or
callbacks need C++ semantics. C++20, no exceptions across the boundary.
- Ship through `libmoq`'s existing release pipeline (`build.sh`, CMake
config), with a smoke that publishes and watches a clock through a local
relay on Linux, macOS, and Windows.
- Unity is deliberately not a target; a C# binding is a separate decision.

## Required

- [#2152](/quest/m2/2152-libmoq-c-abi-catch-up-with-the-moq-ffi-surface.md) -
the C ABI has to carry sessions before a wrapper is worth publishing
Comment on lines +24 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the libmoq track-demand blocker

Completing only #2152 cannot provide the promised one-for-one Swift/Kotlin surface: that quest explicitly excludes dynamic track serving and moves it to /quest/m1/libmoq-track-demand.md, while the C++ wrapper is built on libmoq. Add that quest as a blocker, otherwise this SDK quest becomes ready while a required C ABI capability is still absent. quest/CLAUDE.mdL75-L82

Useful? React with 👍 / 👎.

36 changes: 0 additions & 36 deletions quest/m3/livekit-shim.md

This file was deleted.

Loading
Loading