Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
221c87c
chore(quest): claim quest/m2/cluster-routing/routes/session.md
kixelated Nov 6, 2026
b755d0e
feat(net): present each route's claims at a stream's whole interest
kixelated Nov 6, 2026
6225073
feat(net): one route node per origin, fed the routes it serves itself
kixelated Nov 6, 2026
97419fa
test(net): the route node serves claims through their route's stream
kixelated Nov 6, 2026
30a653f
feat(relay)!: remove cluster.id and refuse zero-cost links
kixelated Nov 6, 2026
8059e16
feat(net)!: lite-07 names a claim's route instead of its hop chain
kixelated Nov 6, 2026
d6ad5ba
feat(net)!: lite-07 announce streams route through the origin's node
kixelated Nov 6, 2026
dbadd77
fix(net): serve lite-07 claims through every feasible route
kixelated Nov 6, 2026
676345f
fix(net): a track that ended keeps its copy when its front ends
kixelated Nov 6, 2026
2923b4c
fix(net): only a grant this side issued scopes a lite-07 peer
kixelated Nov 6, 2026
f1732ec
test: library relays proxy, and lite-07 tests follow routes
kixelated Nov 6, 2026
67c4e65
chore(net): drop the route layer's dead-code allowance
kixelated Nov 6, 2026
917341a
fix(net): drop a lite-06 route carrying this node's route identity
kixelated Nov 6, 2026
28bb2b9
docs: lite-07 routes, proxy opt-in, and lite-06 translation
kixelated Nov 6, 2026
6662e4d
test(net): a lost next hop restarts an epochless route, and lite-07 c…
kixelated Nov 6, 2026
92cc680
test(net): a lite-07 route and its claims count against the announce cap
kixelated Nov 6, 2026
db9b5e3
chore(quest): narrow routes/session.md to bounded per-stream state
kixelated Nov 6, 2026
80fd341
Merge remote-tracking branch 'origin/main' into quest/m2/cluster-rout…
kixelated Nov 6, 2026
2c9c396
chore(net): allow route_hop unused without the async session
kixelated Nov 6, 2026
8ea5612
Merge remote-tracking branch 'origin/main' into feat/route-takeover-c…
kixelated Nov 6, 2026
a627b58
feat(net): lite-07 carries ANNOUNCE_TAKEOVER on Route IDs
kixelated Nov 6, 2026
b9f3078
test: skip native JS lite-07 lanes until js/net speaks the route layer
kixelated Nov 6, 2026
fb3cc76
Merge remote-tracking branch 'origin/main' into feat/route-takeover-c…
kixelated Nov 6, 2026
e896ae7
fix(net): lite-07 receive admits each claim by the limit, bounds pend…
kixelated Nov 6, 2026
0e32a18
fix(net): coalesce a lite-07 stream's unread route outputs per identity
kixelated Nov 6, 2026
06c02b0
Merge remote-tracking branch 'origin/main' into feat/route-takeover-c…
kixelated Nov 6, 2026
2329bb1
chore: leave MIGRATE.md to the release cut
kixelated Nov 6, 2026
6746f6e
refactor(net): name the outbox's merged changes for clippy
kixelated Nov 6, 2026
2d6790d
fix(net): ack routes ended unread at once, so a stalled outbox holds …
kixelated Nov 6, 2026
554fae6
fix(net): present a handle's mounts on lite-07
kixelated Nov 6, 2026
4689bbc
Merge remote-tracking branch 'origin/main' into feat/route-takeover-c…
kixelated Nov 6, 2026
8e03f17
test: retarget the drill mutations and allow the gated lite-07 intero…
pullfrog[bot] Nov 6, 2026
45100ff
chore(quest): claim quest/m2/announce-takeover.md
kixelated Nov 6, 2026
b6c425c
Merge remote-tracking branch 'origin/quest/m2/cluster-routing/routes/…
kixelated Nov 6, 2026
b7a7836
Merge branch 'base' into pr-8023
Nov 6, 2026
71f01d0
feat(net)!: a job's exact route continues its claim's instance
kixelated Nov 6, 2026
9c5b2b9
test(net): supply default node in legacy request helpers
Nov 6, 2026
08b3ea2
test(cli): a player reads through a claim on its first request
kixelated Nov 6, 2026
278923a
docs: describe claim continuity by instance, drop ANNOUNCE_TAKEOVER
kixelated Nov 6, 2026
4e1f0ec
Merge base and resolve route-session hop API conflicts
Nov 6, 2026
7757ecc
feat(js)!: a job continues its claim's instance, drop ANNOUNCE_TAKEOVER
kixelated Nov 6, 2026
5d8077b
Merge remote-tracking branch 'origin/quest/m2/cluster-routing/routes/…
kixelated Nov 6, 2026
8cc983e
fix(net): build the route node without the fuzz feature, retarget a d…
kixelated Nov 7, 2026
eea486e
test(bindings): a job route continues its prefix route's instance
kixelated Nov 7, 2026
5bb0240
fix(js): a peer pinned to a moved claim still reaches its job
kixelated Nov 7, 2026
e067fe5
fix(net): build the route node without the fuzz feature, retarget a d…
kixelated Nov 7, 2026
c419bbc
Merge remote-tracking branch 'origin/main' into quest/m2/cluster-rout…
kixelated Nov 7, 2026
140af50
fix(net): lite-07 receive streams honor their limit, hold echo slots,…
kixelated Nov 7, 2026
f2e0311
fix(net): keep upstream-learned claims from lite-07 upstreams, answer…
kixelated Nov 7, 2026
7e0897d
style(net): format the new lite-07 tests
kixelated Nov 7, 2026
d5ed89f
test(drill): the lost-publisher mutation compiles against the routed …
kixelated Nov 7, 2026
9c828bc
Merge remote-tracking branch 'origin/main' into quest/m2/cluster-rout…
kixelated Nov 7, 2026
f51f529
fix(net): a coalesced Serve keeps its place behind a takeover
kixelated Nov 7, 2026
42f1afb
Merge remote-tracking branch 'origin/main' into quest/m2/cluster-rout…
kixelated Nov 7, 2026
1f5d053
fix(relay): a removed cluster ID names its removal at startup
kixelated Nov 7, 2026
467cacd
Merge remote-tracking branch 'origin/main' into quest/m2/cluster-rout…
kixelated Nov 7, 2026
70771b3
fix(net): a node that does not proxy keeps lite-06-learned claims to …
kixelated Nov 7, 2026
8a56b87
Merge remote-tracking branch 'origin/main' into quest/m2/cluster-rout…
kixelated Nov 7, 2026
3c18d32
Merge remote-tracking branch 'origin/quest/m2/cluster-routing/routes/…
kixelated Nov 7, 2026
c317c4b
Merge remote-tracking branch 'origin/main' into quest/m2/announce-tak…
kixelated Nov 7, 2026
9f9c45d
fix(net): a job's Serve keeps its place ahead of its claim's Unserve
kixelated Nov 7, 2026
4365701
fix(net): a route reports the instance it serves now, in Rust and the…
kixelated Nov 7, 2026
335ebe3
chore(dart): regenerate the UniFFI bindings for the instance_id doc
kixelated Nov 7, 2026
a62431f
merge: preserve cached-track cleanup with base retry argument
Nov 7, 2026
b02bdff
fix(net): initialize routing node in base setup tests
Nov 7, 2026
76b5c75
Merge commit 'b02bdff9f86596376b9c85b379f9bf7dd84895f5' into quest/m2…
kixelated Nov 7, 2026
4a3523a
Merge remote-tracking branch 'origin/main' into quest/m2/announce-tak…
kixelated Nov 7, 2026
915074a
doc: the C and Dart guides describe how a request's route takes its p…
kixelated Nov 7, 2026
8906c97
quest: MIGRATE.md stays for the release cut; the C note lives in ## I…
kixelated Nov 7, 2026
3bd7363
doc: route-hash agreement and job continuation hold on moq-lite 07
kixelated Nov 7, 2026
a6ded82
Merge remote-tracking branch 'origin/main' into quest/m2/cluster-rout…
kixelated Nov 7, 2026
b327b8e
test(interop): skip the browser route key lane until JS speaks the ro…
kixelated Nov 7, 2026
c7e9a82
Merge remote-tracking branch 'origin/main' into quest/m2/announce-tak…
kixelated Nov 7, 2026
c1d875e
test(net): the lite-07 serve-loop helper names its node
kixelated Nov 7, 2026
b8494e1
Merge remote-tracking branch 'origin/main' into pr-8023
kixelated Nov 7, 2026
0b70a60
fix(net): bound the lite-07 ROUTE_REQUEST backlog by identity
kixelated Nov 7, 2026
2a7e718
fix(net): a lite-07 answer gives route outputs a turn beside replies
kixelated Nov 7, 2026
84c4db9
Merge commit '2a7e718e033d119b7693891c6878f2cbf60cad85' into t3/705f899f
kixelated Nov 7, 2026
41ecad4
Merge remote-tracking branch 'origin/main' into t3/705f899f
kixelated Nov 7, 2026
1ca3186
Merge remote-tracking branch 'origin/main' into quest/m2/announce-tak…
kixelated Nov 7, 2026
e3a8c57
Merge origin/main into quest/m2/announce-takeover
kixelated Nov 7, 2026
5d6e7fd
fix(net): serve a job before its claim's restart
kixelated Nov 7, 2026
10a9767
Merge origin/main into quest/m2/announce-takeover
kixelated Nov 8, 2026
baecc27
quest: drop route-roles' requirement on the finished announce-takeove…
kixelated Nov 8, 2026
9e40dba
fix(net): reading a hidden job's instance binds it
kixelated Nov 8, 2026
651b2d5
fix(net): wrap the Python route docstring and regenerate Dart bindings
kixelated Nov 8, 2026
b7e7479
fix(py): rewrap the request route docstring
kixelated Nov 8, 2026
c76e987
fix(net): a refused bound job hands its request back to the worker
kixelated Nov 8, 2026
a8aeafd
Merge remote-tracking branch 'origin/main' into quest/m2/announce-tak…
kixelated Nov 8, 2026
1c11604
Merge remote-tracking branch 'origin/main' into quest/m2/announce-tak…
kixelated Nov 8, 2026
cceb38e
Merge remote-tracking branch 'origin/main' into quest/m2/announce-tak…
kixelated Nov 8, 2026
59d06f0
Merge remote-tracking branch 'origin/main' into quest/m2/announce-tak…
kixelated Nov 8, 2026
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 dart/moq/test/moq_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -691,9 +691,10 @@ void main() {
final pending = origin.consume().request(path: 'live/cam');
final request = (await dynamic.requested().timeout(timeout))!;
expect(request.path(), 'live/cam');
// A hidden job route answers nobody until it announces.
// A hidden job route answers nobody until it announces, under the instance its
// prefix route lends.
final served = request.route(options: RouteOptions(hidden: true));
expect(served.instanceId(), isNot(dynamic.instanceId()));
expect(served.instanceId(), dynamic.instanceId());
served.announce();
await pending.timeout(timeout);
// Closing the job's broadcast ends its route.
Expand Down
6 changes: 3 additions & 3 deletions dart/moq_ffi/lib/src/moq.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24509,10 +24509,10 @@ void _checkApiChecksums() {
if (uniffi_moq_ffi_checksum_method_moqbroadcastrequest_reject() != 9727) {
throw UniffiInternalError.panicked("UniFFI API checksum mismatch");
}
if (uniffi_moq_ffi_checksum_method_moqbroadcastrequest_route() != 16046) {
if (uniffi_moq_ffi_checksum_method_moqbroadcastrequest_route() != 8265) {
throw UniffiInternalError.panicked("UniFFI API checksum mismatch");
}
if (uniffi_moq_ffi_checksum_method_moqbroadcastrequest_serve() != 2580) {
if (uniffi_moq_ffi_checksum_method_moqbroadcastrequest_serve() != 10832) {
throw UniffiInternalError.panicked("UniFFI API checksum mismatch");
}
if (uniffi_moq_ffi_checksum_method_moqfollowing_available() != 26337) {
Expand Down Expand Up @@ -24548,7 +24548,7 @@ void _checkApiChecksums() {
if (uniffi_moq_ffi_checksum_method_moqroute_cancel() != 41411) {
throw UniffiInternalError.panicked("UniFFI API checksum mismatch");
}
if (uniffi_moq_ffi_checksum_method_moqroute_instance_id() != 18728) {
if (uniffi_moq_ffi_checksum_method_moqroute_instance_id() != 57871) {
throw UniffiInternalError.panicked("UniFFI API checksum mismatch");
}
if (uniffi_moq_ffi_checksum_method_moqroute_requested() != 49558) {
Expand Down
8 changes: 5 additions & 3 deletions doc/bin/relay/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ every frame once. A publisher whose groups restart, such as an encoder
restarting from group 0, publishes under a new instance, which replaces the
old broadcast instead of resuming it; the RTMP, SRT, and WHIP ingests mint one
per connection. Among instances every relay ranks routes in [one
order](/concept/identity#choosing-a-route), ending in a hash of the instance
and the path, so the relays of a mesh agree on each path's stream and a pool's
paths spread over its hosts. Seamless failover needs moq-lite 07 on every link
order](/concept/identity#choosing-a-route), ending in a hash of the path with
the route's origin node on moq-lite 07, or with the instance on older wires, so
the relays of a mesh agree on each path's stream and a pool's paths spread over
its hosts. On moq-lite 07 a path therefore stays on its host when the instance
changes. Seamless failover needs moq-lite 07 on every link
the route crosses. Over moq-lite 06 and older, or moq-transport, the receiving
relay mints an instance per announcement, so failover onto such a route is
announced as a restart. Viewers re-request on a restart, and a relay drops its
Expand Down
67 changes: 36 additions & 31 deletions doc/concept/identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ one is, so it fences out an older run. Nothing assumes it is a clock.
| A restarted or replaced publisher | A fresh one | A restart, then the new stream from its start |
| A publisher that continues its sequences across restarts | Stable, with a higher epoch each run | The same stream; readers continue without a seam |
| Independent publishers of one source | One each | Two streams; see [redundant ingest](/setup/redundancy) |
| A pool of hosts producing paths on demand | One per host and one per job | See [work on demand](#work-on-demand) |
| A pool of hosts producing paths on demand | One per host, carried by its jobs; a fresh one per restart | See [work on demand](#work-on-demand) |

## Choosing a route

Expand All @@ -73,9 +73,11 @@ A route resolving a path is chosen in this order:
because the sequence space continues.
3. Among different instances: the preferred link, then an identified chain
over an anonymous one, then the lowest cost, then a broadcast published on
this origin, then the shortest hop chain, then a hash of the instance and
the full requested path. Every relay agrees on the hash, and it spreads a
pool's paths over its hosts. A route that entered a node for exactly the
this origin, then the shortest hop chain, then a hash of the full requested
path with the route's origin node, which moq-lite 07 carries, or else with
the instance. Relays agree on the hash only where routes carry a consistent
origin node, which takes moq-lite 07 on every hop, and it spreads a pool's paths
over its hosts. A route that entered a node for exactly the
requested path is not hashed: the newest one wins, so a restarted publisher
takes over from its lingering session at once.

Expand All @@ -94,7 +96,7 @@ it as minted. A minted instance names the announcement, not the publisher's
stream, so:

- An announcement ending and starting again, such as a job re-announced on one
session, is a new stream, never spliced to its predecessor.
session, is a new stream, never spliced to the earlier one.
- Continuity does not survive such a hop: a reconnect of the same publisher
through it reads as another stream. Do not rely on seamless failover through
an older hop.
Expand Down Expand Up @@ -161,7 +163,7 @@ or relay now serving another refuses them rather than answer with the wrong
stream. A catalog's `instance` is weaker. It never proves a subscription may
resume across routes, and against a minted instance it cannot be checked at
all. Set it only when you know the stream; omit it for a broadcast produced on
demand, which has none until a worker answers.
demand, whose instance changes each time its job restarts.

## Work on demand

Expand All @@ -175,12 +177,12 @@ the prefix route without a restart.
A job that may restart independently, such as a transcoder claiming
`transcode`, instead creates each requested path's own exact route. A claim
promises capability, not content. Each requested path is a separate job,
announced at its exact path under a fresh instance of its own.
announced at its exact path.

```rust
let claim = origin.route_prefix("transcode", RouteOptions::default())?;
while let Some(request) = claim.requested().await {
// A fresh instance for this job, never one shared with another path or run.
// Takes the claim's instance, so readers resolved through the claim continue.
// Hidden until its tracks exist.
let job = request.route(RouteOptions::hidden())?;
let video = job.broadcast().create_track("video", info.clone())?;
Expand All @@ -193,33 +195,36 @@ while let Some(request) = claim.requested().await {
JavaScript mirrors this with `request.route({ hidden: true })` and
`job.announce()`; see [`@moq/net`](/lib/js/net) and [`moq-net`](/lib/rs/moq-net).
A hidden job route is unannounced until it announces, and dropping it before
then refuses the request. Ending a job's route withdraws only its path: the
then refuses the request. It takes the claim's instance when it announces, or
earlier when its instance is first read, as a signer built from it does. Read
first, the job announces under it or not at all: if another job under the claim
ends in between, moving the claim to a fresh instance, `announce` fails with
`Unroutable` and the request reaches the worker again. Ending a job's route withdraws its path, and the
prefix route keeps answering later requests.

A lazy job's lifecycle, as a viewer of `transcode/alice` sees it through a relay:

1. **Request.** The relay resolves the path through the claim and forwards the
request. The worker starts job A and publishes `transcode/alice` under
instance A.
2. **Takeover.** Publishing the answer through the claim proves it was job A,
so job A's route takes over the claim: what the viewer already resolved
through the claim beneath `transcode/alice` keeps playing, now as instance
A. A Rust follower is handed nothing new (a JavaScript follower still moves
to a new broadcast). A different worker, or a job restarted under the same
claim, never inherits it. Only moq-lite 07 carries the takeover
across relays. Older wires, and paths where a relay cannot vouch for it,
send an ordinary announcement instead, so the first subscription through
the claim is reset there: a follower is handed the new broadcast, and a
holder's track ends with an error, and it requests again. Either way the
second request reaches job A.
3. **Steady state.** Requests resolve instance A by its exact route. Routes
carrying instance A through other relays fail over without a seam.
4. **Job restart.** The worker drops job A's route. The path falls back
to the claim, the next request asks the worker again, and job B publishes
under a fresh instance. Followers are handed each new broadcast; relays drop
job A's cached groups, so a viewer returning within the cache window never
gets them. Other paths under the claim, such as `transcode/bob`, are
untouched.
1. **Request.** The relay resolves the path through the claim, under the
claim's instance, and forwards the request. The worker starts a job, whose
route takes the claim's instance and publishes `transcode/alice` under it.
2. **Continue.** The job's exact route carries the instance the viewer
resolved, so on moq-lite 07 every relay keeps the viewer on it: it keeps
playing, and a Rust or JavaScript follower is handed nothing new. Older
wires mint an instance per announcement, so there the first subscription
through the claim is reset: a follower is handed the new broadcast, and a
holder's track ends with an error, and it requests again. The same happens
on any wire under a claim that names a stable instance, whose jobs each take
a fresh one, since a restarted job could not keep that promise.
3. **Steady state.** Requests resolve the job by its exact route. Routes
carrying its instance through other relays fail over without a seam.
4. **Job restart.** The worker drops the job's route, and the claim moves to a
fresh instance at once, so readers see a restart. The next request starts a
new job under the claim's new instance, a new stream; a request pinned to
the old instance is refused as unroutable. Relays drop the old job's cached
groups, so a viewer returning within the cache window never gets them.
Other running jobs, such as `transcode/bob`, keep their instance, while
paths the claim answered with a held broadcast resolve afresh under its new
one.
5. **Done.** Closing the last job leaves only the claim, which serves the next
request as in step 1.

Expand Down
38 changes: 18 additions & 20 deletions doc/concept/moq-lite.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ hidden names work exactly as before.
- **One stream, one instance.** Every broadcast mints a random instance and
keeps it across reconnects. Replicas of the same content announce that same
instance. A prefix claim is its own stream: its worker answers each requested
path by announcing that exact path under the output's own instance.
Restarting one output is then a new instance every hop sees, so a viewer
returning within a relay's cache window never gets the old output's groups.
On moq-lite 07 that announcement is a takeover of the claim: what a viewer
already resolved through the claim keeps playing from the output rather than
being reset to the new route. A different worker, or a replacement output,
never inherits those viewers.
path by announcing that exact path under the claim's instance, so on
moq-lite 07 a viewer resolved through the claim keeps playing (a claim that
names a stable instance gives each output a fresh one instead). When that
output ends, the claim takes a fresh instance, so a restarted output is a
new stream every hop sees, and a viewer returning within a relay's cache
window never gets the old output's groups. A different worker has its own
instance, so it never inherits those viewers.
- **Most specific, then highest epoch, then rank.** The most specific prefix
wins. Within one instance only its highest epoch is eligible, ahead of cost.
Among instances the order is link preference, cost, a broadcast published
Expand All @@ -138,19 +138,17 @@ hidden names work exactly as before.
sequence. A relay drops its copy of the old stream, so a new subscriber never
sees its cached groups.

The instance, the epoch, and the restart and takeover messages travel on
moq-lite 07, which is opt-in. `ANNOUNCE_START`, `ANNOUNCE_RESTART` and
`ANNOUNCE_TAKEOVER` carry the instance and epoch, and `TRACK`, `TRACKS`,
`SUBSCRIBE` and `FETCH` name the instance they pin. A restart onto another
instance, or a higher epoch of the same one, is advertised before the old one
is withdrawn, so the path is never absent. Older versions carry a takeover as
an ordinary announcement, so the first subscription through a claim is reset
once the output's route arrives. Older moq-lite versions and moq-transport
carry no instance, so the receiving session mints one for each announcement,
marked with a leading `~`: a reconnect or a re-announcement through such a hop
is another stream. A peer that ignores the end and start may keep cached
objects of the old run under the same names, so an end and a start is not a
cache flush.
The instance, the epoch, and the restart message travel on moq-lite 07, which
is opt-in. `ANNOUNCE_START` and `ANNOUNCE_RESTART` carry the instance and
epoch, and `TRACK`, `TRACKS`, `SUBSCRIBE` and `FETCH` name the instance they
pin. A restart onto another instance, or a higher epoch of the same one, is
advertised before the old one is withdrawn, so the path is never absent. Older
moq-lite versions and moq-transport carry no instance, so the receiving session
mints one for each announcement, marked with a leading `~`: a reconnect or a
re-announcement through such a hop is another stream, and the first
subscription through a claim is reset once the output's route arrives. A peer
that ignores the end and start may keep cached objects of the old run under
the same names, so an end and a start is not a cache flush.

Independent importers use distinct instances, even when fed one stream. Only
replicas of one publisher's bytes share its instance, such as one publisher
Expand Down
12 changes: 8 additions & 4 deletions doc/lib/c/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,21 @@ if (moq_route_announce((uint32_t)route) < 0)

The route keeps the broadcast announced: `moq_route_free()` retracts it, and
`moq_publish_close()` ends both. A route carries only a cost and the stream's instance;
relays add the hops. Each route mints a fresh instance (`moq_route_instance()`) and keeps it
across `moq_route_withdraw()` and `moq_route_announce()`, so a restarted publisher, being a
new route, is a new stream that `moq_origin_follow` hands viewers instead of a stalled
relays add the hops. `moq_origin_route()` mints a fresh instance (`moq_route_instance()`) and
keeps it across `moq_route_withdraw()` and `moq_route_announce()`, so a restarted publisher,
being a new route, is a new stream that `moq_origin_follow` hands viewers instead of a stalled
broadcast; the path stays `MOQ_ANNOUNCE_KIND_PRESENT` throughout. The follow callback
reports each instance as `MOQ_FOLLOW_EVENT_ONLINE` with its handle,
`MOQ_FOLLOW_EVENT_OFFLINE` when the path goes offline, and one `MOQ_FOLLOW_EVENT_END`. Set `instance` only for a publisher that continues its group sequences
across restarts, and raise `epoch` on each: readers stitch every route of one instance
together, so restarting the sequences under an old instance splices unrelated groups into
their tracks. To serve paths on demand, `moq_origin_route_prefix()` delivers each request to
answer with `moq_broadcast_request_serve()`, `moq_broadcast_request_route()`, or
`moq_broadcast_request_reject()`.
`moq_broadcast_request_reject()`. Unless one is named, a prefix route moves to a fresh
instance each time a job holding its current one ends, and a job from
`moq_broadcast_request_route()` takes the prefix route's, so on moq-lite 07 a reader resolved through the
prefix route does not restart; older wires mint another instance for the job's route, restarting it. The string `moq_route_instance()` writes is borrowed until the
next `moq_route_instance()` on that route or `moq_route_free()`.

Raw video encoders take an exact numerator/denominator frame rate, such as 30000/1001. Publication track names stay separate from codec settings. For simulcast, give each rendition the next one down as the `lower` handle in its `moq_video_encode_options`: it stops encoding, advertised as disabled, while its bandwidth grant cannot fund it, and resumes once the grant recovers. A rung only disables while the session probes for bandwidth, which on native transports takes moq-quic with BBR3. For pre-encoded video, publish from the lowest rendition up and set `moq_video_init.lower` to the next lower media handle. Reserve each track with `moq_allocator_reserve_media`, then bind its reservation with `moq_rate_control_create` and call `moq_rate_control_update` on every frame. Apply changed targets to your encoder and `moq_publish_media_set_enabled`, resuming video on a keyframe; close the control and reservation separately when done.

Expand Down
2 changes: 1 addition & 1 deletion doc/lib/dart/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ one to one; the API reference has the names, and [Layers](/lib/layers)
shows which module holds each.

- **Names.** Types drop the `Moq` prefix (`Session`, `BroadcastProducer`, `RouteOptions`) as aliases, so the generated names still work. `MoqContainer`, `MoqRoute`, and the exceptions keep it, since Flutter owns those names.
- **Routes.** `route(path)` publishes a broadcast and `routePrefix(prefix)` answers requests beneath a prefix, each returning a `MoqRoute`. Hold it: cancelling or disposing it retracts the route, while a broadcast handle alone does not keep it announced. It keeps its instance across `withdraw()` and `announce()`, so a new run is a new route.
- **Routes.** `route(path)` publishes a broadcast and `routePrefix(prefix)` answers requests beneath a prefix, each returning a `MoqRoute`. Hold it: cancelling or disposing it retracts the route, while a broadcast handle alone does not keep it announced. A `route(path)` keeps its instance across `withdraw()` and `announce()`, so a new run is a new route. Unless one is named, a request's own route (`request.route(options: ...)`) takes its prefix route's instance (on moq-lite 07; older wires mint another, restarting readers resolved through the prefix route), and the prefix route moves to a fresh one each time such a route holding its current one ends.
- **Audio needs cuts.** Video groups at its keyframes, but audio forms a group only where you call `cut()`: after every frame, or less often to batch frames into fewer groups.
- **Timeline breaks.** On a seek or pause, call `discontinuity()`, then keep timestamps moving forward and resume video on a keyframe.
- **Media reads.** `media.TrackConsumer`, `audio.Consumer`, and `video.Consumer` return a `Read` in order: a `ReadFrame`, a `ReadGap` where media was lost, or a `ReadEnd` where the publisher stopped its encoder. A gap breaks continuity. An encoder end is not stream EOF: following frames before its endpoint are the old encoder's drain; the first at or past it starts fresh. A decoder reads everything the track still holds unless a live player sets `start: media.DecodeStart.latest` in its decoder output, which joins at the newest group and skips the cached backlog.
Expand Down
Loading
Loading