diff --git a/doc/lib/js/net.md b/doc/lib/js/net.md index 100d66dd37d..ecbeb240c85 100644 --- a/doc/lib/js/net.md +++ b/doc/lib/js/net.md @@ -41,7 +41,7 @@ broadcast.announce(); - **GOAWAY moves the session.** The connection dials the replacement at once while the old session keeps serving its groups, up to `goaway.handover` (default 10s, or the relay's deadline when that is sooner). New requests keep opening on the old session until the replacement's route outranks it, which is a restart without an epoch: a request on the old session ends when that session closes, so request the path again. An empty GOAWAY redials the same URL. A redirect stays on the same host unless `goaway.redirect` is `"follow"`. - **One send estimate per connection.** `Bandwidth.Allocator` divides it by track priority, max-min fair within a tier. An idle track claims nothing. Publishers reserve against it so their targets sum to the estimate. - **One track per name.** Concurrent subscriptions share one request and producer. An on-demand producer that replaces an ended one continues the name's group and datagram sequences; only a new broadcast restarts them. `createTrack` and `insertTrack` start at 0. -- **Timing is per track, with no default.** A track that declares a `timescale` carries a `timestamp` on every frame; one without is [untimed](/concept/moq-lite#subscriptions) and its frames carry none. Unlike Rust, omitting it means untimed. Nothing stamps a frame for you: the publisher passes the timestamp, and the `Timed` type (`{ value, at }`) carries one into the `@moq/json` and `@moq/flate` producers. +- **Timing is per track, with no default.** A track that declares a `timescale` carries a `timestamp` on every frame; one without is [untimed](/concept/moq-lite#subscriptions) and its frames carry none. Omitting it means untimed, as in Rust. Nothing stamps a frame for you: the publisher passes the timestamp, and the `Timed` type (`{ value, at }`) carries one into the `@moq/json` and `@moq/flate` producers. - **Subscriber staleness is `maxDelay`.** It is media time. Passing the old `maxAge` key throws a `TypeError` naming `maxDelay`. Publisher retention is the separate `Track.Info.maxAge`. - **Hidden paths** stay out of discovery unless the announce request opts in. See [hidden broadcasts](/concept/moq-lite#hidden-broadcasts). - **Authorization is in band.** On moq-lite 07 (offered only when `moq-lite-07-wip` is listed in `webtransport.protocols`; the WebSocket fallback cannot offer it) and on moq-transport draft-17+ with the [MoQ Auth extension](/draft/moq-auth), `auth.grant` watches what the relay lets this side publish and subscribe to, and `auth.add(token)` presents another token without reconnecting. Publishing outside the grant closes the connection with `SessionCode.Unauthorized`, naming the path. On moq-lite, a subscription the grant stops covering resets with `StreamCode.Unauthorized` and the session stays up. diff --git a/doc/setup/upgrade.md b/doc/setup/upgrade.md index 231454d9d82..7e0ecf65f8f 100644 --- a/doc/setup/upgrade.md +++ b/doc/setup/upgrade.md @@ -279,6 +279,11 @@ These land with the next breaking release, not the 2026-09-23 train. `Option`. The `write_frame`, `append_datagram`, and `insert_datagram` calls take `impl Into>`, so passing a `Timestamp` still compiles. + An undeclared timescale is untimed: `track::Info::default()`, + `create_track(name, None)`, and `accept(None)` no longer default to + milliseconds, and `Timescale` has no `Default`. A track you write + timestamps to declares one, such as + `Info::default().with_timescale(Timescale::MILLI)`. - moq-e2ee: `Frame` and `Datagram` timestamps are optional, and its writers take `impl Into>`. - moq-archive: recording an untimed track fails with `Error::Untimed`, so diff --git a/js/net/src/lite/track.test.ts b/js/net/src/lite/track.test.ts index c1dd73f3ae7..58b7cc12c2a 100644 --- a/js/net/src/lite/track.test.ts +++ b/js/net/src/lite/track.test.ts @@ -47,7 +47,7 @@ test("TrackInfo round-trips on draft-05", async () => { }); test("TrackInfo for Rust's default Info matches cross-language wire bytes", async () => { - // Rust's `Info::default()` is millisecond-timed; JS has no default timescale. + // Neither language has a default timescale; an untimed track declares milliseconds on lite-05. const model = infoDefaults({ timescale: Timescale.MILLI }); const info = new TrackInfo({ ...model, timescale: model.timescale ?? undefined }); expect(await bytes((w) => info.encode(w, Version.DRAFT_05), Version.DRAFT_05)).toEqual( diff --git a/quest/m1/README.md b/quest/m1/README.md index 5f18aba7535..16ee1c6e993 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -50,7 +50,6 @@ session outcomes, and embedder refusals inside session outcomes. - [FETCH max-delay](/quest/m1/fetch-max-delay.md) - historical readers obey publisher max-age and an optional tighter reader budget, including while holding a group - [Upstream position regression](/quest/m1/largest-regression.md) - a relay copy that sees upstream's largest group go backwards on moq-transport or epochless lite-07 fails loud instead of serving the old instance's cache - [Untimed decisions](/quest/m1/untimed-decisions.md) - the maintainer decides whether moq-archive keeps refusing untimed tracks, and whether a malformed FETCH object ends its track -- [Untimed by default in Rust](/quest/m1/rust-untimed-default.md) - an undeclared Rust timescale means untimed, and shared-clock publishers declare milliseconds - [FFI shape](/quest/m1/ffi-shape/README.md) - the bindings mirror Rust's layers: net at the root, then media, json, flate, audio, and video namespaces built from the handle below - [Publishing never invents a timestamp](/quest/m1/publish-timestamp.md) - no Rust or binding publish API fills in a timestamp; an untimed payload goes out untimed - [moq-net's async feature](/quest/m1/net-async-feature.md) - async helpers and both sessions sit behind a default `async` feature, and CI builds moq-net without it; Controlled time's net migration requires it diff --git a/quest/m1/lite-untimed.md b/quest/m1/lite-untimed.md index e9a021ece14..7d2c7f94d49 100644 --- a/quest/m1/lite-untimed.md +++ b/quest/m1/lite-untimed.md @@ -28,8 +28,9 @@ Decided (2026-10-02): one PR for both languages. Both model quests have landed (#4822). Shipping one language first would break Rust-JS interop on lite-07-wip in between. -Decided 2026-10-08: [Rust untimed default](/quest/m1/rust-untimed-default.md) -lands first, since both edit the same Timescale defaults. +Rust has no `Timescale` default: the lite encoder and the datagram decoder +name `Timescale::MILLI` for an untimed track's send times, which this quest +replaces on lite-07. Draft work: @@ -46,7 +47,3 @@ JS-to-Rust on lite-07. lite-06 still receives a timestamp. Run `just drafts check` and `just test interop --all`. Public API: none. Wire: lite-07-wip only, which is unpublished. - -## Required - -- [Rust untimed default](/quest/m1/rust-untimed-default.md) - touches the same Timescale defaults in `rs/moq-net` and lands first; this rebases onto it diff --git a/quest/m1/publish-timestamp.md b/quest/m1/publish-timestamp.md index fa4277e9a5d..aec7b48dc42 100644 --- a/quest/m1/publish-timestamp.md +++ b/quest/m1/publish-timestamp.md @@ -65,10 +65,6 @@ Swift wrappers default them to 0. Public API: breaking. Wire: none here; absence on the wire lands with the untimed implementation quests. -## Required - -- [An undeclared Rust timescale means untimed](/quest/m1/rust-untimed-default.md) - each track declares its timescale first, so a publisher that stops stamping writes onto a track that already says whether it is timed - ## Related - [FFI shape](/quest/m1/ffi-shape/README.md) - moves the data producers this changes into `json` and `flate` namespaces in the same merge, so this is ready once #4519 lands rather than waiting on the codec child (2026-10-06 audit) diff --git a/quest/m1/rust-untimed-default.md b/quest/m1/rust-untimed-default.md deleted file mode 100644 index 5954f874780..00000000000 --- a/quest/m1/rust-untimed-default.md +++ /dev/null @@ -1,47 +0,0 @@ -# [M] An undeclared Rust timescale means untimed - -## Goal - -A Rust track that declares no timescale is untimed, as in `@moq/net`: -`track::Info::default()` and `track::Request::accept(None)` leave -`timescale` as `None`. Every publisher that stamps from the shared broadcast -clock declares milliseconds explicitly, so data tracks and media share one -timeline. - -## Plan - -Decided 2026-10-07 (with #4968, where `@moq/net` treats an omitted or `null` -timescale as untimed and `@moq/publish` declares milliseconds): - -- Remove the implicit millisecond default: `impl Default for Timescale` - (`rs/moq-net/src/model/time.rs`) and its use in `track::Info::default()` - and `accept(None)` (`rs/moq-net/src/model/track.rs`). -- Declare milliseconds in every Rust track that relies on the default today, - in the same PR, or its writers' stamped frames are refused on a track that - turned untimed. Known: `hang` catalog (`default_track_info`), moq-mux MSF - and timeline tracks, moq-boy status, moq-c `data_track`, moq-ffi json and - flate tracks, moq-room chat (`chat::info`), and moq-stats' JSON tracks - (`create_track(name, None)`). Find the rest by `Info::default()`, - `create_track(_, None)`, and `accept(None)`. moq-ffi and moq-c media tracks - already pin microseconds. The shared timeline itself is the catalog clock - every importer shifts onto (`catalog::Timebase`). -- This quest only declares the timescale. Who stamps a frame, and with which - clock, is [Publishing never invents a timestamp](/quest/m1/publish-timestamp.md)'s - (decided 2026-10-08); writers keep stamping as they do today until it - lands. -- The untimed model (#4822) keeps `Default` as the timescale a lite encoder - declares for an untimed track's send times, and the lite datagram decoder - falls back with `entry.timescale.unwrap_or_default()` - (`rs/moq-net/src/lite/subscriber.rs`); both name `Timescale::MILLI` - explicitly instead, as does moq-audio's decode quantum floor - (`rs/moq-audio/src/decode/consumer.rs`). -- Update the bindings and `doc/lib` pages that describe the default. - -Public API: breaking (`Timescale` loses `Default`; an undeclared track is -untimed). Wire: none. - -## Related - -- [Publishing never invents a timestamp](/quest/m1/publish-timestamp.md) - requires this: the same publishers stop filling in a timestamp once each track declares its timescale -- [FFI shape](/quest/m1/ffi-shape/README.md) - #4519 moves the moq-ffi json and flate tracks this edits; whichever lands second rebases -- [lite-07 untimed](/quest/m1/lite-untimed.md) - requires this: the same `rs/moq-net` lines diff --git a/quest/m3/teleop-mavlink.md b/quest/m3/teleop-mavlink.md index bcf46bcffb0..1ba75a4346f 100644 --- a/quest/m3/teleop-mavlink.md +++ b/quest/m3/teleop-mavlink.md @@ -30,8 +30,7 @@ naming imply one leg. Read the header only: the 24-bit msgid in v2, 8-bit in v1. Payloads stay opaque bytes and route to a delivery class through a config table, with an unknown msgid falling to a default. Every track declares `Timescale::MILLI` -explicitly, since an undeclared Rust timescale means untimed -([Rust untimed default](/quest/m1/rust-untimed-default.md)). +explicitly, since an undeclared Rust timescale means untimed. This is what makes frame-aware routing free of the dialect tax. CRC_EXTRA (the per-message schema hash) is only needed to validate a frame, never to read its @@ -90,7 +89,3 @@ small, real contribution and its own future quest. - [Robot teleoperation primitive](/quest/m3/teleop-robot.md) - [Operator arbitration](/quest/m3/teleop-arbitration.md) - -## Related - -- [Rust untimed default](/quest/m1/rust-untimed-default.md) - tracks that leave out their timescale are untimed diff --git a/quest/m3/teleop-robot.md b/quest/m3/teleop-robot.md index ca809f45971..3747f731b5f 100644 --- a/quest/m3/teleop-robot.md +++ b/quest/m3/teleop-robot.md @@ -97,9 +97,8 @@ documented in `doc/concept/hang.md` (#4461). Media and command tracks keep their own timescales; convert PTS explicitly into the broadcast clock before joining samples. The robot's video and telemetry share a clock; the operator's command broadcast supplies its own mapping. Command and telemetry tracks -declare `Timescale::MILLI` explicitly: once an undeclared Rust timescale means -untimed ([Rust untimed default](/quest/m1/rust-untimed-default.md)), a track -that leaves it out carries no timestamps to join. +declare `Timescale::MILLI` explicitly: an undeclared Rust timescale means +untimed, so a track that leaves it out carries no timestamps to join. The clock assumption: the two hosts' wall clocks are synchronized by the deployment, not by the library. State this beside the API, since a join across @@ -117,4 +116,3 @@ control then it is the wrong abstraction. ## Related - [arbitration](/quest/m3/teleop-arbitration.md) - which controller is obeyed -- [Rust untimed default](/quest/m1/rust-untimed-default.md) - why command and telemetry tracks declare their timescale diff --git a/rs/hang/src/catalog/root.rs b/rs/hang/src/catalog/root.rs index 2602cc365f9..3489106df68 100644 --- a/rs/hang/src/catalog/root.rs +++ b/rs/hang/src/catalog/root.rs @@ -126,8 +126,11 @@ impl Catalog<()> { /// /// Keeps the bare `moq_net` retention rather than the media one: the catalog is /// snapshot mode, so the useful value is the live edge, which is always kept. + /// Timed in milliseconds, so snapshots share the broadcast clock with media. pub fn default_track_info() -> moq_net::track::Info { - moq_net::track::Info::default().with_priority(PRIORITY.catalog) + moq_net::track::Info::default() + .with_timescale(moq_net::Timescale::MILLI) + .with_priority(PRIORITY.catalog) } /// The subscription preferences used for the catalog track (high priority so diff --git a/rs/hang/src/container/frame.rs b/rs/hang/src/container/frame.rs index e6907513ecf..e03900bf001 100644 --- a/rs/hang/src/container/frame.rs +++ b/rs/hang/src/container/frame.rs @@ -16,9 +16,10 @@ pub const TIMESCALE: Timescale = Timescale::MICRO; /// [`create_track`](moq_net::broadcast::Producer::create_track) or /// [`accept`](moq_net::track::Request::accept). /// -/// Pins the track's timescale to [`TIMESCALE`]. `moq_net::track::Info::default()` is milliseconds, -/// which would quantize the net-level frame timestamps that moq-lite-05 and later delta-encode on -/// the wire, even though the container prefix stays at microseconds. Chain +/// Pins the track's timescale to [`TIMESCALE`], since `moq_net::track::Info::default()` is +/// untimed and refuses timestamped frames. Microseconds match the container prefix, so the +/// net-level frame timestamps that moq-lite-05 and later delta-encode on the wire aren't +/// quantized. Chain /// [`with_timescale`](moq_net::track::Info::with_timescale) for a container that carries the /// source's own scale instead (CMAF and Matroska both do). /// diff --git a/rs/moq-audio/src/decode/consumer.rs b/rs/moq-audio/src/decode/consumer.rs index 8875b579724..9b9cd6c49dc 100644 --- a/rs/moq-audio/src/decode/consumer.rs +++ b/rs/moq-audio/src/decode/consumer.rs @@ -541,7 +541,7 @@ fn now() -> Instant { /// So a packet is discontinuous when it misses `expected` by more than one unit of /// the coarsest timescale on the path, plus one unit of the stamp's own scale for /// the rounding in the arithmetic that produced `expected`. The coarsest timescale -/// is the stamp's own scale floored at [`Timescale::default`](moq_net::Timescale): +/// is the stamp's own scale floored at [`Timescale::MILLI`](moq_net::Timescale::MILLI): /// the legacy hang container re-stamps every frame in microseconds whatever the /// source used, and a wire that cannot carry a timescale at all (moq-lite before /// 05, IETF moq-transport) falls back to milliseconds, so a millisecond is the @@ -550,7 +550,7 @@ fn now() -> Instant { /// swallows a lost one. fn discontinuous(expected: moq_net::Timestamp, timestamp: moq_net::Timestamp) -> bool { let scale = expected.scale().max(timestamp.scale()); - let quantum = scale.min(moq_net::Timescale::default()); + let quantum = scale.min(moq_net::Timescale::MILLI); let tolerance = (scale.as_u64() as u128).div_ceil(quantum.as_u64() as u128) + 1; expected.as_scale(scale).abs_diff(timestamp.as_scale(scale)) > tolerance } diff --git a/rs/moq-bench/src/connection.rs b/rs/moq-bench/src/connection.rs index ca5cfa80e3b..b41b54c7d78 100644 --- a/rs/moq-bench/src/connection.rs +++ b/rs/moq-bench/src/connection.rs @@ -131,7 +131,10 @@ pub async fn run(ctx: Connection) { continue; } }; - let track = match broadcast.create_track(TRACK, None) { + let track = match broadcast.create_track( + TRACK, + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + ) { Ok(track) => track, Err(err) => { tracing::error!(connection, %err, "failed to create track"); @@ -604,6 +607,11 @@ impl Drop for Gauge<'_> { mod tests { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + async fn wait_for(counter: &AtomicU64, value: u64) { while counter.load(Ordering::Relaxed) < value { tokio::task::yield_now().await; @@ -632,7 +640,7 @@ mod tests { let stats = Arc::new(Stats::default()); let broadcast = broadcast::Info::new().produce(); - let track = broadcast.create_track(TRACK, None).unwrap(); + let track = broadcast.create_track(TRACK, timed()).unwrap(); let consumer = broadcast.consume(); // 10fps (100ms/frame), 8-byte frames, 2 payload frames per group. @@ -676,7 +684,7 @@ mod tests { let stats = Arc::new(Stats::default()); let broadcast = broadcast::Info::new().produce(); - let track = broadcast.create_track(TRACK, None).unwrap(); + let track = broadcast.create_track(TRACK, timed()).unwrap(); let consumer = broadcast.consume(); let task = tokio::spawn(produce(0, "bench/test".into(), rolled(10, 4, 0), track, stats.clone())); @@ -707,7 +715,7 @@ mod tests { let stats = Arc::new(Stats::default()); let broadcast = broadcast::Info::new().produce(); - let track = broadcast.create_track(TRACK, None).unwrap(); + let track = broadcast.create_track(TRACK, timed()).unwrap(); let consumer = broadcast.consume(); // 10fps, 300-byte messages, lone-keyframe groups (the chat shape). @@ -740,7 +748,7 @@ mod tests { let stats = Arc::new(Stats::default()); let broadcast = broadcast::Info::new().produce(); - let track = broadcast.create_track(TRACK, None).unwrap(); + let track = broadcast.create_track(TRACK, timed()).unwrap(); let consumer = broadcast.consume(); // 50 bytes is well under the serialized header (roughly 170 bytes). @@ -785,7 +793,7 @@ mod tests { // One legitimate peer under the bench namespace with a single finished group. let peer = origin.create_broadcast("bench/current/0/0").unwrap(); peer.announce(Default::default()).unwrap(); - let track = peer.create_track(TRACK, None).unwrap(); + let track = peer.create_track(TRACK, timed()).unwrap(); let mut group = track.append_group().unwrap(); group .write_frame(moq_net::Timestamp::now(), Bytes::from_static(b"{}")) @@ -817,7 +825,7 @@ mod tests { let broadcast = origin.create_broadcast("bench/run/chat").unwrap(); broadcast.announce(Default::default()).unwrap(); - let track = broadcast.create_track(TRACK, None).unwrap(); + let track = broadcast.create_track(TRACK, timed()).unwrap(); tokio::task::yield_now().await; let mut group = track.append_group().unwrap(); let header = serde_json::json!({ @@ -854,7 +862,7 @@ mod tests { let stats = Arc::new(Stats::default()); let broadcast = broadcast::Info::new().produce(); - let mut track = broadcast.create_track(TRACK, None).unwrap(); + let mut track = broadcast.create_track(TRACK, timed()).unwrap(); let consumer = broadcast.consume(); // Group 0 lands intact. @@ -912,7 +920,7 @@ mod tests { let stats = Arc::new(Stats::default()); let broadcast = broadcast::Info::new().produce(); - let mut track = broadcast.create_track(TRACK, None).unwrap(); + let mut track = broadcast.create_track(TRACK, timed()).unwrap(); let consumer = broadcast.consume(); let task = { diff --git a/rs/moq-boy/src/input.rs b/rs/moq-boy/src/input.rs index a1dceed183f..c331e0914d3 100644 --- a/rs/moq-boy/src/input.rs +++ b/rs/moq-boy/src/input.rs @@ -185,7 +185,12 @@ mod tests { /// Publish a viewer with a live command track. fn viewer(origin: &moq_net::origin::Producer) -> (moq_net::broadcast::Producer, moq_net::track::Producer) { let broadcast = origin.create_broadcast("alice").unwrap(); - let track = broadcast.create_track("command", None).unwrap(); + let track = broadcast + .create_track( + "command", + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + ) + .unwrap(); broadcast.announce(moq_net::origin::Route::default()).unwrap(); (broadcast, track) } diff --git a/rs/moq-boy/src/status.rs b/rs/moq-boy/src/status.rs index 4bb73576323..7ae3e28d4dd 100644 --- a/rs/moq-boy/src/status.rs +++ b/rs/moq-boy/src/status.rs @@ -40,7 +40,10 @@ pub struct StatusPublisher { impl StatusPublisher { pub fn new(broadcast: &mut moq_net::broadcast::Producer) -> anyhow::Result { - let producer = broadcast.create_track("status", None)?; + let producer = broadcast.create_track( + "status", + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + )?; Ok(Self { producer: moq_json::snapshot::Producer::new(producer, moq_json::snapshot::Config::default()), diff --git a/rs/moq-c/src/publish.rs b/rs/moq-c/src/publish.rs index 9c8b5832059..6726686d1a1 100644 --- a/rs/moq-c/src/publish.rs +++ b/rs/moq-c/src/publish.rs @@ -770,7 +770,10 @@ impl Publish { publish: impl FnOnce(&moq_mux::catalog::Producer, moq_net::track::Producer) -> moq_mux::Result, ) -> Result { let broadcast = self.broadcasts.get_mut(broadcast).ok_or(Error::BroadcastNotFound)?; - let track = broadcast.producer.create_track(name, None)?; + let track = broadcast.producer.create_track( + name, + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + )?; Ok(publish(&broadcast.catalog, track)?) } diff --git a/rs/moq-c/src/test.rs b/rs/moq-c/src/test.rs index 032e59845c0..0cd26c5372a 100644 --- a/rs/moq-c/src/test.rs +++ b/rs/moq-c/src/test.rs @@ -1626,7 +1626,6 @@ fn raw_track_datagram_publish_consume() { ) }); - // Millisecond-aligned so the value survives the default (millisecond) timescale exactly. let payload = b"hello datagram"; let mut sequence: u64 = u64::MAX; assert_eq!( diff --git a/rs/moq-cli/src/fetch.rs b/rs/moq-cli/src/fetch.rs index bddf5b1f6c2..3b0c15b7632 100644 --- a/rs/moq-cli/src/fetch.rs +++ b/rs/moq-cli/src/fetch.rs @@ -158,6 +158,11 @@ mod tests { use crate::args::{Command, Invocation}; use crate::test_env::EnvGuard; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// The frames of each finished group on the `data` track. fn frames(sequence: u64) -> [Vec; 2] { [ @@ -182,7 +187,7 @@ mod tests { let broadcast = origin.create_broadcast("demo").expect("broadcast"); broadcast.announce(Default::default()).expect("announce"); - let data = broadcast.create_track("data", None).expect("data track"); + let data = broadcast.create_track("data", timed()).expect("data track"); for sequence in 0..3 { let mut group = data.append_group().expect("group"); for frame in frames(sequence) { @@ -190,8 +195,8 @@ mod tests { } group.finish().expect("finish"); } - let empty = broadcast.create_track("empty", None).expect("empty track"); - let live = broadcast.create_track("live", None).expect("live track"); + let empty = broadcast.create_track("empty", timed()).expect("empty track"); + let live = broadcast.create_track("live", timed()).expect("live track"); let mut open = live.append_group().expect("open group"); open.write_frame(moq_net::Timestamp::ZERO, b"first".as_ref()) .expect("frame"); @@ -245,7 +250,7 @@ mod tests { .create_broadcast(".hidden") .expect("hidden broadcast"); hidden.announce(Default::default()).expect("announce hidden"); - let secret = hidden.create_track("data", None).expect("hidden track"); + let secret = hidden.create_track("data", timed()).expect("hidden track"); let mut group = secret.append_group().expect("group"); group .write_frame(moq_net::Timestamp::ZERO, b"secret".as_ref()) diff --git a/rs/moq-e2ee/benches/protect.rs b/rs/moq-e2ee/benches/protect.rs index 0211c9c20bb..24872a8b047 100644 --- a/rs/moq-e2ee/benches/protect.rs +++ b/rs/moq-e2ee/benches/protect.rs @@ -24,7 +24,9 @@ fn produce(credential: &Credential, semantic: &str) -> moq_e2ee::track::Producer let generation = credential.produce(); let name = generation.name(semantic).unwrap(); // Advancing timestamps plus a zero max age keep only the newest group cached. - let info = moq_net::track::Info::default().with_max_age(Duration::ZERO); + let info = moq_net::track::Info::default() + .with_timescale(moq_net::Timescale::MILLI) + .with_max_age(Duration::ZERO); let net = moq_net::broadcast::Info::new() .produce() .create_track(name.as_str(), info) diff --git a/rs/moq-e2ee/src/tests.rs b/rs/moq-e2ee/src/tests.rs index 3b5fcc99fd9..e1df1322d59 100644 --- a/rs/moq-e2ee/src/tests.rs +++ b/rs/moq-e2ee/src/tests.rs @@ -22,6 +22,11 @@ use crate::protect::{nonce, open, protect}; use crate::{group, track}; use moq_net::Epoch; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const VECTORS: &str = include_str!("../../../drafts/moq-e2ee-00.json"); fn vectors() -> Value { @@ -290,7 +295,7 @@ fn subscribe_all() -> moq_net::track::Subscription { fn net_track(name: &Name) -> moq_net::track::Producer { moq_net::broadcast::Info::new() .produce() - .create_track(name.as_str(), None) + .create_track(name.as_str(), timed()) .unwrap() } @@ -457,7 +462,7 @@ fn wrong_track_name_is_identity() { let generation = test_generation(); let net = moq_net::broadcast::Info::new() .produce() - .create_track("not-a-physical-name", None) + .create_track("not-a-physical-name", timed()) .unwrap(); assert_eq!( generation.produce(net.clone()).map(|_| ()).unwrap_err().to_string(), diff --git a/rs/moq-e2ee/tests/transport.rs b/rs/moq-e2ee/tests/transport.rs index 798fb834df1..462aae56f37 100644 --- a/rs/moq-e2ee/tests/transport.rs +++ b/rs/moq-e2ee/tests/transport.rs @@ -9,6 +9,11 @@ use moq_e2ee::credential::Config; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TEST_TIMEOUT: Duration = Duration::from_secs(10); fn produce_origin(hop: u64) -> moq_net::origin::Producer { @@ -39,7 +44,7 @@ async fn connect_protected(version: Version, track: &str) -> Fixture { let consumer_origin = produce_origin(2); let broadcast = publisher.create_broadcast(&path).unwrap(); - let net = broadcast.create_track(name.as_str(), None).unwrap(); + let net = broadcast.create_track(name.as_str(), timed()).unwrap(); broadcast .announce(moq_net::origin::Route::default().with_epoch(generation.epoch().clone())) .unwrap(); diff --git a/rs/moq-ffi/src/test.rs b/rs/moq-ffi/src/test.rs index a1f2cb099c1..62a0fc3da4e 100644 --- a/rs/moq-ffi/src/test.rs +++ b/rs/moq-ffi/src/test.rs @@ -19,6 +19,11 @@ use std::future::Future; use std::sync::Arc; use std::time::Duration; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); /// Wait until the FFI runtime has polled work spawned ahead of this call. @@ -1221,7 +1226,7 @@ async fn fetches_cached_group_without_subscribing() { #[tokio::test] async fn fetches_cached_media_group_and_decodes_container() { let broadcast = moq_net::broadcast::Info::new().produce(); - let track = broadcast.create_track("media", None).unwrap(); + let track = broadcast.create_track("media", timed()).unwrap(); let consumer = MoqBroadcastConsumer::new(broadcast.consume()); let mut media = moq_mux::container::Producer::new( track, @@ -1273,7 +1278,7 @@ async fn fetches_cached_media_group_and_decodes_container() { #[tokio::test] async fn fetch_media_group_rejects_invalid_container_before_fetching() { let broadcast = moq_net::broadcast::Info::new().produce(); - let _track = broadcast.create_track("media", None).unwrap(); + let _track = broadcast.create_track("media", timed()).unwrap(); let consumer = MoqBroadcastConsumer::new(broadcast.consume()); let result = MoqMediaContainerGroupConsumer::fetch( @@ -2016,7 +2021,7 @@ async fn announced_hides_dot_paths_unless_asked() { #[tokio::test] async fn resolve_rejects_a_reference_without_an_origin() { let broadcast = moq_net::broadcast::Info::new().produce(); - let _audio = broadcast.create_track("audio", None).unwrap(); + let _audio = broadcast.create_track("audio", timed()).unwrap(); let consumer = MoqBroadcastConsumer::new(broadcast.consume()); // An absent or empty reference still names this broadcast, so it needs no origin. A reference diff --git a/rs/moq-flate/src/snapshot/mod.rs b/rs/moq-flate/src/snapshot/mod.rs index 236331aeb95..fc0c36d08e0 100644 --- a/rs/moq-flate/src/snapshot/mod.rs +++ b/rs/moq-flate/src/snapshot/mod.rs @@ -42,6 +42,11 @@ mod test { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + fn cfg(compression: bool) -> Config { Config { compression: if compression { @@ -55,7 +60,7 @@ mod test { fn producer(compression: bool) -> (Producer, moq_net::track::Subscriber) { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let consumer = track.subscribe(None); (Producer::new(track, cfg(compression)), consumer) @@ -83,7 +88,7 @@ mod test { fn a_lost_group_waits_for_its_replacement() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let mut consumer = consume(track.subscribe(None), false); let waiter = kio::Waiter::noop(); diff --git a/rs/moq-flate/src/stream/mod.rs b/rs/moq-flate/src/stream/mod.rs index 4656e2bb976..fc29cdfbe4b 100644 --- a/rs/moq-flate/src/stream/mod.rs +++ b/rs/moq-flate/src/stream/mod.rs @@ -59,6 +59,11 @@ mod test { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + fn cfg(compression: bool) -> Config { Config { compression: if compression { @@ -72,7 +77,7 @@ mod test { fn producer(compression: bool) -> (Producer, moq_net::track::Subscriber) { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let consumer = track.subscribe(None); (Producer::new(track, cfg(compression)), consumer) @@ -200,7 +205,7 @@ mod test { /// write failure (the real one is a frame over moq-net's 32 MB per-group cache) without /// allocating 32 MB to provoke it. Borrowed from moq-json's stream tests. fn rejecting_track() -> moq_net::track::Producer { - let mut info = moq_net::track::Info::default(); + let mut info = timed(); info.timescale = Some(moq_net::Timescale::new((1u64 << 62) - 1).unwrap()); moq_net::broadcast::Info::new() @@ -375,7 +380,7 @@ mod test { fn a_second_group_is_a_rolled_log() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let subscriber = track.subscribe(replaying()); @@ -413,7 +418,7 @@ mod test { fn a_second_group_is_reported_while_the_first_is_open() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let subscriber = track.subscribe(replaying()); @@ -450,7 +455,7 @@ mod test { fn a_late_lower_group_is_still_reported() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let subscriber = track.subscribe(replaying()); diff --git a/rs/moq-hls/src/export/mod.rs b/rs/moq-hls/src/export/mod.rs index 467ff30fc5d..20da20ce270 100644 --- a/rs/moq-hls/src/export/mod.rs +++ b/rs/moq-hls/src/export/mod.rs @@ -505,6 +505,11 @@ async fn watch( #[cfg(test)] mod tests { + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// Build an origin producer, spawning its driver on the ambient runtime. fn produce_origin() -> moq_net::origin::Producer { let (producer, driver) = moq_net::origin::Producer::new(moq_net::origin::Config::default()); @@ -971,7 +976,7 @@ mod tests { registration.set(video_config()).unwrap(); drop(reserved); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1041,7 +1046,7 @@ mod tests { let mut registration = catalog.test_video("video0").unwrap(); registration.set(video_config()).unwrap(); drop(reserved); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1237,7 +1242,7 @@ mod tests { config.coded_height = None; registration.set(config).unwrap(); drop(reserved); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1291,7 +1296,7 @@ mod tests { drop(reserved); // Three GOPs, 2s apart: groups 0 and 1 are complete, group 2 is the live edge. - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1372,7 +1377,7 @@ mod tests { registration.set(video_config()).unwrap(); drop(reserved); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1419,14 +1424,14 @@ mod tests { audio_registration.set(audio_config).unwrap(); drop(reserved); - let video_track = broadcast.create_track("video0", None).unwrap(); + let video_track = broadcast.create_track("video0", timed()).unwrap(); let mut video = catalog .media_producer( video_track, moq_mux::catalog::hang::Container::Legacy(moq_mux::container::Kind::Data), ) .unwrap(); - let audio_track = broadcast.create_track("audio0", None).unwrap(); + let audio_track = broadcast.create_track("audio0", timed()).unwrap(); let mut audio = catalog .media_producer( audio_track, @@ -1516,7 +1521,7 @@ mod tests { registration.set(video_config()).unwrap(); drop(reserved); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1572,7 +1577,7 @@ mod tests { drop(reserved); assert_eq!(catalog.snapshot().clock, None); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1674,7 +1679,7 @@ mod tests { registration.set(video_config()).unwrap(); drop(reserved); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1747,7 +1752,7 @@ mod tests { ("video1", &[300, 2_300, 4_300, 6_300, 8_300]), ("video2", &[4_000, 6_000, 8_000]), ] { - let track = broadcast.create_track(name, None).unwrap(); + let track = broadcast.create_track(name, timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1831,7 +1836,7 @@ mod tests { .unwrap(); drop(reserved); - let track = broadcast.create_track("audio0", None).unwrap(); + let track = broadcast.create_track("audio0", timed()).unwrap(); let mut audio = catalog .media_producer( track, @@ -1881,7 +1886,7 @@ mod tests { drop(reserved); // 3s GOPs against the default 1s minimum: every segment is one whole GOP. - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -1921,14 +1926,14 @@ mod tests { audio_registration.set(audio_config).unwrap(); drop(reserved); - let video_track = broadcast.create_track("video0", None).unwrap(); + let video_track = broadcast.create_track("video0", timed()).unwrap(); let mut video = catalog .media_producer( video_track, moq_mux::catalog::hang::Container::Legacy(moq_mux::container::Kind::Data), ) .unwrap(); - let audio_track = broadcast.create_track("audio0", None).unwrap(); + let audio_track = broadcast.create_track("audio0", timed()).unwrap(); let mut audio = catalog .media_producer( audio_track, @@ -2014,7 +2019,7 @@ mod tests { // Two GOPs: group 0 is complete, while group 1 stays at the live edge until the // publisher finishes. - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -2111,7 +2116,7 @@ mod tests { registration.set(config.clone()).unwrap(); drop(reserved); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -2200,7 +2205,7 @@ mod tests { ) -> Box { let broadcast = origin.create_broadcast("media").expect("publish allowed"); broadcast.announce(Default::default()).expect("publish allowed"); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = moq_mux::container::Producer::new( track, @@ -2304,7 +2309,7 @@ mod tests { recorder: moq_mux::timeline::Recorder, start_micros: u64, ) -> moq_mux::container::Producer { - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = moq_mux::container::Producer::new( track, moq_mux::catalog::hang::Container::Legacy(moq_mux::container::Kind::Data), @@ -2711,7 +2716,7 @@ mod tests { registration.set(config).unwrap(); drop(reserved); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -2770,7 +2775,7 @@ mod tests { drop(reserved); // Groups 0 and 1 are complete; group 2 is the live edge until the publisher drops. - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -3079,7 +3084,7 @@ mod tests { video0.set(video_config()).unwrap(); drop(reserved); - let track = broadcast.create_track("video0", None).unwrap(); + let track = broadcast.create_track("video0", timed()).unwrap(); let mut media = catalog .media_producer( track, @@ -3129,7 +3134,7 @@ mod tests { // Enroll the earlier timeline before the rendition, so the catalog snapshot that adds // the rendition already indexes it. The reference decision runs in that same update. - let earlier = broadcast.create_track("a", None).unwrap(); + let earlier = broadcast.create_track("a", timed()).unwrap(); let mut earlier_media = catalog .media_producer( earlier, diff --git a/rs/moq-hls/src/server/routes.rs b/rs/moq-hls/src/server/routes.rs index d12c56d6168..3396b01be1d 100644 --- a/rs/moq-hls/src/server/routes.rs +++ b/rs/moq-hls/src/server/routes.rs @@ -378,6 +378,11 @@ fn server_error(err: crate::Error) -> Response { mod tests { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + #[test] fn parses_multisegment_broadcast_and_encoded_rendition() { assert_eq!( @@ -662,7 +667,7 @@ mod tests { let pair = lite_pair().await; let mut broadcast = pair.pub_origin.create_broadcast("live").expect("publish"); broadcast.announce(Default::default()).expect("announce"); - let (_catalog, _registration, _track, mut media) = publish_video(&mut broadcast, video_config(), None); + let (_catalog, _registration, _track, mut media) = publish_video(&mut broadcast, video_config(), timed()); write_three_gops(&mut media); let server = Server::new(pair.sub_origin.consume(), crate::export::Config::default()); @@ -705,7 +710,7 @@ mod tests { let pair = lite_pair_on(version, moq_net::Hop::random()).await; let mut broadcast = pair.pub_origin.create_broadcast("live").expect("publish"); broadcast.announce(Default::default()).expect("announce"); - let (_catalog, _registration, track, mut media) = publish_video(&mut broadcast, video_config(), None); + let (_catalog, _registration, track, mut media) = publish_video(&mut broadcast, video_config(), timed()); let origin = pair.sub_origin.consume(); tokio::time::timeout(TIMEOUT, origin.routed("live")) @@ -761,7 +766,7 @@ mod tests { let origin = moq_tokio::origin::spawn(); let mut broadcast = origin.create_broadcast("live").expect("publish"); broadcast.announce(Default::default()).expect("announce"); - let (_catalog, _registration, _track, mut media) = publish_video(&mut broadcast, video_config(), None); + let (_catalog, _registration, _track, mut media) = publish_video(&mut broadcast, video_config(), timed()); let app = Server::new(origin.consume(), crate::export::Config::default()).router(); assert_eq!(status(&app, "/live/master.m3u8").await, StatusCode::NOT_FOUND); @@ -777,7 +782,7 @@ mod tests { let origin = moq_tokio::origin::spawn(); let mut broadcast = origin.create_broadcast("live").expect("publish"); broadcast.announce(Default::default()).expect("announce"); - let (_catalog, _registration, _track, mut media) = publish_video(&mut broadcast, video_config(), None); + let (_catalog, _registration, _track, mut media) = publish_video(&mut broadcast, video_config(), timed()); write_three_gops(&mut media); let server = Server::new(origin.consume(), crate::export::Config::default()); @@ -844,7 +849,7 @@ mod tests { .await; let mut broadcast = pair.pub_origin.create_broadcast("live").expect("publish"); broadcast.announce(Default::default()).expect("announce"); - let (_catalog, _registration, _track, mut media) = publish_video(&mut broadcast, video_config(), None); + let (_catalog, _registration, _track, mut media) = publish_video(&mut broadcast, video_config(), timed()); write_three_gops(&mut media); let app = Server::new(pair.sub_origin.consume(), crate::export::Config::default()).router(); @@ -862,7 +867,7 @@ mod tests { let pair = lite_pair().await; let mut broadcast = pair.pub_origin.create_broadcast("live").expect("publish"); broadcast.announce(Default::default()).expect("announce"); - let (catalog, registration, track, mut media) = publish_video(&mut broadcast, video_config(), None); + let (catalog, registration, track, mut media) = publish_video(&mut broadcast, video_config(), timed()); write_three_gops(&mut media); let app = Server::new(pair.sub_origin.consume(), crate::export::Config::default()).router(); @@ -899,7 +904,7 @@ mod tests { let reserved = catalog.reserve(); let mut config = video_config(); config.broadcast = Some(moq_net::path::Relative::new("../source").to_owned()); - let track = media_broadcast.create_track("video0", None).unwrap(); + let track = media_broadcast.create_track("video0", timed()).unwrap(); let mut media = reserved .video( track, diff --git a/rs/moq-json/examples/telemetry.rs b/rs/moq-json/examples/telemetry.rs index 874b389165d..b6356b11929 100644 --- a/rs/moq-json/examples/telemetry.rs +++ b/rs/moq-json/examples/telemetry.rs @@ -79,7 +79,10 @@ fn telemetry(tick: u64) -> Value { fn wire_bytes(config: snapshot::Config, ticks: u64) -> usize { let track = moq_net::broadcast::Info::new() .produce() - .create_track("telemetry", None) + .create_track( + "telemetry", + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + ) .unwrap(); let consumer = track.subscribe(None); let mut producer = Producer::::new(track, config); @@ -106,7 +109,10 @@ fn wire_bytes(config: snapshot::Config, ticks: u64) -> usize { fn verify(producer_config: snapshot::Config, ticks: u64) { let track = moq_net::broadcast::Info::new() .produce() - .create_track("telemetry", None) + .create_track( + "telemetry", + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + ) .unwrap(); let consumer = track.subscribe(None); let mut producer = Producer::::new(track, producer_config.clone()); @@ -144,7 +150,10 @@ fn verify(producer_config: snapshot::Config, ticks: u64) { fn verify_late_joiner(producer_config: snapshot::Config, ticks: u64) -> usize { let track = moq_net::broadcast::Info::new() .produce() - .create_track("telemetry", None) + .create_track( + "telemetry", + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + ) .unwrap(); let consumer = track.subscribe(None); let mut producer = Producer::::new(track, producer_config.clone()); diff --git a/rs/moq-json/src/snapshot/mod.rs b/rs/moq-json/src/snapshot/mod.rs index dbb0fa5484d..862b466e9d0 100644 --- a/rs/moq-json/src/snapshot/mod.rs +++ b/rs/moq-json/src/snapshot/mod.rs @@ -57,6 +57,11 @@ mod test { use super::*; use crate::Compression; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// An uncompressed config with the given delta ratio. fn cfg(delta_ratio: u32) -> Config { Config::default().with_delta_ratio(delta_ratio) @@ -84,7 +89,7 @@ mod test { fn producer(config: Config) -> (Producer, moq_net::track::Subscriber) { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let consumer = track.subscribe(None); (Producer::new(track, config), consumer) @@ -131,7 +136,7 @@ mod test { fn a_lost_group_waits_for_its_replacement() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let mut consumer = Consumer::::new(track.subscribe(None), consumer::Config::default()); let waiter = kio::Waiter::noop(); @@ -165,7 +170,7 @@ mod test { fn a_lost_group_on_a_finished_track_ends_cleanly() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let mut consumer = Consumer::::new(track.subscribe(None), consumer::Config::default()); let waiter = kio::Waiter::noop(); @@ -450,7 +455,7 @@ mod test { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let consumer = track.subscribe(None); let mut producer = Producer::::new(track, Config::default()); @@ -476,7 +481,7 @@ mod test { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let consumer = track.subscribe(None); let mut producer = Producer::::new(track, Config::default()); @@ -520,7 +525,7 @@ mod test { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let consumer = track.subscribe(None); let mut producer = Producer::::new(track, cfg(0)); @@ -703,7 +708,7 @@ mod test { /// write failure (the reported one is a frame over moq-net's 32 MB per-group cache) without /// allocating 32 MB to provoke it. fn rejecting_track() -> moq_net::track::Producer { - let mut info = moq_net::track::Info::default(); + let mut info = timed(); info.timescale = Some(moq_net::Timescale::new((1u64 << 62) - 1).unwrap()); moq_net::broadcast::Info::new() @@ -742,7 +747,7 @@ mod test { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let consumer = track.subscribe(None); let mut producer = Producer::::new(track, cfg(0)); @@ -791,7 +796,7 @@ mod test { // keep waiting on it rather than ending the stream. Regression for the backlog-collapse poll. let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let mut group = track.append_group().unwrap(); let consumer_track = track.subscribe(None); diff --git a/rs/moq-json/src/stream/mod.rs b/rs/moq-json/src/stream/mod.rs index 343394b96c4..874fdc49e2f 100644 --- a/rs/moq-json/src/stream/mod.rs +++ b/rs/moq-json/src/stream/mod.rs @@ -58,10 +58,15 @@ mod test { use super::*; use crate::Compression; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + fn producer(config: Config) -> (Producer, moq_net::track::Subscriber) { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let consumer = track.subscribe(None); (Producer::new(track, config), consumer) @@ -222,7 +227,7 @@ mod test { // A map with non-string keys can't be represented as JSON, so serialization fails. let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let mut subscriber = track.subscribe(None); let mut producer = Producer::>::new(track, Config::default()); @@ -245,7 +250,7 @@ mod test { /// write failure (the reported one is a frame over moq-net's 32 MB per-group cache) without /// allocating 32 MB to provoke it. fn rejecting_track() -> moq_net::track::Producer { - let mut info = moq_net::track::Info::default(); + let mut info = timed(); info.timescale = Some(moq_net::Timescale::new((1u64 << 62) - 1).unwrap()); moq_net::broadcast::Info::new() @@ -318,7 +323,7 @@ mod test { fn a_second_group_is_reported_while_the_first_is_open() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); // Ask for a replay window, so the first group is delivered rather than skipped by the diff --git a/rs/moq-json/src/window/mod.rs b/rs/moq-json/src/window/mod.rs index b67473b6486..f3bf0567dfa 100644 --- a/rs/moq-json/src/window/mod.rs +++ b/rs/moq-json/src/window/mod.rs @@ -66,10 +66,15 @@ mod test { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + fn producer(config: ProducerConfig) -> (Producer, moq_net::track::Subscriber) { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let consumer = track.subscribe(None); (Producer::new(track, config), consumer) @@ -91,7 +96,7 @@ mod test { /// A track whose timestamp conversion rejects every frame after its group is published. fn rejecting_track() -> moq_net::track::Producer { - let mut info = moq_net::track::Info::default(); + let mut info = timed(); info.timescale = Some(moq_net::Timescale::new((1u64 << 62) - 1).unwrap()); moq_net::broadcast::Info::new() @@ -361,7 +366,7 @@ mod test { fn a_fresh_consumer_adopts_the_offset_without_skipping_history() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let mut producer = Producer::::new(track, ProducerConfig::default().with_op_ratio(0)); @@ -468,7 +473,7 @@ mod test { ] { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let mut consumer = consumer(track.subscribe(None), false); let mut encoder = Encoder::::new(ProducerConfig::default().with_op_ratio(0)); @@ -656,7 +661,7 @@ mod test { fn cut_finishes_the_group_and_the_next_edit_restates() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("test", None) + .create_track("test", timed()) .unwrap(); let replay = moq_net::track::Subscription::default().with_max_delay(std::time::Duration::from_secs(30)); let mut groups = track.subscribe(replay); diff --git a/rs/moq-mux/src/binary.rs b/rs/moq-mux/src/binary.rs index 79550f3528d..ea59994b2e4 100644 --- a/rs/moq-mux/src/binary.rs +++ b/rs/moq-mux/src/binary.rs @@ -19,7 +19,8 @@ //! # catalog: &moq_mux::catalog::Producer, //! # jpeg: bytes::Bytes, //! # ) -> moq_mux::Result<()> { -//! let track = broadcast.create_track("thumbnail", None)?; +//! let info = moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI); +//! let track = broadcast.create_track("thumbnail", info)?; //! let config = moq_mux::binary::Config::default().with_mime("image/jpeg"); //! let mut thumbnail = catalog.binary_snapshot(track, config)?; //! thumbnail.update(jpeg)?; @@ -391,6 +392,11 @@ mod test { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + fn catalog() -> (moq_net::broadcast::Producer, crate::catalog::Producer) { let mut broadcast = moq_net::broadcast::Info::new().produce(); let catalog = crate::catalog::Producer::new(&mut broadcast, crate::catalog::Config::default()).unwrap(); @@ -399,7 +405,7 @@ mod test { /// Create the moq-net track a data producer publishes on, as a caller does for a media track. fn track(broadcast: &mut moq_net::broadcast::Producer, name: &str) -> moq_net::track::Producer { - broadcast.create_track(name, None).unwrap() + broadcast.create_track(name, timed()).unwrap() } fn entry(catalog: &crate::catalog::Producer, name: &str) -> BinaryConfig { @@ -714,7 +720,7 @@ mod test { .binary_snapshot(track(&mut broadcast, "data"), Config::default()) .unwrap(); - assert!(broadcast.create_track("data", None).is_err()); + assert!(broadcast.create_track("data", timed()).is_err()); } /// A data track listed in an application's own section, beside its own per-track fields. diff --git a/rs/moq-mux/src/catalog/producer.rs b/rs/moq-mux/src/catalog/producer.rs index b3359c8d25e..587dc990191 100644 --- a/rs/moq-mux/src/catalog/producer.rs +++ b/rs/moq-mux/src/catalog/producer.rs @@ -340,7 +340,7 @@ impl Producer { // The MSF track is the same catalog in another encoding, so it takes the same // priority. Leaving it at the default would rank a subscriber reading MSF // below every media track on a relay's upstream leg. - let msf_info = moq_net::track::Info::default().with_priority(hang::catalog::PRIORITY.catalog); + let msf_info = hang::Catalog::default_track_info(); let msf_track = broadcast.create_track(moq_msf::DEFAULT_NAME, msf_info)?; // Disable deltas for now to stay byte-compatible with consumers that only read snapshots. diff --git a/rs/moq-mux/src/catalog/tracks.rs b/rs/moq-mux/src/catalog/tracks.rs index c19ecbe6f84..cda94009b6b 100644 --- a/rs/moq-mux/src/catalog/tracks.rs +++ b/rs/moq-mux/src/catalog/tracks.rs @@ -71,7 +71,8 @@ use super::hang::{Catalog, CatalogExt}; /// # broadcast: &mut moq_net::broadcast::Producer, /// # catalog: &moq_mux::catalog::Producer, /// # ) -> moq_mux::Result<()> { -/// let track = broadcast.create_track("telemetry", None)?; +/// let info = moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI); +/// let track = broadcast.create_track("telemetry", info)?; /// // The producer fixes the mode, so the one passed here is only a placeholder. /// let entry = Mavlink { config: BinaryConfig::new(Mode::Stream), sysid: 1 }; /// let mut telemetry = catalog.binary_stream(track, entry)?; @@ -732,6 +733,11 @@ impl> Drop for Rendition { mod tests { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + fn video_track() -> (moq_net::broadcast::Producer, super::super::Producer, VideoTrack) { let mut broadcast = moq_net::broadcast::Info::new().produce(); let catalog = super::super::Producer::new(&mut broadcast, super::super::Config::default()).unwrap(); @@ -1380,7 +1386,7 @@ mod tests { fn measures_a_custom_track() { let (broadcast, catalog) = produce(); let reserved = catalog.reserve(); - let net = broadcast.create_track("gps", None).unwrap(); + let net = broadcast.create_track("gps", timed()).unwrap(); let mut track = reserved .track( net, diff --git a/rs/moq-mux/src/container/consumer.rs b/rs/moq-mux/src/container/consumer.rs index bf6f4fe2d78..1ea8a3f1940 100644 --- a/rs/moq-mux/src/container/consumer.rs +++ b/rs/moq-mux/src/container/consumer.rs @@ -727,6 +727,11 @@ mod tests { use bytes::Bytes; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// Mint a standalone track for tests via a throwaway broadcast, since tracks are /// born from their broadcast (no public `track::Producer::new`). fn track_producer( @@ -2015,7 +2020,7 @@ mod tests { #[tokio::test] async fn decode_error_propagates() { tokio::time::pause(); - let track = track_producer("test", None); + let track = track_producer("test", timed()); let consumer_track = track.subscribe(None); let mut consumer = Consumer::new(consumer_track, FailingDecode); @@ -2768,7 +2773,7 @@ mod tests { tokio::time::pause(); // DurationWire is a test-only container that doesn't stamp moq_net frame // timestamps; leave the track untimed so model-layer validation matches. - let track = track_producer("test", None); + let track = track_producer("test", timed()); let consumer_track = track.subscribe(moq_net::track::Subscription::default().with_max_delay(Duration::from_secs(10))); // The max delay dwarfs the gap, so only duration coverage can trigger the skip. @@ -2806,7 +2811,7 @@ mod tests { #[tokio::test] async fn a_nonsequential_contiguous_jump_does_not_bump_playhead() { - let track = track_producer("test", None); + let track = track_producer("test", timed()); let mut consumer = Consumer::new( track.subscribe(moq_net::track::Subscription::default().with_max_delay(Duration::from_secs(10))), DurationWire, @@ -2837,7 +2842,7 @@ mod tests { async fn duration_below_gap_does_not_skip() { tokio::time::pause(); // DurationWire is untimed at the moq_net frame layer. - let track = track_producer("test", None); + let track = track_producer("test", timed()); let consumer_track = track.subscribe(moq_net::track::Subscription::default().with_max_delay(Duration::from_secs(10))); let mut consumer = Consumer::new(consumer_track, DurationWire); diff --git a/rs/moq-mux/src/container/flv/export_test.rs b/rs/moq-mux/src/container/flv/export_test.rs index c245ee9e374..9c99b7853e1 100644 --- a/rs/moq-mux/src/container/flv/export_test.rs +++ b/rs/moq-mux/src/container/flv/export_test.rs @@ -10,6 +10,11 @@ use hang::catalog::{AudioCodec, VideoCodec}; use super::{Export, Import}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Open an FLV export on the hang catalog of `source`. async fn open_export(source: crate::Source) -> Export { let catalog = source @@ -535,7 +540,7 @@ fn build_multitrack_broadcast() -> (moq_net::broadcast::Consumer, Vec>, let descriptions: Vec> = vec![avcc_level(0x1f), avcc_level(0x1e)]; for (description, (width, height)) in descriptions.iter().zip([(640, 360), (1920, 1080)]) { - let track = producer.create_track(producer.unique_name(".avc1"), None).unwrap(); + let track = producer.create_track(producer.unique_name(".avc1"), timed()).unwrap(); let mut config = VideoConfig::new(H264 { profile: 0x42, constraints: 0xc0, @@ -569,7 +574,7 @@ fn build_multitrack_broadcast() -> (moq_net::broadcast::Consumer, Vec>, tracks.push(video); } - let audio_track = producer.create_track(producer.unique_name(".aac"), None).unwrap(); + let audio_track = producer.create_track(producer.unique_name(".aac"), timed()).unwrap(); let mut audio_config = AudioConfig::new(AAC { profile: 2 }, 44100, 2); audio_config.container = Container::Legacy; audio_config.description = Some(Bytes::from_static(&ASC)); @@ -732,7 +737,7 @@ async fn export_without_multitrack_keeps_best_audio_rendition() { { let mut publish = |name: &str, config: AudioConfig| { - let track = producer.create_track(name, None).unwrap(); + let track = producer.create_track(name, timed()).unwrap(); catalog .modify() .unwrap() @@ -862,7 +867,7 @@ async fn export_rebinds_to_a_better_rendition_before_the_header() { // The small rendition is Annex-B (no description), so its header can't resolve // until a keyframe arrives, which keeps the stream header pending. - let small = producer.create_track(producer.unique_name(".avc3"), None).unwrap(); + let small = producer.create_track(producer.unique_name(".avc3"), timed()).unwrap(); let mut config = VideoConfig::new(H264 { profile: 0x42, constraints: 0xc0, @@ -893,7 +898,7 @@ async fn export_rebinds_to_a_better_rendition_before_the_header() { // The best rendition shows up before any header went out. let description = avcc_level(0x1e); - let large = producer.create_track(producer.unique_name(".avc1"), None).unwrap(); + let large = producer.create_track(producer.unique_name(".avc1"), timed()).unwrap(); let mut config = VideoConfig::new(H264 { profile: 0x42, constraints: 0xc0, @@ -967,7 +972,7 @@ async fn export_rebinds_to_a_better_audio_rendition_before_the_header() { sample_rate: u32, bitrate: u64, ) -> Producer { - let track = producer.create_track(name, None).unwrap(); + let track = producer.create_track(name, timed()).unwrap(); let mut config = AudioConfig::new(AAC { profile: 2 }, sample_rate, 2); config.container = Container::Legacy; config.bitrate = Some(bitrate); @@ -988,7 +993,7 @@ async fn export_rebinds_to_a_better_audio_rendition_before_the_header() { let _weak = audio(&mut producer, &mut catalog, "a", &WEAK_ASC, 48_000, 64_000); // Annex-B video (no description) keeps the header pending until its keyframe. - let video = producer.create_track(producer.unique_name(".avc3"), None).unwrap(); + let video = producer.create_track(producer.unique_name(".avc3"), timed()).unwrap(); let mut config = VideoConfig::new(H264 { profile: 0x42, constraints: 0xc0, @@ -1018,7 +1023,7 @@ async fn export_rebinds_to_a_better_audio_rendition_before_the_header() { // The better audio rendition shows up before any header went out, then video resolves. let _strong = audio(&mut producer, &mut catalog, "z", &STRONG_ASC, 44_100, 128_000); let description = avcc_level(0x1e); - let large = producer.create_track(producer.unique_name(".avc1"), None).unwrap(); + let large = producer.create_track(producer.unique_name(".avc1"), timed()).unwrap(); let mut config = VideoConfig::new(H264 { profile: 0x42, constraints: 0xc0, @@ -1118,8 +1123,8 @@ async fn export_authors_dts_and_composition_time_for_reordered_avc() { let consumer = producer.consume(); let mut catalog = crate::catalog::Producer::new(&mut producer, crate::catalog::Config::default()).unwrap(); - let video_track = producer.create_track(producer.unique_name(".avc1"), None).unwrap(); - let audio_track = producer.create_track(producer.unique_name(".aac"), None).unwrap(); + let video_track = producer.create_track(producer.unique_name(".avc1"), timed()).unwrap(); + let audio_track = producer.create_track(producer.unique_name(".aac"), timed()).unwrap(); let mut video_config = VideoConfig::new(H264 { profile: 0x42, @@ -1234,8 +1239,8 @@ async fn a_track_leaving_the_catalog_is_read_to_its_end() { let consumer = producer.consume(); let mut catalog = crate::catalog::Producer::new(&mut producer, crate::catalog::Config::default()).unwrap(); - let video_track = producer.create_track(producer.unique_name(".avc1"), None).unwrap(); - let audio_track = producer.create_track(producer.unique_name(".aac"), None).unwrap(); + let video_track = producer.create_track(producer.unique_name(".avc1"), timed()).unwrap(); + let audio_track = producer.create_track(producer.unique_name(".aac"), timed()).unwrap(); let audio_name = audio_track.name().to_string(); let mut video_config = VideoConfig::new(H264 { diff --git a/rs/moq-mux/src/container/fmp4/export_test.rs b/rs/moq-mux/src/container/fmp4/export_test.rs index efb7c1674a0..06b7e4f4154 100644 --- a/rs/moq-mux/src/container/fmp4/export_test.rs +++ b/rs/moq-mux/src/container/fmp4/export_test.rs @@ -7,6 +7,11 @@ use mp4_atom::{DecodeMaybe, Encode}; use crate::container::test_util::{IDR, Live, PPS, SPS, raw_frame, video_frame}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// The media track's full retention window, so an exporter started after publishing /// can still read every retained group. These tests write or import a whole broadcast /// and only then export it, which the @@ -2151,7 +2156,7 @@ async fn cmaf_source_exports_at_the_frame_timestamp() { let mut broadcast = moq_net::broadcast::Info::new().produce(); let consumer = broadcast.consume(); let mut catalog = crate::catalog::Producer::new(&mut broadcast, crate::catalog::Config::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); catalog .modify() .unwrap() diff --git a/rs/moq-mux/src/container/fmp4/import_test.rs b/rs/moq-mux/src/container/fmp4/import_test.rs index 565edcc90d2..2bd005e4399 100644 --- a/rs/moq-mux/src/container/fmp4/import_test.rs +++ b/rs/moq-mux/src/container/fmp4/import_test.rs @@ -1397,7 +1397,12 @@ async fn join_after(earlier: Earlier, start_us: u64) { let mut data = match earlier { Earlier::Data => { - let track = broadcast.create_track("telemetry", None).unwrap(); + let track = broadcast + .create_track( + "telemetry", + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + ) + .unwrap(); let mut stream = catalog.binary_stream(track, crate::binary::Config::default()).unwrap(); stream.append(&b"before"[..]).unwrap(); Some(stream) diff --git a/rs/moq-mux/src/container/fmp4/muxer.rs b/rs/moq-mux/src/container/fmp4/muxer.rs index 8046205b15d..39cb8021d07 100644 --- a/rs/moq-mux/src/container/fmp4/muxer.rs +++ b/rs/moq-mux/src/container/fmp4/muxer.rs @@ -345,6 +345,11 @@ mod tests { use hang::catalog::VideoCodec; use moq_net::Timestamp; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + fn frame(micros: u64, keyframe: bool) -> Frame { Frame { timestamp: Timestamp::from_micros(micros).unwrap(), @@ -360,7 +365,7 @@ mod tests { async fn legacy_group_round_trips() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("v", None) + .create_track("v", timed()) .unwrap(); let mut subscriber = track.subscribe(None).ordered(); let mut producer = crate::container::Producer::new(track, HangContainer::Legacy(crate::container::Kind::Data)); @@ -402,7 +407,7 @@ mod tests { async fn a_duration_marker_times_the_trailing_sample() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("v", None) + .create_track("v", timed()) .unwrap(); let mut subscriber = track.subscribe(None).ordered(); let mut producer = crate::container::Producer::new(track, HangContainer::Legacy(crate::container::Kind::Video)); @@ -425,7 +430,7 @@ mod tests { async fn dimensionless_vp8_init_waits_for_keyframe_geometry() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("v", None) + .create_track("v", timed()) .unwrap(); let mut subscriber = track.subscribe(None).ordered(); let mut producer = crate::container::Producer::new(track, HangContainer::Legacy(crate::container::Kind::Data)); diff --git a/rs/moq-mux/src/container/group.rs b/rs/moq-mux/src/container/group.rs index 92c756af347..db0d677981e 100644 --- a/rs/moq-mux/src/container/group.rs +++ b/rs/moq-mux/src/container/group.rs @@ -87,6 +87,11 @@ mod tests { use super::*; use crate::catalog::hang::Container as Hang; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + fn frame(timestamp_us: u64, payload: &'static [u8], keyframe: bool) -> Frame { Frame { timestamp: moq_net::Timestamp::from_micros(timestamp_us).unwrap(), @@ -100,7 +105,7 @@ mod tests { #[tokio::test] async fn reads_a_group_to_completion() { let broadcast = moq_net::broadcast::Info::new().produce(); - let track = broadcast.create_track("media", None).unwrap(); + let track = broadcast.create_track("media", timed()).unwrap(); let consumer = broadcast.consume(); let mut media = crate::container::Producer::new(track, Hang::Legacy(crate::container::Kind::Data)); @@ -156,7 +161,7 @@ mod tests { #[tokio::test] async fn a_duration_marker_times_the_last_frame() { let broadcast = moq_net::broadcast::Info::new().produce(); - let track = broadcast.create_track("media", None).unwrap(); + let track = broadcast.create_track("media", timed()).unwrap(); let consumer = broadcast.consume(); let mut media = crate::container::Producer::new(track, Hang::Legacy(crate::container::Kind::Video)); diff --git a/rs/moq-mux/src/container/producer.rs b/rs/moq-mux/src/container/producer.rs index 65be578fda4..d8ea0703a10 100644 --- a/rs/moq-mux/src/container/producer.rs +++ b/rs/moq-mux/src/container/producer.rs @@ -764,6 +764,11 @@ mod tests { use crate::catalog::hang::Container; use moq_net::Timestamp; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// Mint a standalone track for tests via a throwaway broadcast, since tracks are /// born from their broadcast (no public `track::Producer::new`). fn track_producer( @@ -1432,7 +1437,7 @@ mod tests { let broadcast = moq_net::broadcast::Info::new().produce(); let timelines = crate::timeline::Timelines::new(&broadcast); let recorder = timelines.track("video", crate::timeline::Config::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); let mut producer = Producer::new(track, Container::Legacy(crate::container::Kind::Video)) .with_buffer(std::time::Duration::from_secs(10)) .with_recorder(recorder); @@ -1459,7 +1464,7 @@ mod tests { let broadcast = moq_net::broadcast::Info::new().produce(); let timelines = crate::timeline::Timelines::new(&broadcast); let recorder = timelines.track("video", crate::timeline::Config::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); let mut producer = Producer::new(track, Container::Legacy(crate::container::Kind::Video)).with_recorder(recorder); diff --git a/rs/moq-mux/src/container/ts/export_test.rs b/rs/moq-mux/src/container/ts/export_test.rs index cb6f3a916db..365c6b45199 100644 --- a/rs/moq-mux/src/container/ts/export_test.rs +++ b/rs/moq-mux/src/container/ts/export_test.rs @@ -21,6 +21,11 @@ use crate::container::ts::{Export, catalog as tscat, stats}; use crate::container::{Frame, Kind, Producer}; use moq_net::Timestamp; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const SC: &[u8] = &[0, 0, 0, 1]; // Reusable H.264 parameter-set and slice NALs (NAL type = first byte & 0x1f). const SPS: &[u8] = &[0x67, 0x42, 0xc0, 0x1f, 0xde]; @@ -2530,14 +2535,14 @@ async fn si_pids_are_re_emitted_on_their_own_interval() { let name = track.name().to_string(); // SI snapshot tracks, one group each: an SDT (2s) and a NIT (10s). - let mut sdt_track = broadcast.create_track("0x0011-0x42.si", None).unwrap(); + let mut sdt_track = broadcast.create_track("0x0011-0x42.si", timed()).unwrap(); sdt_track .write_frame( Timestamp::ZERO, Bytes::from(make_long_section(0x42, 1, 0, 0, 0, &[0xaa; 8])), ) .unwrap(); - let mut nit_track = broadcast.create_track("0x0010-0x40.si", None).unwrap(); + let mut nit_track = broadcast.create_track("0x0010-0x40.si", timed()).unwrap(); nit_track .write_frame( Timestamp::ZERO, @@ -2860,7 +2865,7 @@ fn publish_sdt( broadcast: &mut moq_net::broadcast::Producer, catalog: &mut crate::catalog::Producer, ) -> moq_net::track::Producer { - let mut track = broadcast.create_track("0x0011-0x42.si", None).unwrap(); + let mut track = broadcast.create_track("0x0011-0x42.si", timed()).unwrap(); track .write_frame( Timestamp::ZERO, @@ -3906,7 +3911,7 @@ async fn stale_si_entry_does_not_block_output() { .unwrap(); // A track that exists (the subscription resolves) but never produces a group. - let ghost = broadcast.create_track("ghost.si", None).unwrap(); + let ghost = broadcast.create_track("ghost.si", timed()).unwrap(); let avcc = crate::codec::h264::build_avcc(&[Bytes::from_static(SPS)], &[Bytes::from_static(PPS)]).unwrap(); let track = broadcast @@ -4947,11 +4952,11 @@ async fn repointed_si_entry_resubscribes() { ) .unwrap(); - let mut track_a = broadcast.create_track("a.si", None).unwrap(); + let mut track_a = broadcast.create_track("a.si", timed()).unwrap(); track_a .write_frame(Timestamp::ZERO, Bytes::from(sdt_a.clone())) .unwrap(); - let mut track_b = broadcast.create_track("b.si", None).unwrap(); + let mut track_b = broadcast.create_track("b.si", timed()).unwrap(); track_b .write_frame(Timestamp::ZERO, Bytes::from(sdt_b.clone())) .unwrap(); @@ -5084,7 +5089,7 @@ async fn si_revision_after_final_media_frame_is_flushed() { ) .unwrap(); - let mut si_track = broadcast.create_track("0x0011-0x42.si", None).unwrap(); + let mut si_track = broadcast.create_track("0x0011-0x42.si", timed()).unwrap(); si_track .write_frame(Timestamp::ZERO, Bytes::from(sdt_v1.clone())) .unwrap(); @@ -5196,7 +5201,7 @@ async fn si_cadence_rig(pid: u16, table_id: u8, interval: Duration) -> SiCadence .unwrap(); let si_name = format!("{pid:#06x}-{table_id:#04x}.si"); - let si_track = broadcast.create_track(si_name.as_str(), None).unwrap(); + let si_track = broadcast.create_track(si_name.as_str(), timed()).unwrap(); let avcc = crate::codec::h264::build_avcc(&[Bytes::from_static(SPS)], &[Bytes::from_static(PPS)]).unwrap(); let track = broadcast @@ -8141,7 +8146,7 @@ async fn a_repointed_si_entry_never_reads_a_replacement() { ..Default::default() }; let si_track = |broadcast: &mut moq_net::broadcast::Producer, name: &str, sdt: &[u8]| { - let mut track = broadcast.create_track(name, None).unwrap(); + let mut track = broadcast.create_track(name, timed()).unwrap(); track.write_frame(Timestamp::ZERO, Bytes::copy_from_slice(sdt)).unwrap(); track }; diff --git a/rs/moq-mux/src/container/ts/si.rs b/rs/moq-mux/src/container/ts/si.rs index f11f2602ac8..ee5b7cd7ea4 100644 --- a/rs/moq-mux/src/container/ts/si.rs +++ b/rs/moq-mux/src/container/ts/si.rs @@ -364,12 +364,15 @@ impl Entry { None if frames.is_empty() => return Ok(()), // Deterministic, greppable name; fall back to a unique suffix on the // (pathological) collision with an existing track. - None => self.track.insert( - match broadcast.create_track(format!("{pid:#06x}-{table_id:#04x}.si"), None) { - Ok(track) => track, - Err(_) => broadcast.unique_track(".si", None)?, - }, - ), + None => { + let info = moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI); + self.track.insert( + match broadcast.create_track(format!("{pid:#06x}-{table_id:#04x}.si"), info.clone()) { + Ok(track) => track, + Err(_) => broadcast.unique_track(".si", info)?, + }, + ) + } }; let mut group = track.append_group()?; for payload in frames { diff --git a/rs/moq-mux/src/json.rs b/rs/moq-mux/src/json.rs index fb2ccd3dcff..47c9425ea1c 100644 --- a/rs/moq-mux/src/json.rs +++ b/rs/moq-mux/src/json.rs @@ -20,7 +20,8 @@ //! # broadcast: &mut moq_net::broadcast::Producer, //! # catalog: &moq_mux::catalog::Producer, //! # ) -> moq_mux::Result<()> { -//! let track = broadcast.create_track("chat", None)?; +//! let info = moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI); +//! let track = broadcast.create_track("chat", info)?; //! let config = moq_mux::json::Config::default().with_compression(true); //! let mut chat = catalog.json_stream::(track, config)?; //! chat.append(&Message { text: "hello".to_string() })?; @@ -449,6 +450,11 @@ mod test { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + fn catalog() -> (moq_net::broadcast::Producer, crate::catalog::Producer) { let mut broadcast = moq_net::broadcast::Info::new().produce(); let catalog = crate::catalog::Producer::new(&mut broadcast, crate::catalog::Config::default()).unwrap(); @@ -457,7 +463,7 @@ mod test { /// Create the moq-net track a data producer publishes on, as a caller does for a media track. fn track(broadcast: &mut moq_net::broadcast::Producer, name: &str) -> moq_net::track::Producer { - broadcast.create_track(name, None).unwrap() + broadcast.create_track(name, timed()).unwrap() } fn entry(catalog: &crate::catalog::Producer, name: &str) -> JsonConfig { @@ -851,7 +857,7 @@ mod test { fn an_unrecognized_mode_or_compression_is_refused() { let track = moq_net::broadcast::Info::new() .produce() - .create_track("chat", None) + .create_track("chat", timed()) .unwrap(); let mut config = JsonConfig::new(Mode::Unknown("windowed".to_string())); diff --git a/rs/moq-mux/src/timeline.rs b/rs/moq-mux/src/timeline.rs index 7c7430e46cd..a97adb468c0 100644 --- a/rs/moq-mux/src/timeline.rs +++ b/rs/moq-mux/src/timeline.rs @@ -354,7 +354,9 @@ pub struct Producer { impl Producer { /// The properties a timeline track is created with. pub fn info() -> moq_net::track::Info { - moq_net::track::Info::default().with_priority(hang::catalog::PRIORITY.catalog) + moq_net::track::Info::default() + .with_timescale(moq_net::Timescale::MILLI) + .with_priority(hang::catalog::PRIORITY.catalog) } /// Publish onto `track`, starting at record zero. diff --git a/rs/moq-net/benches/group.rs b/rs/moq-net/benches/group.rs index a9245ddc894..13df04e69af 100644 --- a/rs/moq-net/benches/group.rs +++ b/rs/moq-net/benches/group.rs @@ -17,6 +17,11 @@ use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, use futures::FutureExt; use moq_net::{Timestamp, broadcast, frame, group, track}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> track::Info { + track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// A small, fixed payload shared across every frame. Cloning a `Bytes` is a /// refcount bump (no allocation), so the benchmark isolates the per-frame control /// overhead rather than payload allocation. @@ -47,7 +52,7 @@ struct Ctx { /// Build a fresh, empty group via the public producer path. fn fresh_group() -> Ctx { let broadcast = broadcast::Producer::new(broadcast::Info::default()); - let track = broadcast.create_track("bench", None).unwrap(); + let track = broadcast.create_track("bench", timed()).unwrap(); let group = track.append_group().unwrap(); Ctx { _broadcast: broadcast, @@ -195,7 +200,7 @@ struct TrackCtx { /// Build a track holding N cached groups, each with a single small frame. fn filled_track(n: usize, payload: &Bytes) -> TrackCtx { let broadcast = broadcast::Producer::new(broadcast::Info::default()); - let track = broadcast.create_track("bench", None).unwrap(); + let track = broadcast.create_track("bench", timed()).unwrap(); for _ in 0..n { let mut group = track.append_group().unwrap(); group.write_frame(Timestamp::ZERO, payload.clone()).unwrap(); diff --git a/rs/moq-net/benches/origin.rs b/rs/moq-net/benches/origin.rs index 8451c2a235a..e400fef73ad 100644 --- a/rs/moq-net/benches/origin.rs +++ b/rs/moq-net/benches/origin.rs @@ -33,6 +33,11 @@ use futures::FutureExt; use moq_net::{Epoch, Hop, Hops, Pattern, Patterns, Timestamp, announce, auth, broadcast, kio, origin, track}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> track::Info { + track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// `(publishers, subscribers)` shapes for the fan-out benchmarks. const SHAPES: [(usize, usize); 3] = [(100, 10), (1_000, 100), (1_000, 1_000)]; @@ -632,7 +637,7 @@ fn bench_handoff(c: &mut Criterion) { let mut total = Duration::ZERO; for _ in 0..iterations { let incumbent = producer.publish("room/live", origin::Route::default()).unwrap(); - let track = incumbent.create_track("video", None).unwrap(); + let track = incumbent.create_track("video", timed()).unwrap(); let mut first = track.append_group().unwrap(); first.write_frame(Timestamp::ZERO, b"one".as_ref()).unwrap(); first.finish().unwrap(); @@ -643,7 +648,7 @@ fn bench_handoff(c: &mut Criterion) { let started = std::time::Instant::now(); let standby = producer.create_broadcast("room/live").unwrap(); - let track = standby.create_track("video", None).unwrap(); + let track = standby.create_track("video", timed()).unwrap(); let mut second = track.create_group(moq_net::group::Info { sequence: 1 }).unwrap(); second.write_frame(Timestamp::ZERO, b"two".as_ref()).unwrap(); second.finish().unwrap(); @@ -835,7 +840,7 @@ fn bench_relay(c: &mut Criterion) { runtime.spawn(moq_net::time::run(driver)); let broadcast = producer.publish("room/live", origin::Route::default()).unwrap(); let sources: Vec<_> = (0..tracks) - .map(|i| broadcast.create_track(format!("{i}"), None).unwrap()) + .map(|i| broadcast.create_track(format!("{i}"), timed()).unwrap()) .collect(); let mut subscriptions = runtime.block_on(async { let resolved = producer.consume().request_broadcast("room/live", None).await.unwrap(); @@ -945,7 +950,7 @@ impl Generation { let broadcast = producer.create_broadcast(COPY_PATH).unwrap(); let mut held = Vec::with_capacity(tracks); for i in 0..tracks { - let track = broadcast.create_track(format!("{i}"), None).unwrap(); + let track = broadcast.create_track(format!("{i}"), timed()).unwrap(); let mut written = track.append_group().unwrap(); written.write_frame(Timestamp::ZERO, b"f".as_ref()).unwrap(); written.finish().unwrap(); diff --git a/rs/moq-net/benches/session.rs b/rs/moq-net/benches/session.rs index 86b1bf09a35..6183aa58d93 100644 --- a/rs/moq-net/benches/session.rs +++ b/rs/moq-net/benches/session.rs @@ -45,6 +45,11 @@ use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_m use moq_net::{Hop, Timestamp, Version, broadcast, cache, group, origin, track}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> track::Info { + track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// The lite draft production negotiates, the next one (opt-in), and the newest /// IETF draft. const VERSIONS: [&str; 3] = ["moq-lite-06", "moq-lite-07-wip", "moq-transport-22"]; @@ -268,7 +273,7 @@ impl Cluster { let broadcast = origin .publish(path(publisher * shape.broadcasts + index), Default::default()) .unwrap(); - this.tracks.push(broadcast.create_track(TRACK, None).unwrap()); + this.tracks.push(broadcast.create_track(TRACK, timed()).unwrap()); this._broadcasts.push(broadcast); } @@ -657,7 +662,7 @@ impl DashRoom { let path = format!(".stats/{project}/node/{node}"); let broadcast = cluster.relays[node].publish(path.as_str(), Default::default()).unwrap(); for name in &names { - tracks.push(broadcast.create_track(name.as_str(), None).unwrap()); + tracks.push(broadcast.create_track(name.as_str(), timed()).unwrap()); } broadcasts.push(broadcast); paths.push(path); diff --git a/rs/moq-net/benches/track.rs b/rs/moq-net/benches/track.rs index 635aeb937ac..32a1542489b 100644 --- a/rs/moq-net/benches/track.rs +++ b/rs/moq-net/benches/track.rs @@ -34,6 +34,11 @@ use bytes::Bytes; use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use moq_net::{Error, Timestamp, broadcast, cache, track}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> track::Info { + track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Fanout sizes spanning a direct viewer, a small room, and a large room. const FANOUT: [usize; 4] = [1, 8, 64, 512]; @@ -81,7 +86,7 @@ impl Fanout { .with_expiry(cache::DEFAULT_EXPIRY); info.pool = cache::Pool::new(config); let broadcast = broadcast::Producer::new(info); - let track = broadcast.create_track("bench", None).unwrap(); + let track = broadcast.create_track("bench", timed()).unwrap(); let mut subscribers: Vec<_> = (0..subscribers).map(|_| track.subscribe(None)).collect(); let waiters: Vec<_> = (0..subscribers.len()).map(|_| kio::Waiter::noop()).collect(); @@ -174,7 +179,7 @@ impl Swept { let payload = Bytes::from_static(&[0; PAYLOAD]); let tracks = (0..tracks) .map(|i| { - let track = broadcast.create_track(format!("bench{i}"), None).unwrap(); + let track = broadcast.create_track(format!("bench{i}"), timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(Timestamp::ZERO, payload.clone()).unwrap(); group.finish().unwrap(); @@ -284,7 +289,7 @@ fn parallel_write(pool: &cache::Pool, writers: usize, iterations: u64) -> Durati let mut info = broadcast::Info::default(); info.pool = pool; let broadcast = broadcast::Producer::new(info); - let track = broadcast.create_track("bench", None).unwrap(); + let track = broadcast.create_track("bench", timed()).unwrap(); let payload = Bytes::from_static(&[0; PAYLOAD]); // Arrive once setup is done, then park until the main thread has stamped the clock. barrier.wait(); @@ -403,7 +408,7 @@ fn bench_datagram_subscriber_join(c: &mut Criterion) { b.iter_batched( || { let broadcast = broadcast::Info::default().produce(); - let mut track = broadcast.create_track("bench", None).unwrap(); + let mut track = broadcast.create_track("bench", timed()).unwrap(); for millis in 0..buffered { track .append_datagram(Timestamp::from_millis(millis).unwrap(), b"x".as_slice()) @@ -451,7 +456,7 @@ fn bench_parked_read(c: &mut Criterion) { group.bench_function(BenchmarkId::from_parameter(parked), |b| { b.iter_custom(|iterations| { let broadcast = broadcast::Info::default().produce(); - let track = broadcast.create_track("bench", None).unwrap(); + let track = broadcast.create_track("bench", timed()).unwrap(); // Cover setup and every measured append so the same backlog stays parked, // even when Criterion requests more iterations during warm-up. let max_delay = Duration::from_micros( diff --git a/rs/moq-net/benches/viewers.rs b/rs/moq-net/benches/viewers.rs index 0b25499707f..d98864db034 100644 --- a/rs/moq-net/benches/viewers.rs +++ b/rs/moq-net/benches/viewers.rs @@ -16,6 +16,11 @@ use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use futures::FutureExt; use moq_net::{Hop, Timestamp, broadcast, kio, origin}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// `(viewers, broadcasts)` shapes: viewers spread round-robin over the broadcasts. const SHAPES: [(usize, usize); 4] = [(100, 1), (1_000, 1), (1_000, 100), (10_000, 100)]; @@ -71,7 +76,7 @@ impl Room { let _sources = (0..broadcasts) .map(|i| { let broadcast = producer.publish(format!("room/{i}"), origin::Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(Timestamp::ZERO, b"frame".as_ref()).unwrap(); group.finish().unwrap(); diff --git a/rs/moq-net/src/fuzz.rs b/rs/moq-net/src/fuzz.rs index ada1fc11215..cf5dd62e616 100644 --- a/rs/moq-net/src/fuzz.rs +++ b/rs/moq-net/src/fuzz.rs @@ -1053,7 +1053,12 @@ impl FrameRecv { let mut info = broadcast::Info::new(); info.pool = cache::Pool::unbounded(); let broadcast = info.produce(); - let track = broadcast.create_track("frames", None).unwrap(); + let track = broadcast + .create_track( + "frames", + crate::track::Info::default().with_timescale(crate::Timescale::MILLI), + ) + .unwrap(); let budget = budget.map(frame::Budget::new).unwrap_or_default(); let payload = bytes::Bytes::from(vec![0u8; size]); diff --git a/rs/moq-net/src/ietf/publisher.rs b/rs/moq-net/src/ietf/publisher.rs index 8fb4fff3b03..785455f249b 100644 --- a/rs/moq-net/src/ietf/publisher.rs +++ b/rs/moq-net/src/ietf/publisher.rs @@ -3456,7 +3456,11 @@ mod group_priority_test { let log = crate::lite::test_transport::Log::default(); let session = SinkSession::new(log.clone()); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut group = track.create_group(group::Info { sequence: 0 }).unwrap(); group .write_frame(crate::Timestamp::from_millis(0).unwrap(), b"hello".as_slice()) @@ -3477,7 +3481,7 @@ mod group_priority_test { msg, kio::Producer::new(200).consume(), consumer, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft14, GroupSlice::default(), ); @@ -3496,7 +3500,7 @@ mod group_priority_test { /// every moq-transport peer. #[moq_net_sim::test] async fn group_header_carries_the_publisher_priority() { - let header = serve_group_header(track::Info::default().with_priority(hang_audio_priority())).await; + let header = serve_group_header(track::Info::timed().with_priority(hang_audio_priority())).await; assert_eq!( header.publisher_priority, priority::to_wire(hang_audio_priority()), @@ -3512,7 +3516,7 @@ mod group_priority_test { /// not 255, the least urgent value a peer like moxygen would deprioritize. #[moq_net_sim::test] async fn group_header_defaults_to_the_midpoint() { - let header = serve_group_header(track::Info::default()).await; + let header = serve_group_header(track::Info::timed()).await; assert_eq!(header.publisher_priority, 128); } @@ -3522,7 +3526,7 @@ mod group_priority_test { async fn drafts_14_through_16_send_no_timestamp_without_timescale() { for version in [Version::Draft14, Version::Draft15, Version::Draft16, Version::Draft17] { let stamped = ietf::Properties::sends_timescale(version); - let (header, mut buf) = serve_group(version, Some(Timescale::default())).await; + let (header, mut buf) = serve_group(version, Some(Timescale::MILLI)).await; assert_eq!(header.flags.has_extensions, stamped, "{version}"); assert_eq!( crate::coding::decode_varint(&mut buf, version).unwrap(), @@ -3534,7 +3538,7 @@ mod group_priority_test { let mut ext = bytes::Bytes::from(ext); assert!( crate::coding::decode_buf(&mut ext, version, |r, v| { - ietf::decode_object_time(r, Timescale::default(), v) + ietf::decode_object_time(r, Timescale::MILLI, v) }) .unwrap() .is_some(), @@ -3610,7 +3614,11 @@ mod group_priority_test { const BATCH: u64 = 1_000; let session = SinkSession::new(crate::lite::test_transport::Log::default()).with_unacked_fin(); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(track::Subscription::default().with_max_delay(std::time::Duration::from_secs(3600))); let serve = TrackServe::new( @@ -3655,7 +3663,11 @@ mod group_priority_test { let log = crate::lite::test_transport::Log::default(); let session = SinkSession::new(log.clone()).with_unacked_fin(); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(track::Subscription::default().with_max_delay(std::time::Duration::from_secs(3600))); let mut group = track.create_group(group::Info { sequence: 0 }).unwrap(); @@ -3690,7 +3702,11 @@ mod group_priority_test { let log = crate::lite::test_transport::Log::default(); let session = SinkSession::new(log.clone()); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(None); let mut group = track.append_group().unwrap(); @@ -3733,7 +3749,7 @@ mod group_priority_test { RequestId(0), Version::Draft14, ServeRange::default(), - Some(Timescale::default()), + Some(Timescale::MILLI), ); kio::wait(|waiter| serve.poll(waiter)).await.unwrap(); @@ -3757,7 +3773,11 @@ mod group_priority_test { async fn group_waiting_for_stream_credit_expires() { let gate = kio::Producer::new(false); let session = SinkSession::gated_open_uni(gate.consume()); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut subscriber = track.subscribe(None); let mut old = track.append_group().unwrap(); old.write_frame(crate::Timestamp::ZERO, b"old".as_slice()).unwrap(); @@ -3775,7 +3795,7 @@ mod group_priority_test { }, kio::Producer::new(0).consume(), group, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft19, GroupSlice::default(), ); @@ -3800,7 +3820,11 @@ mod group_priority_test { async fn unacknowledged_fin_expires_with_the_group() { let session = SinkSession::new(Default::default()).with_unacked_fin(); let log = session.log.clone(); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut subscriber = track.subscribe(None); let mut old = track.append_group().unwrap(); old.write_frame(crate::Timestamp::ZERO, b"old".as_slice()).unwrap(); @@ -3818,7 +3842,7 @@ mod group_priority_test { }, kio::Producer::new(0).consume(), group, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft19, GroupSlice::default(), ); @@ -3844,7 +3868,11 @@ mod group_priority_test { async fn unacknowledged_fin_follows_priority_updates() { let session = SinkSession::new(Default::default()).with_unacked_fin(); let log = session.log.clone(); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut group = track.create_group(group::Info { sequence: 0 }).unwrap(); group.write_frame(crate::Timestamp::ZERO, b"done".as_slice()).unwrap(); let consumer = group.consume(); @@ -3862,7 +3890,7 @@ mod group_priority_test { }, priority.consume(), consumer, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft19, GroupSlice::default(), ); @@ -3887,7 +3915,11 @@ mod group_priority_test { async fn blocked_final_transport_chunk_expires_with_the_group() { let gate = kio::Producer::new(true); let session = SinkSession::gated_uni(gate.consume()); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut subscriber = track.subscribe(None); let mut old = track.append_group().unwrap(); let mut frame = old @@ -3909,7 +3941,7 @@ mod group_priority_test { }, kio::Producer::new(0).consume(), group, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft19, GroupSlice::default(), ); @@ -3953,7 +3985,11 @@ mod subscribe_cursor_test { let log = Log::default(); let session = SinkSession::new(log.clone()); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "video", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); for sequence in 0..4 { let mut group = track.create_group(group::Info { sequence }).unwrap(); group @@ -3971,7 +4007,7 @@ mod subscribe_cursor_test { RequestId(1), Version::Draft14, ServeRange::default(), - Some(Timescale::default()), + Some(Timescale::MILLI), ); kio::wait(|waiter| serve.poll(waiter)).await.unwrap(); @@ -4009,7 +4045,7 @@ mod serve_tests { fn serve(version: Version) -> Serve { let origin = crate::origin::Config::new(crate::Hop::new(1).unwrap()).produce(); let broadcast = origin.publish("room", crate::origin::Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let session = ScriptedSession::per_stream(vec![Vec::new()]); let log = session.log.clone(); @@ -4848,7 +4884,7 @@ mod serve_tests { Version::Draft16 | Version::Draft17 => ietf::Properties::default(), // Only draft-20 can carry the opt-out. _ if !wanted && Filter::is_draft20(version) => ietf::Properties::default(), - _ => track_properties(&track::Info::default(), true), + _ => track_properties(&track::Info::timed(), true), }; assert_eq!( ok, @@ -5047,7 +5083,7 @@ mod serve_tests { ietf::encode_object_time( &mut Encoder::new(&mut properties, version.into()), stamp, - track::Info::default().timescale.expect("a timed track"), + track::Info::timed().timescale.expect("a timed track"), version, ) .unwrap(); @@ -5571,7 +5607,7 @@ mod serve_tests { } let properties = match ietf::Properties::sends_timescale(version) { false => ietf::Properties::default(), - true => track_properties(&track::Info::default(), true), + true => track_properties(&track::Info::timed(), true), }; assert_eq!(ok.properties, properties, "{version}: wrong properties"); assert_eq!( @@ -6104,6 +6140,7 @@ mod serve_tests { let info = track::Info { max_age: Some(Duration::from_secs(5)), priority: 200, + timescale: Some(crate::Timescale::MILLI), ..Default::default() }; let stamp = crate::Timestamp::from_millis(123_456).unwrap(); @@ -6562,7 +6599,7 @@ mod serve_tests { for version in FETCH_DRAFTS { let origin = crate::origin::Config::new(crate::Hop::new(1).unwrap()).produce(); let broadcast = origin.publish("room", crate::origin::Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track.create_group(group::Info { sequence: 0 }).unwrap(); group.write_frame(timestamp(), b"0-0".to_vec()).unwrap(); group.finish().unwrap(); @@ -6655,7 +6692,7 @@ mod serve_tests { ] { let origin = crate::origin::Config::new(crate::Hop::new(1).unwrap()).produce(); let broadcast = origin.publish("room", crate::origin::Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track.create_group(group::Info { sequence: 0 }).unwrap(); group.write_frame(timestamp(), PAYLOAD.to_vec()).unwrap(); settle().await; @@ -6966,7 +7003,11 @@ mod serve_tests { async fn serve_slice(slice: GroupSlice) -> Vec { let log = Log::default(); let session = SinkSession::new(log.clone()); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut group = track.create_group(group::Info { sequence: 0 }).unwrap(); for payload in [b"aa", b"bb", b"cc", b"dd"] { group.write_frame(timestamp(), payload.as_slice()).unwrap(); @@ -6979,7 +7020,7 @@ mod serve_tests { header(), kio::Producer::new(0).consume(), consumer, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft20, slice, ); @@ -7021,7 +7062,11 @@ mod serve_tests { let log = crate::lite::test_transport::Log::default(); let session = SinkSession::new(log.clone()); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "test", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(None); let mut group = track.append_group().unwrap(); @@ -7105,7 +7150,11 @@ mod tests { /// subscription must preserve everything the producer still retains. #[test] fn serving_subscription_keeps_retained_backlog() { - let producer = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "video", None); + let producer = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); for millis in [0, 1000] { let mut group = producer.append_group().unwrap(); group @@ -9260,7 +9309,11 @@ mod range_tests { /// Clamping it to the live edge would serve a group outside the requested range. #[moq_net_sim::test] async fn a_future_start_is_not_clamped_to_the_live_edge() { - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "video", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); track .create_group(group::Info { sequence: 7 }) .unwrap() @@ -9411,7 +9464,11 @@ mod range_tests { /// one exists, so the earlier group is deliberately left unfinished here. #[test] fn an_empty_newest_group_walks_back_for_the_largest() { - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "video", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); let mut first = track.create_group(group::Info { sequence: 0 }).unwrap(); for _ in 0..3 { first @@ -9438,7 +9495,11 @@ mod range_tests { /// order rather than decrementing by one. #[test] fn the_walkback_crosses_a_gap_in_the_numbering() { - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "video", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); let mut first = track.create_group(group::Info { sequence: 0 }).unwrap(); first .write_frame(crate::Timestamp::from_millis(0).unwrap(), b"frame".as_slice()) diff --git a/rs/moq-net/src/ietf/subscriber.rs b/rs/moq-net/src/ietf/subscriber.rs index 35eee6993ad..793f349f855 100644 --- a/rs/moq-net/src/ietf/subscriber.rs +++ b/rs/moq-net/src/ietf/subscriber.rs @@ -5073,7 +5073,11 @@ mod tests { state.subscribes.insert( *request_id, TrackState::new( - track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), *name, None), + track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + *name, + crate::track::Info::timed(), + ), Path::new(broadcast).to_owned(), kio::Producer::new(Fill::Done), None, @@ -5113,17 +5117,17 @@ mod tests { let mut consumer = { let mut state = subscriber.state.lock(); let track = state.subscribes.get_mut(&RequestId(11)).unwrap(); - track.timescale = Some(Timescale::default()); + track.timescale = Some(Timescale::MILLI); track.producer.as_ref().unwrap().subscribe(None) }; - let timestamp = crate::Timestamp::new(96_000, Timescale::default()).unwrap(); + let timestamp = crate::Timestamp::new(96_000, Timescale::MILLI).unwrap(); let datagram = |alias: u64, group_id: u64, object_id: Option, body: ietf::DatagramBody| { let mut properties = Vec::new(); ietf::encode_object_time( &mut crate::coding::Encoder::new(&mut properties, Version::Draft19.into()), timestamp, - Timescale::default(), + Timescale::MILLI, Version::Draft19, ) .unwrap(); @@ -5180,15 +5184,15 @@ mod tests { let mut consumer = { let mut state = subscriber.state.lock(); let track = state.subscribes.get_mut(&RequestId(11)).unwrap(); - track.timescale = Some(Timescale::default()); + track.timescale = Some(Timescale::MILLI); track.producer.as_ref().unwrap().subscribe(None) }; let datagram = |group_id: u64| { let mut properties = Vec::new(); ietf::encode_object_time( &mut crate::coding::Encoder::new(&mut properties, Version::Draft19.into()), - crate::Timestamp::new(96_000, Timescale::default()).unwrap(), - Timescale::default(), + crate::Timestamp::new(96_000, Timescale::MILLI).unwrap(), + Timescale::MILLI, Version::Draft19, ) .unwrap(); @@ -5762,7 +5766,11 @@ mod tests { TrackState { alias: Some(7), ..TrackState::new( - track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "video", None), + track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ), Path::new("broadcast").to_owned(), kio::Producer::new(Fill::Done), None, diff --git a/rs/moq-net/src/lite/publisher.rs b/rs/moq-net/src/lite/publisher.rs index 12c9b515720..a656e5afc8a 100644 --- a/rs/moq-net/src/lite/publisher.rs +++ b/rs/moq-net/src/lite/publisher.rs @@ -1800,12 +1800,12 @@ impl Request for TrackInfoServe { let info = ready!(self.querying.poll_ok(waiter))?; // TRACK_INFO only flows on Lite05+ (the encode errors otherwise), where the - // timescale is mandatory. An untimed track declares the default, the scale its + // timescale is mandatory. An untimed track declares milliseconds, the scale its // frames' send times go out at (see `wire_timestamp`). writer.buffer(&lite::TrackInfo { priority: info.priority, max_age: info.max_age, - timescale: info.timescale.unwrap_or_default(), + timescale: info.timescale.unwrap_or(crate::Timescale::MILLI), })?; Poll::Ready(Ok(ControlFlow::Break(()))) } @@ -1912,7 +1912,7 @@ impl Request for SubscribeServe { // "don't emit the prefix" (the frames still carry timestamps in the // model, just not on this wire). let timescale = if shared.version.has_track_stream() { - Some(track.info().timescale.unwrap_or_default()) + Some(track.info().timescale.unwrap_or(crate::Timescale::MILLI)) } else { None }; @@ -2075,7 +2075,7 @@ impl Request for FetchServe { // FETCH is gated to lite-05+, which learned the track timescale via // TRACK_INFO. let timescale = if shared.version.has_track_stream() { - Some(group.timescale().unwrap_or_default()) + Some(group.timescale().unwrap_or(crate::Timescale::MILLI)) } else { None }; @@ -2167,7 +2167,7 @@ mod test { use crate::{Timestamp, broadcast}; fn track_producer(name: impl Into>) -> track::Producer { - track::Producer::new(Arc::new(broadcast::Info::default()), name, None) + track::Producer::new(Arc::new(broadcast::Info::default()), name, crate::track::Info::timed()) } /// A pre-06 wire is served from the live edge when it names no start: those drafts @@ -2831,7 +2831,7 @@ fn buffer_frame_info( /// A frame or datagram timestamp as its raw value at the wire `timescale`. /// /// No lite version encodes an absent timestamp yet, so a payload on an untimed track -/// carries its send time on `runtime` instead, at the default scale its TRACK_INFO +/// carries its send time on `runtime` instead, at the millisecond scale its TRACK_INFO /// declares. A timed payload is already at the track's timescale. fn wire_timestamp( timestamp: Option, @@ -3075,8 +3075,11 @@ impl Subscription { /// Returns whether it was handed to the transport. fn serve_datagram(&mut self, datagram: crate::Datagram) -> bool { // Datagrams are lite-05+, which always declares a timescale in TRACK_INFO. - let Ok(timestamp) = wire_timestamp(datagram.timestamp, self.timescale.unwrap_or_default(), &self.runtime) - else { + let Ok(timestamp) = wire_timestamp( + datagram.timestamp, + self.timescale.unwrap_or(crate::Timescale::MILLI), + &self.runtime, + ) else { return false; }; let body = lite::Datagram { @@ -3889,7 +3892,11 @@ mod serve_group_test { /// only a subscriber that asked for a partial group may receive one. #[test] fn position_group_skips_a_missing_head() { - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "video", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); let mut group = track.create_group(group::Info { sequence: 3 }).unwrap(); group.start_at(5).unwrap(); group.write_frame(Timestamp::ZERO, b"tail".to_vec()).unwrap(); @@ -3918,7 +3925,11 @@ mod serve_group_test { /// The end bound caps the end group and leaves the others whole. #[test] fn position_group_caps_the_end_group() { - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "video", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); let mut group = track.create_group(group::Info { sequence: 7 }).unwrap(); for i in 0..4u8 { group.write_frame(Timestamp::ZERO, vec![i]).unwrap(); @@ -3965,12 +3976,16 @@ mod serve_group_test { track_priority: track_priority.consume(), track_priority_seen: 0, version: Version::Lite06, - timescale: Some(crate::Timescale::default()), + timescale: Some(crate::Timescale::MILLI), runtime: crate::time::Clock::sim(), opens: Default::default(), }; - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut group = track.create_group(group::Info { sequence: 0 }).unwrap(); group .write_frame(Timestamp::from_millis(0).unwrap(), b"hello".as_slice()) @@ -4005,12 +4020,16 @@ mod serve_group_test { track_priority: track_priority.consume(), track_priority_seen: 0, version: Version::Lite06, - timescale: Some(crate::Timescale::default()), + timescale: Some(crate::Timescale::MILLI), runtime: crate::time::Clock::sim(), opens: Default::default(), }; - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut subscriber = track.subscribe(None); let mut old = track.append_group().unwrap(); old.write_frame(Timestamp::ZERO, b"old".as_slice()).unwrap(); @@ -4049,12 +4068,16 @@ mod serve_group_test { track_priority: track_priority.consume(), track_priority_seen: 0, version: Version::Lite06, - timescale: Some(crate::Timescale::default()), + timescale: Some(crate::Timescale::MILLI), runtime: crate::time::Clock::sim(), opens: Default::default(), }; - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut subscriber = track.subscribe(None); let mut old = track.append_group().unwrap(); let mut frame = old @@ -4111,12 +4134,16 @@ mod serve_group_test { track_priority: track_priority.consume(), track_priority_seen: 0, version: Version::Lite06, - timescale: Some(crate::Timescale::default()), + timescale: Some(crate::Timescale::MILLI), runtime: crate::time::Clock::sim(), opens: Default::default(), }; - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut subscriber = track.subscribe(None); let mut old = track.append_group().unwrap(); old.write_frame(Timestamp::ZERO, b"old".as_slice()).unwrap(); @@ -4182,12 +4209,16 @@ mod serve_group_test { track_priority: track_priority.consume(), track_priority_seen: 0, version: Version::Lite06, - timescale: Some(crate::Timescale::default()), + timescale: Some(crate::Timescale::MILLI), runtime: crate::time::Clock::sim(), opens: Default::default(), }; - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut group = track.create_group(group::Info { sequence: 0 }).unwrap(); group .write_frame(Timestamp::from_millis(0).unwrap(), b"hello".as_slice()) @@ -4225,12 +4256,16 @@ mod serve_group_test { track_priority: track_priority.consume(), track_priority_seen: 0, version: Version::Lite06, - timescale: Some(crate::Timescale::default()), + timescale: Some(crate::Timescale::MILLI), runtime: crate::time::Clock::sim(), opens: Default::default(), }; - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut subscriber = track.subscribe(None); let mut old = track.append_group().unwrap(); old.write_frame(Timestamp::ZERO, b"old".as_slice()).unwrap(); @@ -4267,12 +4302,16 @@ mod serve_group_test { track_priority: track_priority.consume(), track_priority_seen: 0, version: Version::Lite06, - timescale: Some(crate::Timescale::default()), + timescale: Some(crate::Timescale::MILLI), runtime: crate::time::Clock::sim(), opens: Default::default(), }; - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut group = track.create_group(group::Info { sequence: 0 }).unwrap(); group.write_frame(Timestamp::ZERO, b"hello".as_slice()).unwrap(); let consumer = group.consume(); @@ -4321,7 +4360,7 @@ mod serve_group_test { track_priority: track_priority.consume(), track_priority_seen: 0, version, - timescale: Some(crate::Timescale::default()), + timescale: Some(crate::Timescale::MILLI), runtime: crate::time::Clock::sim(), opens: Default::default(), }; @@ -4353,7 +4392,11 @@ mod serve_group_test { type Task = Box Poll<()>>; - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(track::Subscription::default().with_max_delay(Duration::from_secs(3600))); let log = Log::default(); let (mut run, mut writer, _) = lite07_run(SinkSession::new(log.clone()).with_unacked_fin(), subscriber); @@ -4398,7 +4441,11 @@ mod serve_group_test { /// group the track never produced has no stream and is not counted. #[moq_net_sim::test] async fn lite07_end_counts_the_streams_opened() { - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(None); let (mut run, mut writer, log) = lite07_run(SinkSession::new(Log::default()), subscriber); let mut run = std::pin::pin!(kio::wait(move |waiter| run.poll(&mut writer, waiter))); @@ -4437,7 +4484,11 @@ mod serve_group_test { #[moq_net_sim::test] async fn the_end_drops_the_sequences_never_served() { for version in [Version::Lite05, Version::Lite06] { - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(None); let (mut run, mut writer, log) = lite_run(version, SinkSession::new(Log::default()), subscriber); { @@ -4459,7 +4510,11 @@ mod serve_group_test { #[moq_net_sim::test] async fn the_end_drops_below_a_lowered_start() { let version = Version::Lite06; - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscription = track::Subscription::default() .with_start(track::Position::group(5)) .with_max_delay(Duration::from_secs(60)); @@ -4499,7 +4554,11 @@ mod serve_group_test { #[moq_net_sim::test] async fn the_end_drops_below_a_lowered_start_after_the_grace() { let version = Version::Lite06; - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscription = track::Subscription::default() .with_start(track::Position::group(5)) .with_max_delay(Duration::from_secs(1)); @@ -4536,7 +4595,11 @@ mod serve_group_test { #[moq_net_sim::test] async fn the_end_drops_below_a_start_raised_then_lowered() { let version = Version::Lite06; - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscription = track::Subscription::default() .with_start(track::Position::group(0)) .with_max_delay(Duration::from_secs(1)); @@ -4576,7 +4639,11 @@ mod serve_group_test { /// floor re-pins the cursor there: a group created below it afterwards is not served. #[moq_net_sim::test] async fn a_pre06_update_dropping_the_floor_repins_to_the_latest_group() { - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe( track::Subscription::default() .with_start(track::Position::group(0)) @@ -4611,7 +4678,11 @@ mod serve_group_test { /// track produces is served, even below the dropped floor. #[moq_net_sim::test] async fn a_pre06_update_dropping_the_floor_on_an_empty_track_serves_the_next_group() { - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe( track::Subscription::default() .with_start(track::Position::group(5)) @@ -4642,7 +4713,11 @@ mod serve_group_test { #[moq_net_sim::test] async fn the_end_drops_only_gaps_within_the_grace() { let version = Version::Lite06; - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(track::Subscription::default().with_max_delay(Duration::from_secs(1))); let (mut run, mut writer, log) = lite_run(version, SinkSession::new(Log::default()), subscriber); { @@ -4692,7 +4767,7 @@ mod serve_group_test { track_priority: track_priority.consume(), track_priority_seen: 0, version: Version::Lite07, - timescale: Some(crate::Timescale::default()), + timescale: Some(crate::Timescale::MILLI), runtime: crate::time::Clock::sim(), opens: opens.clone(), }; @@ -4738,7 +4813,11 @@ mod serve_group_test { /// exists, and a catalog never publishes the next one. #[moq_net_sim::test] async fn a_widening_update_serves_the_finished_group_it_had_skipped() { - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); write_group(&mut track, 0, 0); let mut relay = RelayRun::new(&mut track, 1); relay.settle(); @@ -4760,7 +4839,11 @@ mod serve_group_test { /// arrived before the served groups, so the cursor has moved past it. #[moq_net_sim::test] async fn a_widening_update_does_not_rewind_past_served_groups() { - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); for sequence in 0..3 { write_group(&mut track, sequence, sequence); } @@ -4786,7 +4869,11 @@ mod serve_group_test { /// the floor it raised: resolving the start from the held group must not lower it. #[moq_net_sim::test] async fn held_first_group_keeps_an_updated_floor() { - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut relay = RelayRun::new(&mut track, 0); write_group(&mut track, 5, 0); @@ -4818,7 +4905,11 @@ mod serve_group_test { /// a newer group arriving first must not resolve the start past it. #[moq_net_sim::test] async fn held_first_group_resolves_to_the_floor_under_the_source() { - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut relay = RelayRun::new(&mut track, 5); write_group(&mut track, 6, 6); @@ -4837,7 +4928,11 @@ mod serve_group_test { /// SUBSCRIBE_START must not name it, even where the source's feed starts. #[moq_net_sim::test] async fn held_first_group_starts_past_a_skipped_head() { - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let mut relay = RelayRun::new(&mut track, 5); // Group 5's first frame is gone. @@ -4859,7 +4954,11 @@ mod serve_group_test { /// A track that ends without a group still ends the subscription, with no stream owed. #[moq_net_sim::test] async fn lite07_end_counts_zero_streams() { - let track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(None); track.finish().unwrap(); @@ -4874,7 +4973,11 @@ mod serve_group_test { #[moq_net_sim::test] async fn lite07_end_waits_for_every_stream_to_open() { let gate = kio::Producer::new(false); - let mut track = track::Producer::new(Arc::new(broadcast::Info::default()), "test", None); + let mut track = track::Producer::new( + Arc::new(broadcast::Info::default()), + "test", + crate::track::Info::timed(), + ); let subscriber = track.subscribe(None); write_group(&mut track, 0, 0); @@ -5414,7 +5517,7 @@ mod tests { let case = format!("{version:?}, {close:?}, blocked {blocked}"); let origin = crate::origin::Config::new(crate::Hop::new(1).unwrap()).produce(); let broadcast = origin.create_broadcast("room").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let peer_setup = crate::lite::PeerSetup::default(); @@ -5558,7 +5661,7 @@ mod tests { .now_or_never() .expect("track requested") .unwrap(); - let _track = request.accept(None); + let _track = request.accept(crate::track::Info::timed()); assert!(!drive(&mut serve).await, "the subscription ended"); let RequestState::Serve(SubscribeServe::Run(run)) = &serve.state else { panic!("the subscription is not running"); @@ -5634,7 +5737,7 @@ mod tests { .now_or_never() .expect("track requested") .unwrap() - .accept(None); + .accept(crate::track::Info::timed()); assert!(!drive(&mut serve).await, "the subscription ended"); let RequestState::Serve(SubscribeServe::Run(run)) = &serve.state else { panic!("the subscription is not running"); diff --git a/rs/moq-net/src/lite/subscriber.rs b/rs/moq-net/src/lite/subscriber.rs index e0b7fafd506..d0bc17833ed 100644 --- a/rs/moq-net/src/lite/subscriber.rs +++ b/rs/moq-net/src/lite/subscriber.rs @@ -512,8 +512,8 @@ impl Subscriber { return Ok(()); }; - // Datagrams are lite-05+, which always negotiates a timescale; default defensively. - let scale = entry.timescale.unwrap_or_default(); + // Datagrams are lite-05+, which always negotiates a timescale; fall back to milliseconds defensively. + let scale = entry.timescale.unwrap_or(crate::Timescale::MILLI); let timestamp = Timestamp::new(dg.timestamp, scale).map_err(|_| Error::BoundsExceeded(crate::coding::BoundsExceeded))?; @@ -1807,13 +1807,17 @@ mod tests { going_away: Default::default(), auth: crate::auth::Handle::new(false), }); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "video", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); let mut consumer = track.consume().subscribe(None).await.unwrap(); subscriber.subscribes.lock().insert( 7, TrackEntry { producer: track, - timescale: Some(Timescale::default()), + timescale: Some(Timescale::MILLI), tail: Default::default(), }, ); @@ -1859,13 +1863,17 @@ mod tests { auth: crate::auth::Handle::new(false), }); - let mut track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "video", None); + let mut track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); track.finish_at(3).unwrap(); subscriber.subscribes.lock().insert( 7, TrackEntry { producer: track.clone(), - timescale: Some(Timescale::default()), + timescale: Some(Timescale::MILLI), tail: Default::default(), }, ); @@ -1920,7 +1928,11 @@ mod tests { auth: crate::auth::Handle::new(false), }); - let track = track::Producer::new(std::sync::Arc::new(crate::broadcast::Info::default()), "video", None); + let track = track::Producer::new( + std::sync::Arc::new(crate::broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ); let mut cached = track.receive_group(group::Info { sequence: 3 }).unwrap(); cached.write_frame(Timestamp::ZERO, &b"x"[..]).unwrap(); cached.finish().unwrap(); @@ -1929,7 +1941,7 @@ mod tests { 7, TrackEntry { producer: track.clone(), - timescale: Some(Timescale::default()), + timescale: Some(Timescale::MILLI), tail: tail.clone(), }, ); @@ -1989,7 +2001,7 @@ mod tests { }; let broadcast = crate::broadcast::Info::new().produce(); let request = broadcast.reserve_track("video").unwrap(); - let serving = ServeLoop::new(&serve, request, Default::default(), Some(Timescale::default())); + let serving = ServeLoop::new(&serve, request, crate::track::Info::timed(), Some(Timescale::MILLI)); let mut group = serving.serving.create_group(group::Info { sequence: 2 }).unwrap(); group.write_frame(crate::Timestamp::ZERO, b"2".as_slice()).unwrap(); group.finish().unwrap(); @@ -2046,7 +2058,7 @@ mod tests { }; let broadcast = crate::broadcast::Info::new().produce(); let request = broadcast.reserve_track("video").unwrap(); - let serving = ServeLoop::new(&serve, request, Default::default(), Some(Timescale::default())); + let serving = ServeLoop::new(&serve, request, crate::track::Info::timed(), Some(Timescale::MILLI)); let mut reader = broadcast .consume() .track("video") @@ -2139,13 +2151,15 @@ mod tests { let broadcast = crate::broadcast::Info::new().produce(); // The broadcast keeps only a weak handle, so the map below owns the only strong // `track::Producer`: dropping it is what ends the track. - let producer = broadcast.create_track("datagrams", None).unwrap(); + let producer = broadcast + .create_track("datagrams", crate::track::Info::timed()) + .unwrap(); let mut received = producer.subscribe(None); subscriber.subscribes.lock().insert( 7, TrackEntry { producer, - timescale: Some(Timescale::default()), + timescale: Some(Timescale::MILLI), tail: Default::default(), }, ); @@ -2330,14 +2344,12 @@ mod tests { }; let broadcast = crate::broadcast::Info::new().produce(); - let mut producer = broadcast.create_track("catalog.json", None).unwrap(); + let mut producer = broadcast + .create_track("catalog.json", crate::track::Info::timed()) + .unwrap(); let mut sub = Sub::None; - let mut establish = std::pin::pin!(serve.establish( - &mut producer, - &mut sub, - Subscription::default(), - Some(Timescale::default()), - )); + let mut establish = + std::pin::pin!(serve.establish(&mut producer, &mut sub, Subscription::default(), Some(Timescale::MILLI),)); // Parked on the first write: the stream is open and nothing has been sent yet. assert!(futures::poll!(establish.as_mut()).is_pending()); @@ -2400,12 +2412,8 @@ mod tests { let broadcast = crate::broadcast::Info::new().produce(); let request = broadcast.reserve_track("audio").unwrap(); - let mut serving = ServeLoop::new(&serve, request, Default::default(), Some(Timescale::default())); - let establish = serve.prepare_establish( - &mut serving.serving, - Subscription::default(), - Some(Timescale::default()), - ); + let mut serving = ServeLoop::new(&serve, request, Default::default(), Some(Timescale::MILLI)); + let establish = serve.prepare_establish(&mut serving.serving, Subscription::default(), Some(Timescale::MILLI)); serving.mode = ServeMode::Establish(establish); let gate_run = serve.gate(); let mut running = TrackServeRun { @@ -2456,7 +2464,9 @@ mod tests { auth: crate::auth::Handle::new(false), }); let broadcast = crate::broadcast::Info::new().produce(); - let producer = broadcast.create_track("catalog.json", None).unwrap(); + let producer = broadcast + .create_track("catalog.json", crate::track::Info::timed()) + .unwrap(); Self { serve: TrackServe { @@ -2505,7 +2515,7 @@ mod tests { &mut sub, Some(mid_group_demand()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .expect("an older peer must not fail the subscribe"); @@ -2538,7 +2548,7 @@ mod tests { &mut sub, Some(mid_group_demand()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2608,7 +2618,7 @@ mod tests { let empty = Subscription::default().with_end(Position::group(0)); h.serve - .handle_subscription(&mut h.producer, &mut sub, Some(empty), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, Some(empty), true, Some(Timescale::MILLI)) .await .unwrap(); @@ -2629,7 +2639,7 @@ mod tests { &mut sub, Some(Subscription::default()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2638,7 +2648,7 @@ mod tests { let empty = Subscription::default().with_end(Position::group(0)); h.serve - .handle_subscription(&mut h.producer, &mut sub, Some(empty), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, Some(empty), true, Some(Timescale::MILLI)) .await .unwrap(); @@ -2661,7 +2671,7 @@ mod tests { .with_start(Position::group(5)) .with_end(Position::group(5)); h.serve - .handle_subscription(&mut h.producer, &mut sub, Some(empty), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, Some(empty), true, Some(Timescale::MILLI)) .await .unwrap(); @@ -2682,7 +2692,7 @@ mod tests { &mut sub, Some(Subscription::default()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2693,7 +2703,7 @@ mod tests { .with_start(Position::group(5)) .with_end(Position::group(5)); h.serve - .handle_subscription(&mut h.producer, &mut sub, Some(empty), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, Some(empty), true, Some(Timescale::MILLI)) .await .unwrap(); @@ -2738,7 +2748,7 @@ mod tests { &mut sub, Some(Subscription::default()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2751,7 +2761,7 @@ mod tests { &mut sub, Some(mid_group_demand()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .expect("a downstream frame offset must not tear down an older upstream"); @@ -2780,7 +2790,7 @@ mod tests { // Establish from group 3; the peer's START is considered in flight. h.serve - .handle_subscription(&mut h.producer, &mut sub, demand(3), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, demand(3), true, Some(Timescale::MILLI)) .await .unwrap(); assert!(applies(&sub), "a fresh subscription accepts its START"); @@ -2796,7 +2806,7 @@ mod tests { .with_end(Position::group(9)), ), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2804,7 +2814,7 @@ mod tests { // The start moves: a buffered START is stale while it sits elsewhere. h.serve - .handle_subscription(&mut h.producer, &mut sub, demand(8), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, demand(8), true, Some(Timescale::MILLI)) .await .unwrap(); assert!(!applies(&sub), "a moved start must invalidate a buffered START"); @@ -2812,7 +2822,7 @@ mod tests { // The start returns: the declaration matches the demand again, so the // publisher's skip (it sends no replacement START) must land. h.serve - .handle_subscription(&mut h.producer, &mut sub, demand(3), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, demand(3), true, Some(Timescale::MILLI)) .await .unwrap(); assert!(applies(&sub), "demand returning restores the START"); @@ -2831,21 +2841,21 @@ mod tests { // Establish from group 5: the floor tracks the request until START lands. h.serve - .handle_subscription(&mut h.producer, &mut sub, demand(5), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, demand(5), true, Some(Timescale::MILLI)) .await .unwrap(); assert_eq!(h.producer.start_sequence(), Some(5)); // Forward: a reader waiting in [5, 8) must fail over, not stall. h.serve - .handle_subscription(&mut h.producer, &mut sub, demand(8), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, demand(8), true, Some(Timescale::MILLI)) .await .unwrap(); assert_eq!(h.producer.start_sequence(), Some(8)); // Backward: the reopened range must stop being a permanent miss. h.serve - .handle_subscription(&mut h.producer, &mut sub, demand(3), true, Some(Timescale::default())) + .handle_subscription(&mut h.producer, &mut sub, demand(3), true, Some(Timescale::MILLI)) .await .unwrap(); assert_eq!(h.producer.start_sequence(), Some(3)); @@ -2858,7 +2868,7 @@ mod tests { &mut sub, Some(Subscription::default()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -3272,7 +3282,7 @@ mod tests { let assigned = crate::Hop::new(777).unwrap(); let local = origin.publish("room/host", origin::Route::default()).unwrap(); - let track = local.create_track("video", None).unwrap(); + let track = local.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"local".as_ref()).unwrap(); group.finish().unwrap(); @@ -5507,9 +5517,10 @@ impl kio::Task for FetchServeRun { // it. The track::Info only takes effect if the track isn't accepted yet // (a fetch with no live subscription); otherwise the group inherits the // accepted timescale. Relay-served FETCH is lite-05+, so `timescale` is - // `Some`; fall back to the default scale defensively rather than + // `Some`; fall back to milliseconds defensively rather than // panicking. - let group_info = track::Info::default().with_timescale(self.timescale.unwrap_or_default()); + let group_info = + track::Info::default().with_timescale(self.timescale.unwrap_or(crate::Timescale::MILLI)); // The joined fetches pick the group up from the cache only when next // polled, so their demand outlives the request until then. let joined = request.result.clone(); diff --git a/rs/moq-net/src/lite/track.rs b/rs/moq-net/src/lite/track.rs index c34edfef269..01f6b5c9098 100644 --- a/rs/moq-net/src/lite/track.rs +++ b/rs/moq-net/src/lite/track.rs @@ -199,20 +199,14 @@ mod test { assert_eq!(got.timescale, Timescale::MICRO); } - #[test] - fn track_info_default_timescale_roundtrips() { - let mut info = info_sample(); - info.timescale = Timescale::default(); - assert_eq!(info_roundtrip(Version::Lite05, &info).timescale, Timescale::MILLI); - } - #[test] fn track_info_defaults_match_cross_language_wire_bytes() { let info = crate::track::Info::default(); let info = TrackInfo { priority: info.priority, max_age: info.max_age, - timescale: info.timescale.unwrap(), + // The default track is untimed, which declares milliseconds like the publisher. + timescale: info.timescale.unwrap_or(Timescale::MILLI), }; let mut buf = Vec::new(); info.encode(&mut Encoder::new(&mut buf, Version::Lite05.into()), Version::Lite05) diff --git a/rs/moq-net/src/model/bandwidth.rs b/rs/moq-net/src/model/bandwidth.rs index c49f104c23b..816719aaa26 100644 --- a/rs/moq-net/src/model/bandwidth.rs +++ b/rs/moq-net/src/model/bandwidth.rs @@ -632,7 +632,7 @@ mod tests { fn track(priority: u8) -> (broadcast::Producer, track::Producer) { let broadcast = broadcast::Info::default().produce(); let track = broadcast - .create_track("t", track::Info::default().with_priority(priority)) + .create_track("t", track::Info::timed().with_priority(priority)) .unwrap(); (broadcast, track) } diff --git a/rs/moq-net/src/model/broadcast.rs b/rs/moq-net/src/model/broadcast.rs index 2b5742f8edd..e537218d26b 100644 --- a/rs/moq-net/src/model/broadcast.rs +++ b/rs/moq-net/src/model/broadcast.rs @@ -285,7 +285,7 @@ impl Producer { /// Produce a new track and insert it into the broadcast. /// /// Pass a name and an optional [`track::Info`], so a bare name works: - /// `create_track("video", None)`. + /// `create_track("video", None)` creates an untimed track. pub fn create_track( &self, name: impl Into>, @@ -1076,7 +1076,9 @@ mod test { let producer = Info::new().produce(); let name = producer.unique_name(".opus"); assert_eq!(name, "0.opus"); - let track = producer.create_track(name.clone(), None).unwrap(); + let track = producer + .create_track(name.clone(), crate::track::Info::timed()) + .unwrap(); assert_eq!(producer.unique_name(".opus"), "1.opus"); drop(track); } @@ -1085,7 +1087,7 @@ mod test { fn unique_names_survive_closed_track_pruning() { let producer = Info::new().produce(); let consumer = producer.consume(); - let track = producer.unique_track(".opus", None).unwrap(); + let track = producer.unique_track(".opus", crate::track::Info::timed()).unwrap(); assert_eq!(track.name(), "0.opus"); drop(track); assert!(matches!(consumer.track_inner("0.opus"), Err(Error::NotFound))); @@ -1095,7 +1097,7 @@ mod test { #[test] fn unique_name_skips_a_live_collision() { let producer = Info::new().produce(); - let track = producer.create_track("0.opus", None).unwrap(); + let track = producer.create_track("0.opus", crate::track::Info::timed()).unwrap(); assert_eq!(producer.unique_name(".opus"), "1.opus"); drop(track); assert_eq!(producer.unique_name(".opus"), "2.opus"); @@ -1143,7 +1145,7 @@ mod test { demand.unused().await.unwrap(); // Producing alone is not demand. - let _track = producer.create_track("a", None).unwrap(); + let _track = producer.create_track("a", crate::track::Info::timed()).unwrap(); assert!(!demand.is_used()); // A consumer appearing wakes a parked `used`. @@ -1188,7 +1190,7 @@ mod test { let producer = Info::new().produce(); let consumer = producer.consume(); let watched = consumer.demand(); - let _video = producer.create_track("video", None).unwrap(); + let _video = producer.create_track("video", crate::track::Info::timed()).unwrap(); let track = consumer.track("video").unwrap(); assert!(watched.is_used()); @@ -1219,7 +1221,7 @@ mod test { let mut producer = Info::new().produce(); // Create the track before any consumer exists. - let track1 = producer.assert_create_track("track1", None); + let track1 = producer.assert_create_track("track1", crate::track::Info::timed()); track1.append_group().unwrap(); let consumer = producer.consume(); @@ -1228,7 +1230,7 @@ mod test { let mut track1_sub = consumer.track("track1").unwrap().subscribe(None).await.unwrap(); track1_sub.assert_group(); - let track2 = producer.assert_create_track("track2", None); + let track2 = producer.assert_create_track("track2", crate::track::Info::timed()); let consumer2 = producer.consume(); let mut track2_consumer = consumer2.track("track2").unwrap().subscribe(None).await.unwrap(); @@ -1248,7 +1250,7 @@ mod test { consumer.assert_not_closed(); // Create a new track and insert it into the broadcast (resolves immediately). - let track1 = producer.assert_create_track("track1", None); + let track1 = producer.assert_create_track("track1", crate::track::Info::timed()); let mut track1c = consumer.track("track1").unwrap().subscribe(None).await.unwrap(); // A track nobody publishes stays pending until accepted. @@ -1309,7 +1311,7 @@ mod test { assert_eq!(request.name(), "track1"); // Accept it, which resolves both waiting subscribers. - let track3 = request.accept(None); + let track3 = request.accept(crate::track::Info::timed()); let mut track1 = track1_fut.await.unwrap(); let mut track2 = track2_fut.await.unwrap(); @@ -1339,7 +1341,7 @@ mod test { // Subscribe to a track and serve it. let track1_fut = subscribe_pending!(consumer, "track1"); - let producer1 = broadcast.assert_request().accept(None); + let producer1 = broadcast.assert_request().accept(crate::track::Info::timed()); let mut track1 = track1_fut.await.unwrap(); // Close the producer (simulating publisher disconnect). @@ -1352,7 +1354,7 @@ mod test { // Subscribe again to the same track: should get a NEW producer, not the stale one. let track2_fut = subscribe_pending!(consumer, "track1"); - let producer2 = broadcast.assert_request().accept(None); + let producer2 = broadcast.assert_request().accept(crate::track::Info::timed()); let mut track2 = track2_fut.await.unwrap(); track2.assert_not_closed(); track2.assert_not_clone(&track1); @@ -1372,7 +1374,7 @@ mod test { let consumer = producer.consume(); let track = consumer.track("media").unwrap(); - let mut first = dynamic.assert_request().accept(None); + let mut first = dynamic.assert_request().accept(crate::track::Info::timed()); assert_eq!(first.append_group().unwrap().sequence, 0); assert_eq!(first.append_datagram(crate::Timestamp::ZERO, b"x").unwrap(), 1); first.create_group(crate::group::Info { sequence: 8 }).unwrap(); @@ -1381,19 +1383,19 @@ mod test { drop((first, track)); let track = consumer.track("media").unwrap(); - let mut second = dynamic.assert_request().accept(None); + let mut second = dynamic.assert_request().accept(crate::track::Info::timed()); assert_eq!(second.append_group().unwrap().sequence, 13); assert_eq!(second.append_datagram(crate::Timestamp::ZERO, b"x").unwrap(), 14); second.finish().unwrap(); drop((second, track)); // A track the producer creates itself continues the same namespace. - let third = producer.create_track("media", None).unwrap(); + let third = producer.create_track("media", crate::track::Info::timed()).unwrap(); assert_eq!(third.append_group().unwrap().sequence, 15); let next = Info::new().produce(); assert_eq!( - next.create_track("media", None) + next.create_track("media", crate::track::Info::timed()) .unwrap() .append_group() .unwrap() @@ -1424,7 +1426,7 @@ mod test { // Subscribe to a track that doesn't exist yet, then serve it. let c1_fut = subscribe_pending!(bc, "unknown_track"); - let producer1 = broadcast.assert_request().accept(None); + let producer1 = broadcast.assert_request().accept(crate::track::Info::timed()); let consumer1 = c1_fut.await.unwrap(); // The producer should NOT be unused yet because there's a consumer. @@ -1462,7 +1464,7 @@ mod test { producer1.abort(Error::Cancel).unwrap(); let c4_fut = subscribe_pending!(bc, "unknown_track"); - let producer2 = broadcast.assert_request().accept(None); + let producer2 = broadcast.assert_request().accept(crate::track::Info::timed()); let consumer4 = c4_fut.await.unwrap(); drop(consumer4); assert!( @@ -1486,7 +1488,7 @@ mod test { let subscribing = subscribe_pending!(bc, "video"); // The producer creates the track before any handler drains the queue. - let track = producer.create_track("video", None).unwrap(); + let track = producer.create_track("video", crate::track::Info::timed()).unwrap(); let mut sub = subscribing.await.expect("fulfilled by create_track"); // The fulfilled subscription is live against this very producer. @@ -1625,7 +1627,7 @@ mod test { "close rejected a claimed request" ); - let _track = request.accept(None); + let _track = request.accept(crate::track::Info::timed()); assert!(accepted.await.is_ok(), "the handler's accept reaches the consumer"); drop(abandoned); assert!(dropped.await.is_err(), "the handler dropping it rejects the consumer"); @@ -1646,7 +1648,7 @@ mod test { let track = consumer.track("track1").unwrap(); let pending_fetch = track.fetch_group(0, None); let fetch = dynamic.requested_group().await.unwrap(); - let group = fetch.accept(None).unwrap(); + let group = fetch.accept(crate::track::Info::timed()).unwrap(); group.finish().unwrap(); pending_fetch.await.unwrap(); @@ -1654,7 +1656,7 @@ mod test { producer.close(); assert!(matches!(subscriber.recv_group().await, Err(Error::Unroutable))); - let stale = request.accept(None); + let stale = request.accept(crate::track::Info::timed()); assert!(stale.append_group().is_err()); } @@ -1665,7 +1667,7 @@ mod test { let producer = Info::new().produce(); let consumer = producer.consume(); - let track = producer.create_track("track1", None).unwrap(); + let track = producer.create_track("track1", crate::track::Info::timed()).unwrap(); let mut subscriber = consumer.track("track1").unwrap().subscribe(None).await.unwrap(); producer.close(); @@ -1690,7 +1692,7 @@ mod test { producer.close(); assert!(matches!(pending.await, Err(Error::Unroutable))); - let track = request.accept(None); + let track = request.accept(crate::track::Info::timed()); assert!(track.append_group().is_err()); let mut subscriber = track.subscribe(None); assert!(matches!(subscriber.recv_group().await, Err(Error::Unroutable))); @@ -1737,7 +1739,7 @@ mod test { async fn an_idle_teardown_yields_to_a_returning_viewer() { let producer = Info::new().produce(); let consumer = producer.consume(); - let track = producer.create_track("video", None).unwrap(); + let track = producer.create_track("video", crate::track::Info::timed()).unwrap(); // The unused wake a teardown acts on. assert!(track.demand().poll_unused(&kio::Waiter::noop()).is_ready()); @@ -1769,7 +1771,7 @@ mod test { fn abort_unused_accepts_an_already_closed_track_with_consumers() { let producer = Info::new().produce(); let consumer = producer.consume(); - let track = producer.create_track("video", None).unwrap(); + let track = producer.create_track("video", crate::track::Info::timed()).unwrap(); let _viewer = consumer.track("video").unwrap(); assert!(track.demand().is_used()); track.clone().abort(Error::Cancel).unwrap(); diff --git a/rs/moq-net/src/model/front.rs b/rs/moq-net/src/model/front.rs index 22a6c9c4844..088f318566c 100644 --- a/rs/moq-net/src/model/front.rs +++ b/rs/moq-net/src/model/front.rs @@ -716,7 +716,7 @@ mod tests { } fn info() -> track::Info { - track::Info::default() + track::Info::timed() } fn name(s: &str) -> Arc { @@ -1137,7 +1137,7 @@ mod tests { }); let other = track::Info { max_age: Some(Duration::from_secs(1)), - ..track::Info::default() + ..track::Info::timed() }; // The source it replaced keeps feeding the track (the audit's F3)... assert_actions( diff --git a/rs/moq-net/src/model/group.rs b/rs/moq-net/src/model/group.rs index 13a3c99f33c..10b9b80b196 100644 --- a/rs/moq-net/src/model/group.rs +++ b/rs/moq-net/src/model/group.rs @@ -70,20 +70,24 @@ pub struct Info { } impl Info { - /// Create a producer for this group on a default (millisecond) track. + /// Create a producer for this group on a millisecond track. /// /// Test-only: real groups are created via [`track::Producer`], which /// supplies the parent track's [`track::Info`]. This helper exists for in-crate /// tests that don't exercise timestamps. #[cfg(test)] pub(crate) fn produce(self) -> Producer { - Producer::new(self, track::Info::default(), Default::default()) + Producer::new( + self, + track::Info::default().with_timescale(Timescale::MILLI), + Default::default(), + ) } /// Create a producer for this group on an untimed track. Test-only, like [`Self::produce`]. #[cfg(test)] pub(crate) fn produce_untimed(self) -> Producer { - Producer::new(self, track::Info::default().with_timescale(None), Default::default()) + Producer::new(self, track::Info::default(), Default::default()) } } @@ -1996,7 +2000,7 @@ mod test { for mode in 0..3 { let pool = cache::Pool::unbounded(); let cache = cache::Track::new(pool.clone(), kio::Weak::new()); - let mut group = Producer::new(Info::from(0u64), track::Info::default(), cache); + let mut group = Producer::new(Info::from(0u64), track::Info::timed(), cache); for _ in 0..FRAME_SLOTS { group.write_frame(Timestamp::ZERO, bytes::Bytes::new()).unwrap(); } @@ -2405,7 +2409,7 @@ mod test { fn prefetched_consumer(pool: &cache::Pool, max_age: std::time::Duration) -> (Producer, Consumer) { let cache = cache::Track::new(pool.clone(), kio::Weak::new()); - let track = track::Info::default().with_max_age(max_age); + let track = track::Info::timed().with_max_age(max_age); let mut producer = Producer::new(Info { sequence: 0 }, track, cache); producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap(); producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap(); @@ -2836,7 +2840,7 @@ mod test { fn frame_is_charged_as_written() { let pool = cache::Pool::unbounded(); let cache = cache::Track::new(pool.clone(), kio::Weak::new()); - let mut producer = Producer::new(Info { sequence: 0 }, track::Info::default(), cache); + let mut producer = Producer::new(Info { sequence: 0 }, track::Info::timed(), cache); let before = pool.used(); let mut frame = producer @@ -2863,7 +2867,7 @@ mod test { fn frame_commit_charges_the_tail() { let pool = cache::Pool::unbounded(); let cache = cache::Track::new(pool.clone(), kio::Weak::new()); - let mut producer = Producer::new(Info { sequence: 0 }, track::Info::default(), cache); + let mut producer = Producer::new(Info { sequence: 0 }, track::Info::timed(), cache); let before = pool.used(); let mut frame = producer diff --git a/rs/moq-net/src/model/origin.rs b/rs/moq-net/src/model/origin.rs index 17bba3fa1d5..f750d74b4b4 100644 --- a/rs/moq-net/src/model/origin.rs +++ b/rs/moq-net/src/model/origin.rs @@ -5338,7 +5338,7 @@ mod tests { fn a_finished_logical_track_does_not_warn() { let warns = count_drop_warnings("track::Producer dropped without finish", || { let broadcast = Arc::new(broadcast::Info::default()); - let source = track::Producer::new(broadcast.clone(), "catalog.json", None); + let source = track::Producer::new(broadcast.clone(), "catalog.json", crate::track::Info::timed()); let mut group = source.create_group(7u64.into()).unwrap(); group.write_frame(crate::Timestamp::ZERO, b"snapshot".as_ref()).unwrap(); group.finish().unwrap(); @@ -6717,7 +6717,7 @@ mod tests { broadcast.unannounce(); settle(|| resolved.is_closed()).await; - let source = request.accept(None); + let source = request.accept(crate::track::Info::timed()); let mut group = source.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"late".as_ref()).unwrap(); group.finish().unwrap(); @@ -6758,7 +6758,7 @@ mod tests { .await .expect("the front asked the source") .expect("request"); - let source = request.resolving_start().accept(None); + let source = request.resolving_start().accept(crate::track::Info::timed()); let mut group = source.create_group(0u64.into()).unwrap(); group.write_frame(crate::Timestamp::ZERO, b"snapshot".as_ref()).unwrap(); group.finish().unwrap(); @@ -6817,7 +6817,7 @@ mod tests { .dynamic("room", Route::default().with_hops(hops(&[10]))) .unwrap(); let upstream = broadcast::Info::new().produce(); - let source = upstream.create_track("video", None).unwrap(); + let source = upstream.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = source.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"live".as_ref()).unwrap(); @@ -6895,7 +6895,7 @@ mod tests { .dynamic("room", Route::default().with_hops(hops(&[10]))) .unwrap(); let upstream = broadcast::Info::new().produce(); - let source = upstream.create_track("video", None).unwrap(); + let source = upstream.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = source.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"live".as_ref()).unwrap(); let fronts = || producer.shared.lock().fronts.len(); @@ -7025,7 +7025,7 @@ mod tests { .dynamic("room", Route::default().with_hops(hops(&[10, 7])).with_cost(100)) .unwrap(); let upstream = broadcast::Info::new().produce(); - let source = upstream.create_track("video", None).unwrap(); + let source = upstream.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = source.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"live".as_ref()).unwrap(); let fronts = || producer.shared.lock().fronts.len(); @@ -7118,7 +7118,11 @@ mod tests { // A reader is mid-way through the open group when the route changes. let track = resolved.track("log").unwrap(); let subscribing = moq_net_sim::spawn(async move { track.subscribe(None).await }); - let source = dynamic.requested_track().await.unwrap().accept(None); + let source = dynamic + .requested_track() + .await + .unwrap() + .accept(crate::track::Info::timed()); let mut group = source.create_group(0u64.into()).unwrap(); group.write_frame(crate::Timestamp::ZERO, b"a".as_ref()).unwrap(); group.write_frame(crate::Timestamp::ZERO, b"b".as_ref()).unwrap(); @@ -7166,14 +7170,14 @@ mod tests { let serving = fetched.clone(); let _fetching = moq_net_sim::spawn(async move { while let Ok(request) = fetches.requested_group().await { - let mut group = request.accept(None).unwrap(); + let mut group = request.accept(crate::track::Info::timed()).unwrap(); for frame in [b"a", b"b", b"c"] { group.write_frame(crate::Timestamp::ZERO, frame.as_ref()).unwrap(); } serving.lock().unwrap().push(group); } }); - let mut next = request.resolving_start().accept(None); + let mut next = request.resolving_start().accept(crate::track::Info::timed()); next.start_at(0).unwrap(); let mut next_group = next.create_group(0u64.into()).unwrap(); next_group.start_at(2).unwrap(); @@ -7266,7 +7270,7 @@ mod tests { .await .expect("the front asked the source") .expect("request"); - let source = request.accept(None); + let source = request.accept(crate::track::Info::timed()); let mut group = source.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"cached".as_ref()).unwrap(); group.finish().unwrap(); @@ -7292,7 +7296,7 @@ mod tests { settle(|| resolved.is_closed()).await; // A fresh source copy numbers groups past what the cache already delivered. - let source = request.accept(None); + let source = request.accept(crate::track::Info::timed()); let mut group = source.create_group(1u64.into()).unwrap(); group.write_frame(crate::Timestamp::ZERO, b"late".as_ref()).unwrap(); group.finish().unwrap(); @@ -8261,7 +8265,7 @@ mod tests { let source = broadcast::Info::new().produce(); for name in ["a", "b"] { - let track = source.create_track(name, None).unwrap(); + let track = source.create_track(name, crate::track::Info::timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, name.as_bytes()).unwrap(); group.finish().unwrap(); @@ -8485,7 +8489,7 @@ mod tests { let pending = consumer.request_broadcast("room/alice", None); let request = queued(&server).await; let source = broadcast::Info::new().produce(); - let track = source.create_track("video", None).unwrap(); + let track = source.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap(); group.finish().unwrap(); @@ -8534,7 +8538,7 @@ mod tests { async fn assert_resumes(rig: &mut ResumeRig, server: &Dynamic) { let request = queued(server).await; let replacement = broadcast::Info::new().produce(); - let track = replacement.create_track("video", None).unwrap(); + let track = replacement.create_track("video", crate::track::Info::timed()).unwrap(); // The same content: group 0 was already delivered through the old route, // so the track resumes at group 1. let mut group = track.append_group().unwrap(); @@ -8577,7 +8581,7 @@ mod tests { .unwrap(); let pending = consumer.request_broadcast("room/alice", None); let source = broadcast::Info::new().produce(); - let track = source.create_track("video", None).unwrap(); + let track = source.create_track("video", crate::track::Info::timed()).unwrap(); queued(&first).await.accept(&source); let resolved = pending.await.expect("resolves"); let mut subscription = resolved.track("video").unwrap().subscribe(None).await.unwrap(); @@ -8614,7 +8618,7 @@ mod tests { Route::default().with_epoch(crate::Epoch::mint()).with_cost(1), ) .unwrap(); - let old_track = old.create_track("video", None).unwrap(); + let old_track = old.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); let mut subscription = resolved.track("video").unwrap().subscribe(None).await.unwrap(); deliver(&old_track, &mut subscription, b"old").await; @@ -8625,7 +8629,7 @@ mod tests { Route::default().with_epoch(crate::Epoch::mint()).with_cost(9), ) .unwrap(); - let new_track = new.create_track("video", None).unwrap(); + let new_track = new.create_track("video", crate::track::Info::timed()).unwrap(); deliver(&old_track, &mut subscription, b"sticky").await; let replaced = consumer.request_broadcast("room/alice", None).await.expect("resolves"); @@ -8655,7 +8659,7 @@ mod tests { let old = producer .publish("room/alice", Route::default().with_epoch(first.clone())) .unwrap(); - let _old_track = old.create_track("video", None).unwrap(); + let _old_track = old.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); assert_eq!( resolved.info().epoch.as_ref(), @@ -8667,7 +8671,7 @@ mod tests { let new = producer .publish("room/alice", Route::default().with_epoch(epoch.clone())) .unwrap(); - let new_track = new.create_track("video", None).unwrap(); + let new_track = new.create_track("video", crate::track::Info::timed()).unwrap(); let named = consumer.request_broadcast("room/alice", epoch.clone()); let bare = consumer.request_broadcast("room/alice", None); @@ -8704,9 +8708,9 @@ mod tests { let new = producer .publish("room/alice", Route::default().with_epoch(crate::Epoch::mint())) .unwrap(); - let new_track = new.create_track("video", None).unwrap(); + let new_track = new.create_track("video", crate::track::Info::timed()).unwrap(); let stale = broadcast::Info::new().produce(); - let _stale_track = stale.create_track("video", None).unwrap(); + let _stale_track = stale.create_track("video", crate::track::Info::timed()).unwrap(); request.accept(&stale); let resolved = pending.await.expect("resolves the winner"); @@ -8731,7 +8735,7 @@ mod tests { let request = queued(&dynamic).await; let stale = broadcast::Info::new().produce(); - let _stale_track = stale.create_track("video", None).unwrap(); + let _stale_track = stale.create_track("video", crate::track::Info::timed()).unwrap(); let epoch = crate::Epoch::mint(); if answer_first { request.accept(&stale); @@ -8743,7 +8747,7 @@ mod tests { let request = queued(&dynamic).await; let fresh = broadcast::Info::new().produce(); - let fresh_track = fresh.create_track("video", None).unwrap(); + let fresh_track = fresh.create_track("video", crate::track::Info::timed()).unwrap(); request.accept(&fresh); let resolved = pending.await.expect("the request carries over to the new instance"); assert_eq!(resolved.info().epoch.as_ref(), Some(&epoch)); @@ -8785,7 +8789,7 @@ mod tests { dynamic.update(dynamic.route().with_epoch(epoch.clone())).unwrap(); let fresh = broadcast::Info::new().produce(); - let fresh_track = fresh.create_track("video", None).unwrap(); + let fresh_track = fresh.create_track("video", crate::track::Info::timed()).unwrap(); queued(&dynamic).await.accept(&fresh); let resolved = pending.await.expect("the request carries over"); assert_eq!(resolved.info().epoch.as_ref(), Some(&epoch)); @@ -8811,11 +8815,11 @@ mod tests { .unwrap(); dynamic.update(dynamic.route().with_epoch(old.clone())).unwrap(); let stale = broadcast::Info::new().produce(); - let _stale_track = stale.create_track("video", None).unwrap(); + let _stale_track = stale.create_track("video", crate::track::Info::timed()).unwrap(); request.accept(&stale); let fresh = broadcast::Info::new().produce(); - let fresh_track = fresh.create_track("video", None).unwrap(); + let fresh_track = fresh.create_track("video", crate::track::Info::timed()).unwrap(); queued(&dynamic).await.accept(&fresh); let resolved = pending.await.expect("the request carries over"); assert_eq!(resolved.info().epoch.as_ref(), Some(&old)); @@ -8833,7 +8837,7 @@ mod tests { let dynamic = producer.dynamic("room", Route::default()).unwrap(); let pending = consumer.request_broadcast("room/alice", None); let old = broadcast::Info::new().produce(); - let _old_track = old.create_track("video", None).unwrap(); + let _old_track = old.create_track("video", crate::track::Info::timed()).unwrap(); queued(&dynamic).await.accept(&old); let resolved = pending.await.expect("resolves"); @@ -8846,7 +8850,7 @@ mod tests { let retry = consumer.request_broadcast("room/alice", None); let fresh = broadcast::Info::new().produce(); - let _fresh_track = fresh.create_track("video", None).unwrap(); + let _fresh_track = fresh.create_track("video", crate::track::Info::timed()).unwrap(); queued(&dynamic).await.accept(&fresh); let replaced = retry.await.expect("the new instance resolves"); assert!(!replaced.is_clone(&resolved), "rejoined the first instance's front"); @@ -8933,8 +8937,8 @@ mod tests { async fn incompatible_successor_is_refused() { for replacement in [ track::Info::default().with_timescale(crate::Timescale::MICRO), - track::Info::default().with_priority(7), - track::Info::default().with_max_age(Duration::from_secs(7)), + track::Info::timed().with_priority(7), + track::Info::timed().with_max_age(Duration::from_secs(7)), ] { let (mut rig, incumbent, source) = ResumeRig::new(&[10]).await; let standby_server = rig.standby(&[10, 20]); @@ -9043,7 +9047,7 @@ mod tests { let pending = consumer.request_broadcast("room/alice", None); let request = queued(&incumbent).await; let source = broadcast::Info::new().produce(); - let track = source.create_track("video", None).unwrap(); + let track = source.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap(); group.finish().unwrap(); @@ -9071,7 +9075,7 @@ mod tests { None => { let request = queued(&echo).await; let replacement = broadcast::Info::new().produce(); - let track = replacement.create_track("video", None).unwrap(); + let track = replacement.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap(); group.finish().unwrap(); @@ -9108,7 +9112,7 @@ mod tests { let pending = consumer.request_broadcast("room/alice", None); let request = queued(&server_a).await; let source_a = broadcast::Info::new().produce(); - let track_a = source_a.create_track("video", None).unwrap(); + let track_a = source_a.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track_a.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"from-a".as_ref()).unwrap(); group.finish().unwrap(); @@ -9160,7 +9164,7 @@ mod tests { let pending = consumer.request_broadcast("room/alice", None); let request = queued(&server_b).await; let source_b = broadcast::Info::new().produce(); - let track_b = source_b.create_track("video", None).unwrap(); + let track_b = source_b.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track_b.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"from-b".as_ref()).unwrap(); group.finish().unwrap(); @@ -9199,7 +9203,7 @@ mod tests { .update(Route::default().with_hops(hops(&[10])).with_cost(9)) .unwrap(); - let track = source.create_track("audio", None).unwrap(); + let track = source.create_track("audio", crate::track::Info::timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"steady".as_ref()).unwrap(); group.finish().unwrap(); @@ -9282,7 +9286,7 @@ mod tests { let consumer = producer.consume(); let broadcast = producer.publish("room/alice", Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); let mut subscription = resolved @@ -9329,7 +9333,7 @@ mod tests { let pending = consumer.request_broadcast("room/alice", None); let request = queued(&server).await; let source = broadcast::Info::new().produce(); - let track = source.create_track("video", None).unwrap(); + let track = source.create_track("video", crate::track::Info::timed()).unwrap(); request.accept(&source); let resolved = pending.await.expect("resolves"); @@ -9395,7 +9399,7 @@ mod tests { let consumer = producer.consume(); let broadcast = producer.publish("room/alice", Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"cached".as_ref()).unwrap(); group.finish().unwrap(); @@ -9459,7 +9463,7 @@ mod tests { .unwrap(); let pending = producer.consume().request_broadcast("live", None); let source = broadcast::Info::new().produce(); - let track = source.create_track("video", None).unwrap(); + let track = source.create_track("video", crate::track::Info::timed()).unwrap(); queued(&server).await.accept(&source); let resolved = pending.await.unwrap(); @@ -9495,7 +9499,7 @@ mod tests { async fn a_group_the_source_aborts_ends_while_the_track_lives() { let producer = origin(1).produce(); let broadcast = producer.publish("live", Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = producer.consume().request_broadcast("live", None).await.unwrap(); let mut subscription = resolved.track("video").unwrap().subscribe(None).await.unwrap(); @@ -9529,7 +9533,7 @@ mod tests { let leaf_consumer = leaf.consume(); let broadcast = leaf.publish("room/alice", Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(crate::Timestamp::ZERO, b"cached".as_ref()).unwrap(); group.finish().unwrap(); @@ -9625,7 +9629,7 @@ mod tests { let first = producer .publish("room/alice", Route::default().with_epoch(epoch())) .unwrap(); - let track = first.create_track("video", None).unwrap(); + let track = first.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); let mut subscription = resolved .track("video") @@ -9678,7 +9682,7 @@ mod tests { .unwrap(); let pending = producer.consume().request_broadcast("room/alice", None); let upstream = broadcast::Info::new().produce(); - let old = upstream.create_track("video", None).unwrap(); + let old = upstream.create_track("video", crate::track::Info::timed()).unwrap(); queued(&server).await.accept(&upstream); let resolved = pending.await.expect("resolves"); let mut subscription = resolved.track("video").unwrap().subscribe(None).await.unwrap(); @@ -9779,7 +9783,7 @@ mod tests { let first = producer .publish("room/alice", Route::default().with_epoch(epoch())) .unwrap(); - let track = first.create_track("video", None).unwrap(); + let track = first.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); let mut subscription = resolved.track("video").unwrap().subscribe(None).await.unwrap(); let mut group = track.append_group().unwrap(); @@ -9815,7 +9819,7 @@ mod tests { let first = producer .publish("room/alice", Route::default().with_epoch(epoch())) .unwrap(); - let track = first.create_track("video", None).unwrap(); + let track = first.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); let mut subscription = resolved.track("video").unwrap().subscribe(None).await.unwrap(); let mut group = track.append_group().unwrap(); @@ -9870,7 +9874,7 @@ mod tests { let first = producer .publish("room/alice", Route::default().with_epoch(epoch())) .unwrap(); - let track = first.create_track("video", None).unwrap(); + let track = first.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); let mut subscription = resolved.track("video").unwrap().subscribe(None).await.unwrap(); let mut group = track.append_group().unwrap(); @@ -10028,7 +10032,7 @@ mod tests { let run = moq_net_sim::spawn(crate::time::run_sim(driver)); let consumer = producer.consume(); let broadcast = producer.publish("room/alice", Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); let sub = resolved.track("video").unwrap().subscribe(None).await.unwrap(); @@ -10052,7 +10056,7 @@ mod tests { let run = moq_net_sim::spawn(crate::time::run_sim(driver)); let consumer = producer.consume(); let broadcast = producer.publish("room/alice", Route::default()).unwrap(); - let mut track = broadcast.create_track("video", None).unwrap(); + let mut track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); let sub = resolved.track("video").unwrap().subscribe(None).await.unwrap(); let pending = resolved.track("video").unwrap().subscribe(None); @@ -10079,7 +10083,7 @@ mod tests { let producer = origin(1).produce(); let consumer = producer.consume(); let broadcast = producer.publish("room/alice", Route::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let resolved = consumer.request_broadcast("room/alice", None).await.expect("resolves"); let sub = resolved.track("video").unwrap().subscribe(None).await.unwrap(); moq_net_sim::timeout(Duration::from_secs(1), track.demand().used()) @@ -10362,7 +10366,7 @@ mod tests { let expiry = Duration::from_secs(1); let origin = expiring_origin(expiry); let broadcast = origin.create_broadcast("test").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let mut stalled = track.append_group().unwrap(); stalled.write_frame(crate::Timestamp::ZERO, b"x".as_slice()).unwrap(); @@ -10394,7 +10398,7 @@ mod tests { } .produce(); let broadcast = origin.create_broadcast("test").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", crate::track::Info::timed()).unwrap(); let mut stalled = track.append_group().unwrap(); stalled.write_frame(crate::Timestamp::ZERO, b"x".as_slice()).unwrap(); diff --git a/rs/moq-net/src/model/resume.rs b/rs/moq-net/src/model/resume.rs index 5071940cd7a..4b80c64ff9a 100644 --- a/rs/moq-net/src/model/resume.rs +++ b/rs/moq-net/src/model/resume.rs @@ -1086,14 +1086,18 @@ mod test { /// A route's copy of the track. fn copy() -> track::Producer { - track::Producer::new(Arc::new(broadcast::Info::default()), "video", None) + track::Producer::new( + Arc::new(broadcast::Info::default()), + "video", + crate::track::Info::timed(), + ) } /// The logical track `routes` serves. fn logical(routes: &Producer) -> track::Producer { track::Request::new(Arc::new(broadcast::Info::default()), "video") .routes(routes.consume()) - .accept(None) + .accept(crate::track::Info::timed()) } fn subscribe(logical: &track::Producer, max_delay: Duration) -> track::Subscriber { diff --git a/rs/moq-net/src/model/time.rs b/rs/moq-net/src/model/time.rs index 0c616cd0720..8d433a1571b 100644 --- a/rs/moq-net/src/model/time.rs +++ b/rs/moq-net/src/model/time.rs @@ -41,8 +41,7 @@ impl Timescale { Ok(scale) => scale, Err(_) => unreachable!(), }; - /// 1,000,000 units per second (`1_000_000`). Widely used by container formats; - /// this crate's own default is [`Self::MILLI`]. + /// 1,000,000 units per second (`1_000_000`). Widely used by container formats. pub const MICRO: Self = match Self::new(1_000_000) { Ok(scale) => scale, Err(_) => unreachable!(), @@ -106,14 +105,6 @@ impl From for NonZero { } } -impl Default for Timescale { - /// Milliseconds ([`Self::MILLI`]): what a track uses when its producer doesn't pick - /// one, and what a lite encoder declares for an untimed track's send times. - fn default() -> Self { - Self::MILLI - } -} - impl std::fmt::Debug for Timescale { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { @@ -305,8 +296,7 @@ impl Timestamp { } } - /// Current point on the local monotonic clock, expressed in the default timescale - /// ([`Timescale::MILLI`]). + /// Current point on the local monotonic clock, in milliseconds ([`Timescale::MILLI`]). /// /// A convenience for publishers stamping their own frames, and the only model API /// that reads the local clock; drivers stamp frames from the instant they are @@ -384,8 +374,8 @@ impl Ord for Timestamp { } impl From for Timestamp { - /// Convert an [`Instant`](crate::time::Instant) into a millisecond-scale timestamp - /// (the default timescale), measured from the model clock's anchor. + /// Convert an [`Instant`](crate::time::Instant) into a millisecond-scale timestamp, + /// measured from the model clock's anchor. /// /// One-way only: there is no inverse, since the anchor is jittered to keep a /// [`Timestamp`] from being read back as a clock. diff --git a/rs/moq-net/src/model/track.rs b/rs/moq-net/src/model/track.rs index 706d479a347..9483097dacb 100644 --- a/rs/moq-net/src/model/track.rs +++ b/rs/moq-net/src/model/track.rs @@ -92,7 +92,7 @@ pub struct Info { /// /// A track is all timed or all untimed: every frame and datagram on a timed track /// carries a timestamp, none on an untimed one does, and a write that doesn't match - /// is refused with [`Error::TimestampMismatch`]. Defaults to [`Timescale::MILLI`]. On + /// is refused with [`Error::TimestampMismatch`]. Defaults to `None` (untimed). On /// Lite05+ it is reported in TRACK_INFO and the publisher zigzag-delta encodes /// per-frame timestamps at this scale on the wire; on moq-transport draft 17 and /// later it is the TIMESCALE Track Property. A track received without one @@ -130,7 +130,7 @@ pub struct Info { impl Default for Info { fn default() -> Self { Self { - timescale: Some(Timescale::default()), + timescale: None, max_age: None, priority: DEFAULT_PRIORITY, } @@ -141,7 +141,7 @@ impl Info { /// Set the per-frame timestamp scale, or `None` for an untimed track, returning `self` /// for chaining. /// - /// Defaults to [`Timescale::MILLI`]. On Lite05+ this scale is reported in TRACK_INFO + /// Defaults to `None` (untimed). On Lite05+ this scale is reported in TRACK_INFO /// and used to encode per-frame timestamps on the wire. pub fn with_timescale(mut self, timescale: impl Into>) -> Self { self.timescale = timescale.into(); @@ -161,6 +161,14 @@ impl Info { } } +#[cfg(test)] +impl Info { + /// A millisecond track, for tests that write timestamps. + pub(crate) fn timed() -> Self { + Self::default().with_timescale(Timescale::MILLI) + } +} + /// The sequence namespace a track name keeps within its broadcast, shared by every /// producer that serves the name, so a replacement appends after whatever an earlier one /// wrote. Holds one past the highest sequence written. @@ -4847,7 +4855,7 @@ mod test { #[test] fn append_datagram_shares_group_sequence() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let ts = Timestamp::from_millis(10).unwrap(); // Interleave groups and datagrams: they draw from one monotonic counter. @@ -4860,7 +4868,7 @@ mod test { #[test] fn append_datagram_roundtrip() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut dg = producer.subscribe(None); let ts = Timestamp::from_millis(42).unwrap(); @@ -4874,7 +4882,7 @@ mod test { #[test] fn insert_datagram_preserves_sequence() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut dg = producer.subscribe(None); let ts = Timestamp::from_millis(5).unwrap(); @@ -4890,7 +4898,7 @@ mod test { #[test] fn insert_datagram_leaves_a_gap() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut dg = producer.subscribe(None); let ts = Timestamp::from_millis(0).unwrap(); @@ -4904,7 +4912,7 @@ mod test { #[test] fn insert_datagram_out_of_order_does_not_rewind() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut dg = producer.subscribe(None); let ts = Timestamp::from_millis(0).unwrap(); @@ -4922,7 +4930,7 @@ mod test { #[test] fn insert_datagram_duplicate_is_best_effort() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut dg = producer.subscribe(None); let ts = Timestamp::from_millis(0).unwrap(); @@ -4940,7 +4948,7 @@ mod test { #[test] fn insert_datagram_stale_does_not_rewind_after_append() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut dg = producer.subscribe(None); let ts = Timestamp::from_millis(0).unwrap(); @@ -4959,7 +4967,7 @@ mod test { #[test] fn insert_datagram_cloned_producers_share_counter() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut other = producer.clone(); let mut dg = producer.subscribe(None); let ts = Timestamp::from_millis(0).unwrap(); @@ -4978,7 +4986,7 @@ mod test { #[test] fn insert_datagram_after_finish_is_closed() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let ts = Timestamp::from_millis(0).unwrap(); producer.finish().unwrap(); assert!(matches!( @@ -4990,7 +4998,7 @@ mod test { #[test] fn insert_datagram_after_abort_fails() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut other = producer.clone(); let ts = Timestamp::from_millis(0).unwrap(); producer.abort(Error::Cancel).unwrap(); @@ -4999,7 +5007,7 @@ mod test { #[test] fn insert_datagram_respects_finish_at() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let ts = Timestamp::from_millis(0).unwrap(); producer.finish_at(10).unwrap(); producer @@ -5015,7 +5023,7 @@ mod test { /// Datagram sequence advances do not move a route takeover past an open group. #[test] fn resume_position_uses_the_latest_group() { - let mut datagram_only = track_producer("datagram-only", None); + let mut datagram_only = track_producer("datagram-only", Info::timed()); let datagram_only_consumer = datagram_only.consume(); datagram_only .insert_datagram(8, Timestamp::ZERO, bytes::Bytes::from_static(b"x")) @@ -5026,7 +5034,7 @@ mod test { "a datagram creates no group position to resume" ); - let mut producer = track_producer("mixed", None); + let mut producer = track_producer("mixed", Info::timed()); let consumer = producer.consume(); let mut group = producer.create_group(group::Info { sequence: 3 }).unwrap(); group @@ -5048,7 +5056,7 @@ mod test { /// so consuming one must not move the other's cursor. #[test] fn recv_datagram_leaves_the_ordered_cursor_alone() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut datagrams = producer.subscribe(None); let mut subscriber = producer.subscribe(None).ordered(); let ts = Timestamp::from_millis(5).unwrap(); @@ -5091,7 +5099,7 @@ mod test { #[test] fn datagram_rejects_oversized() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let big = bytes::Bytes::from(vec![0u8; crate::model::datagram::MAX_DATAGRAM_PAYLOAD + 1]); let ts = Timestamp::from_millis(0).unwrap(); assert!(matches!( @@ -5106,7 +5114,7 @@ mod test { #[test] fn datagram_fanout_to_subscribers() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); // Two independent subscribers, each with its own datagram cursor. let mut a = producer.subscribe(None); let mut b = producer.subscribe(None); @@ -5152,7 +5160,7 @@ mod test { #[test] fn datagram_replay_starts_after_evicted_entries_with_zero_delay() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for _ in 0..MAX_DATAGRAMS + 1 { producer.append_datagram(Timestamp::ZERO, b"old".as_slice()).unwrap(); } @@ -5180,7 +5188,7 @@ mod test { #[test] fn datagram_buffer_drops_oldest_at_capacity() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut slow = producer.subscribe(None); let mut fast = producer.subscribe(None); let count = MAX_DATAGRAMS * 3; @@ -5197,7 +5205,7 @@ mod test { #[test] fn datagram_recv_pends_until_written() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut dg = producer.subscribe(None); assert!( @@ -5215,7 +5223,7 @@ mod test { /// miss, whether or not a group followed it. #[test] fn a_datagram_group_is_not_fetchable() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let consumer = producer.consume(); let _subscriber = producer.subscribe(None); let sequence = producer.append_datagram(Timestamp::ZERO, &b"x"[..]).unwrap(); @@ -5242,7 +5250,7 @@ mod test { /// what dropped the others. #[test] fn the_group_range_bounds_datagrams() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_start(Position::group(5))); subscriber.set_groups(..7); for sequence in [4, 5, 7, 6] { @@ -5260,7 +5268,7 @@ mod test { /// frame; a start at frame 0 keeps it. #[test] fn a_mid_group_start_drops_the_start_groups_datagram() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut mid = producer.subscribe(Subscription::default().with_start(Position { group: 5, frame: 1 })); let mut whole = producer.subscribe(Subscription::default().with_start(Position::group(5))); for sequence in [5, 6] { @@ -5278,7 +5286,7 @@ mod test { /// dropped stays dropped once the cap rises again: nothing holds it like a parked group. #[test] fn a_range_update_applies_to_buffered_datagrams() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); for sequence in 1..=4 { producer @@ -5310,7 +5318,7 @@ mod test { let version = lite::Version::Lite05; // Origin publishes a datagram; the publisher reads it and encodes the wire body. - let mut origin = track_producer("test", None); + let mut origin = track_producer("test", Info::timed()); let mut origin_dg = origin.subscribe(None); let ts = Timestamp::from_millis(7).unwrap(); let seq = origin.append_datagram(ts, &b"payload"[..]).unwrap(); @@ -5327,7 +5335,7 @@ mod test { // Subscriber decodes the body and writes it downstream, preserving the sequence. let wire = lite::Datagram::decode(body, version).unwrap(); - let mut downstream = track_producer("test", None); + let mut downstream = track_producer("test", Info::timed()); let mut downstream_dg = downstream.subscribe(None); downstream .insert_datagram( @@ -5345,7 +5353,7 @@ mod test { #[test] fn evict_expired_groups() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); // Create 3 groups at time 0. producer.append_group().unwrap(); // seq 0 @@ -5384,7 +5392,7 @@ mod test { /// track with long groups (a per-minute rollup, say) fails its readers at every boundary. #[moq_net_sim::test] async fn aging_out_a_finished_group_keeps_the_clean_end() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut group = producer.create_group(group::Info { sequence: 0 }).unwrap(); let mut consumer = group.consume(); @@ -5406,7 +5414,7 @@ mod test { /// schedule, so reclamation stays intact. #[moq_net_sim::test] async fn active_reader_survives_expiry() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); // A finished group with one frame per step of the read loop below. @@ -5443,7 +5451,7 @@ mod test { /// re-stamp on a time bound between refills. #[moq_net_sim::test] async fn slow_prefetch_reader_survives_expiry() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); let mut group = producer.create_group(0u64.into()).unwrap(); @@ -5471,7 +5479,7 @@ mod test { /// window later. #[moq_net_sim::test] async fn delivery_restarts_the_expiry_clock() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(replay()); let mut group = producer.create_group(0u64.into()).unwrap(); @@ -5498,7 +5506,7 @@ mod test { /// retention windows must not be expired mid-write. #[test] fn streaming_frame_writes_keep_the_group_alive() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut straggler = producer.create_group(0u64.into()).unwrap(); // The live edge moves on, so the straggler is demoted and expirable. producer.create_group(1u64.into()).unwrap().finish().unwrap(); @@ -5532,7 +5540,7 @@ mod test { /// not be expired by the next track write on its stale frame-open stamp. #[test] fn coalesced_frame_completion_keeps_the_group_alive() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut straggler = producer.create_group(0u64.into()).unwrap(); // The live edge moves on, so the straggler is demoted and expirable. producer.create_group(1u64.into()).unwrap().finish().unwrap(); @@ -5569,7 +5577,7 @@ mod test { /// the expiry clock so the subscriber gets to read what it was just handed. #[moq_net_sim::test] async fn parked_reoffer_restarts_the_expiry_clock() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); subscriber.set_groups(..1); @@ -5600,7 +5608,7 @@ mod test { #[test] fn evict_keeps_max_sequence() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.append_group().unwrap(); // seq 0 // Advance time past the LRU window. @@ -5619,7 +5627,7 @@ mod test { #[test] fn no_eviction_when_fresh() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.append_group().unwrap(); // seq 0 producer.append_group().unwrap(); // seq 1 producer.append_group().unwrap(); // seq 2 @@ -5633,7 +5641,7 @@ mod test { #[test] fn consumer_skips_evicted_groups() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.append_group().unwrap(); // seq 0 let mut consumer = producer.subscribe(None); @@ -5659,7 +5667,7 @@ mod test { ..Default::default() }), name, - None, + Info::timed(), ) } @@ -5850,7 +5858,7 @@ mod test { /// pool's LRU window can't age content out no matter how small the window is. #[test] fn max_age_does_not_drive_wall_eviction() { - let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(1))); + let producer = track_producer("test", Info::timed().with_max_age(Duration::from_secs(1))); producer.append_group().unwrap(); // seq 0 // Far past max_age in wall time, but inside the pool's LRU window. @@ -5876,7 +5884,7 @@ mod test { #[test] fn max_delay_clamped_to_cache() { - let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2))); + let producer = track_producer("test", Info::timed().with_max_age(Duration::from_secs(2))); // A max delay budget beyond the cache is capped in the aggregate; a group can't be // waited for longer than the publisher keeps it. The subscriber's own preference @@ -5912,9 +5920,9 @@ mod test { #[test] fn optional_age_keeps_publisher_metadata_and_local_policy_separate() { - assert_eq!(Info::default().max_age, None); + assert_eq!(Info::timed().max_age, None); for age in [None, Some(Duration::ZERO), Some(Duration::from_secs(30))] { - let producer = track_producer_capped("optional", Info::default().with_max_age(age), Duration::from_secs(1)); + let producer = track_producer_capped("optional", Info::timed().with_max_age(age), Duration::from_secs(1)); assert_eq!(producer.info.max_age, age); assert_eq!(producer.subscribe(None).info().max_age, age); assert_eq!( @@ -5930,7 +5938,7 @@ mod test { // ceiling; a publisher already below the ceiling is left alone (it's a min). let capped = track_producer_capped( "test", - Info::default().with_max_age(Duration::from_secs(60)), + Info::timed().with_max_age(Duration::from_secs(60)), Duration::from_secs(1), ); assert_eq!(capped.state.read().max_age_bound(), Some(Duration::from_secs(1))); @@ -5938,7 +5946,7 @@ mod test { let under = track_producer_capped( "test", - Info::default().with_max_age(Duration::from_millis(500)), + Info::timed().with_max_age(Duration::from_millis(500)), Duration::from_secs(1), ); assert_eq!(under.state.read().max_age_bound(), Some(Duration::from_millis(500))); @@ -5950,7 +5958,7 @@ mod test { fn origin_cache_duration_does_not_wall_evict() { let producer = track_producer_capped( "test", - Info::default().with_max_age(Duration::from_secs(60)), + Info::timed().with_max_age(Duration::from_secs(60)), Duration::from_secs(1), ); producer.append_group().unwrap(); // seq 0 @@ -5965,7 +5973,7 @@ mod test { #[test] fn max_delay_clamped_via_every_update_path() { - let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2))); + let producer = track_producer("test", Info::timed().with_max_age(Duration::from_secs(2))); let over = Subscription::default().with_max_delay(Duration::from_secs(10)); // The clamp lives in the aggregation, so it applies no matter which entry point @@ -5982,7 +5990,7 @@ mod test { #[test] fn max_delay_aggregate_clamps_across_subscribers() { - let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2))); + let producer = track_producer("test", Info::timed().with_max_age(Duration::from_secs(2))); // The aggregate takes the max, then clamps once. Equivalent to clamping each // subscriber first, since `min` distributes over `max`. @@ -5994,7 +6002,7 @@ mod test { #[test] fn churned_subscribers_do_not_accumulate() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let consumer = producer.consume(); let _steady = producer.subscribe(None); @@ -6013,7 +6021,7 @@ mod test { #[test] fn aggregate_poll_prunes_after_a_peak() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let waiter = kio::Waiter::noop(); let _steady = producer.subscribe(None); assert!(producer.poll_subscription_changed(&waiter).is_ready()); @@ -6059,7 +6067,7 @@ mod test { /// answer, fresh to any positive budget. #[test] fn an_answer_past_a_reset_group_skips_the_cache() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); append_at(&mut producer, 0); let mut open = producer.append_group().unwrap(); open.write_frame(Timestamp::from_millis(500).unwrap(), bytes::Bytes::from_static(b"x")) @@ -6081,7 +6089,7 @@ mod test { #[test] fn real_time_skips_a_backlog_to_the_live_edge() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..5 { append_at(&mut producer, second * 1000); } @@ -6099,7 +6107,7 @@ mod test { #[test] fn real_time_skips_a_backlog_after_catching_up() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); append_at(&mut producer, 0); let mut subscriber = producer.subscribe(None); assert_eq!(drain(&mut subscriber), vec![0]); @@ -6114,7 +6122,7 @@ mod test { #[test] fn a_newer_edge_changes_an_active_catch_up() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..5 { append_at(&mut producer, second * 1000); } @@ -6139,7 +6147,7 @@ mod test { #[test] fn a_growing_edge_changes_an_active_catch_up() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); append_at(&mut producer, 0); append_at(&mut producer, 1000); let mut edge = producer.append_group().unwrap(); @@ -6167,7 +6175,7 @@ mod test { #[test] fn a_budget_admits_groups_within_it() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..5 { append_at(&mut producer, second * 1000); } @@ -6182,7 +6190,7 @@ mod test { #[test] fn a_budget_reaches_back_over_the_cache() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..5 { append_at(&mut producer, second * 1000); } @@ -6203,7 +6211,7 @@ mod test { #[test] fn a_named_start_is_a_floor_not_a_request() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..5 { append_at(&mut producer, second * 1000); } @@ -6232,7 +6240,7 @@ mod test { #[test] fn a_floor_above_the_live_edge_waits_there() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..3 { append_at(&mut producer, second * 1000); } @@ -6254,7 +6262,7 @@ mod test { #[test] fn a_late_lower_group_within_the_budget_is_delivered() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); for (sequence, millis) in [(5, 0), (6, 1000), (7, 2000)] { let mut group = producer.create_group(group::Info { sequence }).unwrap(); group @@ -6278,7 +6286,7 @@ mod test { #[test] fn drift_is_measured_in_presentation_time_not_arrival_time() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); // A relay ingesting a backlog creates every group at once, so arrival time says // they are all equally fresh. Their timestamps say otherwise, which is the whole // point of measuring in presentation time. @@ -6292,7 +6300,7 @@ mod test { #[test] fn a_stamped_successor_expires_an_unstamped_group() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); producer.append_group().unwrap(); // seq 0 stalls before its first frame @@ -6305,7 +6313,7 @@ mod test { #[moq_net_sim::test] async fn a_handed_out_group_expires_while_its_first_frame_is_stalled() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); producer.append_group().unwrap(); @@ -6332,7 +6340,7 @@ mod test { /// moves the reach to the next cached group, without changing the track itself. #[moq_net_sim::test] async fn aborted_stamped_successor_wakes_a_parked_read() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut head = producer.append_group().unwrap(); head.write_frame(Timestamp::ZERO, b"head".as_slice()).unwrap(); let mut successor = producer.append_group().unwrap(); @@ -6366,7 +6374,7 @@ mod test { /// proves it stale. #[moq_net_sim::test] async fn a_handed_out_group_wakes_when_a_newer_group_gets_its_first_timestamp() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_max_delay(Duration::from_millis(500))); let mut old = producer.append_group().unwrap(); old.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"old")) @@ -6414,7 +6422,7 @@ mod test { const FRAME: u64 = 2500; // micros, one Opus frame per group const PARKED: u64 = 64; - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_max_delay(Duration::from_millis(200))); // Left open, so every read parks instead of ending. let _open: Vec<_> = (0..PARKED) @@ -6487,7 +6495,7 @@ mod test { /// the deadline cannot convict it, so neither wakes it. #[test] fn a_parked_read_wakes_only_once_the_edge_reaches_its_deadline() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_max_delay(Duration::from_secs(10))); let mut head = producer.append_group().unwrap(); head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head")) @@ -6541,7 +6549,7 @@ mod test { /// the abort nor that group's first frame touches the track, so the read must still wake. #[test] fn a_parked_read_watches_its_edge_abort() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", crate::track::Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_max_delay(Duration::from_secs(5))); let mut head = producer.append_group().unwrap(); head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head")) @@ -6584,7 +6592,7 @@ mod test { /// and so is watched only because the read re-selects its successor. #[test] fn a_parked_read_watches_the_replacement_for_an_aborted_successor() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_max_delay(Duration::from_millis(500))); let mut head = producer.append_group().unwrap(); head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head")) @@ -6635,7 +6643,7 @@ mod test { /// the track, so only the read watching the edge's abort sees the head go stale. #[test] fn a_parked_read_watches_its_rewound_edge_abort() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", crate::track::Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_max_delay(Duration::from_secs(5))); let mut head = producer.append_group().unwrap(); head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head")) @@ -6673,7 +6681,7 @@ mod test { /// more, though the read sits below the group's nearest shown predecessor. #[test] fn revealing_a_presented_group_wakes_a_read_it_expires() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_max_delay(Duration::from_millis(500))); let mut head = producer.append_group().unwrap(); head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head")) @@ -6716,7 +6724,7 @@ mod test { /// they show: a reader ending at the boundary would never see them. #[test] fn a_withheld_group_holds_the_end() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", crate::track::Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_max_delay(Duration::from_secs(60))); producer.finish_at(2).unwrap(); let mut received: Vec<_> = (0..2) @@ -6789,7 +6797,7 @@ mod test { /// frame are separate events and the wire does not order them. #[moq_net_sim::test] async fn real_time_reads_a_live_stream_without_truncating_it() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); let gop = |n: u64| { @@ -6863,7 +6871,7 @@ mod test { /// budget shorter than one GOP would drop the tail of every GOP. #[moq_net_sim::test] async fn a_budget_is_measured_from_the_readers_position() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(Subscription::default().with_max_delay(Duration::from_secs(1))); let mut open = producer.append_group().unwrap(); @@ -6909,7 +6917,7 @@ mod test { /// allowed to probe again past the end of a group. #[moq_net_sim::test] async fn an_ended_group_stays_ended_when_probed_again() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); let mut open = producer.append_group().unwrap(); open.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"a")) @@ -6936,7 +6944,7 @@ mod test { #[moq_net_sim::test] async fn a_drained_group_finishes_cleanly_after_the_live_edge_advances() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); append_at(&mut producer, 0); @@ -6951,7 +6959,7 @@ mod test { #[moq_net_sim::test] async fn a_handed_out_partial_frame_expires_while_its_payload_is_stalled() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); let mut source = producer.append_group().unwrap(); let mut writing = source @@ -6988,7 +6996,7 @@ mod test { // The publisher only keeps a group around for 500ms, so a subscriber asking to // wait ten seconds for one still gives up at 500ms: the same clamp the aggregate // applies, on the subscriber's own side of it. - let mut producer = track_producer("test", Info::default().with_max_age(Duration::from_millis(500))); + let mut producer = track_producer("test", Info::timed().with_max_age(Duration::from_millis(500))); append_at(&mut producer, 0); append_at(&mut producer, 1000); append_at(&mut producer, 2000); @@ -7001,7 +7009,7 @@ mod test { #[test] fn an_explicit_start_gets_no_exemption_from_the_budget() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..4 { append_at(&mut producer, second * 1000); } @@ -7026,7 +7034,7 @@ mod test { /// judging it by its first timestamp would discard exactly the group being filled. #[test] fn a_long_group_is_not_stale_while_its_tail_reaches_the_edge() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); // Group 0 spans 0..2000ms; group 1 starts at 2000ms, where group 0 ends. let mut long = producer.append_group().unwrap(); @@ -7049,7 +7057,7 @@ mod test { /// is further behind the edge than the budget allows. #[test] fn a_long_group_is_stale_once_its_successor_falls_behind() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut long = producer.append_group().unwrap(); for ms in [0u64, 500, 1000] { @@ -7071,7 +7079,7 @@ mod test { /// shrinking the bound is the unsafe direction. #[test] fn an_unstamped_immediate_successor_leaves_reach_unbounded() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); append_at(&mut producer, 0); // seq 0 @@ -7091,7 +7099,7 @@ mod test { /// is still well inside the budget. #[test] fn reach_follows_the_immediate_successor_not_a_later_rewind() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); // Group 0 is bounded by group 1 at 10s. Groups 2 and 3 rewind to 1s and 2s. append_at(&mut producer, 0); @@ -7110,7 +7118,25 @@ mod test { } fn untimed_producer() -> Producer { - track_producer("test", Info::default().with_timescale(None)) + track_producer("test", None) + } + + /// A track that declares no timescale is untimed, whether created or accepted, so a + /// timestamped write is refused rather than given an implicit scale. + #[test] + fn an_undeclared_timescale_is_untimed() { + let broadcast = broadcast::Info::new().produce(); + let mut created = broadcast.create_track("created", None).unwrap(); + let mut accepted = broadcast.reserve_track("accepted").unwrap().accept(None); + + assert_eq!(Info::default().timescale, None); + for track in [&mut created, &mut accepted] { + assert!(matches!( + track.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"x")), + Err(Error::TimestampMismatch) + )); + track.write_frame(None, bytes::Bytes::from_static(b"x")).unwrap(); + } } /// A group of untimed frames. @@ -7174,7 +7200,7 @@ mod test { /// subscriber waiting for a frame that never comes. #[test] fn a_mismatched_frame_appends_no_group() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); assert!(matches!( producer.write_frame(None, bytes::Bytes::from_static(b"x")), Err(Error::TimestampMismatch) @@ -7192,7 +7218,7 @@ mod test { )); untimed.append_datagram(None, &b"x"[..]).unwrap(); - let mut timed = track_producer("timed", None); + let mut timed = track_producer("timed", Info::timed()); assert!(matches!( timed.append_datagram(None, &b"x"[..]), Err(Error::TimestampMismatch) @@ -7207,7 +7233,7 @@ mod test { /// a track using both channels needs one subscription, not two. #[test] fn ordered_carries_datagrams() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut sub = producer.subscribe(None).ordered(); producer @@ -7247,7 +7273,7 @@ mod test { /// blocks. The budget applies as the cursor reads, exactly as on the arrival cursor. #[test] fn next_group_sheds_a_stale_backlog() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..4 { append_at(&mut producer, second * 1000); } @@ -7265,7 +7291,7 @@ mod test { /// could still present something inside it, and the burst is delivered gap-free. #[test] fn next_group_keeps_a_backlog_inside_the_budget() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..4 { append_at(&mut producer, second * 1000); } @@ -7286,7 +7312,7 @@ mod test { /// so nothing says its frames are too old and the ordered cursor keeps it. #[test] fn next_group_keeps_a_group_with_no_proven_reach() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); append_at(&mut producer, 0); // seq 0 producer.append_group().unwrap(); // seq 1 stalls before its first frame append_at(&mut producer, 10_000); // seq 2 @@ -7299,7 +7325,7 @@ mod test { #[test] fn real_time_skips_older_sequences_with_equal_ages() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); append_at(&mut producer, 0); append_at(&mut producer, 0); @@ -7309,7 +7335,7 @@ mod test { #[test] fn fetch_ignores_the_budget() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..4 { append_at(&mut producer, second * 1000); } @@ -7325,7 +7351,7 @@ mod test { /// so it cannot make content available to a subscription look stale. #[moq_net_sim::test] async fn fetched_group_is_not_a_live_drift_edge() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); append_at(&mut producer, 0); @@ -7336,7 +7362,7 @@ mod test { .now_or_never() .expect("fetch request is ready") .unwrap(); - let mut fetched = req.accept(None).unwrap(); + let mut fetched = req.accept(Info::timed()).unwrap(); fetched .write_frame( Timestamp::from_millis(100_000).unwrap(), @@ -7357,7 +7383,7 @@ mod test { /// longer there to jump to. #[test] fn an_evicted_live_edge_convicts_nothing() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); append_at(&mut producer, 0); let edge = append_at(&mut producer, 30_000); @@ -7385,7 +7411,7 @@ mod test { #[test] fn a_lower_sequence_is_never_the_live_edge() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); // A high timestamp on a lower sequence is not a live edge: backfill served on // demand sits there, and so does the tail of a timeline the publisher rewound. // Only groups above the candidate anchor the measure, so neither can convict @@ -7408,7 +7434,7 @@ mod test { #[test] fn a_requested_end_does_not_cap_the_live_edge() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); for second in 0..4 { append_at(&mut producer, second * 1000); } @@ -7423,7 +7449,7 @@ mod test { #[test] fn a_capped_subscriber_measures_drift_against_its_cap() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); append_at(&mut producer, 0); append_at(&mut producer, 1000); @@ -7441,7 +7467,7 @@ mod test { #[test] fn subscriber_control_updates_while_read_future_is_pending() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut subscriber = producer.subscribe(None); let control = subscriber.control(); @@ -7460,7 +7486,7 @@ mod test { // last subscription to the aggregate. When it did, a relay's linger loop // never observed the track going idle, and an identical viewer reconnecting // within the linger window was reset when the stale timer fired. - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let a = producer.subscribe(Subscription::default().with_priority(5)); // Prime the change cursor: the aggregate currently has one subscriber. @@ -7496,7 +7522,7 @@ mod test { // the upstream's viewer count) open forever. use std::sync::atomic::{AtomicBool, Ordering}; - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let a = producer.subscribe(Subscription::default().with_priority(5)); let woken = Arc::new(AtomicBool::new(false)); @@ -7529,7 +7555,7 @@ mod test { // resumed: every session stayed up and nothing flowed. use std::sync::atomic::{AtomicBool, Ordering}; - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let _narrow = producer.subscribe(Subscription::default().with_end(Position::after_group(3))); let mut wide = producer.subscribe(Subscription::default()); @@ -7566,7 +7592,7 @@ mod test { #[test] fn out_of_order_max_sequence_at_front() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); // Arrive out of order: seq 5 first, then 3, then 4. producer.create_group(group::Info { sequence: 5 }).unwrap(); @@ -7600,7 +7626,7 @@ mod test { #[test] fn max_sequence_at_front_blocks_trim() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); // Arrive: seq 5, then seq 3. producer.create_group(group::Info { sequence: 5 }).unwrap(); @@ -7646,7 +7672,7 @@ mod test { #[test] fn abort_drops_open_groups() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.append_group().unwrap(); producer.append_group().unwrap(); @@ -7666,7 +7692,7 @@ mod test { #[test] fn drop_unfinished_clears_cached_groups() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let writer = producer.clone(); writer.append_group().unwrap(); @@ -7687,7 +7713,7 @@ mod test { // abort() closes the channel after recording `abort`. Drop must treat the // read-only guard returned by write() as clean or it emits a false WARN. let warns = count_drop_warnings("track::Producer dropped without finish", || { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let keep = producer.clone(); let writer = producer.clone(); let group = writer.append_group().unwrap(); @@ -7702,7 +7728,7 @@ mod test { #[test] fn drop_unfinished_warns() { let warns = count_drop_warnings("track::Producer dropped without finish", || { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let writer = producer.clone(); writer.append_group().unwrap(); let _consumer = producer.subscribe(None); @@ -7714,7 +7740,7 @@ mod test { #[test] fn drop_finished_keeps_cached_groups() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.append_group().unwrap(); producer.finish().unwrap(); @@ -7732,7 +7758,7 @@ mod test { /// `Dropped`, as it would for a track that never declared its end. #[test] fn drop_short_of_the_boundary_ends_cleanly() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); producer.append_group().unwrap(); producer.finish_at(3).unwrap(); @@ -7755,7 +7781,7 @@ mod test { /// a later cached group is still delivered, and the read finishes cleanly. #[test] fn ordered_ends_cleanly_when_sealed_short_of_the_boundary() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); producer.create_group(group::Info { sequence: 0 }).unwrap(); producer.create_group(group::Info { sequence: 2 }).unwrap(); producer.finish_at(4).unwrap(); @@ -7773,7 +7799,7 @@ mod test { #[test] fn append_finish_cannot_be_rewritten() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); // Finishing an empty track is valid (fin = 0, total groups = 0). assert!(producer.finish().is_ok()); @@ -7783,7 +7809,7 @@ mod test { #[test] fn finish_after_groups() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.append_group().unwrap(); assert!(producer.finish().is_ok()); @@ -7793,7 +7819,7 @@ mod test { #[test] fn finish_at_rejects_a_boundary_at_or_below_the_live_edge() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); producer.create_group(group::Info { sequence: 5 }).unwrap(); // The boundary is exclusive, so it must be strictly above the highest produced @@ -7815,7 +7841,7 @@ mod test { #[test] fn final_sequence_reports_the_declared_boundary() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); assert_eq!(producer.final_sequence(), None); producer.create_group(group::Info { sequence: 5 }).unwrap(); @@ -7830,7 +7856,7 @@ mod test { #[test] fn final_sequence_reports_the_live_edge_after_finish() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.create_group(group::Info { sequence: 5 }).unwrap(); producer.finish().unwrap(); assert_eq!(producer.final_sequence(), Some(6)); @@ -7838,7 +7864,7 @@ mod test { #[test] fn finish_at_declares_a_future_boundary() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); producer.create_group(group::Info { sequence: 5 }).unwrap(); // Learn the track ends at group 6 (exclusive 7) while the live edge is still 5. @@ -7873,7 +7899,7 @@ mod test { #[moq_net_sim::test] async fn readers_end_at_the_boundary_with_missing_lower_groups() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut arrival = producer.subscribe(None); let mut ordered = producer.subscribe(None).ordered(); producer.finish_at(3).unwrap(); @@ -8012,7 +8038,7 @@ mod test { /// but a group below it was still open, so the track was cut off rather than ended. #[test] fn abort_before_the_end_settles_wins() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None); let head = producer.create_group(group::Info { sequence: 0 }).unwrap(); @@ -8028,7 +8054,7 @@ mod test { #[moq_net_sim::test] async fn local_close_keeps_finished_groups_without_declaring_an_end() { - let producer = track_producer("local", None); + let producer = track_producer("local", Info::timed()); let mut consumer = producer.consume().subscribe(None).await.unwrap(); producer.append_group().unwrap().finish().unwrap(); producer.clone().close().unwrap(); @@ -8040,7 +8066,7 @@ mod test { #[moq_net_sim::test] async fn local_close_cannot_mask_an_abort_before_declared_end_settles() { - let mut producer = track_producer("local", None); + let mut producer = track_producer("local", Info::timed()); let mut consumer = producer.consume().subscribe(None).await.unwrap(); let _group = producer.append_group().unwrap(); producer.finish_at(2).unwrap(); @@ -8053,7 +8079,7 @@ mod test { /// replacement route can take over where it left off. #[test] fn local_close_is_not_completion_and_keeps_the_resume_position() { - let producer = track_producer("local", None); + let producer = track_producer("local", Info::timed()); let consumer = producer.consume(); producer.append_group().unwrap().finish().unwrap(); let mut group = producer.append_group().unwrap(); @@ -8076,7 +8102,7 @@ mod test { /// is gone, on both cursors. #[test] fn abort_keeps_finished_groups_for_a_slow_reader() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut arrival = producer.subscribe(None); let mut ordered = producer.subscribe(None).ordered(); @@ -8107,7 +8133,7 @@ mod test { /// The last producer dropping without a finish keeps the finished groups the same way. #[test] fn dropped_producer_keeps_finished_groups_for_a_slow_reader() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None); producer .create_group(group::Info { sequence: 0 }) @@ -8161,7 +8187,7 @@ mod test { /// An abort after every group below the declared end finished leaves the end standing. #[test] fn abort_after_the_end_settles_ends_clean() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None); for sequence in 0..2 { @@ -8181,7 +8207,7 @@ mod test { /// abort gets every group below the end, then the clean end, not the abort. #[test] fn abort_after_the_end_settles_ends_clean_for_an_ordered_reader() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None).ordered(); for sequence in 0..2 { @@ -8205,7 +8231,7 @@ mod test { /// closed track (the channel would turn it into the abort), so these end clean. #[test] fn abort_after_the_end_settles_ends_clean_below_the_cap() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None).ordered(); for sequence in 0..2 { producer @@ -8229,7 +8255,7 @@ mod test { "capped at the end: expected the clean end, got {end:?}" ); - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None).ordered(); for sequence in [0, 2] { producer @@ -8256,7 +8282,7 @@ mod test { #[test] fn recv_group_finishes_without_waiting_for_gaps() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.create_group(group::Info { sequence: 1 }).unwrap(); producer.finish().unwrap(); @@ -8273,7 +8299,7 @@ mod test { #[test] fn next_group_skips_late_arrivals() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None).ordered(); // Seq 5 arrives first. @@ -8310,7 +8336,7 @@ mod test { #[test] fn next_group_returns_arrivals_in_order() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()).ordered(); // Seq 3 arrives first, then seq 5. Both should be returned in arrival order. @@ -8336,7 +8362,7 @@ mod test { #[test] fn ordered_and_arrival_cursors_are_independent() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut ordered = producer.subscribe(replay()).ordered(); let mut arrival = producer.subscribe(replay()); @@ -8360,7 +8386,7 @@ mod test { #[test] fn end_at_caps_next_group() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()).ordered(); for s in 0..6 { @@ -8392,7 +8418,7 @@ mod test { #[test] fn end_at_release_drains_cached_groups() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()).ordered(); for s in 0..6 { @@ -8437,7 +8463,7 @@ mod test { #[test] fn end_at_lower_than_cursor_parks_consumer() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()).ordered(); for s in 0..3 { @@ -8481,7 +8507,7 @@ mod test { #[test] fn end_at_toggling_around_late_arrivals() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()).ordered(); consumer.set_groups(..6); @@ -8527,7 +8553,7 @@ mod test { /// re-offers them, even after the track finishes. #[test] fn end_at_parks_recv_group() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()); for s in 0..3 { @@ -8567,7 +8593,7 @@ mod test { /// it: a relay can ingest a burst micro-reordered (newest first). #[test] fn recv_group_serves_arrivals_behind_the_cap() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()); consumer.set_groups(..2); @@ -8596,7 +8622,7 @@ mod test { #[moq_net_sim::test] async fn group_ranges_preserve_the_floor_when_the_cap_changes() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None); consumer.set_groups(2..=2); producer.create_group(group::Info { sequence: 2 }).unwrap(); @@ -8615,7 +8641,7 @@ mod test { /// them once the cap rises. #[test] fn start_at_drops_parked_recv_groups() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None); consumer.set_groups(..1); @@ -8640,7 +8666,7 @@ mod test { /// after the track finishes. This is what bounds parking by the cache policy. #[test] fn evicted_parked_recv_groups_are_dropped() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None); producer.create_group(group::Info { sequence: 0 }).unwrap(); @@ -8684,7 +8710,7 @@ mod test { } } - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(None); producer.create_group(group::Info { sequence: 0 }).unwrap(); @@ -8715,7 +8741,7 @@ mod test { /// An exclusive cap at 0 is the empty range: no group is delivered, even group 0. #[test] fn end_at_zero_is_the_empty_range() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()); producer.create_group(group::Info { sequence: 0 }).unwrap(); producer.create_group(group::Info { sequence: 1 }).unwrap(); @@ -8738,7 +8764,7 @@ mod test { /// subscriber keeps aggregate demand unbounded. #[test] fn empty_local_cap_holds_while_another_subscriber_requests_everything() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut everything = producer.subscribe(replay()); let mut empty = producer.subscribe(Subscription::default().with_end(Position::group(0))); empty.set_groups((Bound::Unbounded, Position::group(0).group_end())); @@ -8774,7 +8800,7 @@ mod test { /// A frame-limited exclusive end includes the last group and stops before that frame. #[test] fn end_at_frame_limited_last_group() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()).ordered(); let end = Position::after(1, 1).unwrap(); consumer.set_groups((Bound::Unbounded, end.group_end())); @@ -8815,7 +8841,7 @@ mod test { /// An inclusive bound at the last group withholds nothing. #[test] fn end_at_maximum_group_is_unbounded() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let mut consumer = producer.subscribe(replay()).ordered(); consumer.set_groups(..=u64::MAX); @@ -8834,7 +8860,7 @@ mod test { #[test] fn write_frame_rejects_an_oversized_frame_before_appending_its_group() { - let mut producer = track_producer("test", None); + let mut producer = track_producer("test", Info::timed()); let frame = bytes::Bytes::from(vec![0; group::MAX_CACHE_BYTES as usize + 1]); assert!(matches!( @@ -8846,7 +8872,7 @@ mod test { #[test] fn append_group_returns_bounds_exceeded_on_sequence_overflow() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); { let mut state = producer.state.write().ok().unwrap(); state.max_sequence = Some(u64::MAX); @@ -8857,7 +8883,7 @@ mod test { #[moq_net_sim::test] async fn fetch_cache_hit() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); // Produce a cached group. let mut group = producer.append_group().unwrap(); // seq 0 @@ -8881,7 +8907,7 @@ mod test { #[moq_net_sim::test] async fn fetch_miss_signals_dynamic() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -8901,7 +8927,7 @@ mod test { assert_eq!(req.priority(), 7); // Serve it by accepting the request; the fetch then resolves. - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi")) .unwrap(); @@ -8914,7 +8940,7 @@ mod test { #[moq_net_sim::test] async fn fetch_miss_rejects() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -8933,7 +8959,7 @@ mod test { #[moq_net_sim::test] async fn fetch_miss_drop_rejects() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -8952,7 +8978,7 @@ mod test { /// is withdrawn: a later fetch starts a fresh request instead of joining it. #[moq_net_sim::test] async fn fetch_request_unused_once_every_fetch_leaves() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -8977,7 +9003,7 @@ mod test { .unwrap(); drop(req); assert!(fresh.demand().poll_unused(&kio::Waiter::noop()).is_pending()); - fresh.accept(None).unwrap().finish().unwrap(); + fresh.accept(Info::timed()).unwrap().finish().unwrap(); assert_eq!(retry.await.unwrap().sequence, 5); } @@ -8985,7 +9011,7 @@ mod test { /// no handler serves it. #[moq_net_sim::test] async fn fetch_abandoned_while_queued_never_reaches_the_handler() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -8999,7 +9025,7 @@ mod test { /// fetch arriving before the handler drops the abandoned request is not failed with it. #[moq_net_sim::test] async fn fetch_after_the_last_caller_left_is_not_dropped() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9020,7 +9046,7 @@ mod test { .now_or_never() .expect("the retry queues a fresh request") .unwrap(); - fresh.accept(None).unwrap().finish().unwrap(); + fresh.accept(Info::timed()).unwrap().finish().unwrap(); assert_eq!(retry.await.unwrap().sequence, 5); } @@ -9028,7 +9054,7 @@ mod test { /// that queued a fresh request meanwhile resolves from it and that request is moot. #[moq_net_sim::test] async fn fetch_accept_after_withdrawal_caches_the_group() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9047,7 +9073,7 @@ mod test { .expect("the retry queues a fresh request") .unwrap(); - let mut group = req.accept(None).expect("a withdrawn request still accepts"); + let mut group = req.accept(Info::timed()).expect("a withdrawn request still accepts"); group .write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi")) .unwrap(); @@ -9056,7 +9082,7 @@ mod test { let mut fetched = retry.await.unwrap(); assert_eq!(&fetched.read_frame().await.unwrap().unwrap().payload[..], b"hi"); assert!(!fresh.demand().is_used(), "the retry left the fresh request"); - assert!(matches!(fresh.accept(None), Err(Error::Duplicate))); + assert!(matches!(fresh.accept(Info::timed()), Err(Error::Duplicate))); } /// Dropping an auto trait from a published type is a semver break, so the group @@ -9072,7 +9098,7 @@ mod test { /// the group once nobody wants it (an abandoned upstream fetch) leaves it alone. #[moq_net_sim::test] async fn fetch_hit_keeps_the_group_wanted() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9082,7 +9108,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head")) .unwrap(); @@ -9099,7 +9125,7 @@ mod test { /// reader that would skip the head it asked for. #[moq_net_sim::test] async fn accepted_group_starts_at_the_request() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9109,7 +9135,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let _group = req.accept(None).unwrap(); + let _group = req.accept(Info::timed()).unwrap(); let mut whole = consumer.fetch_group(5, None); let req = dynamic @@ -9125,7 +9151,7 @@ mod test { /// the abort, and queues a fresh request. #[moq_net_sim::test] async fn fetch_misses_an_abandoned_group() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9135,7 +9161,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let group = req.accept(None).unwrap(); + let group = req.accept(Info::timed()).unwrap(); drop(first.await.unwrap()); assert!(group.abort_unused(Error::Cancel)); @@ -9145,13 +9171,13 @@ mod test { .now_or_never() .expect("the retry queues a fresh request") .unwrap(); - req.accept(None).unwrap().finish().unwrap(); + req.accept(Info::timed()).unwrap().finish().unwrap(); assert!(retry.await.unwrap().read_frame().await.unwrap().is_none()); } #[moq_net_sim::test] async fn fetch_reject_does_not_poison_retry() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9170,7 +9196,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"retry")) .unwrap(); @@ -9185,7 +9211,7 @@ mod test { /// miss and the fetch goes upstream instead. #[moq_net_sim::test] async fn fetch_ignores_a_group_that_starts_too_late() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9214,7 +9240,7 @@ mod test { assert_eq!((request.sequence(), request.frame_start()), (0, 0)); // Serving it replaces the too-narrow entry rather than colliding with it. - let mut whole = request.accept(None).unwrap(); + let mut whole = request.accept(Info::timed()).unwrap(); whole .write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head")) .unwrap(); @@ -9233,7 +9259,7 @@ mod test { /// group again once the fetch is gone. #[moq_net_sim::test] async fn fetch_fills_the_head_of_a_live_group() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9244,7 +9270,7 @@ mod test { let fetch = consumer.fetch_group(0, None); let request = dynamic.requested_group().await.unwrap(); - let mut head = request.accept(None).unwrap(); + let mut head = request.accept(Info::timed()).unwrap(); for payload in [&b"snapshot"[..], b"delta"] { head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(payload)) .unwrap(); @@ -9286,7 +9312,7 @@ mod test { let fetch = consumer.fetch_group(0, None); let request = dynamic.requested_group().await.unwrap(); - let mut head = request.accept(None).unwrap(); + let mut head = request.accept(Info::timed()).unwrap(); for payload in [&b"snapshot"[..], b"delta"] { head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(payload)) .unwrap(); @@ -9300,7 +9326,7 @@ mod test { /// that still holds the whole group servable, instead of reclaiming the slot. #[moq_net_sim::test] async fn aborted_live_group_keeps_its_fetched_head() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let (live, _head) = live_group_with_head(&producer).await; live.abort(Error::Cancel).unwrap(); @@ -9318,7 +9344,7 @@ mod test { /// the live copy beside it sits idle. #[moq_net_sim::test] async fn reading_the_head_keeps_the_slot_from_expiring() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let (live, _head) = live_group_with_head(&producer).await; live.finish().unwrap(); producer.create_group(group::Info { sequence: 1 }).unwrap(); @@ -9345,7 +9371,7 @@ mod test { .unwrap(); live.finish().unwrap(); let fetch = consumer.fetch_group(0, None); - let mut head = dynamic.requested_group().await.unwrap().accept(None).unwrap(); + let mut head = dynamic.requested_group().await.unwrap().accept(Info::timed()).unwrap(); head.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 10_000])) .unwrap(); head.finish().unwrap(); @@ -9380,7 +9406,7 @@ mod test { /// is already on the wire fails cleanly rather than being handed the narrower group. #[moq_net_sim::test] async fn fetch_widens_or_fails_cleanly() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9406,7 +9432,7 @@ mod test { // A handler may keep tracking the joined demand past the accept, as the lite // subscriber does, which holds the result channel open. let _joined = request.result.clone(); - let mut group = request.accept(None).unwrap(); + let mut group = request.accept(Info::timed()).unwrap(); // A handler numbers the frames from where it was asked to start, as `serve_fetch` // does; that offset is what makes the cached group too narrow for `widest`. group.start_at(2).unwrap(); @@ -9425,7 +9451,7 @@ mod test { #[moq_net_sim::test] async fn fetch_coalesces_concurrent() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9451,7 +9477,7 @@ mod test { let third = consumer.fetch_group(5, None); // One accept resolves all of them. - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi")) .unwrap(); @@ -9464,7 +9490,7 @@ mod test { #[moq_net_sim::test] async fn fetch_coalesced_reject_fails_all() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9493,7 +9519,7 @@ mod test { #[moq_net_sim::test] async fn fetch_queued_fails_when_handlers_leave() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); @@ -9512,7 +9538,7 @@ mod test { async fn fetch_miss_no_dynamic_not_found() { // A track with no `Dynamic` can't serve old content, so a cache miss // resolves to NotFound instead of blocking forever. - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.append_group().unwrap(); // seq 0, but we miss on seq 5 let consumer = producer.consume(); assert!(matches!(consumer.fetch_group(5, None).await, Err(Error::NotFound))); @@ -9520,7 +9546,7 @@ mod test { #[moq_net_sim::test] async fn fetch_past_final_not_found() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); producer.append_group().unwrap(); // seq 0 producer.finish().unwrap(); // final_sequence = 1 @@ -9544,7 +9570,7 @@ mod test { pool: pool.clone(), ..Default::default() }; - let producer = Producer::new(Arc::new(broadcast), "test", None); + let producer = Producer::new(Arc::new(broadcast), "test", Info::timed()); (producer, pool) } @@ -9730,7 +9756,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut backfill = req.accept(None).unwrap(); + let mut backfill = req.accept(Info::timed()).unwrap(); pending.await.unwrap(); backfill .write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 30_000])) @@ -9738,7 +9764,7 @@ mod test { // Accept with a fresh Info: the pre-accept group's writes must still be // drained by this track's future charges. - let producer = request.accept(None); + let producer = request.accept(Info::timed()); producer.append_group().unwrap().finish().unwrap(); producer.append_group().unwrap().finish().unwrap(); @@ -9818,7 +9844,7 @@ mod test { #[test] fn each_track_owns_its_account() { let broadcast = Arc::new(broadcast::Info::default()); - let info = Info::default(); + let info = Info::timed(); let a = Producer::new(broadcast.clone(), "a", info.clone()); let b = Producer::new(broadcast, "b", info); @@ -9923,11 +9949,11 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut backfill = req.accept(None).unwrap(); + let mut backfill = req.accept(Info::timed()).unwrap(); pending.await.unwrap(); // Accept, then demote the backfill with a live group. - let producer = request.accept(None); + let producer = request.accept(Info::timed()); producer.append_group().unwrap().finish().unwrap(); // No further insert: the late write into the demoted backfill is the only @@ -9984,7 +10010,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100])) .unwrap(); @@ -10123,7 +10149,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill")) .unwrap(); @@ -10154,7 +10180,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100])) .unwrap(); @@ -10216,7 +10242,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000])) .unwrap(); @@ -10258,7 +10284,7 @@ mod test { .unwrap(); assert_eq!(req.sequence(), 0); - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"refetched")) .unwrap(); @@ -10279,7 +10305,7 @@ mod test { /// pins the committed semantics it has to preserve. #[test] fn an_aborted_group_releases_its_sequence() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let consumer = producer.consume(); let mut group = producer.create_group(group::Info { sequence: 3 }).unwrap(); @@ -10323,7 +10349,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill")) .unwrap(); @@ -10356,7 +10382,7 @@ mod test { .now_or_never() .expect("should not block") .unwrap(); - let mut group = req.accept(None).unwrap(); + let mut group = req.accept(Info::timed()).unwrap(); group .write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000])) .unwrap(); @@ -10382,7 +10408,7 @@ mod test { assert!(dynamic.demand().is_used()); drop(consumer); assert!(!demand.is_used()); - let producer = request.accept(None); + let producer = request.accept(Info::timed()); assert_eq!(producer.demand().name(), demand.name()); drop(producer); drop(dynamic); @@ -10391,7 +10417,7 @@ mod test { #[test] fn fetch_request_demand_counts_every_joined_caller() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); let first = consumer.fetch_group(3, None); @@ -10410,7 +10436,7 @@ mod test { #[moq_net_sim::test] async fn fetch_aborts_with_track() { - let producer = track_producer("test", None); + let producer = track_producer("test", Info::timed()); let dynamic = producer.dynamic(); let consumer = producer.consume(); diff --git a/rs/moq-net/tests/announce_to_serve.rs b/rs/moq-net/tests/announce_to_serve.rs index b120d629bea..300261fc7bb 100644 --- a/rs/moq-net/tests/announce_to_serve.rs +++ b/rs/moq-net/tests/announce_to_serve.rs @@ -16,6 +16,11 @@ use futures::{StreamExt, channel::mpsc}; use moq_net::{Error, Hop, Timestamp, Version, origin}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Long enough, in virtual time, for anything in flight to reach the far side. const SETTLE: Duration = Duration::from_secs(1); @@ -138,7 +143,7 @@ async fn lifecycle(observer: Observer) -> Vec { // Created, not yet announced: nobody can see or reach it. let broadcast = publisher.create_broadcast(PATH).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); moq_net_sim::sleep(SETTLE).await; log.push(format!( "created: {:?} {}", @@ -193,7 +198,7 @@ async fn lifecycle(observer: Observer) -> Vec { )); let again = again.expect("a reannounced broadcast resolves"); - let audio = broadcast.create_track("audio", None).unwrap(); + let audio = broadcast.create_track("audio", timed()).unwrap(); let mut reader = read(&again, "audio").await; moq_net_sim::timeout(SETTLE, audio.demand().used()) .await diff --git a/rs/moq-net/tests/auth.rs b/rs/moq-net/tests/auth.rs index e402c0b7d5b..8160177d566 100644 --- a/rs/moq-net/tests/auth.rs +++ b/rs/moq-net/tests/auth.rs @@ -15,6 +15,11 @@ use moq_net::{ use support::harness::{now, spawn}; use support::mock::{MockSession, create_mock_session_pair}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Maximum time any single test may run before being treated as a deadlock. const TEST_TIMEOUT: Duration = Duration::from_secs(10); @@ -740,12 +745,12 @@ async fn a_revoked_grant_cancels_its_subscriptions(version: &'static str) { // The server publishes room/x to the client; the client publishes up/y to the server. let server_origin = produce_origin(1); let down = server_origin.create_broadcast("room/x").unwrap(); - let down_track = down.create_track("video", None).unwrap(); + let down_track = down.create_track("video", timed()).unwrap(); down.announce(Default::default()).unwrap(); let client_origin = produce_origin(2); let up = client_origin.create_broadcast("up/y").unwrap(); - let up_track = up.create_track("video", None).unwrap(); + let up_track = up.create_track("video", timed()).unwrap(); up.announce(Default::default()).unwrap(); let received = produce_origin(3); @@ -1142,10 +1147,10 @@ async fn a_narrowing_deafens_one_path(version: &'static str) { let relay = produce_origin(1); let audio = relay.create_broadcast("room/alice/audio").unwrap(); - let audio_track = audio.create_track("opus", None).unwrap(); + let audio_track = audio.create_track("opus", timed()).unwrap(); audio.announce(Default::default()).unwrap(); let video = relay.create_broadcast("room/alice/video").unwrap(); - let video_track = video.create_track("h264", None).unwrap(); + let video_track = video.create_track("h264", timed()).unwrap(); video.announce(Default::default()).unwrap(); let received = produce_origin(3); @@ -1215,10 +1220,10 @@ async fn a_narrowing_aborts_what_the_peer_published(version: &'static str) { let client_origin = produce_origin(2); let mic = client_origin.create_broadcast("room/bob/mic").unwrap(); - let mic_track = mic.create_track("opus", None).unwrap(); + let mic_track = mic.create_track("opus", timed()).unwrap(); mic.announce(Default::default()).unwrap(); let cam = client_origin.create_broadcast("room/bob/cam").unwrap(); - let cam_track = cam.create_track("h264", None).unwrap(); + let cam_track = cam.create_track("h264", timed()).unwrap(); cam.announce(Default::default()).unwrap(); let relay = produce_origin(1); @@ -1268,7 +1273,7 @@ async fn a_narrowing_resets_a_fetch_in_flight(version: &'static str) { let relay = produce_origin(1); let broadcast = relay.create_broadcast("room/x").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); // The group stays open, so the fetch is still in flight when the grant narrows. let mut group = track.append_group().unwrap(); @@ -1328,7 +1333,7 @@ async fn a_widening_brings_back_a_deafened_path(version: &'static str) { let relay = produce_origin(1); let audio = relay.create_broadcast("room/alice/audio").unwrap(); - let audio_track = audio.create_track("opus", None).unwrap(); + let audio_track = audio.create_track("opus", timed()).unwrap(); audio.announce(Default::default()).unwrap(); let received = produce_origin(3); @@ -1385,7 +1390,7 @@ async fn a_widening_brings_back_what_the_peer_published(version: &'static str) { let client_origin = produce_origin(2); let mic = client_origin.create_broadcast("room/bob/mic").unwrap(); - let mic_track = mic.create_track("opus", None).unwrap(); + let mic_track = mic.create_track("opus", timed()).unwrap(); mic.announce(Default::default()).unwrap(); let relay = produce_origin(1); diff --git a/rs/moq-net/tests/datagram.rs b/rs/moq-net/tests/datagram.rs index 907cd647abe..1a77dd3c232 100644 --- a/rs/moq-net/tests/datagram.rs +++ b/rs/moq-net/tests/datagram.rs @@ -12,6 +12,11 @@ use std::time::Duration; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, MockPair, connect_mock, peer}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Maximum time any single test may run before being treated as a deadlock. const TEST_TIMEOUT: Duration = Duration::from_secs(10); @@ -38,7 +43,7 @@ async fn connect_datagram_track() -> Fixture { let consumer_origin = produce_origin(2); let broadcast = publisher.create_broadcast("bench").unwrap(); - let producer = broadcast.create_track("datagrams", None).unwrap(); + let producer = broadcast.create_track("datagrams", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut options = MockConnectOptions::new("moq-lite-05".parse::().unwrap()); @@ -133,7 +138,7 @@ async fn ietf_delivers_datagrams_on(version: &str) { let consumer_origin = produce_origin(2); let broadcast = publisher.create_broadcast("bench").unwrap(); - let mut producer = broadcast.create_track("datagrams", None).unwrap(); + let mut producer = broadcast.create_track("datagrams", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut options = MockConnectOptions::new(version.parse::().unwrap()); @@ -248,7 +253,7 @@ async fn a_fetched_datagram_is_refused_per_version() { } let broadcast = nodes[0].create_broadcast("room").unwrap(); - let mut producer = broadcast.create_track("datagrams", None).unwrap(); + let mut producer = broadcast.create_track("datagrams", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let sequence = producer.append_datagram(Timestamp::ZERO, &b"x"[..]).unwrap(); diff --git a/rs/moq-net/tests/drained_claim.rs b/rs/moq-net/tests/drained_claim.rs index fe492c539d1..6f09bc1d673 100644 --- a/rs/moq-net/tests/drained_claim.rs +++ b/rs/moq-net/tests/drained_claim.rs @@ -14,6 +14,11 @@ use std::{cell::RefCell, rc::Rc, time::Duration}; use moq_net::{Hop, Timestamp, Version, broadcast, origin, track}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TEST_TIMEOUT: Duration = Duration::from_secs(600); fn produce_origin(hop: u64) -> origin::Producer { @@ -57,7 +62,7 @@ impl Worker { drop(moq_net_sim::spawn(async move { while let Ok(request) = handler.requested_broadcast().await { let output = broadcast::Info::new().produce(); - let track = output.create_track("video", None).unwrap(); + let track = output.create_track("video", timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(Timestamp::ZERO, name.as_bytes().to_vec()).unwrap(); group.finish().unwrap(); diff --git a/rs/moq-net/tests/empty_group.rs b/rs/moq-net/tests/empty_group.rs index 93481093ea8..5abe054854f 100644 --- a/rs/moq-net/tests/empty_group.rs +++ b/rs/moq-net/tests/empty_group.rs @@ -9,6 +9,11 @@ use std::time::Duration; use moq_net::{Hop, Timestamp, Version, origin}; use support::harness::{peer, subscribed}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); const VERSIONS: &[&str] = &[ @@ -33,7 +38,7 @@ async fn an_empty_group_arrives_empty() { let _pair = peer(version.parse::().unwrap(), &publisher, &subscriber).await; let broadcast = publisher.create_broadcast("room").unwrap(); - let track = broadcast.create_track("data", None).unwrap(); + let track = broadcast.create_track("data", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let consumer = subscriber.consume(); diff --git a/rs/moq-net/tests/fetch_cancel.rs b/rs/moq-net/tests/fetch_cancel.rs index 94801e0a3da..17bbf328130 100644 --- a/rs/moq-net/tests/fetch_cancel.rs +++ b/rs/moq-net/tests/fetch_cancel.rs @@ -10,9 +10,14 @@ mod support; use std::time::Duration; use bytes::Bytes; -use moq_net::{Hop, Timestamp, Version, origin, track}; +use moq_net::{Hop, Timestamp, Version, origin}; use support::harness::peer; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + fn produce_origin(hop: u64) -> origin::Producer { let (producer, driver) = origin::Producer::new(origin::Config::new(Hop::new(hop).unwrap())); support::harness::spawn(driver); @@ -59,7 +64,7 @@ async fn abandoned_fetch_reaches_the_publisher(version: &str, relays: u64, stage futures::future::Either::Right(_) => panic!("nothing answered the track"), }; let groups = request.dynamic(); - let _track = request.accept(track::Info::default()); + let _track = request.accept(timed()); let request = match futures::future::select(std::pin::pin!(groups.requested_group()), &mut waiting).await { futures::future::Either::Left((request, _)) => request.expect("the fetch reaches the publisher"), futures::future::Either::Right(_) => panic!("nothing answered the fetch"), @@ -77,7 +82,7 @@ async fn abandoned_fetch_reaches_the_publisher(version: &str, relays: u64, stage .expect("the pending fetch is still open"); } Stage::MidResponse => { - let mut group = request.accept(None).unwrap(); + let mut group = request.accept(timed()).unwrap(); group.write_frame(Timestamp::ZERO, Bytes::from_static(b"head")).unwrap(); let mut fetched = waiting.await.unwrap(); @@ -105,7 +110,7 @@ async fn abandoned_fetch_reaches_the_publisher(version: &str, relays: u64, stage assert_eq!(payloads, [&b"head"[..], &b"tail"[..]], "{ctx}"); } Stage::Drained => { - let mut group = request.accept(None).unwrap(); + let mut group = request.accept(timed()).unwrap(); group.write_frame(Timestamp::ZERO, Bytes::from_static(b"head")).unwrap(); group.write_frame(Timestamp::ZERO, Bytes::from_static(b"tail")).unwrap(); @@ -178,12 +183,12 @@ async fn a_refetch_within_the_linger_stays_on_the_relay(version: &str) { futures::future::Either::Right(_) => panic!("nothing answered the track"), }; let groups = request.dynamic(); - let _track = request.accept(track::Info::default()); + let _track = request.accept(timed()); let request = match futures::future::select(std::pin::pin!(groups.requested_group()), &mut waiting).await { futures::future::Either::Left((request, _)) => request.expect("the fetch reaches the publisher"), futures::future::Either::Right(_) => panic!("nothing answered the fetch"), }; - let mut group = request.accept(None).unwrap(); + let mut group = request.accept(timed()).unwrap(); group .write_frame(Timestamp::ZERO, Bytes::from_static(b"segment")) .unwrap(); diff --git a/rs/moq-net/tests/fetch_only.rs b/rs/moq-net/tests/fetch_only.rs index 21880e230f6..e36a1d84582 100644 --- a/rs/moq-net/tests/fetch_only.rs +++ b/rs/moq-net/tests/fetch_only.rs @@ -13,6 +13,11 @@ use std::time::Duration; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(5); fn produce_origin(hop: u64) -> moq_net::origin::Producer { @@ -36,7 +41,7 @@ async fn resolve(origin: &moq_net::origin::Producer) -> moq_net::broadcast::Cons /// Publish a finished track of three groups, and flag any subscription it ever gets. fn publish_finished(origin: &moq_net::origin::Producer) -> (moq_net::broadcast::Producer, Arc) { let broadcast = origin.create_broadcast("bcast").unwrap(); - let mut track = broadcast.create_track("video", None).unwrap(); + let mut track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); for sequence in 0..3u64 { let mut group = track.append_group().unwrap(); @@ -163,7 +168,7 @@ async fn a_subscribe_after_track_status_keeps_timestamps() { moq_net_sim::timeout(TIMEOUT, async { let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let client = produce_origin(2); diff --git a/rs/moq-net/tests/finished_broadcast_mock.rs b/rs/moq-net/tests/finished_broadcast_mock.rs index cf0b33ad0af..b8d3a84b0cc 100644 --- a/rs/moq-net/tests/finished_broadcast_mock.rs +++ b/rs/moq-net/tests/finished_broadcast_mock.rs @@ -19,6 +19,11 @@ use std::time::Duration; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); const PAYLOAD: &[u8] = b"frame"; @@ -33,7 +38,7 @@ fn produce_origin(hop: u64) -> moq_net::origin::Producer { async fn round(finish_broadcast: bool) -> (Vec>, Option) { let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let subscriber = produce_origin(2); diff --git a/rs/moq-net/tests/finite_relay.rs b/rs/moq-net/tests/finite_relay.rs index c42cbf20033..ad204f2ef98 100644 --- a/rs/moq-net/tests/finite_relay.rs +++ b/rs/moq-net/tests/finite_relay.rs @@ -17,6 +17,11 @@ use moq_net::track::Subscription; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); const MAX_DELAY: Duration = Duration::from_secs(5); const GROUPS: u64 = 4; @@ -54,7 +59,7 @@ async fn round(name: &str, arrival: Arrival) -> (Vec, Result<(), moq_net::E let version: Version = name.parse().unwrap(); let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); - let mut track = broadcast.create_track("tail", None).unwrap(); + let mut track = broadcast.create_track("tail", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let relay = produce_origin(2); diff --git a/rs/moq-net/tests/goaway.rs b/rs/moq-net/tests/goaway.rs index 573886bf2ea..c054cf7104d 100644 --- a/rs/moq-net/tests/goaway.rs +++ b/rs/moq-net/tests/goaway.rs @@ -12,6 +12,11 @@ use moq_net::{Hop, Version, goaway::Goaway}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; use support::mock::create_mock_session_pair; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Build an origin producer, spawning its driver on the ambient runtime. fn produce_origin(hop: Hop) -> moq_net::origin::Producer { let (producer, driver) = moq_net::origin::Producer::new(moq_net::origin::Config::new(hop)); @@ -318,9 +323,9 @@ async fn goaway_keeps_new_subscribes(version: Version) { let pub_origin = produce_origin(Hop::random()); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(moq_net::origin::Route::default()).expect("announce"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); // A second track with content ready for the subscribe opened after the GOAWAY. - let audio = broadcast.create_track("audio", None).expect("create track"); + let audio = broadcast.create_track("audio", timed()).expect("create track"); let mut audio_group = audio.append_group().expect("append group"); audio_group .write_frame(moq_net::Timestamp::ZERO, b"audio".as_ref()) diff --git a/rs/moq-net/tests/group_charge.rs b/rs/moq-net/tests/group_charge.rs index 9fea7c400bd..95be7b2377d 100644 --- a/rs/moq-net/tests/group_charge.rs +++ b/rs/moq-net/tests/group_charge.rs @@ -27,6 +27,11 @@ thread_local! { static LIVE: Cell = const { Cell::new(0) }; } +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + struct Counting; // Accounting only. Every call forwards to the system allocator unchanged. @@ -62,7 +67,7 @@ fn measure(frames: usize, payload: usize) -> (usize, u64) { info.pool = pool.clone(); let broadcast = info.produce(); - let track = broadcast.create_track("chat", None).unwrap(); + let track = broadcast.create_track("chat", timed()).unwrap(); // Warm up outside the measurement so the track's own one-time allocations and // any lazy statics aren't billed to the groups. diff --git a/rs/moq-net/tests/head_fetch.rs b/rs/moq-net/tests/head_fetch.rs index 2323814fa4b..e34edfef5a5 100644 --- a/rs/moq-net/tests/head_fetch.rs +++ b/rs/moq-net/tests/head_fetch.rs @@ -14,6 +14,11 @@ use moq_net::track::{Position, Subscription}; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(5); const VERSIONS: &[&str] = &[ @@ -61,7 +66,7 @@ async fn round(version: &str) { let version: Version = version.parse().unwrap(); let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); - let track = broadcast.create_track("catalog.json", None).unwrap(); + let track = broadcast.create_track("catalog.json", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut group = track.append_group().unwrap(); diff --git a/rs/moq-net/tests/history_groups.rs b/rs/moq-net/tests/history_groups.rs index 8268385290f..8f19f90cfcd 100644 --- a/rs/moq-net/tests/history_groups.rs +++ b/rs/moq-net/tests/history_groups.rs @@ -11,7 +11,7 @@ mod support; use std::time::Duration; use moq_net::track::{Info, Position, Subscription}; -use moq_net::{Hop, Timestamp, Version}; +use moq_net::{Hop, Timescale, Timestamp, Version}; use support::harness::{MockConnectOptions, connect_mock}; const TIMEOUT: Duration = Duration::from_secs(10); @@ -42,7 +42,10 @@ async fn round(version: &str, relay: bool, newest_first: bool) -> Vec<(u64, usiz let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); let track = broadcast - .create_track("history", Info::default().with_max_age(FOREVER)) + .create_track( + "history", + Info::default().with_timescale(Timescale::MILLI).with_max_age(FOREVER), + ) .unwrap(); broadcast.announce(Default::default()).unwrap(); @@ -201,7 +204,10 @@ async fn a_late_subscriber_receives_the_relays_cached_history() { let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); let track = broadcast - .create_track("history", Info::default().with_max_age(FOREVER)) + .create_track( + "history", + Info::default().with_timescale(Timescale::MILLI).with_max_age(FOREVER), + ) .unwrap(); broadcast.announce(Default::default()).unwrap(); let relay = produce_origin(2); diff --git a/rs/moq-net/tests/late_lower_group.rs b/rs/moq-net/tests/late_lower_group.rs index 395eabb0c75..efa3a4aeef6 100644 --- a/rs/moq-net/tests/late_lower_group.rs +++ b/rs/moq-net/tests/late_lower_group.rs @@ -71,7 +71,12 @@ async fn session(version: &str, relay: bool, start: Option) -> Vec<(u6 let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); let track = broadcast - .create_track("late", Info::default().with_max_age(FOREVER)) + .create_track( + "late", + Info::default() + .with_timescale(moq_net::Timescale::MILLI) + .with_max_age(FOREVER), + ) .unwrap(); broadcast.announce(Default::default()).unwrap(); @@ -140,7 +145,12 @@ async fn in_process(start: Option) -> Vec<(u64, usize)> { let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); let track = broadcast - .create_track("late", Info::default().with_max_age(FOREVER)) + .create_track( + "late", + Info::default() + .with_timescale(moq_net::Timescale::MILLI) + .with_max_age(FOREVER), + ) .unwrap(); let subscription = Subscription::default().with_max_delay(BUDGET).with_start(start); let mut sub = track.subscribe(subscription); diff --git a/rs/moq-net/tests/loom.rs b/rs/moq-net/tests/loom.rs index f21986f2677..25d201c6512 100644 --- a/rs/moq-net/tests/loom.rs +++ b/rs/moq-net/tests/loom.rs @@ -28,6 +28,11 @@ use bytes::Bytes; use loom::{future::block_on, thread}; use moq_net::{Error, Hop, Timestamp, broadcast, cache, origin}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Preemptions per execution unless `LOOM_MAX_PREEMPTIONS` overrides it. Loom's own /// guidance is that 2 or 3 catches most bugs, and each extra level multiplies the /// search several times over. Unbounded, `back_to_back_groups_arrive_in_order` no @@ -49,7 +54,7 @@ fn frame_reaches_a_parked_subscriber() { model(|| { let broadcast = broadcast::Info::new().produce(); let consumer = broadcast.consume(); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let track_consumer = consumer.track("video").expect("track"); let publisher = thread::spawn(move || { @@ -340,7 +345,7 @@ fn concurrent_tracks_drain_a_shared_pool() { let handles: Vec<_> = ["video", "audio"] .into_iter() .map(|name| { - let track = broadcast.create_track(name, None).expect("create track"); + let track = broadcast.create_track(name, timed()).expect("create track"); thread::spawn(move || { let mut group = track.append_group().expect("append group"); group diff --git a/rs/moq-net/tests/multi_cdn.rs b/rs/moq-net/tests/multi_cdn.rs index 916528220b5..291143613cb 100644 --- a/rs/moq-net/tests/multi_cdn.rs +++ b/rs/moq-net/tests/multi_cdn.rs @@ -15,6 +15,11 @@ use futures::{StreamExt, channel::mpsc}; use moq_net::{Error, Hop, Timestamp, Version, announce, broadcast, origin, track}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> track::Info { + track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TEST_TIMEOUT: Duration = Duration::from_secs(30); /// Frames per group. @@ -151,7 +156,7 @@ impl Topology { let subscriber = produce_origin(4); let broadcast = publisher.create_broadcast("live").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast .announce(origin::Route::default().with_epoch(moq_net::Epoch::mint())) .unwrap(); diff --git a/rs/moq-net/tests/quiet_catalog.rs b/rs/moq-net/tests/quiet_catalog.rs index 94b8c312464..d0d9a6128df 100644 --- a/rs/moq-net/tests/quiet_catalog.rs +++ b/rs/moq-net/tests/quiet_catalog.rs @@ -10,6 +10,11 @@ use std::{future::Future, time::Duration}; use moq_net::{Error, Hop, Timestamp, Version, broadcast, origin, track}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(5); const VERSIONS: &[&str] = &["moq-lite-06", "moq-lite-07-wip", "moq-transport-16", "moq-transport-22"]; @@ -42,7 +47,7 @@ async fn settle() { /// Announce `live/catalog.json` holding one finished snapshot group. fn publish(origin: &origin::Producer) -> (broadcast::Producer, track::Producer) { let broadcast = origin.create_broadcast("live").unwrap(); - let track = broadcast.create_track("catalog.json", None).unwrap(); + let track = broadcast.create_track("catalog.json", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(Timestamp::ZERO, b"snapshot").unwrap(); diff --git a/rs/moq-net/tests/rejoin.rs b/rs/moq-net/tests/rejoin.rs index eb56fe8f710..9ccbcd8597b 100644 --- a/rs/moq-net/tests/rejoin.rs +++ b/rs/moq-net/tests/rejoin.rs @@ -7,6 +7,11 @@ use std::time::Duration; use moq_net::{Hop, Timestamp, Version, group, track}; use support::harness::{MockConnectOptions, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Maximum time any single test may run before being treated as a deadlock. const TEST_TIMEOUT: Duration = Duration::from_secs(10); @@ -39,7 +44,7 @@ async fn rejoin_recovers_the_group_reset_on_leave() { let relay = produce_origin(2); let broadcast = publisher.create_broadcast("bench").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut options = MockConnectOptions::new(version.parse::().unwrap()); @@ -132,7 +137,7 @@ async fn rejoin_skips_a_cache_kept_by_another_handle() { let relay = produce_origin(2); let broadcast = publisher.create_broadcast("bench").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut options = MockConnectOptions::new(version.parse::().unwrap()); @@ -196,7 +201,7 @@ async fn rejoin_mid_group_keeps_the_head_for_later_readers() { let client = produce_origin(3); let broadcast = publisher.create_broadcast("bench").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); // Left open, as a JSON snapshot group stays open for its deltas. let mut open = track.append_group().unwrap(); @@ -269,7 +274,7 @@ async fn rejoin_goes_live_without_the_join_head() { let client = produce_origin(3); let broadcast = publisher.create_broadcast("bench").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut open = track.append_group().unwrap(); open.write_frame(Timestamp::ZERO, b"a0".as_ref()).unwrap(); @@ -339,7 +344,7 @@ async fn rejoin_during_the_cancel_skips_the_cache() { let relay = produce_origin(2); let broadcast = publisher.create_broadcast("bench").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut options = MockConnectOptions::new(version.parse::().unwrap()); @@ -409,7 +414,7 @@ async fn rejoin_waits_for_the_answers_first_frame() { let relay = produce_origin(2); let broadcast = publisher.create_broadcast("bench").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut options = MockConnectOptions::new(version.parse::().unwrap()); @@ -482,7 +487,7 @@ async fn leaving_after_the_cache_window_keeps_the_latest_group() { let relay = produce_origin(2); let broadcast = publisher.create_broadcast("bench").unwrap(); - let track = broadcast.create_track("catalog", None).unwrap(); + let track = broadcast.create_track("catalog", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(Timestamp::ZERO, b"c0".as_ref()).unwrap(); @@ -557,7 +562,7 @@ async fn rejoin_keeps_source_timestamps() { let client = produce_origin(2); let broadcast = publisher.create_broadcast("bench").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut options = MockConnectOptions::new(version.parse::().unwrap()); diff --git a/rs/moq-net/tests/restart.rs b/rs/moq-net/tests/restart.rs index e77c6140ee1..389e4dfb293 100644 --- a/rs/moq-net/tests/restart.rs +++ b/rs/moq-net/tests/restart.rs @@ -13,6 +13,11 @@ use std::{cell::RefCell, rc::Rc, time::Duration}; use moq_net::{Epoch, Hop, Timestamp, Version, announce, broadcast, origin, track}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TEST_TIMEOUT: Duration = Duration::from_secs(60); fn produce_origin(hop: u64) -> origin::Producer { @@ -103,7 +108,7 @@ async fn relay_chain(version: &str) { let _downstream = link(version, &relay, &viewer).await; let old = publisher.publish("live", origin::Route::default()).unwrap(); - let old_track = old.create_track("video", None).unwrap(); + let old_track = old.create_track("video", timed()).unwrap(); let mut announced = viewer.consume().announced(); match next_event(&mut announced).await { announce::Event::Start(announce) => assert_eq!(announce.prefix.as_str(), "live"), @@ -118,7 +123,7 @@ async fn relay_chain(version: &str) { // The publisher restarts without an epoch, its old session still open. let new = publisher.publish("live", origin::Route::default()).unwrap(); - let new_track = new.create_track("video", None).unwrap(); + let new_track = new.create_track("video", timed()).unwrap(); expect_restart(version, &mut announced, "live").await; // The old subscription stays on the old instance. @@ -169,7 +174,7 @@ impl Worker { drop(moq_net_sim::spawn(async move { while let Ok(request) = handler.requested_broadcast().await { let output = broadcast::Info::new().produce(); - let track = output.create_track("video", None).unwrap(); + let track = output.create_track("video", timed()).unwrap(); write(&track, &format!("{hop}")); request.accept(&output); served.borrow_mut().push((output, track)); @@ -252,7 +257,7 @@ impl Claim { drop(moq_net_sim::spawn(async move { while let Ok(request) = handler.requested_broadcast().await { let output = broadcast::Info::new().produce(); - let track = output.create_track("video", None).unwrap(); + let track = output.create_track("video", timed()).unwrap(); request.accept(&output); served.borrow_mut().push((output, track)); } @@ -426,7 +431,7 @@ async fn retired_fetch(version: &str, explicit: bool) { origin::Route::default() }; let old = publisher.publish("live", old_route).unwrap(); - let old_track = old.create_track("video", None).unwrap(); + let old_track = old.create_track("video", timed()).unwrap(); let mut announced = viewer.consume().announced(); assert!(matches!(next_event(&mut announced).await, announce::Event::Start(_))); let (held, mut subscription) = subscribe(&viewer, "live").await; @@ -435,7 +440,7 @@ async fn retired_fetch(version: &str, explicit: bool) { assert_eq!(read(&mut subscription).await, "old:0"); let replacement = publisher.publish("live", new_route).unwrap(); - let replacement_track = replacement.create_track("video", None).unwrap(); + let replacement_track = replacement.create_track("video", timed()).unwrap(); let _late = replacement .create_track("late", Some(track::Info::default().with_priority(17))) .unwrap(); @@ -518,7 +523,7 @@ async fn withdrawal_refuses_new_fetches_but_keeps_open_reads() { let viewer = produce_origin(2); let _session = link(version, &publisher, &viewer).await; let source = publisher.publish("live", origin::Route::default()).unwrap(); - let produced = source.create_track("video", None).unwrap(); + let produced = source.create_track("video", timed()).unwrap(); let mut announced = viewer.consume().announced(); assert!(matches!(next_event(&mut announced).await, announce::Event::Start(_))); let (held, mut subscription) = subscribe(&viewer, "live").await; @@ -554,7 +559,7 @@ async fn a_more_specific_source_cannot_fill_a_held_covering_track() { old.write(0, "old:0"); assert_eq!(read(&mut subscription).await, "old:0"); let replacement = publisher.publish("live/job", origin::Route::default()).unwrap(); - let replacement_track = replacement.create_track("video", None).unwrap(); + let replacement_track = replacement.create_track("video", timed()).unwrap(); write(&replacement_track, "new:0"); write(&replacement_track, "new:1"); assert!(matches!(next_event(&mut announced).await, announce::Event::Start(_))); diff --git a/rs/moq-net/tests/route_change.rs b/rs/moq-net/tests/route_change.rs index 37eaee1283f..8ea059c8ed3 100644 --- a/rs/moq-net/tests/route_change.rs +++ b/rs/moq-net/tests/route_change.rs @@ -21,6 +21,11 @@ use futures::{StreamExt, channel::mpsc}; use moq_net::{Error, Hop, Timestamp, Version, announce, broadcast, origin, track}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TEST_TIMEOUT: Duration = Duration::from_secs(30); /// Frames per group. @@ -105,7 +110,7 @@ impl Topology { let subscriber = produce_origin(4); let broadcast = publisher.create_broadcast("live").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast .announce(origin::Route::default().with_epoch(moq_net::Epoch::mint())) .unwrap(); @@ -536,7 +541,7 @@ async fn lagging_route_dies(version: Version) -> mpsc::UnboundedReceiver, r for (name, hop, cost) in [("P1", 1, None), ("P2", 2, Some(1_000))] { let publisher = produce_origin(hop); let broadcast = publisher.create_broadcast("live").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); let route = match &epoch { Some(epoch) => origin::Route::default().with_epoch(epoch.clone()), None => origin::Route::default(), @@ -873,7 +878,7 @@ async fn producer_replaced(version: &str) { let (tx, mut producers) = mpsc::unbounded(); moq_net_sim::spawn(async move { while let Ok(request) = dynamic.requested_track().await { - if tx.unbounded_send(request.accept(None)).is_err() { + if tx.unbounded_send(request.accept(timed())).is_err() { return; } } diff --git a/rs/moq-net/tests/session_close.rs b/rs/moq-net/tests/session_close.rs index 389b8f1f403..2a4d6c0f4e2 100644 --- a/rs/moq-net/tests/session_close.rs +++ b/rs/moq-net/tests/session_close.rs @@ -10,6 +10,11 @@ use std::time::Duration; use moq_net::{Error, Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); fn produce_origin(hop: u64) -> moq_net::origin::Producer { @@ -32,7 +37,7 @@ struct Setup { async fn setup(version: &str) -> Setup { let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let subscriber = produce_origin(2); diff --git a/rs/moq-net/tests/settled_end_survives_abort.rs b/rs/moq-net/tests/settled_end_survives_abort.rs index e737c31fd25..225553b904f 100644 --- a/rs/moq-net/tests/settled_end_survives_abort.rs +++ b/rs/moq-net/tests/settled_end_survives_abort.rs @@ -7,6 +7,11 @@ mod support; use moq_net::{Error, Hop, Timestamp}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + fn produce_origin(hop: u64) -> moq_net::origin::Producer { let (producer, driver) = moq_net::origin::Producer::new(moq_net::origin::Config::new(Hop::new(hop).unwrap())); support::harness::spawn(driver); @@ -21,7 +26,7 @@ const GROUPS: u64 = 2; async fn round(abort: bool, ordered: bool) -> (Vec, Option) { let origin = produce_origin(1); let broadcast = origin.create_broadcast("bcast").unwrap(); - let mut track = broadcast.create_track("video", None).unwrap(); + let mut track = broadcast.create_track("video", timed()).unwrap(); // From the first group with a replay window: a late reader is owed the whole track, // not the live edge. let subscription = moq_net::track::Subscription::default() diff --git a/rs/moq-net/tests/subscribe_tracks.rs b/rs/moq-net/tests/subscribe_tracks.rs index af34c42403e..960b3cf9163 100644 --- a/rs/moq-net/tests/subscribe_tracks.rs +++ b/rs/moq-net/tests/subscribe_tracks.rs @@ -12,6 +12,11 @@ use moq_net::transport::poll::{RecvStream as _, SendStream as _, Session as _}; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); /// SUBSCRIBE_TRACKS for prefix `room`, with no parameters. Every value fits a one-byte @@ -43,7 +48,7 @@ struct Setup { async fn setup(version: Version) -> Setup { let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("room").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let subscriber = produce_origin(2); diff --git a/rs/moq-net/tests/subscription_end_integrity.rs b/rs/moq-net/tests/subscription_end_integrity.rs index 401c75a6d9c..76dd4f241a2 100644 --- a/rs/moq-net/tests/subscription_end_integrity.rs +++ b/rs/moq-net/tests/subscription_end_integrity.rs @@ -24,6 +24,11 @@ use std::time::Duration; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, MockPair, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); const HEAD: [&[u8]; 2] = [b"head-a", b"head-b"]; const TAIL: [&[u8]; 2] = [b"tail-a", b"tail-b"]; @@ -50,7 +55,7 @@ fn expected() -> Vec> { async fn round(drop_session: bool) -> (Vec>, Option) { let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let subscriber = produce_origin(2); @@ -200,7 +205,7 @@ const DEATH: moq_net::SessionError = moq_net::SessionError::App(7); async fn killed(version: &str, local: bool) -> (Option, Option) { let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let subscriber = produce_origin(2); diff --git a/rs/moq-net/tests/track_abort_over_session.rs b/rs/moq-net/tests/track_abort_over_session.rs index 55bd3854af3..d30370ea68d 100644 --- a/rs/moq-net/tests/track_abort_over_session.rs +++ b/rs/moq-net/tests/track_abort_over_session.rs @@ -8,6 +8,11 @@ use std::time::Duration; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + fn produce_origin(hop: u64) -> moq_net::origin::Producer { let (producer, driver) = moq_net::origin::Producer::new(moq_net::origin::Config::new(Hop::new(hop).unwrap())); support::harness::spawn(driver); @@ -20,7 +25,7 @@ async fn after_abort(version: &str, hops: u32) { let relay = produce_origin(2); let far = produce_origin(3); let broadcast = publisher.create_broadcast("bench").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut options = MockConnectOptions::new(version.parse::().unwrap()); options.server_publish = Some(publisher.consume()); @@ -74,7 +79,7 @@ async fn recreate(finish: bool) { broadcast .announce(moq_net::origin::Route::default().with_epoch(moq_net::Epoch::mint())) .unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); let remote = origin.consume().request_broadcast("bench", None).await.unwrap(); let mut sub = remote.track("video").unwrap().subscribe(None).await.unwrap(); let mut open = track.create_group(moq_net::group::Info { sequence: 2 }).unwrap(); @@ -83,7 +88,7 @@ async fn recreate(finish: bool) { assert_eq!(group.sequence, 2); group.read_frame().await.unwrap().unwrap(); track.abort(moq_net::Error::Cancel).unwrap(); - let fresh = broadcast.create_track("video", None).unwrap(); + let fresh = broadcast.create_track("video", timed()).unwrap(); let mut next = fresh.create_group(moq_net::group::Info { sequence: 3 }).unwrap(); next.write_frame(Timestamp::from_millis(1).unwrap(), b"next".as_ref()) .unwrap(); diff --git a/rs/moq-net/tests/track_stream_demand.rs b/rs/moq-net/tests/track_stream_demand.rs index a0718206c7d..6ac6780e54a 100644 --- a/rs/moq-net/tests/track_stream_demand.rs +++ b/rs/moq-net/tests/track_stream_demand.rs @@ -16,6 +16,11 @@ use support::{ mock::MockSession, }; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); const LATENCY: Duration = Duration::from_millis(10); const VERSIONS: [&str; 3] = ["moq-lite-05", "moq-lite-06", "moq-lite-07-wip"]; @@ -88,7 +93,7 @@ async fn one_used_edge(version: &str, relays: usize, reorder: bool) { while let Ok(request) = dynamic.requested_track().await { served.1.borrow_mut().get_or_insert_with(|| record!(request.demand())); moq_net_sim::sleep(STARTUP).await; - let track = request.accept(None); + let track = request.accept(timed()); let mut group = track.append_group().unwrap(); group.write_frame(Timestamp::ZERO, b"key".as_ref()).unwrap(); served.0.borrow_mut().push((track, group)); @@ -176,7 +181,7 @@ async fn held_tracks_share_the_cap_with_their_subscriptions() { let broadcast = server.create_broadcast("room").unwrap(); let mut tracks = Vec::new(); for i in 0..=CAP { - let track = broadcast.create_track(format!("t{i}"), None).unwrap(); + let track = broadcast.create_track(format!("t{i}"), timed()).unwrap(); let mut group = track.append_group().unwrap(); group.write_frame(Timestamp::ZERO, b"key".as_ref()).unwrap(); let edges = record!(track.demand()); diff --git a/rs/moq-net/tests/track_tail.rs b/rs/moq-net/tests/track_tail.rs index e1b28cd3707..30ee250db82 100644 --- a/rs/moq-net/tests/track_tail.rs +++ b/rs/moq-net/tests/track_tail.rs @@ -16,6 +16,11 @@ use std::time::Duration; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); const PAYLOAD: &[u8] = b"frame"; @@ -71,7 +76,7 @@ struct Pair { async fn connect(version: &str) -> Pair { let publisher = produce_origin(1); let broadcast = publisher.create_broadcast("bcast").unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let subscriber = produce_origin(2); diff --git a/rs/moq-net/tests/untimed.rs b/rs/moq-net/tests/untimed.rs index 926ad201105..bbcbec5317d 100644 --- a/rs/moq-net/tests/untimed.rs +++ b/rs/moq-net/tests/untimed.rs @@ -13,6 +13,11 @@ use bytes::Bytes; use moq_net::{Hop, Timescale, Timestamp, Version, origin, track}; use support::harness::peer; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); /// What a subscriber reads for a timed and an untimed track. @@ -121,7 +126,7 @@ async fn frame_through(hops: &[&str], info: track::Info) -> (Option, #[moq_net_sim::test] async fn frames_arrive_as_published() { for version in VERSIONS { - let (timed, timed_info) = frame_through(&[version], track::Info::default()).await; + let (timed, timed_info) = frame_through(&[version], timed()).await; let (untimed, untimed_info) = frame_through(&[version], untimed()).await; match expected(version) { Arrives::Faithful => { @@ -154,7 +159,7 @@ async fn a_relay_forwards_an_untimed_track_untimed() { &["moq-lite-03", "moq-transport-17"][..], &["moq-transport-16", "moq-transport-20"][..], ] { - let (frame, info) = frame_through(hops, track::Info::default()).await; + let (frame, info) = frame_through(hops, timed()).await; assert_eq!(frame, None, "{hops:?}: the first hop has no timestamps to forward"); assert_eq!(info.timescale, None, "{hops:?}: the relay must not claim a timeline"); } @@ -250,7 +255,7 @@ async fn a_fetched_untimed_frame_arrives_untimed() { futures::future::Either::Left((request, _)) => request.expect("the fetch reaches the publisher"), futures::future::Either::Right(_) => panic!("nothing answered the fetch"), }; - let mut group = request.accept(None).unwrap(); + let mut group = request.accept(timed()).unwrap(); group.write_frame(None, Bytes::from_static(b"segment")).unwrap(); group.finish().unwrap(); diff --git a/rs/moq-net/tests/wide_sequence.rs b/rs/moq-net/tests/wide_sequence.rs index 48c258aa9cd..6ccfbcbed07 100644 --- a/rs/moq-net/tests/wide_sequence.rs +++ b/rs/moq-net/tests/wide_sequence.rs @@ -7,6 +7,11 @@ use std::time::Duration; use moq_net::{Hop, Timestamp, Version}; use support::harness::{MockConnectOptions, connect_mock}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + fn produce_origin(hop: u64) -> moq_net::origin::Producer { let (producer, driver) = moq_net::origin::Producer::new(moq_net::origin::Config::new(Hop::new(hop).unwrap())); support::harness::spawn(driver); @@ -24,8 +29,8 @@ async fn wide_sequence_fails_only_its_subscription() { let client = produce_origin(3); let broadcast = publisher.create_broadcast("bench").unwrap(); - let wide = broadcast.create_track("wide", None).unwrap(); - let narrow = broadcast.create_track("narrow", None).unwrap(); + let wide = broadcast.create_track("wide", timed()).unwrap(); + let narrow = broadcast.create_track("narrow", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut group = wide.create_group(moq_net::group::Info { sequence: WIDE }).unwrap(); group.write_frame(Timestamp::ZERO, b"wide".as_ref()).unwrap(); @@ -80,8 +85,8 @@ async fn a_later_wide_group_fails_only_that_subscription() { let client = produce_origin(3); let broadcast = publisher.create_broadcast("bench").unwrap(); - let wide = broadcast.create_track("wide", None).unwrap(); - let narrow = broadcast.create_track("narrow", None).unwrap(); + let wide = broadcast.create_track("wide", timed()).unwrap(); + let narrow = broadcast.create_track("narrow", timed()).unwrap(); broadcast.announce(Default::default()).unwrap(); let mut group = wide.append_group().unwrap(); group.write_frame(Timestamp::ZERO, b"first".as_ref()).unwrap(); diff --git a/rs/moq-relay/src/cluster.rs b/rs/moq-relay/src/cluster.rs index 29c69ffa1b9..8b056a3402d 100644 --- a/rs/moq-relay/src/cluster.rs +++ b/rs/moq-relay/src/cluster.rs @@ -1959,6 +1959,11 @@ mod tests { use super::*; use crate::Config as RelayConfig; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// The next route and whether it is active. async fn next_update(announced: &mut moq_net::announce::Consumer) -> Option<(moq_net::announce::Announce, bool)> { match announced.next().await? { @@ -2620,7 +2625,7 @@ mod tests { let broadcast = origin.create_broadcast("cam").expect("create"); broadcast.announce(Default::default()).expect("announce"); - let mut track = broadcast.create_track("data", None).expect("track"); + let mut track = broadcast.create_track("data", timed()).expect("track"); track.write_frame(moq_net::Timestamp::ZERO, b"hello").expect("write"); assert!(pool.used() > 0, "writes charge the constructed cache pool"); diff --git a/rs/moq-relay/src/internal.rs b/rs/moq-relay/src/internal.rs index 19a9f7845f5..0298c35f793 100644 --- a/rs/moq-relay/src/internal.rs +++ b/rs/moq-relay/src/internal.rs @@ -711,6 +711,11 @@ fn render_uring(_out: &mut String, _workers: &[UringWorker]) {} mod tests { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// The next route and whether it is active. async fn next_update(announced: &mut moq_net::announce::Consumer) -> Option<(moq_net::announce::Announce, bool)> { match announced.next().await? { @@ -1018,7 +1023,7 @@ mod tests { let mut announced = egress.announced(); let pub_source = pub_origin.create_broadcast("demo/x").unwrap(); pub_source.announce(Default::default()).unwrap(); - let pub_track = pub_source.create_track("video", None).unwrap(); + let pub_track = pub_source.create_track("video", timed()).unwrap(); // Named-tier ingress: a tagged ingress producer writes, so subscriber // `bytes` advance on the regional tier. @@ -1026,7 +1031,7 @@ mod tests { let sub_origin = moq_tokio::origin::spawn().with_stats(regional_ctx.clone()); let sub_source = sub_origin.create_broadcast("demo/x").unwrap(); sub_source.announce(Default::default()).unwrap(); - let sub_track = sub_source.create_track("audio", None).unwrap(); + let sub_track = sub_source.create_track("audio", timed()).unwrap(); tokio::time::sleep(std::time::Duration::from_millis(1)).await; tokio::time::sleep(std::time::Duration::from_millis(1)).await; diff --git a/rs/moq-relay/tests/auth_lifetime.rs b/rs/moq-relay/tests/auth_lifetime.rs index 5d52e3b3553..3fc0696f761 100644 --- a/rs/moq-relay/tests/auth_lifetime.rs +++ b/rs/moq-relay/tests/auth_lifetime.rs @@ -24,6 +24,11 @@ use moq_relay::{Config, Connection, Relay, auth, cluster, web}; use moq_tokio::moq_net; use moq_tokio::moq_net::stats; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); /// What the scripted server answers next, per event. @@ -212,7 +217,7 @@ async fn connect_and_round_trip(url: &url::Url) -> (moq_tokio::Connection, moq_t let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) @@ -460,7 +465,7 @@ async fn a_moved_tier_retags_the_live_session() { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let pub_session = tokio::time::timeout( TIMEOUT, client() @@ -586,7 +591,7 @@ async fn a_rechecked_grant_resizes_live_sessions() { let mut tracks = Vec::new(); for path in ["alice/audio", "alice/video"] { let broadcast = pub_origin.create_broadcast(path).expect("create broadcast"); - let track = broadcast.create_track("media", None).expect("create track"); + let track = broadcast.create_track("media", timed()).expect("create track"); broadcast.announce(Default::default()).expect("announce broadcast"); tracks.push((broadcast, track)); } @@ -773,7 +778,7 @@ async fn http_routes_hold_a_lease() { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) diff --git a/rs/moq-relay/tests/cluster_unknown.rs b/rs/moq-relay/tests/cluster_unknown.rs index ec8a25620d6..a4d9bce682a 100644 --- a/rs/moq-relay/tests/cluster_unknown.rs +++ b/rs/moq-relay/tests/cluster_unknown.rs @@ -7,6 +7,11 @@ use std::time::Duration; use moq_relay::{Config, Relay}; use url::Url; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); const PATH: &str = "robot/cell-clumsy-octopus/cameras/left.hang"; @@ -68,7 +73,7 @@ async fn publish_version(port: u16, version: &str) -> Publisher { let origin = moq_tokio::origin::spawn(); let broadcast = origin.create_broadcast(PATH).expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); // Stream like a real publisher: a fresh group every 100ms, so a subscriber // that attaches at any point receives one (and the test doesn't depend on diff --git a/rs/moq-relay/tests/drills.rs b/rs/moq-relay/tests/drills.rs index 1eeb7b04878..6b7aeb02838 100644 --- a/rs/moq-relay/tests/drills.rs +++ b/rs/moq-relay/tests/drills.rs @@ -32,6 +32,11 @@ use futures::FutureExt; use moq_relay::{Config, Relay}; use moq_tokio::moq_net; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Ceiling for anything a drill waits on. Every wait is bounded, so a broken /// handoff fails as a timeout with a message instead of hanging the suite. const TIMEOUT: Duration = Duration::from_secs(10); @@ -436,7 +441,7 @@ async fn cancel_under_backpressure_releases_the_reader(lane: Lane) { let publisher = moq_tokio::origin::spawn(); let broadcast = publisher.create_broadcast("live").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce broadcast"); - let mut track = broadcast.create_track(TRACK, None).expect("create track"); + let mut track = broadcast.create_track(TRACK, timed()).expect("create track"); let publish_session = tokio::time::timeout( TIMEOUT, client(&url) @@ -567,7 +572,7 @@ async fn relay_killed_mid_group_aborts_then_resumes(lane: Lane) { let publisher = moq_tokio::origin::spawn(); let broadcast = publisher.create_broadcast("live").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce broadcast"); - let mut track = broadcast.create_track(TRACK, None).expect("create track"); + let mut track = broadcast.create_track(TRACK, timed()).expect("create track"); let mut publish_loop = client(&url).publish(publisher.consume()).expect("no connect url"); let subscriber = moq_tokio::origin::spawn(); @@ -690,7 +695,7 @@ async fn interrupted_publisher_republishes_new_content(lane: Lane) { let first = moq_tokio::origin::spawn(); let broadcast = first.create_broadcast("live").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce broadcast"); - let mut track = broadcast.create_track(TRACK, None).expect("create track"); + let mut track = broadcast.create_track(TRACK, timed()).expect("create track"); let first_session = tokio::time::timeout( TIMEOUT, client(&url) @@ -731,7 +736,7 @@ async fn interrupted_publisher_republishes_new_content(lane: Lane) { let second = moq_tokio::origin::spawn(); let broadcast = second.create_broadcast("live").expect("re-create broadcast"); broadcast.announce(Default::default()).expect("announce broadcast"); - let mut track = broadcast.create_track(TRACK, None).expect("re-create track"); + let mut track = broadcast.create_track(TRACK, timed()).expect("re-create track"); let second_session = tokio::time::timeout( TIMEOUT, client(&url) @@ -1007,7 +1012,7 @@ async fn cross_cluster(lane: Lane, link: PeerLink) { let publisher = moq_tokio::origin::spawn(); let broadcast = publisher.create_broadcast("live").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce broadcast"); - let mut track = broadcast.create_track(TRACK, None).expect("create track"); + let mut track = broadcast.create_track(TRACK, timed()).expect("create track"); let url = publish_path.url.clone(); let publish_session = tokio::time::timeout( TIMEOUT, diff --git a/rs/moq-relay/tests/embed.rs b/rs/moq-relay/tests/embed.rs index f725f112c26..f732983fd79 100644 --- a/rs/moq-relay/tests/embed.rs +++ b/rs/moq-relay/tests/embed.rs @@ -15,6 +15,11 @@ use std::time::Duration; use moq_relay::{Config, Relay}; use moq_tokio::moq_net; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); fn certificate(dir: &std::path::Path) -> (std::path::PathBuf, std::path::PathBuf) { @@ -254,7 +259,7 @@ async fn embed_and_stop(mut config: Config) { let broadcast = origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) diff --git a/rs/moq-relay/tests/goaway_cluster.rs b/rs/moq-relay/tests/goaway_cluster.rs index 1d9d8b44c45..fbe07c143c4 100644 --- a/rs/moq-relay/tests/goaway_cluster.rs +++ b/rs/moq-relay/tests/goaway_cluster.rs @@ -15,6 +15,11 @@ use moq_relay::{ }; use url::Url; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TEST_TIMEOUT: Duration = Duration::from_secs(15); /// Bound `fut` by [`TEST_TIMEOUT`], panicking with `step` so a hang names the @@ -137,7 +142,7 @@ async fn cluster_continues_a_group_split_by_goaway_inner() { let upstream_origin = moq_tokio::origin::spawn(); let broadcast = upstream_origin.create_broadcast("cam").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let (port_a, mut accepted_a, _handle_a) = spawn_upstream(upstream_origin.clone()).await; let (port_b, mut accepted_b, _handle_b) = spawn_upstream(upstream_origin.clone()).await; @@ -280,7 +285,7 @@ async fn cluster_migrates_on_upstream_goaway_inner() { let upstream_origin = moq_tokio::origin::spawn(); let broadcast = upstream_origin.create_broadcast("cam").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let (port_a, mut accepted_a, _handle_a) = spawn_upstream(upstream_origin.clone()).await; let (port_b, mut accepted_b, _handle_b) = spawn_upstream(upstream_origin.clone()).await; @@ -480,7 +485,7 @@ async fn cluster_diamond_goaway_seamless_failover_inner() { broadcast .announce(moq_net::origin::Route::default().with_epoch(moq_net::Epoch::mint())) .expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let (top_port, mut top_accepted, _top_handle) = spawn_upstream(top_origin.clone()).await; let top_url = format!("tcp://127.0.0.1:{top_port}/"); @@ -756,7 +761,7 @@ async fn cluster_reconnects_on_empty_uri_goaway_inner() { broadcast .announce(moq_net::origin::Route::default().with_epoch(moq_net::Epoch::mint())) .expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let (port, mut accepted, _handle) = spawn_upstream(upstream_origin.clone()).await; diff --git a/rs/moq-relay/tests/public_rules.rs b/rs/moq-relay/tests/public_rules.rs index e9757e8099f..64bcb361477 100644 --- a/rs/moq-relay/tests/public_rules.rs +++ b/rs/moq-relay/tests/public_rules.rs @@ -12,6 +12,11 @@ use std::time::Duration; use moq_relay::{auth, cluster, web}; use moq_tokio::moq_net; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); fn client() -> moq_tokio::Client { @@ -62,7 +67,7 @@ async fn publish(url: url::Url, name: &str) -> Box { let origin = moq_tokio::origin::spawn(); let broadcast = origin.create_broadcast(name).expect("create broadcast"); broadcast.announce(Default::default()).expect("announce"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) diff --git a/rs/moq-relay/tests/runtime_uring.rs b/rs/moq-relay/tests/runtime_uring.rs index ac446ba20fb..0e66445b5e5 100644 --- a/rs/moq-relay/tests/runtime_uring.rs +++ b/rs/moq-relay/tests/runtime_uring.rs @@ -16,6 +16,11 @@ use moq_tokio::moq_net; const TIMEOUT: Duration = Duration::from_secs(10); const WORKERS: u16 = 2; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Whether this kernel can run the io_uring workers at all. fn supported() -> bool { match moq_uring::Worker::new(Default::default()) { @@ -124,7 +129,7 @@ async fn uring_workers_serve_webtransport_and_raw_quic() { let origin = moq_tokio::origin::spawn(); let broadcast = origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) @@ -419,7 +424,7 @@ async fn an_mtls_client_authenticates_without_a_token() { let origin = moq_tokio::origin::spawn(); let broadcast = origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) @@ -498,7 +503,7 @@ async fn uring_workers_write_qlog_traces() { let origin = moq_tokio::origin::spawn(); let broadcast = origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) diff --git a/rs/moq-relay/tests/runtime_workers.rs b/rs/moq-relay/tests/runtime_workers.rs index a7da861c9d1..58c37517d29 100644 --- a/rs/moq-relay/tests/runtime_workers.rs +++ b/rs/moq-relay/tests/runtime_workers.rs @@ -11,6 +11,11 @@ use std::time::Duration; use moq_relay::{Config, Relay}; use moq_tokio::moq_net; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); const WORKERS: u16 = 4; @@ -84,7 +89,7 @@ async fn workers_serve_quic_and_share_one_origin() { let origin = moq_tokio::origin::spawn(); let broadcast = origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) diff --git a/rs/moq-relay/tests/smoke.rs b/rs/moq-relay/tests/smoke.rs index 7ff048af292..4c94eb74d6c 100644 --- a/rs/moq-relay/tests/smoke.rs +++ b/rs/moq-relay/tests/smoke.rs @@ -12,6 +12,11 @@ use std::time::Duration; use moq_relay::{Config, Connection, Relay, auth, cluster, web}; use moq_tokio::moq_net; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); /// The newest moq-lite ALPN both sides should converge on. Derived from @@ -135,7 +140,7 @@ async fn relay_websocket_round_trip_uses_newest_version() { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) @@ -235,7 +240,7 @@ async fn hidden_broadcasts_need_a_lite07_opt_in() { let pub_origin = moq_tokio::origin::spawn(); let hidden = pub_origin.create_broadcast(".x/y").expect("create hidden"); hidden.announce(Default::default()).expect("announce hidden"); - let track = hidden.create_track("video", None).expect("create track"); + let track = hidden.create_track("video", timed()).expect("create track"); track .append_group() .expect("append group") @@ -436,7 +441,7 @@ async fn relay_websocket_root_path_upgrades() { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) @@ -508,7 +513,7 @@ async fn two_publish_only_clients_coexist() { let pub_a = moq_tokio::origin::spawn(); let broadcast_a = pub_a.create_broadcast("alpha").expect("create broadcast a"); broadcast_a.announce(Default::default()).expect("create broadcast a"); - let track_a = broadcast_a.create_track("video", None).expect("create track a"); + let track_a = broadcast_a.create_track("video", timed()).expect("create track a"); track_a .append_group() .expect("append group a") @@ -518,7 +523,7 @@ async fn two_publish_only_clients_coexist() { let pub_b = moq_tokio::origin::spawn(); let broadcast_b = pub_b.create_broadcast("beta").expect("create broadcast b"); broadcast_b.announce(Default::default()).expect("create broadcast b"); - let track_b = broadcast_b.create_track("video", None).expect("create track b"); + let track_b = broadcast_b.create_track("video", timed()).expect("create track b"); track_b .append_group() .expect("append group b") @@ -650,7 +655,7 @@ async fn internal_tcp_round_trip() { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) @@ -753,7 +758,7 @@ async fn internal_unix_round_trip() { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) @@ -845,7 +850,7 @@ async fn path_round_trip(version: moq_net::Version, pub_url: url::Url, sub_url: let pub_origin = moq_tokio::origin::spawn(); let bc = pub_origin.create_broadcast(broadcast).expect("create broadcast"); bc.announce(Default::default()).expect("create broadcast"); - let track = bc.create_track("video", None).expect("create track"); + let track = bc.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) diff --git a/rs/moq-relay/tests/upstream_links.rs b/rs/moq-relay/tests/upstream_links.rs index 695762f42ad..4a0ebbee03a 100644 --- a/rs/moq-relay/tests/upstream_links.rs +++ b/rs/moq-relay/tests/upstream_links.rs @@ -17,6 +17,11 @@ use moq_relay::{ cluster::{self, Peer}, }; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(15); /// Bound `fut` by [`TIMEOUT`], naming the step that hung. @@ -148,7 +153,7 @@ impl Drop for Publisher { fn publish(origin: &origin::Producer, path: &str) -> Publisher { let broadcast = origin.publish(path, Default::default()).expect("publish"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let writer = tokio::spawn(async move { loop { let Ok(mut group) = track.append_group() else { break }; diff --git a/rs/moq-room/src/chat.rs b/rs/moq-room/src/chat.rs index 9c883873915..fd1dafe3696 100644 --- a/rs/moq-room/src/chat.rs +++ b/rs/moq-room/src/chat.rs @@ -56,7 +56,9 @@ impl Deadline { /// Track settings for the latest chat window. pub fn info() -> track::Info { - track::Info::default().with_priority(PRIORITY) + track::Info::default() + .with_timescale(moq_net::Timescale::MILLI) + .with_priority(PRIORITY) } /// Publishes chat messages; drive `poll_expire` or `expire` to retire them while idle. diff --git a/rs/moq-rtc/src/codec/bitstream_test.rs b/rs/moq-rtc/src/codec/bitstream_test.rs index 7c9397785f7..ed97f715633 100644 --- a/rs/moq-rtc/src/codec/bitstream_test.rs +++ b/rs/moq-rtc/src/codec/bitstream_test.rs @@ -12,6 +12,11 @@ use bytes::{Bytes, BytesMut}; use crate::codec::{self, Bridge, Frame, Track}; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const START_CODE_4: &[u8] = &[0, 0, 0, 1]; fn annexb(nals: &[&[u8]]) -> Bytes { @@ -208,7 +213,7 @@ async fn egress_opus_passthrough() { async fn egress_opus_reads_the_rendition_container() { let producer = moq_net::broadcast::Info::new().produce(); let mut writer = moq_mux::container::Producer::new( - producer.create_track("audio", None).expect("track"), + producer.create_track("audio", timed()).expect("track"), moq_mux::catalog::hang::Container::Loc(moq_mux::container::Kind::Audio), ); let payload = Bytes::from_static(&[0xfc, 0xff, 0xfe]); diff --git a/rs/moq-stats/benches/aggregate.rs b/rs/moq-stats/benches/aggregate.rs index 0102206aa6a..2708db0e2f1 100644 --- a/rs/moq-stats/benches/aggregate.rs +++ b/rs/moq-stats/benches/aggregate.rs @@ -41,7 +41,10 @@ impl Node { .announce(origin::Route::default().with_epoch(epoch.clone())) .unwrap(); let track = source - .create_track(traffic_track(&Tier::default(), Role::Publisher, false), None) + .create_track( + traffic_track(&Tier::default(), Role::Publisher, false), + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + ) .unwrap(); let config = moq_json::snapshot::Config::default().with_delta_ratio(0); let frame = (0..KEYS) diff --git a/rs/moq-stats/src/aggregate.rs b/rs/moq-stats/src/aggregate.rs index f5f3c434542..4b16a9c6106 100644 --- a/rs/moq-stats/src/aggregate.rs +++ b/rs/moq-stats/src/aggregate.rs @@ -625,6 +625,11 @@ fn resolve(origin: &origin::Consumer, path: &PathOwned, epoch: Opt #[cfg(test)] mod tests { + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// Build an origin producer, spawning its driver on the ambient runtime. fn produce_origin() -> moq_net::origin::Producer { let (producer, driver) = moq_net::origin::Producer::new(moq_net::origin::Config::default()); @@ -690,7 +695,7 @@ mod tests { let mut announced = egress.announced(); let source = feed_origin.create_broadcast(path).expect("create_broadcast"); source.announce(origin::Route::default()).expect("announce"); - let track = source.create_track("video", None).expect("create_track"); + let track = source.create_track("video", timed()).expect("create_track"); let (_, active) = next_update(&mut announced).await.expect("announce"); assert!(active); @@ -764,7 +769,7 @@ mod tests { let source = origin.create_broadcast(path.as_str()).expect("create broadcast"); source.announce(origin::Route::default()).expect("announce"); let name = traffic_track(&Tier::default(), Role::Publisher, false); - let track = source.create_track(name, None).expect("create track"); + let track = source.create_track(name, timed()).expect("create track"); let config = moq_json::snapshot::Config::default().with_delta_ratio(0); Self { traffic: moq_json::snapshot::Producer::new(track.clone(), config), diff --git a/rs/moq-stats/src/consume.rs b/rs/moq-stats/src/consume.rs index 099ee5ab778..080ef1a84e6 100644 --- a/rs/moq-stats/src/consume.rs +++ b/rs/moq-stats/src/consume.rs @@ -104,6 +104,11 @@ impl Sessions { #[cfg(test)] mod tests { + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// Build an origin producer, spawning its driver on the ambient runtime. fn produce_origin() -> moq_net::origin::Producer { let (producer, driver) = moq_net::origin::Producer::new(moq_net::origin::Config::default()); @@ -175,7 +180,7 @@ mod tests { let mut announced = egress.announced(); let source = feed_origin.create_broadcast(path).unwrap(); source.announce(origin::Route::default()).unwrap(); - let track = source.clone().create_track("video", None).unwrap(); + let track = source.clone().create_track("video", timed()).unwrap(); let (_, active) = next_update(&mut announced).await.expect("announce"); assert!(active); diff --git a/rs/moq-stats/src/produce.rs b/rs/moq-stats/src/produce.rs index 62072769ea5..e905e32ea9e 100644 --- a/rs/moq-stats/src/produce.rs +++ b/rs/moq-stats/src/produce.rs @@ -530,10 +530,15 @@ struct TrackPair { wrote_compressed: usize, } +/// Stats frames are stamped with the wall clock, so their tracks are timed. +fn info() -> track::Info { + track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + impl TrackPair { fn create(broadcast: &broadcast::Producer, name: &str, sequence: Arc) -> Result { - let plain_track = broadcast.create_track(name, None)?; - let compressed_track = broadcast.create_track(format!("{name}{COMPRESSED_SUFFIX}").as_str(), None)?; + let plain_track = broadcast.create_track(name, info())?; + let compressed_track = broadcast.create_track(format!("{name}{COMPRESSED_SUFFIX}").as_str(), info())?; Ok(Self::from_tracks(plain_track, compressed_track, sequence)) } @@ -627,12 +632,12 @@ impl PendingPair { ) -> Result, moq_net::Error> { let PendingPair { plain, compressed } = self; let plain_track = match plain { - Some(request) => request.accept(None), - None => broadcast.create_track(name, None)?, + Some(request) => request.accept(info()), + None => broadcast.create_track(name, info())?, }; let compressed_track = match compressed { - Some(request) => request.accept(None), - None => broadcast.create_track(format!("{name}{COMPRESSED_SUFFIX}").as_str(), None)?, + Some(request) => request.accept(info()), + None => broadcast.create_track(format!("{name}{COMPRESSED_SUFFIX}").as_str(), info())?, }; Ok(TrackPair::from_tracks(plain_track, compressed_track, sequence)) } @@ -1185,6 +1190,11 @@ fn advertised_path(prefix: &Path, group: &Path, node: Option<&str>) -> PathOwned #[cfg(test)] mod tests { + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + #[tokio::test(start_paused = true)] async fn reclaimed_requested_tracks_resume_after_the_last_group() { use futures::FutureExt; @@ -1302,7 +1312,7 @@ mod tests { let mut announced = egress.announced(); let source = origin.create_broadcast(path).expect("create_broadcast"); source.announce(origin::Route::default()).expect("announce"); - let producer = source.create_track("video", None).expect("create_track"); + let producer = source.create_track("video", timed()).expect("create_track"); let (_, active) = next_update(&mut announced).await.expect("announce"); assert!(active); @@ -1633,7 +1643,7 @@ mod tests { .with_stats(registry.tier(Tier::default()).session("publisher")) .publish("foo/bar", origin::Route::default()) .expect("publish"); - let mut video = source.create_track("video", None).expect("create_track"); + let mut video = source.create_track("video", timed()).expect("create_track"); let egress = data .consume() .with_stats(registry.tier(Tier::default()).session("viewer")); diff --git a/rs/moq-tokio/examples/chat.rs b/rs/moq-tokio/examples/chat.rs index 98241f9e5dd..609c0e707cd 100644 --- a/rs/moq-tokio/examples/chat.rs +++ b/rs/moq-tokio/examples/chat.rs @@ -50,7 +50,9 @@ async fn run_broadcast(origin: moq_net::origin::Producer) -> anyhow::Result<()> // Create a track that we'll insert into the broadcast. // A track is a series of groups representing a live stream. - let mut track = broadcast.create_track("chat", None)?; + // Frames are stamped with the wall clock, so the track declares a timescale. + let info = moq_tokio::moq_net::track::Info::default().with_timescale(moq_tokio::moq_net::Timescale::MILLI); + let mut track = broadcast.create_track("chat", info)?; // Create a group. // Each group is independent and the newest group(s) will be prioritized. diff --git a/rs/moq-tokio/examples/clock.rs b/rs/moq-tokio/examples/clock.rs index 49d6db73666..3182892c58d 100644 --- a/rs/moq-tokio/examples/clock.rs +++ b/rs/moq-tokio/examples/clock.rs @@ -67,7 +67,8 @@ async fn main() -> anyhow::Result<()> { let broadcast = origin .create_broadcast(&config.broadcast) .context("failed to create broadcast")?; - let track = broadcast.create_track(track, None)?; + let info = moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI); + let track = broadcast.create_track(track, info)?; // Announced once the track exists, so a subscriber acting on the // announcement finds it. broadcast diff --git a/rs/moq-tokio/src/client.rs b/rs/moq-tokio/src/client.rs index 362e15788f0..17d53722cdc 100644 --- a/rs/moq-tokio/src/client.rs +++ b/rs/moq-tokio/src/client.rs @@ -911,6 +911,11 @@ async fn connect_session( mod tests { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// A QUIC session whose peer never sends SETUP gives up at the connect deadline, so the /// WebSocket session keeps serving instead of waiting on the upgrade forever. #[tokio::test(start_paused = true)] @@ -1640,7 +1645,9 @@ mod tests { broadcast .announce(Default::default()) .expect("failed to create broadcast"); - let track = broadcast.create_track("video", None).expect("failed to create track"); + let track = broadcast + .create_track("video", timed()) + .expect("failed to create track"); let mut group = track.append_group().expect("failed to append group"); group .write_frame(crate::moq_net::Timestamp::ZERO, b"hello".as_ref()) diff --git a/rs/moq-tokio/src/connection.rs b/rs/moq-tokio/src/connection.rs index d9915fb07c8..f3bf763f31e 100644 --- a/rs/moq-tokio/src/connection.rs +++ b/rs/moq-tokio/src/connection.rs @@ -1568,6 +1568,11 @@ fn terminal(state: &State) -> Error { mod tests { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// Updating with an empty CLI preserves a standing TOML value over typed defaults. #[test] fn cli_does_not_clobber_toml_backoff() { @@ -2522,7 +2527,7 @@ mod tests { let origin = crate::origin::spawn(); let broadcast = origin.create_broadcast("cam").unwrap(); broadcast.announce(Default::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); let write = |payload: &'static [u8]| { let mut group = track.append_group().unwrap(); group.write_frame(moq_net::Timestamp::ZERO, payload).unwrap(); @@ -2623,7 +2628,7 @@ mod tests { let origin = crate::origin::spawn(); let broadcast = origin.create_broadcast("cam").unwrap(); broadcast.announce(Default::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); let write = |payload: &'static [u8]| { let mut group = track.append_group().unwrap(); group.write_frame(moq_net::Timestamp::ZERO, payload).unwrap(); @@ -2811,7 +2816,7 @@ mod tests { let origin = crate::origin::spawn(); let broadcast = origin.create_broadcast("cam").unwrap(); broadcast.announce(Default::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); let mut fallback = Fallback::start(&origin, Quic::Open).await; @@ -2891,7 +2896,7 @@ mod tests { let origin = crate::origin::spawn(); let broadcast = origin.create_broadcast("cam").unwrap(); broadcast.announce(Default::default()).unwrap(); - let track = broadcast.create_track("video", None).unwrap(); + let track = broadcast.create_track("video", timed()).unwrap(); let (addr_a, mut accepted_a, _task_a) = serve(origin.clone()).await; let (addr_b, mut accepted_b, _task_b) = serve(origin.clone()).await; diff --git a/rs/moq-tokio/src/server.rs b/rs/moq-tokio/src/server.rs index 1e34c6ceec1..a4d41062d28 100644 --- a/rs/moq-tokio/src/server.rs +++ b/rs/moq-tokio/src/server.rs @@ -1661,6 +1661,11 @@ impl Request { mod tests { use super::*; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + /// The next route and whether it is active. async fn next_update(announced: &mut moq_net::announce::Consumer) -> Option<(moq_net::announce::Announce, bool)> { match announced.next().await? { @@ -2211,7 +2216,7 @@ mod tests { let origin = crate::origin::spawn(); let broadcast = origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce broadcast"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref()) diff --git a/rs/moq-tokio/tests/backend.rs b/rs/moq-tokio/tests/backend.rs index 4d0fd8da02e..367fea04e1c 100644 --- a/rs/moq-tokio/tests/backend.rs +++ b/rs/moq-tokio/tests/backend.rs @@ -2,6 +2,11 @@ use std::time::Duration; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); /// Inputs for [`connect_test`]. @@ -113,7 +118,9 @@ async fn connect_test(config: ConnectTest<'_>) { broadcast .announce(Default::default()) .expect("failed to create broadcast"); - let track = broadcast.create_track("video", None).expect("failed to create track"); + let track = broadcast + .create_track("video", timed()) + .expect("failed to create track"); let mut group = track.append_group().expect("failed to append group"); group @@ -605,7 +612,9 @@ async fn iroh_connect_test(version: Option<&str>) { broadcast .announce(Default::default()) .expect("failed to create broadcast"); - let track = broadcast.create_track("video", None).expect("failed to create track"); + let track = broadcast + .create_track("video", timed()) + .expect("failed to create track"); let mut group = track.append_group().expect("failed to append group"); group @@ -826,7 +835,9 @@ async fn noq_client_close_drains_finished_track() { let origin = moq_tokio::origin::spawn(); let broadcast = origin.create_broadcast("test").expect("failed to create broadcast"); broadcast.announce(Default::default()).expect("failed to announce"); - let mut track = broadcast.create_track("video", None).expect("failed to create track"); + let mut track = broadcast + .create_track("video", timed()) + .expect("failed to create track"); let mut config = moq_tokio::connect::Config::default(); config.tls.insecure = Some(true); @@ -938,7 +949,9 @@ async fn noq_client_close_drains_migrated_predecessor() { let origin = moq_tokio::origin::spawn(); let broadcast = origin.create_broadcast("test").expect("failed to create broadcast"); broadcast.announce(Default::default()).expect("failed to announce"); - let mut track = broadcast.create_track("video", None).expect("failed to create track"); + let mut track = broadcast + .create_track("video", timed()) + .expect("failed to create track"); let mut config = moq_tokio::connect::Config::default(); config.tls.insecure = Some(true); @@ -1079,7 +1092,9 @@ async fn noq_client_close_keeps_predecessor_handover() { let broadcast = origin.create_broadcast("test").expect("failed to create broadcast"); broadcast.announce(Default::default()).expect("failed to announce"); // Never finished, so the predecessor serving it cannot drain. - let mut track = broadcast.create_track("video", None).expect("failed to create track"); + let mut track = broadcast + .create_track("video", timed()) + .expect("failed to create track"); let mut config = moq_tokio::connect::Config::default(); config.tls.insecure = Some(true); diff --git a/rs/moq-tokio/tests/broadcast.rs b/rs/moq-tokio/tests/broadcast.rs index fc49d1bacf5..df8403a7f1f 100644 --- a/rs/moq-tokio/tests/broadcast.rs +++ b/rs/moq-tokio/tests/broadcast.rs @@ -11,6 +11,11 @@ use moq_tokio::moq_net::{self, Hop}; use std::time::Duration; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); /// Publish a broadcast on the server, subscribe on the client, and verify @@ -28,7 +33,9 @@ async fn broadcast_test(scheme: &str, client_version: Option<&str>, server_versi broadcast .announce(Default::default()) .expect("failed to create broadcast"); - let track = broadcast.create_track("video", None).expect("failed to create track"); + let track = broadcast + .create_track("video", timed()) + .expect("failed to create track"); // Write a group containing a single frame. let mut group = track.append_group().expect("failed to append group"); @@ -138,7 +145,7 @@ async fn lite05_timestamp_roundtrip(scheme: &str) { .announce(Default::default()) .expect("failed to create broadcast"); - // Track with an explicit microsecond timescale (the default is milliseconds). + // Track with an explicit microsecond timescale (the default is untimed). let track = broadcast .create_track( "video", @@ -385,7 +392,9 @@ async fn transport_fetch_roundtrip(version: &str) { broadcast .announce(Default::default()) .expect("failed to announce broadcast"); - let track = broadcast.create_track("video", None).expect("failed to create track"); + let track = broadcast + .create_track("video", timed()) + .expect("failed to create track"); for sequence in 0..3u64 { let mut group = track.append_group().expect("failed to append group"); for frame in 0..2 { @@ -633,12 +642,12 @@ async fn broadcast_moq_lite_05_fetch_during_subscribe_webtransport() { lite05_fetch_during_subscribe("https").await; } -/// On Lite05 timestamps are mandatory: a publisher that doesn't set a timescale gets -/// the default (milliseconds), and frames written without an explicit timestamp are -/// stamped with wall-clock time. The subscriber receives `Some(ts)` at that scale. +/// Lite05 can't declare an untimed track, so a track that sets no timescale goes out +/// in milliseconds, its frames stamped with their send time. The subscriber receives +/// `Some(ts)` at that scale. #[tracing_test::traced_test] #[tokio::test] -async fn broadcast_moq_lite_05_default_timescale() { +async fn broadcast_moq_lite_05_untimed_track() { use moq_tokio::moq_net::Timescale; let pub_origin = moq_tokio::origin::spawn(); @@ -647,9 +656,7 @@ async fn broadcast_moq_lite_05_default_timescale() { let track = broadcast.create_track("video", None).expect("create track"); let mut group = track.append_group().expect("append group"); - group - .write_frame(moq_tokio::moq_net::Timestamp::ZERO, b"hello".as_ref()) - .expect("write frame"); + group.write_frame(None, b"hello".as_ref()).expect("write frame"); group.finish().expect("finish group"); let mut server_config = moq_tokio::listen::Config::default(); @@ -718,7 +725,11 @@ async fn broadcast_moq_lite_05_default_timescale() { .expect("group closed"); let ts = frame_sub.timestamp.expect("timed frame"); - assert_eq!(ts.scale(), Timescale::MILLI, "default timescale is milliseconds"); + assert_eq!( + ts.scale(), + Timescale::MILLI, + "an untimed track's send times are milliseconds" + ); drop(connection); server_handle @@ -741,7 +752,7 @@ async fn broadcast_moq_transport_20_current_group_join() { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); // The group is left open, so the subscriber joins part way through it: the two head // frames are published before the SUBSCRIBE and the tail frame after. @@ -1003,7 +1014,7 @@ async fn broadcast_route_migration() { .with_cost(1), ) .expect("announce"); - let track_a = broadcast_a.create_track("video", None).expect("create track"); + let track_a = broadcast_a.create_track("video", timed()).expect("create track"); for sequence in 0..2u64 { let mut group = track_a .create_group(moq_net::group::Info { sequence }) @@ -1028,7 +1039,7 @@ async fn broadcast_route_migration() { .with_cost(2), ) .expect("announce"); - let track_b = broadcast_b.create_track("video", None).expect("create track"); + let track_b = broadcast_b.create_track("video", timed()).expect("create track"); // A clone to keep producing from the test body once the task owns the rest. let standby_track = track_b.clone(); // B carries the continuation of the same content: groups 2 and 3. @@ -1190,7 +1201,7 @@ async fn rejoin_skips_a_stale_warm_cache(version: &str) { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce"); - let track = broadcast.create_track("audio", None).expect("create track"); + let track = broadcast.create_track("audio", timed()).expect("create track"); let live = track.clone(); for sequence in 0..4u64 { write(&track, sequence, sequence * 20); @@ -1306,7 +1317,7 @@ async fn rejoin_replays_a_current_warm_cache(version: &str, open: bool) { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce"); - let track = broadcast.create_track("catalog.json", None).expect("create track"); + let track = broadcast.create_track("catalog.json", timed()).expect("create track"); let live = track.clone(); let mut group = live.append_group().expect("append group"); group @@ -1433,7 +1444,7 @@ async fn route_reannounce_test(version: Option<&str>) { producer .announce(moq_net::origin::Route::default().with_hops(initial_hops)) .expect("announce"); - let track = producer.create_track("video", None).expect("create track"); + let track = producer.create_track("video", timed()).expect("create track"); { let mut group = track .create_group(moq_net::group::Info { sequence: 0 }) @@ -1748,7 +1759,7 @@ async fn max_age_test(version: &str, published: Option) -> Option moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + const TIMEOUT: Duration = Duration::from_secs(10); const FRAMES: usize = 10; /// Frames are padded so the final group (4 MB of it) cannot fit in the connection's @@ -46,7 +51,7 @@ async fn round(drop_session: bool) -> (usize, Option) { let pub_origin = moq_tokio::origin::spawn(); let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("announce"); - let track = broadcast.create_track("video", None).expect("create track"); + let track = broadcast.create_track("video", timed()).expect("create track"); let mut server_config = moq_tokio::listen::Config::default(); server_config.bind = Some("127.0.0.1:0".parse().unwrap()); diff --git a/rs/moq-uring/benches/session_lite.rs b/rs/moq-uring/benches/session_lite.rs index c83673c2388..ae5772a0937 100644 --- a/rs/moq-uring/benches/session_lite.rs +++ b/rs/moq-uring/benches/session_lite.rs @@ -31,6 +31,11 @@ mod linux { const ALPN: &str = "moq-lite-05"; + /// A millisecond track: the frames written here carry timestamps. + fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) + } + struct Ablation { name: &'static str, config: udp::Config, @@ -105,7 +110,7 @@ mod linux { let broadcast = pub_origin.create_broadcast("bench").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("data", None).expect("create track"); + let track = broadcast.create_track("data", timed()).expect("create track"); let certs = support::certs().expect("certificates"); let mut server_config = diff --git a/rs/moq-uring/tests/session.rs b/rs/moq-uring/tests/session.rs index 6d747b2937e..eeb3e54c5d2 100644 --- a/rs/moq-uring/tests/session.rs +++ b/rs/moq-uring/tests/session.rs @@ -32,6 +32,11 @@ fn worker() -> Option { const ALPN: &str = "moq-lite-05"; const PAYLOAD: &[u8] = b"hello over io_uring"; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + #[test] fn lite_session_over_the_worker() { let Some(mut worker) = worker() else { return }; @@ -54,7 +59,7 @@ fn lite_session_over_the_worker() { // finished group. let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("data", None).expect("create track"); + let track = broadcast.create_track("data", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, PAYLOAD) @@ -183,7 +188,7 @@ fn two_lite_sessions_share_the_server_socket() { let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("data", None).expect("create track"); + let track = broadcast.create_track("data", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, PAYLOAD) diff --git a/rs/moq-uring/tests/web.rs b/rs/moq-uring/tests/web.rs index e0dbe6a9001..4bd98e31fd1 100644 --- a/rs/moq-uring/tests/web.rs +++ b/rs/moq-uring/tests/web.rs @@ -34,6 +34,11 @@ const PAYLOAD: &[u8] = b"hello over webtransport"; const CLOSE_CODE: u32 = 42; const CLOSE_REASON: &str = "bye"; +/// A millisecond track: the frames written here carry timestamps. +fn timed() -> moq_net::track::Info { + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI) +} + /// Build the uring server endpoint serving `h3`. fn h3_endpoint(handle: &moq_uring::Handle, certs: &support::Certs) -> quic::Endpoint { let mut server = quic::server::Config::new(quic::Identity::open(&certs.cert, &certs.key).expect("identity")); @@ -221,7 +226,7 @@ fn lite_session_over_webtransport() { let broadcast = pub_origin.create_broadcast("test").expect("create broadcast"); broadcast.announce(Default::default()).expect("create broadcast"); - let track = broadcast.create_track("data", None).expect("create track"); + let track = broadcast.create_track("data", timed()).expect("create track"); let mut group = track.append_group().expect("append group"); group .write_frame(moq_net::Timestamp::ZERO, PAYLOAD)