Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions c/moq/MIGRATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ The hand-written crate never published as moq-c: a libmoq 0.6 user applies these

`moq_announce_event.has_captures` is removed, changing the C ABI. Read `captures_len` directly: zero means the filter has no wildcards. Rebuild consumers against the regenerated header and matching native library.

### A subscription without `max_delay_present` has no limit
### Subscriptions take a `moq_delay` ([#7697](https://github.com/moq-dev/moq/pull/7697))

A `moq_subscription` with `max_delay_present` false sets no subscriber limit, leaving the publisher's max age alone to bound it, instead of moq-net's default of zero.
Start from `moq_subscription_defaults()`, or set `max_delay_present`, where you relied on a zeroed struct; a NULL pointer still means the defaults.
`moq_subscription.max_delay_us` and `max_delay_present` are `moq_delay delay`. A zeroed `moq_subscription` is moq-net's defaults, the same as `moq_subscription_defaults()` and a NULL pointer: set `delay.max_us` to the old `max_delay_us`, and `delay.max_unlimited` for no subscriber limit. `delay.min_unstated` leaves the target latency unstated.

### Paths with an empty segment are refused ([#6648](https://github.com/moq-dev/moq/pull/6648))

Expand Down
2 changes: 2 additions & 0 deletions c/moq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ The generator is the one the C++ package pins; see [cpp/moq](../../cpp/moq/READM

The implementation is C++17 over the generated C++ bindings, compiled into `libmoq.a` (`moq.lib`) beside moq-ffi's staticlib, installed as `libmoq_c_ffi.a` (`moq_c_ffi.lib`) so moq-cpp's copy can share the prefix, so a C program needs no C++ compiler, but its link adds the C++ runtime, which the CMake package and `moq-c.pc` name. Both libraries use the release CRT (`/MD`) on MSVC in every configuration, so an MSVC consumer must too (`CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL`); its default Debug runtime (`/MDd`) fails to link with LNK2038.

Start every subscription from `moq_subscription_defaults()` and change its fields: it holds moq-net's defaults, a zero `delay` that starts at the newest group and skips a superseded one at once. The bare record `moq_subscription_default()` returns does not, since the generated records carry no duration defaults: its unset `delay.max` sets no subscriber limit and its unset `delay.min` leaves the target latency unstated.

This package and the C++ package, `moq-cpp`, each embed moq-ffi, so one program links one of them, never both, and they install to separate prefixes.
7 changes: 4 additions & 3 deletions c/moq/test/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,13 @@ int main(void)
moq_dispatcher dispatcher = {loop_dispatch, loop_shutdown, NULL};
moq_set_dispatcher(&dispatcher);

/* Only the defaults carry moq-net's zero max delay; a bare record sets no subscriber limit. */
/* Only the defaults carry moq-net's zero delay; a bare record leaves both bounds unset. */
moq_subscription *defaults = moq_subscription_defaults();
CHECK(defaults->max_delay.has_value && defaults->max_delay.value == 0);
CHECK(defaults->delay.min.has_value && defaults->delay.min.value == 0);
CHECK(defaults->delay.max.has_value && defaults->delay.max.value == 0);
moq_subscription_free(defaults);
moq_subscription bare = moq_subscription_default();
CHECK(!bare.max_delay.has_value);
CHECK(!bare.delay.max.has_value);

/* A synchronous error is a returned value with Rust's Display message. */
moq_server_config unbound_config = moq_server_config_default();
Expand Down
5 changes: 5 additions & 0 deletions cpp/moq/MIGRATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Subscriptions take a `moq::Delay` ([#7697](https://github.com/moq-dev/moq/pull/7697))

`moq::Subscription::max_delay` is `moq::Delay delay`: set `delay.max` to the old `max_delay`, or start from `moq::subscription_defaults()`.
`moq::Delay::min` is a `std::optional`: `std::nullopt` leaves the target latency unstated, which a decoder plays as zero. A bare `moq::Subscription{}` leaves both bounds unset, so its `delay.max` still sets no subscriber limit.

### Announcement captures are required ([#7484](https://github.com/moq-dev/moq/pull/7484))

`moq::Announce.captures` is `std::vector<std::string>` instead of an optional vector. Remove optional checks and dereferences; an empty vector means the filter has no wildcards. Regenerate bindings and rebuild with the matching native library.
Expand Down
8 changes: 5 additions & 3 deletions cpp/moq/test/probe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,13 @@ void timestamp_roundtrip()
ok(broadcast->close(), "close timestamp broadcast");
}

// Only the defaults carry moq-net's zero max delay; a bare record sets no subscriber limit.
// Only the defaults carry moq-net's zero delay; a bare record leaves both bounds unset.
void subscription_defaults()
{
CHECK(moq::subscription_defaults().max_delay == std::chrono::nanoseconds::zero());
CHECK(!moq::Subscription{}.max_delay.has_value());
auto defaults = moq::subscription_defaults();
CHECK(defaults.delay.min == std::chrono::nanoseconds::zero());
CHECK(defaults.delay.max == std::chrono::nanoseconds::zero());
CHECK(!moq::Subscription{}.delay.max.has_value());
}

// Exercise the installed audio publisher and consumer rather than only generated signatures.
Expand Down
10 changes: 7 additions & 3 deletions dart/moq/MIGRATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ How to update your code for each breaking change, newest first.

## Unreleased

### Subscriptions take a `Delay` ([#7697](https://github.com/moq-dev/moq/pull/7697))

`Subscription.maxDelay` is `delay`, a `Delay`, and a bare `Subscription()` is moq-net's defaults again: pass `delay: Delay(max: maxDelay)`, and `Delay(max: null)` for no subscriber limit.
`media.Delay` moves to `package:moq/moq.dart`. `Delay` and `Subscription` are extension types over the generated records, so they pass wherever those are taken; `Delay` defaults `min` and `max` to zero, and an explicit null `min` leaves the target latency unstated.

### `Transport.iroh` is removed ([#7345](https://github.com/moq-dev/moq/pull/7345))

Native sessions no longer arrive over iroh, so `Transport` loses its `iroh` value. Drop the case, and regenerate bindings and rebuild with the matching native library.
Expand All @@ -14,8 +19,8 @@ Native sessions no longer arrive over iroh, so `Transport` loses its `iroh` valu

### Decoder outputs take a `Delay` ([#7311](https://github.com/moq-dev/moq/pull/7311))

`audio.DecoderOutput` replaces `maxDelay` and the playout `delay: Duration?` with `delay: media.Delay?` and `estimate: bool = true`.
`video.DecoderOutput.maxDelay` is `delay: media.Delay?`.
`audio.DecoderOutput` replaces `maxDelay` and the playout `delay: Duration?` with `delay: Delay?` and `estimate: bool = true`.
`video.DecoderOutput.maxDelay` is `delay: Delay?`.
`Delay(min: ..., max: ...)`: `max` (null for no limit) is the old `maxDelay`, and `min` is the playout delay, where a cold join starts.
A fixed audio playout delay `d` is `delay: Delay(min: d, max: ...)` with `estimate: false`.

Expand Down Expand Up @@ -46,7 +51,6 @@ Their handles `OriginDynamic`, `BroadcastDynamic`, and `TrackDynamic` are `Origi

The records carry durations as `Duration` fields instead of `*Us` microsecond integers, so the duration extension getters are gone: read `backoff.initial`, `stats.rtt`, `grant.expires`, `info.maxAge`, `fill.maxDelay()`, and `consumer.delay()` directly.
`audio.EncoderOutput.frameDuration` is nullable: pass null where you passed `frameDurationUs: 0` for the codec's own frame.
`Subscription.maxDelay` defaults to null, so a `Subscription()` that omits it gets no staleness limit instead of moq-net's 0: start from `defaultSubscription()`, or pass `maxDelay: Duration.zero`, for the old default.
A negative `Duration` throws `ArgumentError` wherever it crosses into the native library.

### Connection state
Expand Down
3 changes: 0 additions & 3 deletions dart/moq/lib/media.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@ typedef ContainerInit = MoqContainerInit;
/// A width and height pair, in pixels.
typedef Dimensions = MoqDimensions;

/// How far behind the live edge a decoder plays: `min` is the playout delay and where a cold join starts, and `max`, null for no limit, is how far a stalled group may fall behind before it is skipped. The subscription carries both; a `min` past `max` starts at `max`.
typedef Delay = MoqDelay;

/// Where an audio or video decoder starts on a track that already holds groups: `latest` for a live player.
typedef DecodeStart = MoqDecodeStart;

Expand Down
44 changes: 36 additions & 8 deletions dart/moq/lib/src/aliases.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import 'package:moq_ffi/moq_ffi.dart';
// Re-export the UniFFI types without their `Moq` prefix, so consumers spell
// them the way the Rust API does. These are type aliases, not wrappers: the
// values are the exact same objects, so every generated method applies
// unchanged and the prefixed names stay valid.
// unchanged and the prefixed names stay valid. `Delay` and `Subscription` are
// extension types instead, which are the same objects too but give an omitted
// delay bound the zero moq-net defaults to, where the generated record leaves
// it null.
//
// Four types are deliberately not aliased, because the unprefixed name is
// already taken where this package is used:
Expand Down Expand Up @@ -134,14 +137,39 @@ typedef Frame = MoqFrame;
/// An exact media time: `value` units at `scale` units per second.
typedef Timestamp = MoqTimestamp;

/// Tunes how a track subscription is delivered: priority, latency budget, ranges, and group ordering.
typedef Subscription = MoqSubscription;

/// moq-net's defaults, whose `maxDelay` of zero skips a superseded group at once.
/// How far behind the live edge a subscriber or decoder plays: where it starts, and how late it may fall.
extension type Delay._(MoqDelay _delay) implements MoqDelay {
/// `min` is the target latency and where a cold join starts, and `max` is how far a
/// non-latest group may fall behind before it is skipped. Both default to zero, moq-net's
/// default. An explicit null `min` leaves it unstated, which a decoder plays as zero, and an
/// explicit null `max` sets no subscriber limit. A `min` past `max` starts at `max`.
Delay({Duration? min = Duration.zero, Duration? max = Duration.zero})
: this._(MoqDelay(min: min, max: max));
}

/// Tunes how a track subscription is delivered: priority, delay, ranges, and group ordering.
extension type Subscription._(MoqSubscription _subscription)
implements MoqSubscription {
/// An omitted `delay` is a zero [Delay], so a bare `Subscription()` is moq-net's defaults.
Subscription({
int priority = 0,
MoqDelay? delay,
List<MoqRange>? ranges,
MoqOrder? order,
}) : this._(
MoqSubscription(
priority: priority,
delay: delay ?? Delay(),
ranges: ranges,
order: order,
),
);
}

/// moq-net's defaults, whose zero `delay` starts at the newest group and skips a superseded one at once.
///
/// A null subscription means the same. A `Subscription()` leaves `maxDelay` null, which
/// sets no subscriber limit, so pass `maxDelay: Duration.zero` when building one to match.
Subscription defaultSubscription() => moqSubscriptionDefaults();
/// A null subscription and a bare `Subscription()` mean the same.
Subscription defaultSubscription() => Subscription._(moqSubscriptionDefaults());

/// A span of a track to deliver: from `start` up to `end`, or onward when `end` is null.
typedef Range = MoqRange;
Expand Down
20 changes: 12 additions & 8 deletions dart/moq/test/moq_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,23 @@ void main() {
});

test('a default subscription skips superseded groups at once', () {
expect(defaultSubscription().maxDelay, Duration.zero);
expect(Subscription().maxDelay, isNull);
for (final subscription in [defaultSubscription(), Subscription()]) {
expect(subscription.delay.min, Duration.zero);
expect(subscription.delay.max, Duration.zero);
}
final unset = Delay(min: null, max: null);
expect(unset.min, isNull);
expect(unset.max, isNull);
});

test('a negative duration in a raw record is refused', () {
final broadcast = BroadcastProducer();
final track = broadcast.publishTrack(name: 'negative', info: null);
expect(
() => track.consume(
subscription: Subscription(maxDelay: const Duration(microseconds: -1)),
subscription: Subscription(
delay: Delay(max: const Duration(microseconds: -1)),
),
),
throwsArgumentError,
);
Expand Down Expand Up @@ -105,7 +112,7 @@ void main() {

final consumer = track.consume(
subscription: Subscription(
maxDelay: const Duration(seconds: 10),
delay: Delay(max: const Duration(seconds: 10)),
ranges: [Range(start: RelativeMoqAnchor(2))],
order: Order.ascending,
),
Expand Down Expand Up @@ -759,10 +766,7 @@ void main() {

final fixedOutput = moq_audio.DecoderOutput(
format: moq_audio.SampleFormat.f32,
delay: moq_media.Delay(
min: Duration(milliseconds: 80),
max: Duration.zero,
),
delay: Delay(min: Duration(milliseconds: 80)),
estimate: false,
);
final fixed = await moq_audio.Consumer.subscribe(
Expand Down
Loading
Loading