Skip to content
Merged
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
5 changes: 3 additions & 2 deletions quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,9 @@ lite-07; bitrate enforcement is not part of this release.
- [Keep-alive from the idle timeout](/quest/m2/quic-keep-alive.md) - the keep-alive interval defaults to a fraction of the negotiated idle timeout; an explicit `quic.keep_alive` overrides
- [Socket close](/quest/m2/quic-socket-close.md) - moq-tokio's QUIC endpoint releases its socket on close, so it drops its wrapper
- [Windows.Graphics.Capture](/quest/m2/capture-wgc.md) - the WGC display and window backend verified on real Windows hardware
- [Windows capture parity](/quest/m2/capture-windows.md) - system audio and a settled app-capture policy
- [Linux capture parity](/quest/m2/capture-linux.md) - Wayland window/system-audio capture with explicit display-selection and app-capture limits
- [Windows capture parity](/quest/m2/capture-windows.md) - system and per-app audio and a settled app-capture policy
- [Linux capture parity](/quest/m2/capture-linux.md) - Wayland window/system-audio capture, per-app audio, and explicit display-selection and app-capture limits
- [Per-app audio on macOS](/quest/m2/capture-app-audio-macos.md) - one application's audio through ScreenCaptureKit
- [cpal loads libasound at runtime](/quest/m2/cpal-alsa-runtime.md) - condition: a cpal release whose Linux build carries no load-time libasound requirement
- [Audio capture without ALSA link](/quest/m2/capture-alsa-link.md) - moq-audio capture and playback build on Linux without linking libasound, and capture becomes a default feature
- [Ship capture and playback](/quest/m2/cli-packaging.md) - a released moq binary can capture and play, which no distribution currently enables
Expand Down
22 changes: 22 additions & 0 deletions quest/m2/capture-app-audio-macos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# [S] Per-app audio on macOS

## Goal

`moq_audio::capture` captures one application's audio on macOS, so a creator
can stream a game without their music. The Linux and Windows halves live in
their capture parity quests.

## Plan

Decided 2026-10-26 (maintainer). ScreenCaptureKit already delivers system
audio here (`Source::System`), and an `SCContentFilter` that includes only one
application's windows limits the audio to that app. Apps are listed from
`SCShareableContent`, so only a running app can be chosen. It needs the
Screen Recording permission, like system audio. Per-app audio is its own
source variant naming the application; `Source::System` keeps capturing all
desktop audio through its display filter.

## Related

- [Linux capture parity](/quest/m2/capture-linux.md) - per-app audio through PipeWire nodes
- [Windows capture parity](/quest/m2/capture-windows.md) - per-app audio through WASAPI process loopback
8 changes: 8 additions & 0 deletions quest/m2/capture-linux.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ unimplemented, because xdg-desktop-portal owns source selection:
- **Applications** have no portal concept behind them, so decide explicitly
whether Linux offers app capture rather than leaving a variant that errors at
runtime.
- **Per-app audio** (decided 2026-10-26, maintainer: yes) captures one
application's output from its PipeWire stream node, so a creator can stream
a game without their music. Apps are listed from the nodes present, so only
a running app can be chosen. An app is every stream node with its
`application.process.id` (one game may open several), mixed together, and
capture follows nodes as the app opens and closes them, matching the
Windows process tree and the macOS application. The Windows and macOS halves
share the same `moq_audio::capture` source.

## Related

Expand Down
7 changes: 7 additions & 0 deletions quest/m2/capture-windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ including the cursor, move to
exists but is macOS-only and returns `Unsupported` elsewhere. Unlike macOS
this does not go through the screen-capture API, so it is an independent path
and does not inherit the Screen Recording permission coupling.
- **Per-app audio** (decided 2026-10-26, maintainer: yes) uses WASAPI process
loopback (`AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK`) to capture one
process tree's output, through the same `moq_audio::capture` source as the
Linux and macOS halves. Microsoft documents build 20348 as the floor, though
it works from Windows 10 2004 (build 19041) in practice. Activate it at
setup and refuse per-app audio with the reason where activation fails,
rather than gating on a build number.

WGC does not answer app capture. It captures one monitor or one window per
item and has no per-process source, so the per-window-composition or
Expand Down
11 changes: 11 additions & 0 deletions quest/m3/studio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,21 @@ surface and zero-copy texture import, land before the cut.

- [Zero-copy source import](/quest/m1/studio-gpu/import.md) - captured and decoded frames reach the compositor as GPU textures instead of a CPU upload
- [Studio on macOS and Windows](/quest/m3/studio/app.md) - the studio runs on macOS and Windows, not only Linux
- [Viewer-side composition](/quest/m3/studio/compose/README.md) - sources as their own tracks plus a layout track, composed by viewers, with a server-composed program as the fallback
- [Undo and the scene model](/quest/m3/studio/scene-model.md) - undo history, linked and cloned sources, copy and paste, a downstream keyer, and per-item filters
- [Autosave](/quest/m3/studio/autosave.md) - every edit is written to the scene file, with no save step or quit prompt
- [Scene editing](/quest/m3/studio/scene-editing.md) - rename and reorder scenes, and resize the canvas while live through a new rendition
- [Broadcast sources play in sync](/quest/m3/studio/broadcast-sync.md) - subscribed broadcasts and guests play audio and video on the publisher's timestamps, with `moq play`'s delay
- [Wayland display and window sources](/quest/m3/studio/wayland-sources.md) - portal-picked sources that reopen without the picker
- [Live encode control](/quest/m3/studio/encode-control.md) - simulcast rungs that follow the bandwidth grant, live bitrate and resolution, keyframes on request
- [Stream health](/quest/m3/studio/health.md) - per-track delivery, viewer reports, and latency, with causes named
- [Relay failover](/quest/m3/studio/failover.md) - the same epoch on two relays, so a relay failure does not drop the stream
- [Instant replay and DVR](/quest/m3/studio/replay.md) - replays and clips from held groups, and viewer scrubbing through the archive
- [HTML/CSS overlays](/quest/m3/studio/overlays.md) - script-free HTML and CSS graphics through Blitz natively and the DOM in the browser
- [Animation and transitions](/quest/m3/studio/animation.md) - move, slide, wipe, and stinger transitions, and keyframed item properties
- [Video effects](/quest/m3/studio/effects.md) - a curated effect set with CSS meanings, and ML effects baked into their source
- [Snapping and guides](/quest/m3/studio/snapping.md) - preview moves snap to edges, centers, and other items
- [Guided setup](/quest/m3/studio/guided-setup.md) - first launch detects devices, measures the uplink, and proposes settings
- [Destinations](/quest/m3/studio/destinations.md) - RTMP, RTMPS, and WHIP push with per-destination codecs and shared encoders, on a UI-free egress library a hosted service can reuse
- [Multiple canvases](/quest/m3/studio/multi-canvas.md) - landscape and portrait canvases composite at once from shared sources
- [Guests](/quest/m3/studio/guests.md) - remote guests and phone cameras join through a moq-room link or QR code and appear as sources
Expand Down
23 changes: 23 additions & 0 deletions quest/m3/studio/animation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# [M] Animation and transitions

## Goal

Scene changes in `moq-studio` use transitions beyond a cut and fade: move
(items glide between their positions in the two scenes), slide, wipe, and
stinger (a video clip with a cut point). Any item property can be keyframed
with easing, so an item can fly in or a camera can push in, and the same
animation plays in every composition host.

## Plan

Move is OBS's most-installed plugin (Move Transition), and stingers are
standard in broadcast. Animate in moq-scene on the media clock and carry the
transition and keyframes in the layout track, so viewers compose them too.
Overlays animate through their own CSS. No Lottie runtime for now (decided
2026-10-26): the open ones either lack text (velato) or add a C++ build
(ThorVG). A stinger clip is a media source like any other.

## Required

- [Undo and the scene model](/quest/m3/studio/scene-model.md) - keyframes are scene model state
- [Layout track](/quest/m3/studio/compose/layout.md) - where transitions and keyframes travel
11 changes: 9 additions & 2 deletions quest/m3/studio/audio-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@

Each audio source has an ordered processing chain in moq-audio: gain, noise
suppression, noise gate, compressor, and limiter. Each filter is configured
from the studio and saved with the scene collection.
from the studio and saved with the scene collection. Ducking lowers one source
(music) when another (the microphone) is active. Third-party VST3 and CLAP
plugins load into the chain.

## Plan

Prefer maintained crates, such as `nnnoiseless` for RNNoise suppression, over
hand-rolled DSP. Benchmark the chain per source so its cost per added source
shows up as a slope.
shows up as a slope. Host plugins out of process, so a crashing or hung plugin
drops that filter rather than the audio bus: the audio thread never blocks on
the plugin process, a block the plugin misses its deadline for passes through
unfiltered, and a plugin that keeps missing is bypassed and shown as failed.
Use a maintained host crate
(such as `clack` for CLAP) rather than hand-rolling the plugin ABIs.
50 changes: 50 additions & 0 deletions quest/m3/studio/compose/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Viewer-side composition

## Goal

A `moq-studio` broadcast carries each source as its own track plus a layout
track describing the live scene. Viewers compose the picture themselves:
they prioritize sources and toggle elements on and off. A composited program
remains for protocols and devices that cannot compose (RTMP and WHIP
destinations, HLS, legacy players), produced by the studio or by a server.

This README's own work is the end-to-end test: a studio broadcast is composed
by the browser, by `moq compose`, and by the studio itself, and all three
pictures match.

## Plan

Decided 2026-10-26 (maintainer), the studio's flagship and first in its epic:

- **Delivery:** MoQ's value is per-track delivery, so the studio embraces
viewer composition. The program is the fallback, not the default.
- **One compositor, three hosts:** moq-scene renders in the studio, headless
on a server (`moq compose` in moq-cli), and in the browser, so all three
pictures come from one implementation.
- **Parity rule:** every scene feature (transitions, filters, effects,
overlays) renders in all three hosts with the same layout, timing, and
colors, within a tolerance. Each renderer keeps its own text antialiasing
and blur, so the browser can draw overlays as real DOM. A feature that
cannot meet this, such as a CEF browser source or an ML effect, is baked
into its source's own track before publishing rather than living in the
layout.
- **Wire layout:** local sources are tracks in the studio's one broadcast.
Guests stay their own broadcasts, named through
[contribution references](/quest/m2/contribution-ref.md).
- **Layout:** the live scene state (items, transforms, transitions, and each
element's priority and toggleable flag) as a hang JSON track on the media
clock, so any host composites the same frame the studio shows.
- **Browser build:** whichever line [Rust-backed @moq/net](/quest/m2/rs2ts/README.md)
lands (WASM first, then JS built from Rust). Not fixed to WASM.
- **Cost:** every source and the program follow MoQ's pull model and are
encoded only while something subscribes, so a fallback nobody uses costs
nothing.

## Required

- [Source tracks](/quest/m3/studio/compose/sources.md) - each source publishes as its own track in the studio broadcast, encoded only while subscribed
- [Layout track](/quest/m3/studio/compose/layout.md) - the live scene state as a hang JSON track on the media clock, specified in the hang draft
- [Browser composition](/quest/m3/studio/compose/browser.md) - `@moq/watch` composes a broadcast with a layout track through moq-scene's browser build
- [Server composition](/quest/m3/studio/compose/server.md) - `moq compose` renders the program from sources and layout, headless
- [Viewer controls](/quest/m3/studio/compose/controls.md) - viewers toggle elements and the layout's priorities steer what they fetch
- [Hang contribution references](/quest/m2/contribution-ref.md) - guests composed by reference
25 changes: 25 additions & 0 deletions quest/m3/studio/compose/browser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# [L] Browser composition

## Goal

`@moq/watch` composes a broadcast that carries a layout track: it fetches the
tracks the layout shows and draws them through moq-scene's browser build,
matching the studio's picture. A broadcast without one plays its program as
today, so embedders change nothing.

## Plan

The browser build comes from whichever line
[Rust-backed @moq/net](/quest/m2/rs2ts/README.md) lands (decided 2026-10-26),
WASM first and JS built from Rust later, so this quest does not fix the
mechanism. A thin package (e.g. `@moq/scene`) wraps it and `@moq/watch` uses
it. Frames reach it as WebCodecs `VideoFrame`s through the same browser import
path the [web studio](/quest/m3/studio/web/video-web.md) adds.

Prove parity with a fixture: the same broadcast composited here and by the
studio, compared within a stated tolerance.

## Required

- [Layout track](/quest/m3/studio/compose/layout.md) - what the browser composes
- [moq-video in the browser](/quest/m3/studio/web/video-web.md) - browser frames into wgpu
22 changes: 22 additions & 0 deletions quest/m3/studio/compose/controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# [M] Viewer controls

## Goal

A viewer composing a studio broadcast can switch off any element the layout
marks toggleable (a facecam, a chat overlay), and stops fetching its track.
The layout's per-element priorities steer delivery, so under congestion the
least important sources degrade first. The creator sets both per element in
the studio.

## Plan

Map element priority onto the subscription priorities moq-lite already has
(`doc/concept/moq-lite.md`), rather than a second mechanism. A toggled-off
element is unsubscribed, so it costs the viewer and the relay nothing, and
the pull model may stop its encode. Subscriptions are per track, not per
element: a linked source shown by several items stays subscribed while any
of them is on, at the highest priority among them.

## Required

- [Browser composition](/quest/m3/studio/compose/browser.md) - the viewer that exposes the toggles
22 changes: 22 additions & 0 deletions quest/m3/studio/compose/layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# [M] Layout track

## Goal

A studio broadcast carries a layout track: moq-scene's live scene state
(items, the tracks they show, transforms, transitions, and each element's
priority and whether a viewer may toggle it) as a hang JSON track on the
media clock. Any host that reads it composites the same frame the studio
shows. The hang catalog and `drafts/draft-lcurley-moq-hang.md` specify it.

## Plan

Serialize moq-scene's own model rather than a second layout schema, so the
studio, server, and browser cannot drift. Version it and refuse an unknown
version loudly. Keep it free of local paths and credentials: items name
tracks and contribution references, never files. A viewer that refuses a
layout version plays the program instead and says why. A late joiner needs the
current state, so use the JSON track's snapshot mode.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

## Required

- [Source tracks](/quest/m3/studio/compose/sources.md) - the tracks a layout names
20 changes: 20 additions & 0 deletions quest/m3/studio/compose/server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# [M] Server composition

## Goal

`moq compose` in moq-cli subscribes to a studio broadcast's sources and
layout track and publishes the composited program, headless (a GPU, or
lavapipe without one). It can push that program to RTMP and WHIP through the
studio's egress library, so a hosted service makes the fallback composite and
the creator saves the upload.

## Plan

Decided 2026-10-26 (maintainer): a moq-cli subcommand, not a mode of
`moq transcode`. A hosted service embeds the same library. It publishes its
own broadcast, never the studio's names: each publisher produces its own
stream (AGENTS.md). Update `doc/bin/cli.md`.

## Required

- [Layout track](/quest/m3/studio/compose/layout.md) - what the server composes
18 changes: 18 additions & 0 deletions quest/m3/studio/compose/sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# [M] Source tracks

## Goal

Each local source in a `moq-studio` scene (camera, display, window, image,
microphone) publishes as its own track in the studio's broadcast, listed in
its catalog, alongside the program. Each is encoded only while something
subscribes to it.

## Plan

Follow the pull model the program already uses: an unwatched source costs
no encode and no upload. Static sources (images, colors) publish once rather
than as a video stream. Consumer GPUs cap concurrent hardware encode
sessions, so when the hardware refuses one, that source's track fails loudly
with the reason shown rather than silently degrading.
Track names stay stable for a source's life, and an edited source that
changes what it shows gets a new name.
25 changes: 25 additions & 0 deletions quest/m3/studio/effects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# [M] Video effects

## Goal

Sources and scene items in `moq-studio` take a curated set of video effects:
color correction, `.cube` LUTs, blur, crop and masks, chroma and luma key,
and blend modes. They render the same in every composition host. ML effects
such as background removal are offered too, applied before the source
publishes.

## Plan

Write the effects in WGSL to the meanings the CSS Filter Effects and
Compositing specs define (color matrices, blur, drop-shadow, blend modes), so
an effect means the same thing as the CSS overlays use. The layout track
carries effect names and parameters, never shader code, since a shader from
the network costs every viewer's GPU and can lose the device. An effect the
browser cannot reproduce, like an ML model, is baked into its source's track
per the [composition epic](/quest/m3/studio/compose/README.md), so ML effects
apply to a source, never to one scene item. Prefer an
existing model crate for ML effects over training one.

## Required

- [Undo and the scene model](/quest/m3/studio/scene-model.md) - per-item filters this extends
26 changes: 26 additions & 0 deletions quest/m3/studio/encode-control.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# [L] Live encode control

## Goal

`moq-studio` adapts its encode live without a restart: the program and each
source publish simulcast rungs that follow the relay's bandwidth grant, a rung
nobody watches stops encoding, and bitrate and resolution change mid-stream
through catalog updates. OBS's multitrack output does
part of this only on Windows with specific GPUs; this works on every
platform.

## Plan

Today the studio publishes one H.264 rendition at fixed settings and never
reads `bandwidth::Allocator`. Reuse what exists rather than adding a studio
path: the grant follower in `moq_video::encode` and `moq_mux::rate`, the
catalog's in-place updates and new renditions (`doc/concept/hang.md`). The
encode producer already cuts a keyframe when a subscriber joins
(`encode/cuts.rs`), so each rung inherits that and can run a long GOP. Show
each rung's grant, bitrate, and viewers in the Output panel.

## Related

- [Simulcast rung disable](/quest/m1/simulcast-rung-disable.md) - grant-driven rung disable this builds on
- [moq-audio follows the grant](/quest/m2/2848-follow-the-bandwidth-grant-in-moq-audio-instead-of.md) - the audio half
- [Scene editing](/quest/m3/studio/scene-editing.md) - canvas resize through a new rendition
22 changes: 22 additions & 0 deletions quest/m3/studio/failover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# [M] Relay failover

## Goal

`moq-studio` publishes the same broadcast epoch to two relays at once, so
when one relay or the path to it fails, viewers resume from the other
mid-group and the stream does not drop.

## Plan

Publisher epochs already support this: the same epoch on several relays
resumes mid-group, and a restart is a newer epoch (`doc/concept/moq-lite.md`,
Publisher epochs and Multiple CDNs). The studio dials one relay today. Encode
once and publish to both. The second relay is configured, never inferred.
Both links must negotiate the same protocol and draft, one with publisher
epochs, since a copy whose track info differs ends the track; a relay that
does not match is refused at setup with the reason, rather than failing over
as a restart. Show each relay's state in the health panel.

## Related

- [Stream health](/quest/m3/studio/health.md) - where each relay's state shows
Loading
Loading