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
16 changes: 9 additions & 7 deletions doc/bin/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,20 @@ moq --connect https://relay.example.com/anon --broadcast my-stream.hang export t
moq --connect "https://relay.example.com/rooms/1?jwt=$TOKEN" --broadcast alice.hang import ts
```

The `ts`, `fmp4`, and `flv` imports publish on the broadcast clock the catalog
advertises, not the input's own timestamps. The first frame is stamped when it
arrives, every track keeps its offset from the others, and an input that
restarts its timestamps, such as a restarted encoder, continues forward
after the real gap rather than rewinding. So a feed whose PTS starts hours in,
or whose first frame arrives late, still names the right wall time.
The `ts`, `fmp4`, and `flv` imports publish the input's own timestamps, and
the catalog clock maps the first frame to the time it arrived. So a feed whose
PTS starts hours in still names the right wall time, and every track keeps its
offset from the others. A group starting before the previous group's start,
such as a restarted encoder or a looping file wrapping to the top, ends the
import with an error; run it again to publish anew. A keyframe that merely
overlaps the previous group's last frame is not a rewind.

MPEG-TS import carries H.264/H.265 and AAC/MP2/AC-3/E-AC-3, passes SCTE-35 and
subtitle PIDs through as tracks, and round-trips the service tables. A
`discontinuity_indicator` on the program's PCR PID is a system time-base reset,
so it breaks every track's timeline and the exported clock declares the break in
turn. The same flag on an elementary PID other than the program PCR PID, a
turn. A new timeline whose first group starts before the previous group's start
ends the import like any other rewind. The same flag on an elementary PID other than the program PCR PID, a
continuity-counter gap, and the 33-bit timestamp rollover move no clock and
declare nothing. FLV covers H.264 + AAC.

Expand Down
4 changes: 3 additions & 1 deletion doc/bin/hls.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ numbers for different media.
`--listen-tls-cert`/`--listen-tls-key` or `--listen-tls-generate` serve HTTPS,
and `--cors-origin` opens it to browsers.
H.264/H.265 and AAC/Opus renditions are served. Import handles classic HLS;
LL-HLS parts are not implemented yet. The library is
LL-HLS parts are not implemented yet. It publishes the playlist's own media
times, and the catalog clock maps the first imported segment to the time it
arrived, one mapping for every rendition. The library is
[`moq-hls`](https://docs.rs/moq-hls).

## Recording segments
Expand Down
5 changes: 5 additions & 0 deletions doc/bin/rtmp.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ the [`moq-rtmp`](https://docs.rs/moq-rtmp) library, which hands you each
publish or play request to accept, map to a path, or reject. The CLI listener
is unauthenticated; firewall it.

Each push is its own broadcast. Import publishes the encoder's own timestamps
and anchors the catalog clock on the first frame, so it names the wall time the
push arrived. A group starting before the previous group's start, such as an
encoder restarting its timestamps mid-push, ends that push with an error.

A player that advertises enhanced-RTMP multitrack receives every rendition.
Any other player receives one video rendition: the largest picture (then
highest bitrate) in a codec it advertised. A push carries the largest one.
Expand Down
3 changes: 3 additions & 0 deletions doc/bin/srt.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ ffplay srt://localhost:9000
moq --connect https://relay.example.com/anon --broadcast event.hang import srt --connect 'srt://encoder.example.com:9000?streamid=live/cam'
```

Import publishes the feed's own PTS and anchors the catalog clock on its first
frame, as [`import ts`](/bin/cli) does.

`--latency` sets the SRT receive buffer and doubles as the skip threshold on
export. Export paces each SRT payload on the media clock, and re-anchors that
pacing on a declared marker, so a restarted timeline plays out from the
Expand Down
21 changes: 14 additions & 7 deletions doc/lib/rs/moq-mux.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,20 @@ keep it in the payload.
telemetry.append(moq_net::Timed::from(packet).at(received_at))?;
```

The fMP4, MPEG-TS, and FLV importers publish the source's own timestamps unless
built with `live()`, which translates them onto the catalog's broadcast clock:
the first frame is live on arrival, every track of the input shares that one
mapping, and a source that restarts its timestamps continues forward after the
real idle gap. fMP4 passthrough rewrites each fragment's `tfdt` to match. Use
it for a live feed with its own zero; publish verbatim only when the catalog's
clock (`Config::with_clock`) already names the source's zero.
The fMP4, MPEG-TS, FLV, and MKV importers publish the source's own timestamps
(MPEG-TS after unwrapping its 33-bit PTS; fMP4 passthrough keeps each `tfdt`)
and anchor the catalog's broadcast clock instead: the first frame's timestamp
maps to the time it arrived, and every track of the input, like every importer
sharing the catalog, keeps that one mapping. A clock set with
`Config::with_clock` is never re-anchored, for a recording whose zero names its
real start.

Group starts never go backwards. A group starting before the previous group's
start ends the import with `TimestampRewind`, as a restarted encoder or a looping
file wrapping to the top does, flagged MPEG-TS discontinuity or not; republish
it as a new broadcast. Frames may still dip below the previous group's content:
B-frames, and a keyframe overlapping the previous group's last frame. A flagged
MPEG-TS discontinuity that jumps forward continues the broadcast.

```bash
cargo add moq-mux
Expand Down
6 changes: 5 additions & 1 deletion drafts/draft-lcurley-moq-hang.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,10 @@ A consumer MUST NOT submit the marker to a decoder.
Empty groups (zero objects) are permitted and mean nothing.
After a discontinuity the timeline continues forward.
A publisher that stops producing and may resume on the same track (e.g. an encoder idle for lack of demand) SHOULD publish a discontinuity marker when it stops, so the group before the pause does not reach across the gap and read as live.
A group whose timestamps fall below the live edge earlier groups reached is malformed.
The first timestamp of a group MUST NOT be lower than the first timestamp of any group with a lower sequence, and no frame of a group may be lower than the first timestamp of the group before it; a group that breaks either is malformed.
A group MAY start at the same timestamp as the previous group.
Frames MAY present below the previous group's content (B-frames, or a keyframe overlapping the previous group's last frame).
A publisher whose source restarts below the previous group's start begins a new broadcast instead.
A delivered sequence hole is a playhead event unless the boundary is contiguous within 1 ms.
A consumer re-applies startup delay and skip at a playhead event; it does not reset codec state.

Expand Down Expand Up @@ -1104,6 +1107,7 @@ A publisher MAY estimate an unknown final duration from the frame cadence, but M
- A marker group of one empty frame declares a discontinuity. Empty groups mean nothing. Timestamps only move forward; a group below the live edge is malformed. A delivered sequence hole is a playhead event unless contiguous within 1 ms.
- A publisher that stops producing and may resume on the same track SHOULD publish a discontinuity marker when it stops.
- An audio endpoint bounds only the terminal packets that follow it in its own group.
- Replaced the live-edge floor with monotonic group starts: a group may overlap the previous group's content but not start before it.
- Replaced the archive timeline `wall` field with a root `clock` section (`wall` plus `timescale`): one fixed broadcast mapping every track and the archive index convert into, independent of any archive. Zero timescales and walls past the JSON-safe integer range are refused.
- Added optional `bitrate` and `jitter` fields to `json` and `binary` track entries.
- Added the optional `delay` rendition field: how far a rendition's minimum flush lateness trails the broadcast's earliest rendition, never lowered once advertised and never subtracted across renditions.
Expand Down
2 changes: 0 additions & 2 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ transport, benchmark tooling); worktrees isolate commits, not semantics.
- [Full codec string](/quest/m1/publish-codec-string.md) - browser-published video carries the encoder's full RFC 6381 codec string, so native players decode it
- [TS program selection](/quest/m1/ts-programs.md) - `import ts` refuses a multi-program stream unless `--program <n|all>` picks one or publishes each
- [TS export jitter](/quest/m1/ts-export-jitter.md) - the video reorder bound follows later catalogs and the declared reorder depth, so a late B-frame never reorders TS output; an undeclared stream can reorder once per new maximum depth
- [TS import shared shift](/quest/m1/ts-import-shared-shift.md) - unflagged loop wraps move audio and video by one shift, so A/V sync holds across wraps
- [ffmpeg muxdelay](/quest/m1/ts-publish-muxdelay.md) - the documented MPEG-TS publish line adds `-muxdelay 0`, so quiet audio is not clumped
- [Same-hop importers](/quest/m1/hop-aligned-import.md) - importers sharing a `--hop` and fed one stream publish identical groups and timestamps, so failover survives
- [PipeWire duplicate cameras](/quest/m1/pipewire-dup-cameras.md) - a webcam lists once with PipeWire enabled
Expand All @@ -65,7 +64,6 @@ transport, benchmark tooling); worktrees isolate commits, not semantics.
- [Auth client CA](/quest/m1/relay-auth-client-ca.md) - on dev, `auth::Config::validate` and `init` take the client-CA flag, so no caller can skip the check
- [RTMP TLS only](/quest/m1/rtmp-tls-only.md) - an RTMP listener configured for TLS can refuse plaintext instead of sniffing and serving it
- [HLS linger](/quest/m1/hls-linger.md) - `moq_hls::Server` serves an ended broadcast for its playlist window plus grace, so the moq.pro edge drops its own pool
- [Remove live()](/quest/m1/remove-live.md) - on dev, importers publish stream timestamps verbatim, the catalog clock maps them to wall time, and an encoder restart becomes a new epoch
- [iroh versions](/quest/m1/iroh-lite-wip.md) - `iroh://` negotiates the configured versions, so `moq-lite-07-wip` can be opted into
- [Go and Dart doc samples](/quest/m1/doc-samples-go-dart.md) - Go and Dart doc samples compile against their wrappers
- [Data capture in bindings](/quest/m1/data-capture-bindings.md) - moq-ffi and every wrapper pass a data frame's capture time, and the JSON window producer takes one
Expand Down
5 changes: 2 additions & 3 deletions quest/m1/hop-aligned-import.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ encoded stream. Capture is out: two encoders never align.
not a per-process counter. Decide how both processes agree across a
timestamp wrap (TS PTS wraps every 26.5 h) when they started on opposite
sides of it.
- Frame timestamps derive from the input alone too: any re-anchor shift must
come from the stream, not process start or wall clock.
- Frame timestamps derive from the input alone too. Importers publish the
stream's own timestamps and refuse a rewind, so nothing shifts them.
- An importer announces only once it knows its tracks, so it never refuses a
track the incumbent serves.
- Docs (`doc/bin/cli.md` "Redundant publishers", the `--hop` doc comment)
Expand All @@ -41,5 +41,4 @@ survives the standby joining and the incumbent stopping.
## Related

- [Redundant ingest](/quest/m2/redundant-ingest.md) - splicing across first hops and two encoders, which this does not attempt
- [TS import shared shift](/quest/m1/ts-import-shared-shift.md) - the TS re-anchor shift that must stay input-derived
- [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - a redundant pair shares one epoch
19 changes: 14 additions & 5 deletions quest/m1/legacy-end-overshoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

A `@moq/watch` subscriber never aborts a browser-published track with "group
timestamp is below the live edge" when the publisher's next group starts
inside the previous group's estimated end.
inside the previous group's estimated end: the JS consumer enforces
monotonic group starts, like Rust.

## Plan

Expand All @@ -22,12 +23,20 @@ takes the live edge from the end marker, so that keyframe's group reads as
below it and the track aborts. The consumer's covered-group skip tolerates the
same overlap the live-edge check rejects.

Decided (maintainer, #4543): group starts are monotonic, and that is the only
hard rule. A group's keyframe may not start below the previous group's start,
and no frame may sit below the start of the group before its own. Frames,
keyframes included, may dip below the previous group's content (B-frames, or
a keyframe overlapping its last frame), so an estimated end is never a hard
edge. A group may start at the same timestamp as the previous one; strictly
increasing starts are not enforced. Group IDs never move backwards. A group
starting before the previous group's start is a restart, a new broadcast. The Rust `moq-mux` producer and
consumer enforce this since #4543.

- Reproduce with a mocked clock: cut a group, then resume with a keyframe
between the last frame and the estimated end.
- Decide which side is wrong: the producer's estimate reaching past what it
will refuse, or the consumer treating an estimated end as a hard edge.
Fix it there and keep the other side's rule consistent with it.
- Check the Rust `moq-mux` producer for the same estimate.
- Align `js/hang/src/container/consumer.ts` (and the JS producer, if it
differs) to "group starts monotonic", matching `rs/moq-mux/src/container`.

## Related

Expand Down
50 changes: 0 additions & 50 deletions quest/m1/remove-live.md

This file was deleted.

51 changes: 0 additions & 51 deletions quest/m1/ts-import-shared-shift.md

This file was deleted.

4 changes: 0 additions & 4 deletions quest/m1/ts-programs.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,3 @@ clock; `--program all` publishes both broadcasts, each timed correctly. Update
## Closes

- [#4353](https://github.com/moq-dev/moq/issues/4353) - close this issue when the quest finishes

## Related

- [TS import shared shift](/quest/m1/ts-import-shared-shift.md) - its per-program shift assumes the single program this settles
Loading
Loading