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
174 changes: 4 additions & 170 deletions cpp/moq/MIGRATE.md
Original file line number Diff line number Diff line change
@@ -1,174 +1,8 @@
# Migrating the C++ moq library

How to update your code for each breaking change, newest first.
# Migrating the C++ MoQ package

## Unreleased

### Connection state

`Client::connect` returns a connecting native session. Replace `status()`, `connects()`, and both `failure()` polls with `state()` and `state_changed(state.revision)`. `ConnectionState` carries `phase`, `connects`, `failure`, and `version`; `ConnectionPhase` replaces `ConnectionStatus`.

### Exact encoder rates and publication options

Raw video input framerates are exact numerator/denominator records, not integers.
Use `VideoFrameRate{30000, 1001}` and construct producers with `(catalog, AudioProducerOptions{input, output, track, bandwidth})` or the corresponding `VideoProducerOptions`.
The codec-only output records no longer contain track naming. Rebuild against
the matching generated bindings and native library.

### Video rendition preference

`moq::Video` gains `int32_t preference`. Update aggregate initializers with `0` to keep the default ranking and rebuild against the matching generated headers and native library. Higher values are preferred; compatibility transcodes use `-1`.

### `announce` takes an `AnnounceRoute`

`BroadcastProducer::announce` takes `moq::AnnounceRoute{cost, epoch}` instead of a `moq::Route`: the origin builds the hop chain, so `hops` is no longer an input.
`moq::Route` is only what announcements report. To re-announce an observed route, copy its `cost` and `epoch`.

### A broadcast reference waits for its sibling's announcement (#6254)

Resolving a rendition's `broadcast` reference, through `BroadcastConsumer::resolve` and the rendition-based subscribes and fetches, now waits for a sibling nothing announces yet instead of failing as unroutable, so a late sibling plays once it is announced.
Where you skipped such a rendition on that error, bound the call yourself: cancel it.

### Priorities are signed (#6142)

`TrackInfo::priority`, `Subscription::priority`, and `FetchGroupOptions::priority` are `int8_t`, and `GroupRequest::priority()` returns one.
Higher is first and 0 is the unset midpoint; `TrackInfo::priority` defaulted to 127 and now defaults to 0.
To keep a value's moq-lite byte, subtract 128 from it, widening first (`static_cast<int8_t>(int(old) - 128)`): 255 becomes 127, 128 becomes 0, and 0 becomes -128.

### A client asks only about what it watches (#6165)

On moq-lite, a client session asks its relay for announcements only under paths something watches, through `announced` or `announced_broadcast`, instead of under its whole scope at connect.
`OriginConsumer::request_broadcast` watches nothing, so on a client it fails with an unroutable error for a path nothing watches, even when the relay announces it.
Resolve the path with `announced_broadcast`, or hold `announced` over its prefix while requesting.
moq-transport and moq-lite-03/04 sessions are unchanged.

### `moq::ContainerFormat::kFmp4` unwraps to Legacy (#6250)

A container import with `moq::ContainerFormat::kFmp4` now publishes one Legacy frame per sample instead of one CMAF frame per fragment.
Pass the new `moq::ContainerFormat::kCmaf` to keep publishing CMAF.

### Catalog references carry their epoch

`Video` and `Audio` gain `epoch`, the publisher instance their `broadcast` reference names, right after `broadcast`; positional initializers shift by one.
`BroadcastConsumer::resolve(reference)` becomes `resolve(rendition)`, taking the catalog `Rendition` and reading its `broadcast` and `epoch` from it.
Resolving, and the rendition-based consumers, fail with `moq::Error::kMux` for a rendition whose broadcast is proven to serve another epoch.

### Timestamps stop at 2^61 - 1 microseconds (#6168)

Writing a frame, or any other call that takes a timestamp, fails with `TimeOverflow` past `2^61 - 1` microseconds instead of `2^62 - 1`, the most moq-lite-05/06 can carry.
A received timestamp past it fails the group as a malformed track.
Clamp timestamps you build from unbounded input to `2^61 - 1`.

### Finished tracks stay until the broadcast ends (#6157)

A finished track now stays listed, with its catalog rendition and demand, until its broadcast closes or the new `BroadcastProducer::finish` lets go.
Publish a replacement under a new name, and expect a finished track's demand watch to end with the broadcast.

### A claim answers by publishing (#5678)

`OriginProducer::dynamic(prefix)` takes no route, and `OriginDynamic::update` is gone.
Replace `request->accept(broadcast)` with `request->prepare(epoch)`, which returns a `BroadcastPrepared`: fill its `broadcast()`, then `publish()` announces it at the exact path under that epoch and returns its producer.

### Anchors can name a time (#6074)

`Anchor` gains a `Time` case holding `timestamp_us`, the group containing that presentation time, so exhaustive visits need a case for it.
`TrackConsumer::update` now returns a result, failing with `TimestampMismatch` on a time anchor an untimed track can't serve.

### Rebuild the matching raw bindings

The documentation update changes UniFFI's audio-producer method metadata checksums.
Regenerate the C++ binding headers and sources from the rebuilt native library,
then rebuild the application; older generated guards reject the rebuilt library.
Application calls are unchanged.

### JSON consumers return `JsonValue` (#5677)

`JsonSnapshotConsumer::next()` and `JsonStreamConsumer::next()` return `JsonValue` (`value`, `timestamp_us`) instead of a string; `timestamp_us` is empty when untimed.
A snapshot consumer's `next()` now yields every state in order; call `latest()` for the newest.
Timestamps are rounded down to a whole microsecond.

### Ranges filter reads (#5929)

A subscription now reads only the groups its own `ranges` cover.

### Track demand reports the wanted group (#5930)

`TrackDemand::is_used`, `used`, and `unused` are gone.
`wanted()` returns `std::nullopt` while nobody subscribes, otherwise the group subscribers want next, and `wanted_changed()` waits for it to change (the first call returns at once).
Loop on `wanted_changed()` until it returns a value where you awaited `used()`, and `std::nullopt` where you awaited `unused()`.
`TrackInfo` gains `refresh`, the ratio past which a joining subscriber asks for a new group.

### `CongestionControl` gained `kNone` (#5926)

`moq::CongestionControl::kNone` runs no congestion control and no pacing, for tests and controlled links.
A `switch` without a `default` needs one more case.

### Container byte streams use `MediaContainerProducer` (#5919)

`moq::MediaContainerStreamProducer` and `moq::ContainerStreamInit` are gone: a container recovers its own framing, so `moq::MediaContainerProducer` takes chunks split anywhere.
Pass empty `data` and write the stream through it.

```cpp
auto stream = ok(moq::MediaContainerProducer::init(broadcast, {moq::ContainerFormat::kFmp4, {}}));
```

### Key logging is opt-in (#5907)

`SSLKEYLOGFILE` alone no longer writes TLS session secrets.
Set `QuicConfig::keylog` to `true` as well to capture them; without `SSLKEYLOGFILE` the client or server refuses to build.

### Subscriptions carry group ranges (#5732)

`Subscription::group_start` and `group_end` are replaced by `ranges`, a list of `Range` whose ends are an `Anchor` (an absolute group and frame, or a count of groups before the latest), and an optional `order`.
To start at group `n`, pass one range starting at the absolute anchor for group `n`, frame 0.

### Receive estimates come from `Session::recv_bandwidth` (#5777)

The connection stats no longer carry a receive estimate.
Hold the consumer `Session::recv_bandwidth` returns to request PROBE, read it with `rate()` or wait with `rate_changed()`, and `cancel()` it to stop.

```cpp
auto bandwidth = session->recv_bandwidth();
auto rate = bandwidth ? bandwidth->rate() : std::nullopt;
```

### `AnnouncedBroadcast::available()` waits for the next publisher instance (#5700)

The first call returns the broadcast serving the path, as before.
Each later call waits for the next instance to take over the path, instead of returning whatever serves it now.

### A timed track refuses a group that rewinds (#5803)

Writing a frame fails with `TimestampMismatch` when a group's first frame starts before a lower-sequence group's, or after a higher one's.
A publisher whose timeline restarts publishes a new broadcast instead.

### Bandwidth reserves a track's demand (#5845)

`Bandwidth::reserve` takes the track's `demand()` instead of the producer, and no longer fails: pass `ok(track->demand())`.

### Generated types drop the `Moq` prefix (#5727)

`moq::MoqFoo` is `moq::Foo`, the name the aliases already gave it.
`moq::expected` is `tl::expected` at every C++ standard instead of `std::expected` under C++23, and `MOQ_ABI`, `MOQ_ABI_NAME`, and `MOQ_IMPLEMENTATION` are gone.

### Media subscribe and fetch take a catalog rendition (#5834)

`moq::MediaTrackConfig` and `moq::MediaGroupConfig` replace `name` and `container` with a `moq::Rendition` and follow its broadcast and track themselves.

```cpp
auto media = ok(moq::MediaTrackConsumer::subscribe(broadcast, {moq::Rendition::kVideo{id, video}}).get());
```

### Native codecs have audio and video types (#5694)

`BroadcastProducer::encode_audio` / `encode_video` are `moq::AudioProducer::init` / `moq::VideoProducer::init`, which take a `moq::MediaCatalogProducer`.
The audio track name belongs to `AudioProducerOptions::track`, and a zero `frame_duration_us` takes the codec's own frame.
`BroadcastConsumer::decode_audio` / `decode_video` are `moq::AudioConsumer::subscribe` / `moq::VideoConsumer::subscribe`, taking the broadcast first.
Producers expose subscriber state only through `demand()`, and a video `gop` is a `moq::VideoGop` keyframe variant.
`moq::AudioEncoder` / `moq::VideoEncoder` encode without publishing.
### First release

```cpp
moq::AudioEncoderOutput output{moq::AudioCodec::opus()};
auto catalog = ok(moq::MediaCatalogProducer::init(broadcast));
auto audio = ok(moq::AudioProducer::init(catalog, {input, output, "audio", nullptr}));
```
There is no published C++ package to migrate from. Start with the current headers and examples, and build the wrapper, generated bindings, and native library together.
The underlying generated API follows the [moq-ffi migration guide](../../rs/moq-ffi/MIGRATE.md).
21 changes: 17 additions & 4 deletions dart/moq/MIGRATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ Custom implementations of generated interfaces must return `UniffiRustFuture<T>`
Resolving a rendition's `broadcast` reference in the rendition-based subscribes and fetches now waits for a sibling nothing announces yet instead of failing as unroutable, so a late sibling plays once it is announced.
Where you skipped such a rendition on that error, bound the call yourself: add a `.timeout` to the future.

### Shutdown drains and `Moq.close()` is async

`session.shutdown()` still returns at once, but it now requests a draining close instead of cancelling.
`await session.closed()` waits for the drain and reports delivery failures; call `session.cancel(code: 0)` to close at once, as `shutdown()` did.
`Moq.close()` returns `Future<void>` and drains before stopping the client, so await it.

### Media track names belong to the target

Audio and video init records no longer carry a track name. Pass that name on the media producer's named target instead; init records describe only the codec and media.

### Broadcast `finish` no longer closes

`BroadcastProducer.finish()` was a deprecated alias for `close()`; it no longer ends the broadcast at once.
Call `BroadcastProducer.close()` where you called `finish()`.

### JSON snapshot `deltaRatio` is `deltas`

`SnapshotConfig.deltaRatio` is replaced by `bool deltas` (default `true`).
Expand Down Expand Up @@ -221,6 +236,8 @@ final stream = media.ContainerProducer(broadcast: broadcast, init: media.Contain

### Timestamps are optional

An omitted timescale now publishes a raw track untimed instead of selecting microseconds. Explicitly set a microsecond timescale to preserve the released default.

`Frame.timestampUs` and `Datagram.timestampUs` are nullable, null on a frame read from an untimed track, and so are their `timestamp` getters.
A raw write to a timed track without a timestamp throws `TimestampMismatch` instead of going out at 0, and whole-frame media writes require one.
`media.CatalogProducer.now()` samples the broadcast clock for local-event stamps.
Expand Down Expand Up @@ -249,7 +266,3 @@ Replace `!video.stalled` with `video.enabled`.
### Routes have one cost (#4661)

`MoqRoute.cold` is removed; drop the `cold` argument.

### `BroadcastProducer.finish()` is removed (#4152)

Call `close()` instead.
69 changes: 5 additions & 64 deletions dart/moq_ffi/MIGRATE.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,13 @@
# Migrating the Dart moq\_ffi package

How to update your code for each breaking change, newest first.

## Unreleased

### Connection state

Generated connection APIs now expose `MoqConnectionState` and `MoqConnectionPhase`, with `state()` and revision-based `state_changed(after)` instead of status, connect-count, and failure polls. Connect returns before its first dial completes. Regenerate bindings from the matching Rust metadata.

### Exact encoder rates and publication options

Encoder configuration is breaking: `MoqVideoEncoderInput.framerate` is now a
`MoqVideoFrameRate { numerator, denominator }` (use `{30, 1}` for 30 fps or
`{30000, 1001}` for NTSC). Zero components and rates above 1,000,000 fps are
refused. `track` moves out of both encoder output records into
`MoqAudioProducerOptions` / `MoqVideoProducerOptions`, alongside `input`,
`output`, and optional `bandwidth`. Producers now take `(catalog, options)`;
codec-only encoders keep their input/output arguments and take no publication
settings. Regenerate bindings and rebuild the native library together.

### Video rendition preference
### Follow the core API migration guide

`MoqVideo` gains `preference`, defaulting to `0`. Regenerate bindings and rebuild with the matching native library. Higher values are preferred; compatibility transcodes use `-1`.

### `announce` takes a `MoqAnnounceRoute`

`MoqBroadcastProducer.announce` takes `MoqAnnounceRoute` (`cost`, `epoch`) instead of a `MoqRoute`: the origin builds the hop chain, so `hops` is no longer an input.
`MoqRoute` is only what announcements report, and the `InvalidRoute` error is gone.
These bindings are generated from moq-ffi. Apply every change in the
[moq-ffi migration guide](../../rs/moq-ffi/MIGRATE.md), using this language's generated casing.
Regenerate the bindings and rebuild the matching native library together; method metadata checksums are not a separate API migration.
The [hand-written wrapper](../moq/MIGRATE.md) has its own migration guide.

### Native futures support operation cancellation

Expand All @@ -37,43 +18,3 @@ Keep the original call to cancel it: futures returned by `then`, `timeout`, and
`cancel()` is idempotent and waits for native cleanup without closing the object that started the call.
The original future throws `UniffiCancelledException` if cancellation wins; a result already ready can still win the race.
Regenerate with the patched generator from `nix develop` and `just dart generate`; native symbols and UniFFI checksums are unchanged.

### A broadcast reference waits for its sibling's announcement (#6254)

Resolving a rendition's `broadcast` reference, through `MoqBroadcastConsumer.resolve` and the rendition-based subscribes and fetches, now waits for a sibling nothing announces yet instead of failing as unroutable, so a late sibling plays once it is announced.
Where you skipped such a rendition on that error, bound the call yourself: add a `.timeout` to the future.
Regenerate the bindings for the changed `resolve` checksum.

### Priorities are signed (#6142)

`MoqTrackInfo.priority`, `MoqSubscription.priority`, `MoqFetchGroupOptions.priority`, and `MoqGroupRequest.priority()` take `-128..=127`, higher first, and 0 is the unset midpoint.
`MoqTrackInfo.priority` defaulted to 127 and now defaults to 0.
To keep a value's moq-lite byte, subtract 128 from it: 255 becomes 127, 128 becomes 0, and 0 becomes -128.
Regenerate the bindings for the changed checksums.

### A client asks only about what it watches (#6165)

On moq-lite, a client session asks its relay for announcements only under paths something watches, through `announced()` or `announcedBroadcast()`, instead of under its whole scope at connect.
`MoqOriginConsumer.requestBroadcast()` watches nothing, so on a client it fails with an unroutable error for a path nothing watches, even when the relay announces it.
Resolve the path with `announcedBroadcast()`, or hold `announced()` over its prefix while requesting.
moq-transport and moq-lite-03/04 sessions are unchanged.
Regenerate the bindings for the changed `request_broadcast` checksum.

### Catalog references carry their epoch

`MoqVideo` and `MoqAudio` gain `epoch`, the publisher instance their `broadcast` reference names.
`MoqBroadcastConsumer.resolve(reference:)` becomes `resolve(rendition:)`, taking the catalog `MoqRendition` and reading its `broadcast` and `epoch` from it.
Resolving, and the rendition-based consumers, fail a rendition whose broadcast is proven to serve another epoch with `MuxMoqException`.

### Finished tracks stay until the broadcast ends (#6157)

A finished track, raw or media, now stays listed, with its catalog rendition and demand, until its broadcast closes or the new `MoqBroadcastProducer.finish()` lets go, instead of unlisting when its producer finishes or is released.
Publish a replacement under a new name, and expect a finished track's demand watch to end with the broadcast.
Regenerate the bindings for the new `MoqBroadcastFinish` and the changed checksums.

### Regenerate the matching bindings

Documentation on `MoqAudioProducer::write` and `finish` changes their UniFFI metadata checksums.
Keep the generated Dart code and native library together.
For a custom native build, regenerate the matching bindings with `just dart generate`.
The method signatures and behavior are unchanged.
Loading
Loading