diff --git a/doc/lib/js/net.md b/doc/lib/js/net.md index 2294fb07be..9e1ac2284a 100644 --- a/doc/lib/js/net.md +++ b/doc/lib/js/net.md @@ -39,7 +39,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. 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). - **A graceful close waits.** `await connection.close()` withdraws announcements and gives finished tracks up to one second. `abort()` ends immediately. diff --git a/doc/setup/upgrade.md b/doc/setup/upgrade.md index 6c3c91247a..395d1b5fb6 100644 --- a/doc/setup/upgrade.md +++ b/doc/setup/upgrade.md @@ -207,6 +207,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 c1dd73f3ae..58b7cc12c2 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 5dd15b52e4..f7c10c7ba7 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -37,7 +37,6 @@ were done or not worth their cost. - [Held group wakes](/quest/m1/held-group-wakes.md) - a group held across a route switch wakes when its successor's first timestamp or abort makes it stale; cache-max-age's wall-clock budget rides the same wake - [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 - [Shared import clock](/quest/m1/shared-clock.md) - an importer joining a clock already in use offsets its PTS instead of moving it, so captures and imports share one timeline - [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 diff --git a/quest/m1/lite-untimed.md b/quest/m1/lite-untimed.md index e9a021ece1..7d2c7f94d4 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 8db37dd7fc..27af3a8526 100644 --- a/quest/m1/publish-timestamp.md +++ b/quest/m1/publish-timestamp.md @@ -63,10 +63,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 26b7bf9d31..0000000000 --- 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 - [Shared import clock](/quest/m1/shared-clock.md)'s. -- 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 bcf46bcffb..1ba75a4346 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 00211fedec..93a057f74d 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 b54196d93c..0b634e72bd 100644 --- a/rs/hang/src/catalog/root.rs +++ b/rs/hang/src/catalog/root.rs @@ -120,8 +120,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 e6907513ec..e03900bf00 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 8875b57972..9b9cd6c49d 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 6e40b5cf89..6fe05acc88 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"); @@ -574,6 +577,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; @@ -602,7 +610,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. @@ -646,7 +654,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())); @@ -677,7 +685,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). @@ -710,7 +718,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). @@ -755,7 +763,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"{}")) @@ -782,7 +790,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!({ @@ -819,7 +827,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. diff --git a/rs/moq-boy/src/status.rs b/rs/moq-boy/src/status.rs index 4bb7357632..7ae3e28d4d 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 9c8b583205..6726686d1a 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 b9068fce03..f24f1ad938 100644 --- a/rs/moq-c/src/test.rs +++ b/rs/moq-c/src/test.rs @@ -1620,7 +1620,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 bddf5b1f6c..3b0c15b763 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 241da0fce2..377182bffb 100644 --- a/rs/moq-e2ee/benches/protect.rs +++ b/rs/moq-e2ee/benches/protect.rs @@ -25,7 +25,9 @@ fn produce(credential: &Credential, semantic: &str) -> moq_e2ee::track::Producer let generation = credential.generation(Epoch::mint()); 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 6b86fb90e5..640817b6b9 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 { @@ -286,7 +291,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() } @@ -452,7 +457,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 28ee9850c6..966e74b7ff 100644 --- a/rs/moq-e2ee/tests/transport.rs +++ b/rs/moq-e2ee/tests/transport.rs @@ -10,6 +10,11 @@ use moq_net::Epoch; 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 { @@ -40,7 +45,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/flate.rs b/rs/moq-ffi/src/flate.rs index 2c86f74559..d729d2fd5c 100644 --- a/rs/moq-ffi/src/flate.rs +++ b/rs/moq-ffi/src/flate.rs @@ -48,7 +48,10 @@ impl MoqBroadcastProducer { ) -> Result, MoqError> { let _guard = crate::ffi::enter(); self.with_state(|state| { - let track = state.broadcast.create_track(name, None)?; + let track = state.broadcast.create_track( + name, + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + )?; let producer = state.catalog.binary_snapshot(track, Config::from(config))?; Ok(Arc::new(MoqFlateSnapshotProducer { inner: std::sync::Mutex::new(Some(producer)), @@ -66,7 +69,10 @@ impl MoqBroadcastProducer { ) -> Result, MoqError> { let _guard = crate::ffi::enter(); self.with_state(|state| { - let track = state.broadcast.create_track(name, None)?; + let track = state.broadcast.create_track( + name, + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + )?; let producer = state.catalog.binary_stream(track, Config::from(config))?; Ok(Arc::new(MoqFlateStreamProducer { inner: std::sync::Mutex::new(Some(producer)), diff --git a/rs/moq-ffi/src/json.rs b/rs/moq-ffi/src/json.rs index 6b10c40e10..f7cecf7712 100644 --- a/rs/moq-ffi/src/json.rs +++ b/rs/moq-ffi/src/json.rs @@ -114,7 +114,10 @@ impl MoqBroadcastProducer { ) -> Result, MoqError> { let _guard = crate::ffi::enter(); self.with_state(|state| { - let track = state.broadcast.create_track(name, None)?; + let track = state.broadcast.create_track( + name, + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + )?; let config = moq_mux::json::Config::default() .with_compression(config.compression) .with_delta_ratio(config.delta_ratio); @@ -136,7 +139,10 @@ impl MoqBroadcastProducer { ) -> Result, MoqError> { let _guard = crate::ffi::enter(); self.with_state(|state| { - let track = state.broadcast.create_track(name, None)?; + let track = state.broadcast.create_track( + name, + moq_net::track::Info::default().with_timescale(moq_net::Timescale::MILLI), + )?; let config = moq_mux::json::Config::default().with_compression(config.compression); let producer = state.catalog.json_stream::(track, config)?; Ok(Arc::new(MoqJsonStreamProducer { diff --git a/rs/moq-ffi/src/test.rs b/rs/moq-ffi/src/test.rs index 2cc1d08949..666b8f06ed 100644 --- a/rs/moq-ffi/src/test.rs +++ b/rs/moq-ffi/src/test.rs @@ -16,6 +16,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. @@ -1070,7 +1075,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, @@ -1120,7 +1125,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 = consumer @@ -1758,7 +1763,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 236331aeb9..fc0c36d08e 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 03e5e86d4c..dadcb07cce 100644 --- a/rs/moq-flate/src/stream/mod.rs +++ b/rs/moq-flate/src/stream/mod.rs @@ -48,6 +48,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 { @@ -61,7 +66,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) @@ -189,7 +194,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() @@ -225,7 +230,7 @@ mod test { fn an_undecodable_record_ends_the_track() { 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, cfg(true)); @@ -249,7 +254,7 @@ mod test { fn a_reader_inside_the_group_sees_the_real_error() { 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()); @@ -303,7 +308,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()); @@ -341,7 +346,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()); @@ -378,7 +383,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 2d0d85b34d..59ed1fb8b6 100644 --- a/rs/moq-hls/src/export/mod.rs +++ b/rs/moq-hls/src/export/mod.rs @@ -512,6 +512,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()); @@ -986,7 +991,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, @@ -1057,7 +1062,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, @@ -1257,7 +1262,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, @@ -1312,7 +1317,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, @@ -1394,7 +1399,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, @@ -1442,14 +1447,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, @@ -1540,7 +1545,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, @@ -1597,7 +1602,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, @@ -1701,7 +1706,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, @@ -1775,7 +1780,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, @@ -1860,7 +1865,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, @@ -1911,7 +1916,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, @@ -1952,14 +1957,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, @@ -2046,7 +2051,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, @@ -2144,7 +2149,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, @@ -2241,7 +2246,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, @@ -2353,7 +2358,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), @@ -2771,7 +2776,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, @@ -2831,7 +2836,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, @@ -3144,7 +3149,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, @@ -3195,7 +3200,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 afb99720ae..2eb0de140f 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!( @@ -625,7 +630,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()); @@ -665,7 +670,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); @@ -681,7 +686,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()); @@ -748,7 +753,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(); @@ -766,7 +771,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(); @@ -803,7 +808,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 874b389165..b6356b1192 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 dbb0fa5484..862b466e9d 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 343394b96c..874fdc49e2 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 b67473b648..f3bf0567df 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 49ba04b3d3..2fc0653421 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)?; @@ -389,6 +390,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(); @@ -397,7 +403,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 { @@ -706,7 +712,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 dd4ebdb9fb..28307e40ff 100644 --- a/rs/moq-mux/src/catalog/producer.rs +++ b/rs/moq-mux/src/catalog/producer.rs @@ -339,7 +339,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 15e6cc57ca..d7cb8f00b4 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)?; @@ -725,6 +726,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(); @@ -1373,7 +1379,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 7fc0e95eb0..90cacda3fd 100644 --- a/rs/moq-mux/src/container/consumer.rs +++ b/rs/moq-mux/src/container/consumer.rs @@ -726,6 +726,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( @@ -2014,7 +2019,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); @@ -2767,7 +2772,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. @@ -2805,7 +2810,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, @@ -2836,7 +2841,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 03532bd4b5..1b2e0de67e 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 c6cdeb3d63..060b13dd53 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 @@ -2150,7 +2155,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/muxer.rs b/rs/moq-mux/src/container/fmp4/muxer.rs index ee169fe192..adc80c5fc9 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 92c756af34..db0d677981 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 a197c9cc4c..0f63adb482 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 52fb87c816..52b80c5ea3 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]; @@ -2503,14 +2508,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, @@ -2818,7 +2823,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, @@ -3864,7 +3869,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 @@ -4902,11 +4907,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(); @@ -5036,7 +5041,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(); @@ -5145,7 +5150,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 diff --git a/rs/moq-mux/src/container/ts/si.rs b/rs/moq-mux/src/container/ts/si.rs index f11f2602ac..ee5b7cd7ea 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 911ceb4a22..9f036d0ff6 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() })?; @@ -450,6 +451,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(); @@ -458,7 +464,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 { @@ -846,7 +852,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 7c7430e46c..a97adb468c 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 a6461a1759..7d8197bab4 100644 --- a/rs/moq-net/benches/group.rs +++ b/rs/moq-net/benches/group.rs @@ -20,6 +20,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. @@ -50,7 +55,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, @@ -198,7 +203,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 82c75de8f5..2e415bfcf7 100644 --- a/rs/moq-net/benches/origin.rs +++ b/rs/moq-net/benches/origin.rs @@ -24,6 +24,11 @@ use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, use futures::FutureExt; use moq_net::{Epoch, Hop, Hops, Pattern, Patterns, Timestamp, announce, broadcast, kio, origin, track}; +/// 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)]; @@ -576,7 +581,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(); @@ -587,7 +592,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(); @@ -633,7 +638,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(); @@ -743,7 +748,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 c949251303..1caebe9873 100644 --- a/rs/moq-net/benches/session.rs +++ b/rs/moq-net/benches/session.rs @@ -42,6 +42,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"]; @@ -241,7 +246,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); } @@ -551,7 +556,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 7300f67e93..af51cccc61 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(); @@ -419,7 +424,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(); // Long enough that no read expires, so every append measures the same backlog. let mut sub = track.subscribe(track::Subscription::default().with_max_delay(Duration::from_secs(3600))); let mut micros = 0; diff --git a/rs/moq-net/benches/viewers.rs b/rs/moq-net/benches/viewers.rs index 0b25499707..d98864db03 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 0ec56ed368..73ce98b6de 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 6457dcdd26..98640e8137 100644 --- a/rs/moq-net/src/ietf/publisher.rs +++ b/rs/moq-net/src/ietf/publisher.rs @@ -3219,7 +3219,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()) @@ -3240,7 +3244,7 @@ mod group_priority_test { msg, 200, consumer, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft14, GroupSlice::default(), ); @@ -3259,7 +3263,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()), @@ -3275,7 +3279,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); } @@ -3285,7 +3289,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(), @@ -3297,7 +3301,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(), @@ -3327,7 +3331,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(); @@ -3370,7 +3378,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(); @@ -3394,7 +3402,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(); @@ -3412,7 +3424,7 @@ mod group_priority_test { }, 0, group, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft19, GroupSlice::default(), ); @@ -3437,7 +3449,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(); @@ -3455,7 +3471,7 @@ mod group_priority_test { }, 0, group, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft19, GroupSlice::default(), ); @@ -3481,7 +3497,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 @@ -3503,7 +3523,7 @@ mod group_priority_test { }, 0, group, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft19, GroupSlice::default(), ); @@ -3547,7 +3567,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 @@ -3565,7 +3589,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(); @@ -3603,7 +3627,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(); @@ -4370,7 +4394,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, @@ -4409,7 +4433,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(); @@ -5996,7 +6020,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(); @@ -6009,7 +6037,7 @@ mod serve_tests { header(), 0, consumer, - Some(Timescale::default()), + Some(Timescale::MILLI), Version::Draft20, slice, ); @@ -6099,7 +6127,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 @@ -8169,7 +8201,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() @@ -8320,7 +8356,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 @@ -8347,7 +8387,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 5ed8e6dd67..f1b4870f20 100644 --- a/rs/moq-net/src/ietf/subscriber.rs +++ b/rs/moq-net/src/ietf/subscriber.rs @@ -4836,7 +4836,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, @@ -4876,17 +4880,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(); @@ -4943,15 +4947,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(); @@ -5443,7 +5447,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 d777c68176..87c893d0de 100644 --- a/rs/moq-net/src/lite/publisher.rs +++ b/rs/moq-net/src/lite/publisher.rs @@ -1381,12 +1381,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(()))) } @@ -1486,7 +1486,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 }; @@ -1637,7 +1637,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 }; @@ -1729,7 +1729,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 @@ -2245,7 +2245,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, @@ -2488,8 +2488,11 @@ impl Subscription { /// transport's datagram limit or the send fails (congestion / no capacity right now). fn serve_datagram(&mut self, datagram: crate::Datagram) { // 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; }; let body = lite::Datagram { @@ -3205,7 +3208,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(); @@ -3234,7 +3241,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(); @@ -3281,12 +3292,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()) @@ -3321,12 +3336,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(); @@ -3365,12 +3384,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 @@ -3427,12 +3450,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(); @@ -3498,12 +3525,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()) @@ -3541,12 +3572,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(); @@ -3583,12 +3618,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(); @@ -3628,7 +3667,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: Default::default(), }; @@ -3653,7 +3692,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))); @@ -3698,7 +3741,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(), }; @@ -3744,7 +3787,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(); @@ -3766,7 +3813,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); } @@ -3792,7 +3843,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); @@ -3824,7 +3879,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); @@ -3843,7 +3902,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. @@ -3865,7 +3928,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(); @@ -3880,7 +3947,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); @@ -4409,7 +4480,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(); @@ -4547,7 +4618,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"); diff --git a/rs/moq-net/src/lite/subscriber.rs b/rs/moq-net/src/lite/subscriber.rs index f7c8ed0470..b1d80091f6 100644 --- a/rs/moq-net/src/lite/subscriber.rs +++ b/rs/moq-net/src/lite/subscriber.rs @@ -460,8 +460,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))?; @@ -1629,13 +1629,17 @@ mod tests { cost: None, going_away: Default::default(), }); - 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(), }, ); @@ -1680,13 +1684,17 @@ mod tests { going_away: Default::default(), }); - 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(), }, ); @@ -1752,7 +1760,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(); @@ -1805,7 +1813,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") @@ -1895,13 +1903,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(), }, ); @@ -2080,14 +2090,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()); @@ -2146,7 +2154,9 @@ mod tests { going_away: Default::default(), }); 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 { @@ -2194,7 +2204,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"); @@ -2227,7 +2237,7 @@ mod tests { &mut sub, Some(mid_group_demand()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2297,7 +2307,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(); @@ -2318,7 +2328,7 @@ mod tests { &mut sub, Some(Subscription::default()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2327,7 +2337,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(); @@ -2350,7 +2360,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(); @@ -2371,7 +2381,7 @@ mod tests { &mut sub, Some(Subscription::default()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2382,7 +2392,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(); @@ -2427,7 +2437,7 @@ mod tests { &mut sub, Some(Subscription::default()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2440,7 +2450,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"); @@ -2469,7 +2479,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"); @@ -2485,7 +2495,7 @@ mod tests { .with_end(Position::group(9)), ), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2493,7 +2503,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"); @@ -2501,7 +2511,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"); @@ -2520,21 +2530,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)); @@ -2547,7 +2557,7 @@ mod tests { &mut sub, Some(Subscription::default()), true, - Some(Timescale::default()), + Some(Timescale::MILLI), ) .await .unwrap(); @@ -2954,7 +2964,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(); @@ -4856,9 +4866,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 c34edfef26..01f6b5c909 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 c49f104c23..816719aaa2 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 bc0b2a3c81..b3fef35200 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>, @@ -1025,7 +1025,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); } @@ -1034,7 +1036,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))); @@ -1044,7 +1046,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"); @@ -1092,7 +1094,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`. @@ -1137,7 +1139,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()); @@ -1168,7 +1170,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(); @@ -1177,7 +1179,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(); @@ -1197,7 +1199,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. @@ -1258,7 +1260,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(); @@ -1288,7 +1290,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). @@ -1301,7 +1303,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); @@ -1321,7 +1323,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(); @@ -1330,19 +1332,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() @@ -1373,7 +1375,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. @@ -1411,7 +1413,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!( @@ -1435,7 +1437,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. @@ -1574,7 +1576,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"); @@ -1595,7 +1597,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(); @@ -1603,7 +1605,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()); } @@ -1614,7 +1616,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(); @@ -1639,7 +1641,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))); @@ -1686,7 +1688,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()); @@ -1718,7 +1720,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 fe74590d06..9d90ebb656 100644 --- a/rs/moq-net/src/model/front.rs +++ b/rs/moq-net/src/model/front.rs @@ -713,7 +713,7 @@ mod tests { } fn info() -> track::Info { - track::Info::default() + track::Info::timed() } fn name(s: &str) -> Arc { @@ -1103,7 +1103,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 1ea02ec174..efed66e3ad 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()) } } @@ -2344,7 +2348,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(); @@ -2775,7 +2779,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 @@ -2802,7 +2806,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 be84d9ad28..09dad77735 100644 --- a/rs/moq-net/src/model/origin.rs +++ b/rs/moq-net/src/model/origin.rs @@ -5970,7 +5970,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(); @@ -6011,7 +6011,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(); @@ -6070,7 +6070,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(); @@ -6148,7 +6148,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(); @@ -6277,7 +6277,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(); @@ -6370,7 +6370,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(); @@ -6418,14 +6422,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(); @@ -6518,7 +6522,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(); @@ -6544,7 +6548,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(); @@ -7497,7 +7501,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(); @@ -7721,7 +7725,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(); @@ -7770,7 +7774,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(); @@ -7813,7 +7817,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(); @@ -7851,7 +7855,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; @@ -7862,7 +7866,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(); assert!(matches!(next_group(&mut subscription).await, Err(Error::Unroutable))); let replaced = consumer.request_broadcast("room/alice", None).await.expect("resolves"); @@ -7882,7 +7886,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(), @@ -7894,7 +7898,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); @@ -7931,9 +7935,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"); @@ -7957,7 +7961,7 @@ mod tests { let epoch = crate::Epoch::mint(); dynamic.update(dynamic.route().with_epoch(epoch.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); assert!( matches!(pending.await, Err(Error::Unroutable)), @@ -7967,7 +7971,7 @@ mod tests { let retry = consumer.request_broadcast("room/alice", None); 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 = retry.await.expect("the new instance resolves"); assert_eq!(resolved.info().epoch.as_ref(), Some(&epoch)); @@ -7989,14 +7993,14 @@ mod tests { // The answer and the change land in the same tick. 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 epoch = crate::Epoch::mint(); dynamic.update(dynamic.route().with_epoch(epoch.clone())).unwrap(); 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 new instance resolves"); assert_eq!(resolved.info().epoch.as_ref(), Some(&epoch)); @@ -8087,8 +8091,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]); @@ -8197,7 +8201,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(); @@ -8225,7 +8229,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(); @@ -8262,7 +8266,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(); @@ -8314,7 +8318,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(); @@ -8353,7 +8357,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(); @@ -8435,7 +8439,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 @@ -8482,7 +8486,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"); @@ -8548,7 +8552,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(); @@ -8612,7 +8616,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(); @@ -8648,7 +8652,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(); @@ -8682,7 +8686,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(); @@ -8778,7 +8782,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") @@ -8831,7 +8835,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(); @@ -8932,7 +8936,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(); @@ -8968,7 +8972,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(); @@ -9023,7 +9027,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(); @@ -9181,7 +9185,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(); @@ -9205,7 +9209,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); @@ -9232,7 +9236,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()) @@ -9472,7 +9476,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(); @@ -9504,7 +9508,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 32fd3cffc8..89886ab877 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 0c616cd072..8d433a1571 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 c6c0716d64..e6ccea091d 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. @@ -4721,7 +4729,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. @@ -4734,7 +4742,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(); @@ -4748,7 +4756,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(); @@ -4764,7 +4772,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(); @@ -4778,7 +4786,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(); @@ -4796,7 +4804,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(); @@ -4814,7 +4822,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(); @@ -4833,7 +4841,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(); @@ -4852,7 +4860,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!( @@ -4864,7 +4872,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(); @@ -4873,7 +4881,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 @@ -4889,7 +4897,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")) @@ -4900,7 +4908,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 @@ -4922,7 +4930,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(); @@ -4965,7 +4973,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!( @@ -4980,7 +4988,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); @@ -4998,7 +5006,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; @@ -5015,7 +5023,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!( @@ -5033,7 +5041,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(); @@ -5060,7 +5068,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] { @@ -5078,7 +5086,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] { @@ -5096,7 +5104,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 @@ -5128,7 +5136,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(); @@ -5145,7 +5153,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( @@ -5163,7 +5171,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 @@ -5202,7 +5210,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(); @@ -5224,7 +5232,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. @@ -5261,7 +5269,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(); @@ -5289,7 +5297,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(); @@ -5316,7 +5324,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(); @@ -5350,7 +5358,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(); @@ -5387,7 +5395,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); @@ -5418,7 +5426,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. @@ -5437,7 +5445,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 @@ -5451,7 +5459,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); @@ -5477,7 +5485,7 @@ mod test { ..Default::default() }), name, - None, + Info::timed(), ) } @@ -5668,7 +5676,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. @@ -5694,7 +5702,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 @@ -5730,9 +5738,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!( @@ -5748,7 +5756,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))); @@ -5756,7 +5764,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))); @@ -5768,7 +5776,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 @@ -5783,7 +5791,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 @@ -5800,7 +5808,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`. @@ -5812,7 +5820,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); @@ -5831,7 +5839,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()); @@ -5877,7 +5885,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")) @@ -5899,7 +5907,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); } @@ -5917,7 +5925,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]); @@ -5932,7 +5940,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); } @@ -5957,7 +5965,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(); @@ -5985,7 +5993,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); } @@ -6000,7 +6008,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); } @@ -6021,7 +6029,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); } @@ -6050,7 +6058,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); } @@ -6072,7 +6080,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 @@ -6096,7 +6104,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. @@ -6110,7 +6118,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 @@ -6123,7 +6131,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(); @@ -6150,7 +6158,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(); @@ -6184,7 +6192,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")) @@ -6232,7 +6240,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) @@ -6305,7 +6313,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")) @@ -6359,7 +6367,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")) @@ -6410,7 +6418,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")) @@ -6460,7 +6468,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| { @@ -6534,7 +6542,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(); @@ -6580,7 +6588,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")) @@ -6607,7 +6615,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); @@ -6622,7 +6630,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 @@ -6659,7 +6667,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); @@ -6672,7 +6680,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); } @@ -6697,7 +6705,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(); @@ -6720,7 +6728,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] { @@ -6742,7 +6750,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 @@ -6762,7 +6770,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); @@ -6781,7 +6789,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. @@ -6845,7 +6871,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) @@ -6863,7 +6889,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) @@ -6878,7 +6904,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 @@ -6918,7 +6944,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); } @@ -6936,7 +6962,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); } @@ -6957,7 +6983,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 @@ -6970,7 +6996,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); @@ -6980,7 +7006,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); } @@ -6996,7 +7022,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); @@ -7007,7 +7033,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(), @@ -7028,7 +7054,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); @@ -7056,7 +7082,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 @@ -7079,7 +7105,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); } @@ -7094,7 +7120,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); @@ -7112,7 +7138,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(); @@ -7131,7 +7157,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. @@ -7167,7 +7193,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)); @@ -7200,7 +7226,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()); @@ -7237,7 +7263,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(); @@ -7271,7 +7297,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(); @@ -7317,7 +7343,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(); @@ -7337,7 +7363,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(); @@ -7358,7 +7384,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(); @@ -7373,7 +7399,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); @@ -7385,7 +7411,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(); @@ -7403,7 +7429,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(); @@ -7426,7 +7452,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(); @@ -7444,7 +7470,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()); @@ -7454,7 +7480,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()); @@ -7464,7 +7490,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 @@ -7486,7 +7512,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(); @@ -7501,7 +7527,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)); @@ -7509,7 +7535,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. @@ -7544,7 +7570,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(); @@ -7573,7 +7599,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(); @@ -7589,7 +7615,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(); @@ -7601,7 +7627,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(); @@ -7614,7 +7640,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(); @@ -7637,7 +7663,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(); @@ -7668,7 +7694,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 }) @@ -7722,7 +7748,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 { @@ -7742,7 +7768,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 { @@ -7766,7 +7792,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 @@ -7790,7 +7816,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 @@ -7817,7 +7843,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(); @@ -7834,7 +7860,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. @@ -7871,7 +7897,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. @@ -7897,7 +7923,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()); @@ -7921,7 +7947,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 { @@ -7953,7 +7979,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 { @@ -7998,7 +8024,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 { @@ -8042,7 +8068,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); @@ -8088,7 +8114,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 { @@ -8128,7 +8154,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); @@ -8157,7 +8183,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(); @@ -8176,7 +8202,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); @@ -8201,7 +8227,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(); @@ -8245,7 +8271,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(); @@ -8276,7 +8302,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(); @@ -8299,7 +8325,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())); @@ -8335,7 +8361,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())); @@ -8376,7 +8402,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); @@ -8395,7 +8421,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!( @@ -8407,7 +8433,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); @@ -8418,7 +8444,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 @@ -8442,7 +8468,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(); @@ -8462,7 +8488,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(); @@ -8475,7 +8501,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(); @@ -8494,7 +8520,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(); @@ -8513,7 +8539,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(); @@ -8538,7 +8564,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); } @@ -8546,7 +8572,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(); @@ -8560,7 +8586,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(); @@ -8581,7 +8607,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); } @@ -8589,7 +8615,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(); @@ -8608,7 +8634,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(); @@ -8617,7 +8643,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 @@ -8633,7 +8659,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(); @@ -8643,7 +8669,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(); @@ -8660,7 +8686,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(); @@ -8670,7 +8696,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 @@ -8686,7 +8712,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(); @@ -8696,7 +8722,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)); @@ -8706,13 +8732,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(); @@ -8731,7 +8757,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(); @@ -8746,7 +8772,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(); @@ -8775,7 +8801,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(); @@ -8794,7 +8820,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(); @@ -8805,7 +8831,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(); @@ -8847,7 +8873,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(); @@ -8861,7 +8887,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(); @@ -8879,7 +8905,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(); @@ -8906,7 +8932,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(); @@ -8941,7 +8967,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(); @@ -8967,7 +8993,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(); @@ -8986,7 +9012,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(); @@ -9012,7 +9038,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(); @@ -9025,7 +9051,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(); @@ -9054,7 +9080,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(); @@ -9073,7 +9099,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))); @@ -9081,7 +9107,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 @@ -9105,7 +9131,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) } @@ -9291,7 +9317,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])) @@ -9299,7 +9325,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(); @@ -9379,7 +9405,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); @@ -9484,11 +9510,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 @@ -9545,7 +9571,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(); @@ -9684,7 +9710,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(); @@ -9715,7 +9741,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(); @@ -9777,7 +9803,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(); @@ -9819,7 +9845,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(); @@ -9840,7 +9866,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(); @@ -9884,7 +9910,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(); @@ -9917,7 +9943,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(); @@ -9943,7 +9969,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); @@ -9952,7 +9978,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); @@ -9971,7 +9997,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 02a2ad23c4..6287b9c2b4 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); @@ -137,7 +142,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: {:?} {}", @@ -192,7 +197,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/datagram.rs b/rs/moq-net/tests/datagram.rs index 907cd647ab..1a77dd3c23 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 fe492c539d..6f09bc1d67 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/fetch_cancel.rs b/rs/moq-net/tests/fetch_cancel.rs index 94801e0a3d..17bbf32813 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 21880e230f..e36a1d8458 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 cf0b33ad0a..b8d3a84b0c 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/goaway.rs b/rs/moq-net/tests/goaway.rs index 573886bf2e..c054cf7104 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 9bfbd7bd3c..d190c0bd66 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. @@ -63,7 +68,7 @@ fn measure() -> (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 2323814fa4..e34edfef5a 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 0cf508dd2c..c00630c04a 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(); @@ -202,7 +205,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/loom.rs b/rs/moq-net/tests/loom.rs index f21986f267..25d201c651 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/quiet_catalog.rs b/rs/moq-net/tests/quiet_catalog.rs index 17eb3d8a79..aa94c743e6 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 eb56fe8f71..9ccbcd8597 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/route_change.rs b/rs/moq-net/tests/route_change.rs index d70a6c93ef..df75a58c62 100644 --- a/rs/moq-net/tests/route_change.rs +++ b/rs/moq-net/tests/route_change.rs @@ -20,6 +20,11 @@ use futures::{StreamExt, channel::mpsc}; 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 TEST_TIMEOUT: Duration = Duration::from_secs(30); /// Frames per group. @@ -98,7 +103,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(); @@ -464,7 +469,7 @@ async fn lagging_route_dies(version: Version) -> mpsc::UnboundedReceiver 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 e737c31fd2..225553b904 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 af34c42403..960b3cf916 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 401c75a6d9..76dd4f241a 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 ce029c99cb..63e82f6f12 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()); @@ -69,7 +74,7 @@ async fn recreate(finish: bool) { let origin = produce_origin(1); let broadcast = origin.create_broadcast("bench").unwrap(); broadcast.announce(Default::default()).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(); @@ -78,7 +83,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 a0718206c7..6ac6780e54 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 b938b0430d..c340f8de90 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 926ad20110..bbcbec5317 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 48c258aa9c..6ccfbcbed0 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 34854c898b..a440e5aaf2 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? { @@ -2618,7 +2623,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 a76038a2a4..fed90521ae 100644 --- a/rs/moq-relay/src/internal.rs +++ b/rs/moq-relay/src/internal.rs @@ -695,6 +695,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? { @@ -1000,7 +1005,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. @@ -1008,7 +1013,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 556f50fdc6..4f989d20f9 100644 --- a/rs/moq-relay/tests/auth_lifetime.rs +++ b/rs/moq-relay/tests/auth_lifetime.rs @@ -23,6 +23,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. @@ -211,7 +216,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()) @@ -459,7 +464,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() @@ -634,7 +639,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 4871610de3..220634c25c 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 = "opalin/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 f487b98d45..fdd499b15b 100644 --- a/rs/moq-relay/tests/drills.rs +++ b/rs/moq-relay/tests/drills.rs @@ -30,6 +30,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) +} + /// 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); @@ -419,7 +424,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) @@ -542,7 +547,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(); @@ -662,7 +667,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) @@ -703,7 +708,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) @@ -960,7 +965,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 e9c0cf0f21..165f037fe8 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 5dd263a6b7..2a60c529a5 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; @@ -277,7 +282,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; @@ -473,7 +478,7 @@ async fn cluster_diamond_goaway_seamless_failover_inner() { let top_origin = moq_tokio::origin::spawn(); let broadcast = top_origin.create_broadcast("diamond").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 (top_port, mut top_accepted, _top_handle) = spawn_upstream(top_origin.clone()).await; let top_url = format!("tcp://127.0.0.1:{top_port}/"); @@ -749,7 +754,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 e9757e8099..64bcb36147 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 51a492afa3..55bcbfc551 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 80bf4b02af..ba8dba21e4 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 e9e1d943c0..a51c90cb43 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 695762f42a..4a0ebbee03 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 9c88387391..fd1dafe369 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 7c9397785f..ed97f71563 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 0102206aa6..2708db0e2f 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 cedf4a7da2..d64c62e975 100644 --- a/rs/moq-stats/src/aggregate.rs +++ b/rs/moq-stats/src/aggregate.rs @@ -616,6 +616,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()); @@ -679,7 +684,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); @@ -753,7 +758,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 3d01b86c87..f850a5ac74 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()); @@ -173,7 +178,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 dab7e73d18..c424c13e75 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; @@ -1300,7 +1310,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); @@ -1637,7 +1647,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 98241f9e5d..609c0e707c 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 d86f378273..033f107dd3 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 6ccede73f0..9600960771 100644 --- a/rs/moq-tokio/src/client.rs +++ b/rs/moq-tokio/src/client.rs @@ -891,6 +891,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)] @@ -1620,7 +1625,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 5c5d8f555c..0ae47a8e7c 100644 --- a/rs/moq-tokio/src/connection.rs +++ b/rs/moq-tokio/src/connection.rs @@ -1532,6 +1532,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() { @@ -2486,7 +2491,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(); @@ -2587,7 +2592,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(); @@ -2685,7 +2690,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; @@ -2765,7 +2770,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 ecb2e98ff5..66cc63aac6 100644 --- a/rs/moq-tokio/src/server.rs +++ b/rs/moq-tokio/src/server.rs @@ -1658,6 +1658,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? { @@ -2206,7 +2211,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 12479d771e..47f61a0096 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 d78b806f1b..e1436a159b 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 c83673c238..ae5772a093 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 6d747b2937..eeb3e54c5d 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 e0dbe6a900..4bd98e31fd 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)