diff --git a/cpp/moq/MIGRATE.md b/cpp/moq/MIGRATE.md index 50c37346b2f..6ca4ebcf703 100644 --- a/cpp/moq/MIGRATE.md +++ b/cpp/moq/MIGRATE.md @@ -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(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). diff --git a/dart/moq/MIGRATE.md b/dart/moq/MIGRATE.md index fcae653ce62..7c5ee352a58 100644 --- a/dart/moq/MIGRATE.md +++ b/dart/moq/MIGRATE.md @@ -36,6 +36,21 @@ Custom implementations of generated interfaces must return `UniffiRustFuture` 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` 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`). @@ -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. @@ -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. diff --git a/dart/moq_ffi/MIGRATE.md b/dart/moq_ffi/MIGRATE.md index 3d827bd5232..612358bb729 100644 --- a/dart/moq_ffi/MIGRATE.md +++ b/dart/moq_ffi/MIGRATE.md @@ -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 @@ -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. diff --git a/go/ffi/MIGRATE.md b/go/ffi/MIGRATE.md index a9d2c9beff3..b1665d13984 100644 --- a/go/ffi/MIGRATE.md +++ b/go/ffi/MIGRATE.md @@ -1,69 +1,10 @@ # Migrating the Go moq-ffi module -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 - -`MoqVideo` gains `Preference int32`. Update unkeyed struct literals or use keyed fields, leaving `Preference: 0` for the default ranking. Regenerate bindings 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. - -### 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: pass a `ctx` with a deadline. -Regenerate the bindings for the changed `resolve` checksum. - -### Priorities are signed (#6142) - -`MoqTrackInfo.Priority`, `MoqSubscription.Priority`, `MoqFetchGroupOptions.Priority`, and `MoqGroupRequest.Priority()` are `int8`, 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 *string`, the publisher instance their `Broadcast` reference names. -`MoqBroadcastConsumer.Resolve` takes the catalog `MoqRendition` instead of its `reference`, and reads its `broadcast` and `epoch` from it. -Resolving, and the rendition-based consumers, fail a rendition whose broadcast is proven to serve another epoch with `Mux`. -Regenerate the bindings for the changed records and checksums. - -### 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 +### Follow the core API migration guide -Documentation on `MoqAudioProducer::write` and `finish` changes their UniFFI metadata checksums. -Regenerate the Go sources and bundle the matching static libraries, then rebuild callers. -The method signatures and behavior are unchanged. +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](../wrapper/MIGRATE.md) has its own migration guide. diff --git a/go/wrapper/MIGRATE.md b/go/wrapper/MIGRATE.md index 68bab16c90b..eda0c862f21 100644 --- a/go/wrapper/MIGRATE.md +++ b/go/wrapper/MIGRATE.md @@ -29,6 +29,21 @@ the matching generated bindings and native library. 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: pass a `ctx` with a deadline. +### Shutdown drains + +`Session.Shutdown()` keeps its signature, but it now requests a draining close instead of cancelling. +Call `Session.Closed(ctx)` to wait for the drain and receive delivery failures, or `Session.Cancel(0)` to close at once, as `Shutdown()` did. +`Client.Close()` now waits for the drain and returns its error instead of always `nil`; allow for that wait in cleanup. + +### 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` `json.SnapshotOptions.DeltaRatio *uint32` and `json.DefaultDeltaRatio` are replaced by `Deltas *bool` (nil enables deltas). @@ -227,6 +242,8 @@ A frame timestamp is a `*time.Duration`, `nil` on a frame read from an untimed t ### 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. + A raw write to a timed track without a timestamp fails with `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. Set `Timestamp` on every write to a timed track. @@ -260,10 +277,6 @@ Read `Enabled` where you read `!Stalled`. `VideoDecoderOutput.Format` is gone; set `Surface: true` to keep the decoder's native surface. Call `frame.Pixels(moq.VideoPixelFormatRgba)` instead of reading `frame.Data`, and `Close()` the frame when done. -### `BroadcastProducer.Finish()` is removed (#4152) - -Call `Close()` instead. - ## 0.5 -> 0.7 ### The module path is `moq.dev/moq` diff --git a/js/clock/MIGRATE.md b/js/clock/MIGRATE.md index 7939225d4c8..207f0b2da1e 100644 --- a/js/clock/MIGRATE.md +++ b/js/clock/MIGRATE.md @@ -1,34 +1,8 @@ # Migrating @moq/clock -How to update your code for each breaking change, newest first. - ## Unreleased -### `Manual.install()` returns an `Installed` handle (#6510) - -`install()` returns an `Installed` handle instead of a restore function, matching Rust's `Manual::install`. -Call `installed.close()` where you called `restore()`: - -```ts -// Before -const restore = clock.install(); -restore(); - -// After -const installed = clock.install(); -installed.close(); -``` - -### Manual time steps through timer deadlines - -Rename `await clock.advanceAsync(duration)` to `await clock.advance(duration)`. -The async method now visits every due deadline and drains tasks before continuing, -so timers that re-arm during the advance can fire again before its target. - -Rename synchronous `clock.advance(duration)` calls to `clock.jump(duration)`. -Use `jump` to model a stalled event loop, firing due timers late at the target; -use awaited `advance` for normal time progression. There are no compatibility aliases. +### First release -Drive each `Manual` serially. Until `advance` or `fire` settles, another -`advance` or `fire` rejects and `jump` throws without moving time. Timer callbacks -may arm timers, but must leave driving that clock to their caller. +The released repository contained a private clock example, not this published library. +Use the current clock API directly; intermediate `Manual` method renames do not describe a published upgrade path. diff --git a/js/hang/MIGRATE.md b/js/hang/MIGRATE.md index bce652a82da..1382f9c7f38 100644 --- a/js/hang/MIGRATE.md +++ b/js/hang/MIGRATE.md @@ -4,6 +4,18 @@ How to update your code for each breaking change, newest first. ## Unreleased +### Timeline records describe one track + +`Timeline.Range` is replaced by `Position { group, frame? }`; a record has inclusive `start` and exclusive `end` positions for its own track instead of a `tracks` map of group ranges. +`Record.segment` is `sequence`, and `pts` / `duration` are absent for untimed records. +`Timeline.DEFAULT_NAME` is replaced by `SUFFIX`; name each media track's timeline separately and advertise it in `archive.timelines`. + +### Container reads are a tagged union + +`Container.Consumer.next()` returns a `Container.Read` union instead of an object with optional `frame` and continuity counters. +Switch on `kind`: `frame` carries `frame` and `group`, `gap` carries `timestamp` and `duration`, and `end` carries `group` and `end`. +Reset decoder state on `gap`, and process `end` as the declared media end rather than testing `continuous` or `discontinuity`. + ### CMAF decoding is a separate module Replace `Container.Cmaf` with `import * as Cmaf from "@moq/hang/cmaf"`. @@ -23,10 +35,8 @@ Rename an extension field that was called `version`. ### Archive timelines declare a per-track duration (#5715) -Each `archive.timelines` value is `{ track, duration }` instead of the timeline track's name, and `archive.durationMax` is removed. -`duration` is the nominal record duration in the archive timescale, absent for an untimed track. -Read the track name from the value's `track`, and use the per-track `duration` instead of `durationMax`. -A catalog on the other side of this change parses with no `archive`; see the [upgrade guide](https://github.com/moq-dev/moq/blob/main/doc/setup/upgrade.md). +`archive.track` becomes `archive.timelines`, keyed by each media track name. Each value is `{ track, duration }`, naming that media track's timeline and nominal duration, absent when untimed. +Replace the shared `durationMax` with each track's `duration`. The archive version is 3 rather than 1; update publishers and consumers together. ### Timeline records are optionally timed (#5720) diff --git a/js/publish/MIGRATE.md b/js/publish/MIGRATE.md index 3fc028d89f6..5e94e894c7f 100644 --- a/js/publish/MIGRATE.md +++ b/js/publish/MIGRATE.md @@ -19,15 +19,8 @@ Read it with `get` or `peek` as before; the broadcast alone replaces the produce `Support.Audio.aac` is `"full" | "none"` like `opus`, instead of a boolean. Replace `aac === true` with `aac === "full"`. -### `BroadcastInput` has a `linger` input +### Video tracks advertise refresh (#6048) -Code that builds a complete `BroadcastInput` adds `linger`, such as `new Signal(false)` to keep turning `announce` off unannouncing at once. -`new Broadcast({ ... })` defaults it to `false`, so constructor calls need no change. - -### `Encoder.cut()` is not rate limited, and video tracks advertise refresh (#6048) - -`Encoder.cut()` no longer spaces keyframes 500 ms apart: every call forces one on the next frame, so frequent calls produce all-keyframe video. -Debounce your own calls if you relied on the spacing. Every `Broadcast.video()` track now advertises a refresh ratio of 2, so a producer registered there must open a group with a keyframe whenever `track.demand().wanted` passes the largest group. The bundled `Encoder` already does; a custom producer has to as well. diff --git a/kt/moq-ffi/MIGRATE.md b/kt/moq-ffi/MIGRATE.md index 81fb99c14cd..adf9c765ad6 100644 --- a/kt/moq-ffi/MIGRATE.md +++ b/kt/moq-ffi/MIGRATE.md @@ -1,62 +1,10 @@ # Migrating the Kotlin 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 - -`MoqVideo` gains `preference: Int`, defaulting to `0`. Update positional constructors and regenerate the bindings 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. - -### 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: cancel the coroutine or wrap it in `withTimeout`. -Regenerate the bindings for the changed `resolve` checksum. - -### 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; it defaults to `null`, so only positional construction changes. -`MoqBroadcastConsumer.resolve` takes the rendition's `epoch` after `reference`; pass it alongside, or `null` to resolve whichever instance answers. -Resolving, and the rendition-based consumers, throw `MoqException.Mux` for a rendition whose broadcast is proven to serve another epoch. -Regenerate the bindings for the changed records and checksums. - -### 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 +### Follow the core API migration guide -Documentation on `MoqAudioProducer::write` and `finish` changes their UniFFI metadata checksums. -Regenerate the Kotlin sources and package matching native libraries, then rebuild callers. -The method signatures and behavior are unchanged. +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. diff --git a/kt/moq/MIGRATE.md b/kt/moq/MIGRATE.md index f15d69691be..1f3c1289afb 100644 --- a/kt/moq/MIGRATE.md +++ b/kt/moq/MIGRATE.md @@ -29,6 +29,22 @@ the matching generated bindings and native library. 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: cancel the coroutine or wrap it in `withTimeout`. +### Shutdown drains + +`session.shutdown()` still returns at once, but it now requests a draining close instead of cancelling. +Await the suspending `session.closed()` to wait for the drain and catch delivery failures. +`Moq.close()` now cancels at once; to drain a `Moq`, call `moq.shutdown()`, await `moq.session.closed()`, then close it. +If you adopted the release line's async `drain()`, call `shutdown()` and then await `closed()` instead. + +### 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 `end()`; it no longer ends the broadcast at once. +Call `BroadcastProducer.end()` where you called `finish()`. + ### JSON snapshot `deltaRatio` is `deltas` `SnapshotConfig.deltaRatio: UInt` is replaced by `deltas: Boolean` (default `true`). @@ -226,6 +242,8 @@ val stream = ContainerProducer(broadcast, ContainerInit(ContainerFormat.FMP4, by ### 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.timestamp` and `Datagram.timestamp` are `Duration?`, null on a frame read from an untimed track. 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. `CatalogProducer.now()` samples the broadcast clock for local-event stamps. @@ -261,10 +279,6 @@ Replace `!video.stalled` with `video.enabled`. `VideoDecoderOutput.format` is gone; set `surface = true` to keep the decoder's native surface. Replace `frame.data` with `frame.use { it.pixels(VideoPixelFormat.RGBA) }`. -### `BroadcastProducer.finish()` is removed (#4152) - -Call `end()` to finish the broadcast and keep the handle, or `close()` to release it. - ## 0.4 -> 0.5 ### Durations are `kotlin.time.Duration` diff --git a/py/moq-ffi/MIGRATE.md b/py/moq-ffi/MIGRATE.md index a71e2fe9f9b..677af200662 100644 --- a/py/moq-ffi/MIGRATE.md +++ b/py/moq-ffi/MIGRATE.md @@ -1,62 +1,10 @@ # Migrating the Python 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 - -`MoqVideo` gains `preference`, defaulting to `0`. Prefer keyword construction and rebuild the generated bindings 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. - -### 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: cancel the task or wrap it in `asyncio.wait_for`. -Regenerate the bindings for the changed `resolve` checksum. - -### 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. -`MoqOriginConsumer.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. -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; it defaults to `None`, so only positional construction changes. -`MoqBroadcastConsumer.resolve` takes the catalog `MoqRendition` instead of its `reference`, and reads its `broadcast` and `epoch` from it. -Resolving, and the rendition-based consumers, raise `MoqError.Mux` for a rendition whose broadcast is proven to serve another epoch. -Rebuild or reinstall the complete wheel so its generated Python code and native library match. - -### 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 +### Follow the core API migration guide -Documentation on `MoqAudioProducer::write` and `finish` changes their UniFFI metadata checksums. -Rebuild or reinstall the complete wheel so its generated Python code and native library match. -The method signatures and behavior are unchanged. +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-rs/MIGRATE.md) has its own migration guide. diff --git a/py/moq-rs/MIGRATE.md b/py/moq-rs/MIGRATE.md index cb060da1319..66c1f00e880 100644 --- a/py/moq-rs/MIGRATE.md +++ b/py/moq-rs/MIGRATE.md @@ -29,6 +29,22 @@ the matching generated bindings and native library. 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 the task or wrap it in `asyncio.wait_for`. +### Session shutdown drains + +`Session.shutdown()` still returns at once, but it now requests a draining close instead of cancelling. +`await session.closed()` waits for the drain and raises delivery failures, and a clean `async with` exit now waits for it too. +Call `session.cancel(0)` to close at once, as `shutdown()` did. +If you adopted the release line's async `drain()`, call `session.shutdown()` and then `await session.closed()` instead. + +### 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 `delta_ratio` is `deltas` `SnapshotProducer(delta_ratio=...)` is replaced by `deltas: bool = True`. @@ -223,6 +239,8 @@ A string passed where a list of strings belongs (`tls_roots="ca.pem"`) raises `T ### 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. + A frame read from an untimed track has `timestamp` set to `None`. Raw writes no longer default to zero, so a write to a timed track without a timestamp raises `TimestampMismatch`; whole-frame media writes require one. Pass `timestamp=` on every write to a timed track. @@ -256,10 +274,6 @@ A `VideoDecodedFrame`'s fields are methods: `timestamp_us()`, `width()`, `height `VideoDecoderOutput.format` is gone; set `surface=True` to keep the decoder's native surface. Read `frame.pixels(moq.VideoPixelFormat.RGBA)` instead of `frame.data`. -### `BroadcastProducer.finish()` is removed (#4152) - -Call `broadcast.close()` instead. - ## 0.4 -> 0.5 ### Publish split by kind diff --git a/quest/m1/api-audit/README.md b/quest/m1/api-audit/README.md index f8faa4dab6c..cf2956aad5e 100644 --- a/quest/m1/api-audit/README.md +++ b/quest/m1/api-audit/README.md @@ -66,7 +66,6 @@ Rejected in planning: ## Required -- [MIGRATE matches release](/quest/m1/api-audit/migrate.md) - every break already on main has a correct entry measured against release, and entries for unreleased packages are gone - [Two broadcast resolutions](/quest/m1/api-audit/resolve.md) - Rust, JS, and the bindings resolve a broadcast with `request(path, epoch)` or `follow(path, epoch)`, and every racy two-step caller is gone - [The origin announces](/quest/m1/api-audit/announce-owner.md) - a broadcast producer cannot announce; the origin hands out the announcement, so `Prepared` and standalone broadcasts lose methods that always fail - [Unpublish what nothing uses](/quest/m1/api-audit/unpublish.md) - packages, features, and listeners with no consumer stop shipping or stop being on by default diff --git a/quest/m1/api-audit/migrate.md b/quest/m1/api-audit/migrate.md deleted file mode 100644 index 386725e58b3..00000000000 --- a/quest/m1/api-audit/migrate.md +++ /dev/null @@ -1,59 +0,0 @@ -# [S] MIGRATE matches release - -## Goal - -Every break already on `main` has a `MIGRATE.md` entry under `## Unreleased` -that is correct against `release`, not against an intermediate `main`. -Entries that describe APIs that never shipped are rewritten or deleted, and -packages that were never published carry no migration entries. - -## Plan - -Decided 2026-10-22 (API audit). Known gaps, verify each against -`git show origin/release:`: - -- Bindings: the synchronous shutdown request and awaited `closed()` result are - synchronized by [the shutdown change](https://github.com/moq-dev/moq/pull/6436). - Verify its entries against `release`, including the additive async `drain()` - backport mapping, rather than documenting the intermediate async shutdown. -- Bindings: merge the contradictory `finish` entries ("removed (#4152)" and - "the new `finish` lets go (#6157)") into one that names the final API: - once [stateless broadcasts](/quest/m1/broadcast-lifecycle/stateless.md) - lands, the broadcast `finish()` is gone, so a publisher finishes its tracks - and closes the broadcast. If this lands first, leave the entry for that - quest to rewrite rather than document the linger handle. -- Bindings: `rs/moq-ffi/MIGRATE.md` says a raw track is timed by default, - but `MoqTrackInfo.timescale` defaults to `None` (untimed) while release - defaulted to microseconds; the net change is timed to untimed by default. -- Bindings: `MoqAudioInit.track` and `MoqVideoInit.track` moved to - `MoqMediaTarget::Named`, with no entry. -- `cpp/moq/MIGRATE.md`: about 130 lines for a package never released; empty - it. The generated-binding MIGRATEs (`go/ffi`, `py/moq-ffi`, `kt/moq-ffi`, - `swift/ffi`, `dart/moq_ffi`) list 4 of about 35 breaks plus checksum noise; - point them at `rs/moq-ffi/MIGRATE.md` instead. -- `rs/moq-net`: `finish_at(Position)`, `final_sequence` renamed `end()`, - `finished()` returning `Position`, and the transport error remapping - (`IdleTimeout`, `StreamError::from(Transport)`) have no entries. -- `rs/hang`: the #5715 entries describe a `timelines` shape that never - shipped; #4034 (`Range` to `Position`, `segment` to `sequence`, - `DEFAULT_NAME` to `SUFFIX`) and JS #5288 (`Container.Consumer.next()` - returns the `Read` union) have none. -- `rs/moq-archive`, `rs/moq-hls`: drop entries for unreleased intermediates - (`Writer`, `Info::epoch`, `Config::timebase`); add `Key::Groups`, - `Error::Bounds` to `Span`, `Rendition::segment(tag, n)`, and the segment URL - change to `seg/{reference}.{n}.m4s`. -- `rs/moq-video`: `frame::macos` to `apple`, `vulkan::Channels` to `Format`, - `Image` accessors becoming fields, `Handles::new` removed. -- `rs/moq-transcode`: `Rung::height` became `Rung::size`, meaning the short - side. Rename the field `short_side` in the same PR so the entry names the - final shape; moq.pro's `rs/transcode/src/ladder.rs` reads `.height`. -- `rs/moq-cli`: `--linger` worked on fmp4, mkv, flv, h264, and h265 on - release and now refuses a nonzero value; iroh left the default features - (#5581). -- `js/publish`: the "`Encoder.cut()` is not rate limited" entry describes an - API added after release (#4184); `cut()` is additive. -- `js/clock`: was private on release, so its rename entries become a - first-release note. - -Docs only, apart from the `short_side` rename. Other children write their own -entries. diff --git a/quest/m1/broadcast-lifecycle/stateless.md b/quest/m1/broadcast-lifecycle/stateless.md index c1ef1eaab72..7cec0fc9672 100644 --- a/quest/m1/broadcast-lifecycle/stateless.md +++ b/quest/m1/broadcast-lifecycle/stateless.md @@ -31,7 +31,10 @@ Decided with the maintainer on 2026-10-23; see the epic for why. its `CACHE_WINDOW` use there. - Update `doc/concept` with one paragraph on the model (online = announced, end of content = track FIN, retention = app policy), plus `doc/lib/*`, and - the Impact bullet says how to migrate every affected package. + the Impact bullet says how to migrate every affected package. It + supersedes the `MIGRATE.md` entries on `finish` (#5795, #6157, and the + binding entries measured against release's `finish()` alias for + `close()`), for the cut to collapse. - Coordinate with [publication owner](/quest/m1/api-audit/announce-owner.md), which touches the same handles. diff --git a/rs/hang/MIGRATE.md b/rs/hang/MIGRATE.md index 0348b315056..1cf81edf5c8 100644 --- a/rs/hang/MIGRATE.md +++ b/rs/hang/MIGRATE.md @@ -9,6 +9,12 @@ How to update your code for each breaking change, newest first. The inherent `Catalog::from_str` is replaced by `impl FromStr for Catalog`, with the same `Error`. Call `json.parse::()`, or bring `std::str::FromStr` into scope to keep calling `Catalog::from_str`. +### Timeline records describe one track + +`timeline::Range` is replaced by `Position { group, frame }`. A `Record` has `start` and exclusive `end` positions for its own track instead of a `tracks` map of inclusive group ranges. +`Record::segment` is `sequence`, and `Record::new` takes the start and end positions. `pts` and `duration` are optional for untimed records. +`timeline::DEFAULT_NAME` is replaced by `SUFFIX`; construct a per-track name with `timeline::default_name(track)`. + ### Priorities are signed (#6142) `catalog::Priorities` fields and `container::track_info`'s priority are `i8`, higher first, with 0 unset. @@ -40,9 +46,9 @@ Rename that member, such as to `app_version`. ### Archive timelines declare a per-track duration (#5715) -Each `Archive::timelines` value is a `Timeline` (the timeline `track` plus its nominal `duration` in the archive timescale, `None` when untimed) instead of the timeline track's name, and `Archive::duration_max` is removed. -A catalog on the other side of this change decodes with no archive; see the [upgrade guide](https://github.com/moq-dev/moq/blob/main/doc/setup/upgrade.md). -Insert `Timeline::new(track, duration)` and read the track name from `.track`. +`Archive::track` becomes `Archive::timelines`, keyed by each media track name. Each value is `Timeline { track, duration }`, naming that media track's timeline and nominal duration, `None` when untimed. +Replace `Archive::new(track)` with `Archive::new()` and insert each `Timeline::new(track, duration)`; `duration_max` is removed. +The archive version is 3 rather than 1. Older catalog shapes are not read as this archive, so update publishers and consumers together. ### Timeline records are optionally timed (#5720) diff --git a/rs/moq-archive/MIGRATE.md b/rs/moq-archive/MIGRATE.md index c08212303ca..dfb4799fc65 100644 --- a/rs/moq-archive/MIGRATE.md +++ b/rs/moq-archive/MIGRATE.md @@ -4,27 +4,14 @@ How to update your code for each breaking change, newest first. ## Unreleased -### Recording format version 3 stores a signed priority (#6142) +### Recording format version 3 replaces version 1 -`Info::priority`, `Info::new`, and `Error::Priority` use `i8`, higher first, with 0 unset. -`VERSION` is 3, whose `.info` stores that `i8` as written; version 2 stored the moq-lite byte, `priority + 128`. -Readers still read version 2 recordings, converting the byte, so a stored 127 reads as -1; a decoded version 2 `Info` keeps `version: 2` and refuses to `encode`. -A `Producer` refuses to resume a version 2 recording with `Error::Version(2)`, so start a new prefix. +Release wrote version 1; the current codecs refuse it. Start a new recording prefix and retain an older reader for existing version 1 recordings. +`Info::priority` and `Info::new` take signed `i8` priority, with 0 unset, rather than the moq-lite byte. Subtract 128 from an old byte to preserve its wire priority. +`Info::timescale` is optional, and `Info::new` takes `Some(timescale)` for timed tracks or `None` for untimed tracks. -### `Producer` replaces `Writer` (#6160) +### Objects are keyed by timeline segment -`Writer`, its `Control`, and `writer::{Config, Retention}` are removed; record with `Producer::new(store, broadcast, producer::Config::default()).run().await` instead. -A producer records every track of one broadcast, found with TRACKS rather than a catalog, and needs no timeline: the layout is per-track objects keyed by group, frame, and timestamp, documented in the crate docs. -Run one producer per broadcast under a key prefix you pick, such as `/@`; it stores no epoch, so `Info::epoch` and `Info::with_epoch` are removed. -`writer::Retention` becomes `producer::Dvr`, set with `Config::with_dvr`, and works on untimed tracks too. -`Error::{EpochMismatch, Enrolled, Untimed, Timeline, Closed}` are removed; a failed source listing is `Error::Source`. -The new layout is not readable by `Reader`, which still replays timeline recordings. - -### Untimed tracks record untimed (#5720) - -`Info::new` and `Info::timescale` take and return `Option`, and `Error::TimescaleMismatch` carries `Option` fields. -`segment::Frame::timestamp` is `Option`. -`Object::decode(buf, timed)` and `Store::get_segments(track, segment, timed)` take whether the track is timed. -Mismatched timing is `Error::Timing`. -Untimed recordings use the version 2 format, which an older version 2 reader refuses. -Pass `Some(timescale)` to `Info::new`, and `info.timescale.is_some()` as `timed`. +`Key::Groups`, `Key::groups`, and the `Store` group-range operations are removed. Use `Key::Segments` / `Key::segments` and `Store::get_segments` for timeline record sequences. +`Error::Bounds { smallest, largest }` is `Error::Span`; update exhaustive matches. +`segment::Frame::timestamp` is optional. Pass the track's timing to `Object::decode(buf, timed)` and `Store::get_segments(track, segment, timed)`; a timing mismatch is `Error::Timing`. diff --git a/rs/moq-cli/MIGRATE.md b/rs/moq-cli/MIGRATE.md index f015f1cb53b..23ddb3624b5 100644 --- a/rs/moq-cli/MIGRATE.md +++ b/rs/moq-cli/MIGRATE.md @@ -8,6 +8,10 @@ How to update your invocation and config for each breaking change, newest first. Enable `nvidia` instead; the old names were aliases for it. +### Iroh is opt-in + +The default source-build features no longer include `iroh`. Add `--features iroh` when building a binary that must use Iroh endpoints. + ### `import fmp4` and `import hls` unwrap to Legacy (#6250) Both now publish one `legacy` frame per sample instead of one `cmaf` frame per fragment. diff --git a/rs/moq-ffi/MIGRATE.md b/rs/moq-ffi/MIGRATE.md index 970199b6486..43c4ed58feb 100644 --- a/rs/moq-ffi/MIGRATE.md +++ b/rs/moq-ffi/MIGRATE.md @@ -35,6 +35,22 @@ To re-announce an observed route, copy its `cost` and `epoch`. 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: cancel it or race it against a timeout. +### Session shutdown drains + +`MoqSession::shutdown()` still returns `()` at once, but it now requests a draining close instead of cancelling with code 0. +Await `closed()` to wait for the drain and receive delivery failures, or call `cancel(0)` to close at once, as `shutdown()` did. +If you adopted the release line's async `drain()`, call `shutdown()` and then await `closed()` instead. + +### Media track naming moves out of init + +`MoqAudioInit::track` and `MoqVideoInit::track` are removed. +Pass that name through `MoqMediaTarget::Named { name }` when constructing `MoqMediaTrackProducer::audio` or `video`; init records describe the codec and media only. + +### Broadcast `finish` no longer closes + +`MoqBroadcastProducer::finish()` was a deprecated alias for `close()`; it no longer ends the broadcast at once. +Call `MoqBroadcastProducer::close()` where you called `finish()`. + ### JSON snapshot `delta_ratio` is `deltas` `MoqJsonSnapshotConfig::delta_ratio` is replaced by `deltas: bool` (default `true`). @@ -91,13 +107,6 @@ It defaults to `false` in every generated binding; a Rust struct literal adds `c `MoqAnchor` gains `Time { timestamp_us }`, the group containing that presentation time, so exhaustive matches need an arm for it. `MoqTrackConsumer::update` now returns `Result<(), MoqError>`, failing with `TimestampMismatch` on a time anchor an untimed track can't serve. -### Regenerate bindings after the API documentation update - -The new documentation on `MoqAudioProducer::write` and `finish` changes their -UniFFI metadata checksums, even though their signatures and behavior are unchanged. -Regenerate each language's bindings from the rebuilt library and update them together. -Older generated bindings with active checksum guards reject this library during initialization. - ### JSON consumers return `MoqJsonValue` (#5677) `MoqJsonSnapshotConsumer::next` and `MoqJsonStreamConsumer::next` return `MoqJsonValue { value, timestamp_us }` instead of a string; `timestamp_us` is null when untimed. @@ -255,7 +264,8 @@ The Display text is unchanged. `MoqFrame.timestamp_us` and `MoqDatagram.timestamp_us` are optional, null on a frame read from an untimed track, and default to null. `MoqTrackInfo.timescale` is null on a received untimed track, and a null timescale on publish makes the track untimed. -A raw track you publish is timed by default, so a raw write without a timestamp fails with `TimestampMismatch` instead of going out at 0. +A raw track you publish is now untimed by default: a null `timescale` no longer selects microseconds. +Set `timescale: Some(1_000_000)` to keep the released default, and provide a timestamp on each timed write. Data operations take an optional timestamp in microseconds, and whole-frame media writes require one. `MoqMediaCatalogProducer::now()` samples the broadcast clock for local-event stamps. Wrap raw frame timestamps in `Some`. @@ -284,10 +294,6 @@ An exhaustive match needs one more arm, and a `Quic` check that meant any QUIC s `MoqVideoDecoderOutput.format` is gone; set `surface: true` to keep the decoder's native surface, readable as a `MoqVideoSurface`. Read RGBA with `pixels(MoqVideoPixelFormat::Rgba)` instead of `data`. -### Broadcast `finish` is removed (#4152) - -`MoqBroadcastProducer::finish()` is gone; close the broadcast instead. - ### The `android-logcat` feature is removed (#4608) Android builds always log to logcat; drop the feature. diff --git a/rs/moq-hls/MIGRATE.md b/rs/moq-hls/MIGRATE.md index 6b9ce1f99cd..cb692a6075c 100644 --- a/rs/moq-hls/MIGRATE.md +++ b/rs/moq-hls/MIGRATE.md @@ -28,6 +28,11 @@ match cursor.next().await { } ``` +### Segment requests carry a timeline reference + +`export::Rendition::segment(n)` is `segment(tag, n)`. Use the tag in the playlist segment URI instead of choosing one locally. +Media playlist segment URIs gain the tag before the number, after any generation prefix: `seg/{n}.m4s` becomes `seg/{tag}.{n}.m4s`. Route the tag and number separately to `segment`. + ### HLS import unwraps fMP4 to Legacy (#6250) `import::Import` now publishes one `Legacy` frame per sample instead of one `Cmaf` frame per fragment. @@ -66,13 +71,6 @@ match rendition.media_playlist(query) { } ``` -### Imports always anchor their own timebase (#5967) - -`import::Config::timebase` and `Error::ForeignTimebase` are removed. -Drop the field from `Config`, and any match arm on `ForeignTimebase`. -Each import mints its timebase from the catalog it writes, and its first fragment anchors it. -A placed timebase only made redundant importers share timestamps; each importer now publishes its own epoch instead. - ### A rendition waits for its own timeline (#6009) `export::Broadcaster::rendition` and the `renditions()` cursor's `Event::Added` see a rendition only once the catalog advertises its own timeline, not as soon as its init is known. diff --git a/rs/moq-net/MIGRATE.md b/rs/moq-net/MIGRATE.md index a2d342bdfe6..5217703883f 100644 --- a/rs/moq-net/MIGRATE.md +++ b/rs/moq-net/MIGRATE.md @@ -39,6 +39,17 @@ A `!Send` transport wraps itself in a type that checks the thread on use, such a Replace `poll_largest(&kio::Waiter::noop())` and `largest().await` on a producer with `largest()`. To wait for each newer one, keep a reader and follow its stream: `let mut reader = producer.consume();`, then `reader.largest().await`. +### Transport failures keep their meaning + +Handle `Error::IdleTimeout` separately from `Error::Transport`. A local transport close is session cancellation, not clean end-of-content. +Converting a transport failure to `StreamError` now gives `Unroutable` rather than `Internal`, so retry or re-resolve that route where you classified stream errors. + +### Track ends use positions + +`track::Producer::finish_at` takes a `track::Position` instead of an exclusive group sequence. +Pass `Position::group(n)` where you passed `n`, or a position within the last group to end after a frame. +`Producer::final_sequence()` is `end()` and returns `Option`; `Subscriber::finished()` and the ordered reader return `Position` instead of `u64`. + ### IETF whole-group FETCH answers name the requested group ([#6199](https://github.com/moq-dev/moq/issues/6199)) On moq-transport drafts 14 through 19, a whole-group `FETCH_OK` now names `{g, 0}` instead of `{g+1, 0}`. @@ -115,7 +126,7 @@ A group followed by a frameless one is judged against the next group that has a ### Ending a track aborts its open groups (#6111) Aborting a track (`abort` or `abort_unused`) now aborts its unfinished groups with the same error, so their readers end with it and their writers fail. -Dropping the last `track::Producer` without a finish aborts them with `Error::Dropped`, and `close` with `Error::SessionClosed`. +Dropping the last `track::Producer` without a finish aborts them with `Error::Dropped`, and `close` with `Error::Stream(StreamError::Session(SessionError::Cancel))`. `track::Producer::close`, `track::Producer::received`, and `group::Producer::info` are now public. ### Priorities are signed, with 0 unset (#6142) @@ -270,7 +281,7 @@ A track received over moq-lite-05/06 arrives untimed, since its timestamps may b One received over moq-transport keeps its timestamps unchecked, and a relay serves it to moq-lite untimed. A publisher whose timeline restarts, such as an HLS `EXT-X-DISCONTINUITY` that resets decode times, publishes a new broadcast instead. -### `broadcast::Producer::finish` is back, and lingers (#5795) +### Broadcast `finish` returns a linger handle (#5795) `finish()` ends the broadcast cleanly and returns a `Finish` that keeps its finished tracks servable until their media is stale. Dropping the `Finish` closes the broadcast, so `close()` still ends it now. @@ -454,10 +465,10 @@ let client = Client::new().with_limits(limits); Broadcast identity is the path, whatever the publisher or first hop. A publisher that restarts its group numbering must publish under a new broadcast name. -### Broadcast `finish` and `abort` are removed (#4152) +### Broadcast `abort` is removed (#4152) -Close a broadcast with `close()`; its end carries no cause. -`broadcast::Consumer::is_finished` is gone, and `closed()` on `Consumer` and `Dynamic` returns `()`. +`broadcast::Producer::abort` is removed; use `close()` for an immediate end without a cause. +`broadcast::Consumer::is_finished` is gone, and `closed()` on `Consumer` and `Dynamic` returns `()`; the broadcast end carries no cause. ### No `VarInt` diff --git a/rs/moq-transcode/MIGRATE.md b/rs/moq-transcode/MIGRATE.md index 60ed04c6019..41fab2d4966 100644 --- a/rs/moq-transcode/MIGRATE.md +++ b/rs/moq-transcode/MIGRATE.md @@ -4,6 +4,12 @@ How to update your code for each breaking change, newest first. ## Unreleased +### Ladder resolution names its short side + +`Rung::height` is `short_side`, and `Rung::new` takes the shorter coded dimension, not always the height. +The scalar fields in `ladder::Error::Empty` and `Unordered` are `short_side` and `below_short_side`. +For landscape output the value is unchanged; for portrait output it is the width, and the other dimension follows the source aspect ratio. + ### OpenH264 is opt-in (#5771) The default features drop `openh264`, so a GPU-less host has no H.264 encoder or decoder. diff --git a/rs/moq-transcode/src/catalog.rs b/rs/moq-transcode/src/catalog.rs index 4b7594abc7d..e1a9f2ff4d6 100644 --- a/rs/moq-transcode/src/catalog.rs +++ b/rs/moq-transcode/src/catalog.rs @@ -288,7 +288,7 @@ pub(crate) fn resolve_rungs(ladder: &Ladder, source_name: &str, source: &VideoCo let mut resolved: Vec = Vec::new(); for rung in ladder.rungs() { - let side = u64::from(rung.size); + let side = u64::from(rung.short_side); let source_side = source_width.min(source_height); if side > source_side { continue; @@ -307,8 +307,8 @@ pub(crate) fn resolve_rungs(ladder: &Ladder, source_name: &str, source: &VideoCo continue; } resolved.push(Resolved { - name: format!("video/{}p", rung.size), - short_side: rung.size, + name: format!("video/{}p", rung.short_side), + short_side: rung.short_side, size: moq_video::Size::new(width as u32, height as u32), bitrate: rung.bitrate, framerate, diff --git a/rs/moq-transcode/src/ladder.rs b/rs/moq-transcode/src/ladder.rs index 78efba1d38b..82dc9c39e84 100644 --- a/rs/moq-transcode/src/ladder.rs +++ b/rs/moq-transcode/src/ladder.rs @@ -9,7 +9,7 @@ pub struct Rung { /// Output short side in pixels. Rounded down to even when it enters a [`Ladder`] /// (I420 chroma is 2x2). - pub size: u32, + pub short_side: u32, /// The configured maximum in bits per second: the CBR target advertised in the /// derivative catalog. @@ -17,9 +17,9 @@ pub struct Rung { } impl Rung { - /// A rung at `size` pixels and `bitrate` bits per second. - pub fn new(size: u32, bitrate: moq_net::bandwidth::Rate) -> Self { - Self { size, bitrate } + /// A rung at `short_side` pixels and `bitrate` bits per second. + pub fn new(short_side: u32, bitrate: moq_net::bandwidth::Rate) -> Self { + Self { short_side, bitrate } } } @@ -29,10 +29,10 @@ impl Rung { pub enum Error { /// A rung that encodes nothing: a size that rounds to zero pixels, or no /// bitrate to spend. - #[error("rung {size}p at {bitrate} bps encodes nothing")] + #[error("rung {short_side}p at {bitrate} bps encodes nothing")] Empty { /// The configured size, before rounding to even. - size: u32, + short_side: u32, /// The configured maximum, in bits per second. bitrate: u64, }, @@ -52,14 +52,14 @@ pub enum Error { /// Resolution runs backwards against bitrate: a rung costs more than the one /// below it without being taller, so bitrate and picture disagree on which /// rendition is lower. - #[error("rung {size}p at {bitrate} bps does not rise above {below_size}p at {below_bitrate} bps")] + #[error("rung {short_side}p at {bitrate} bps does not rise above {below_short_side}p at {below_bitrate} bps")] Unordered { /// The more expensive rung's size, in pixels. - size: u32, + short_side: u32, /// The more expensive rung's maximum, in bits per second. bitrate: u64, /// The cheaper rung's size, in pixels. - below_size: u32, + below_short_side: u32, /// The cheaper rung's maximum, in bits per second. below_bitrate: u64, }, @@ -88,32 +88,32 @@ impl Ladder { pub fn new(rungs: impl IntoIterator) -> Result { let mut rungs: Vec = rungs.into_iter().collect(); for rung in &mut rungs { - if rung.size < 2 || rung.bitrate.as_bps() == 0 { + if rung.short_side < 2 || rung.bitrate.as_bps() == 0 { return Err(Error::Empty { - size: rung.size, + short_side: rung.short_side, bitrate: rung.bitrate.as_bps(), }); } // Normalize before checking uniqueness: odd sizes can share a track name. - rung.size &= !1; + rung.short_side &= !1; } - rungs.sort_by_key(|rung| (rung.bitrate, rung.size)); + rungs.sort_by_key(|rung| (rung.bitrate, rung.short_side)); for pair in rungs.windows(2) { let (below, rung) = (pair[0], pair[1]); if below.bitrate == rung.bitrate { return Err(Error::DuplicateBitrate { bitrate: rung.bitrate.as_bps(), - first: below.size, - second: rung.size, + first: below.short_side, + second: rung.short_side, }); } - if rung.size <= below.size { + if rung.short_side <= below.short_side { return Err(Error::Unordered { - size: rung.size, + short_side: rung.short_side, bitrate: rung.bitrate.as_bps(), - below_size: below.size, + below_short_side: below.short_side, below_bitrate: below.bitrate.as_bps(), }); } @@ -148,7 +148,7 @@ mod tests { use super::*; fn sizes(ladder: &Ladder) -> Vec { - ladder.rungs().iter().map(|rung| rung.size).collect() + ladder.rungs().iter().map(|rung| rung.short_side).collect() } /// The operator writes the ladder top-down (or in whatever order), and it @@ -199,9 +199,9 @@ mod tests { assert_eq!( err, Error::Unordered { - size: 720, + short_side: 720, bitrate: 2_500_000, - below_size: 720, + below_short_side: 720, below_bitrate: 1_200_000, } ); @@ -219,9 +219,9 @@ mod tests { assert_eq!( err, Error::Unordered { - size: 360, + short_side: 360, bitrate: 3_000_000, - below_size: 1080, + below_short_side: 1080, below_bitrate: 1_000_000, } ); @@ -232,13 +232,16 @@ mod tests { assert_eq!( Ladder::new([Rung::new(1, moq_net::bandwidth::Rate::from_bps(350_000))]).unwrap_err(), Error::Empty { - size: 1, + short_side: 1, bitrate: 350_000 } ); assert_eq!( Ladder::new([Rung::new(240, moq_net::bandwidth::Rate::from_bps(0))]).unwrap_err(), - Error::Empty { size: 240, bitrate: 0 } + Error::Empty { + short_side: 240, + bitrate: 0 + } ); } diff --git a/rs/moq-video/MIGRATE.md b/rs/moq-video/MIGRATE.md index d33ab7d015f..2ff59bc2932 100644 --- a/rs/moq-video/MIGRATE.md +++ b/rs/moq-video/MIGRATE.md @@ -16,10 +16,14 @@ An `End` now drains the decoder, so the last picture before a pause arrives with On Wayland the main display and any non-`x11:` id now fail with `Error::Unsupported` instead of opening the system picker with a selection shared by every publication in the process. Pass `Source::Portal(portal::Selection::new(portal::Kind::Screen))` to ask the picker, and keep that selection for the publication's reopens. -### `Control::cut` is not rate limited, and capture advertises refresh (#6048) +### Native frame imports use platform and format roles + +`frame::macos` is `frame::apple`. +`frame::vulkan::Channels` is `Format`. `Image` exposes `device`, `memory`, `size`, `allocation_size`, and `format` as fields; replace `device_uuid()` with `device.device_uuid`, `size()` with `size`, `allocation_size()` with `allocation_size`, and `channels()` with `format`. +Construct `Handles { memory, timeline }` directly instead of calling `Handles::new`. + +### Capture advertises refresh (#6048) -`encode::Control::cut` no longer spaces keyframes 500 ms apart: every request forces one on the next frame, so frequent calls produce all-keyframe video. -Debounce your own calls if you relied on the spacing. Capture tracks now advertise `refresh: Some(2)` when every backend the selection may open honors a cut, and the driver forces a keyframe when `Demand::wanted` passes the largest group. ### OpenH264 is opt-in (#5771) @@ -44,19 +48,12 @@ Set `encode::Config::color` to the first frame's `frame.surface.color()`, and re `encode::Producer::new` and `with_track` take a `moq_mux::catalog::Reserved` instead of the `catalog::Producer`, so the caller declares the encoder's GOP as the timeline hint. Pass `catalog.reserve().with_timeline_hint(gop)` instead of the catalog. -### Capture takes one `encode::Capture`, and `Control::new` returns the driver +### Capture takes one `encode::Capture` `publish_capture(broadcast, catalog, capture, encode, clock)` is `publish_capture(broadcast, catalog, encode::Capture)`. `Capture` has no clock: the driver stamps on `catalog.clock()`. -`encode::Control::new` returns the control handle and the driver; dropping the last `Control` cancels an in-flight open or encode. Build it from `encode::Capture::default()`, setting `capture` and `encode`. -### `Control::cut` returns a `Result` - -`cut()` fails with `Error::CutUnsupported` once the driver knows its backend cannot force a keyframe, and the publish carries on at its cadence. -It counts cadence keyframes too, so a request may be served without forcing one. -On `Error::CutUnsupported`, wait for the next cadence keyframe. - ### `Encoded` marks keyframes (#4295) `encode::Encoded::new` takes a third `keyframe: bool`, also readable as `Encoded::keyframe`. diff --git a/swift/MIGRATE.md b/swift/MIGRATE.md index df8d029a81c..8b430f73027 100644 --- a/swift/MIGRATE.md +++ b/swift/MIGRATE.md @@ -29,6 +29,15 @@ the matching generated bindings and native library. Resolving a rendition's `broadcast` reference, through `BroadcastConsumer.resolve(_:epoch:)` 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 the task. +### 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` `Json.SnapshotProducer(deltaRatio:)` and `MoqJsonSnapshotConfig.deltaRatio` are replaced by `deltas: Bool` (default `true`). @@ -191,7 +200,8 @@ The `Client` setters are `Client(...)` arguments, with `tls`, `quic`, `websocket `setReconnect(false)` is `once: true`. A value the native side cannot use throws `MoqError.Config`, including a bad bind address. `Server` takes its config at init too, and a request takes its origins in `accept(publish:consume:)`, where `nil` inherits the server's origin. -`Session.shutdown()` is `async throws` and drains before returning. +`Session.shutdown()` still returns at once, but it now requests a draining close instead of cancelling; `try await session.closed()` waits for the drain and throws delivery failures. +Call `session.cancel(code: 0)` to close at once, as `shutdown()` did, and replace the release line's awaited `drain()` with `shutdown()` followed by `try await session.closed()`. ```swift let client = try Client(tls: ClientTls(insecure: true), consume: origin) @@ -229,6 +239,8 @@ let stream = try Media.ContainerProducer(broadcast: broadcast, initData: Media.C ### 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. + Raw `writeFrame(_:timestampUs:)` takes a `UInt64?` defaulting to `nil`, and a frame read from an untimed track has a `nil` `timestampUs`. A 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. @@ -263,10 +275,6 @@ Read `enabled` where you read `!stalled`. `VideoDecoderOutput.format` is gone; set `surface: true` to keep the decoder's native surface. Call `try frame.pixels(format: .rgba)` instead of reading `frame.data`. -### `BroadcastProducer.finish()` is `close()` (#4152) - -Close the broadcast instead; tracks already subscribed carry on to their own end. - ## 0.4 -> 0.5 ### Publish split by kind diff --git a/swift/ffi/MIGRATE.md b/swift/ffi/MIGRATE.md index 1869c0a2795..51bdd9b006f 100644 --- a/swift/ffi/MIGRATE.md +++ b/swift/ffi/MIGRATE.md @@ -1,62 +1,10 @@ # Migrating the Swift MoqFFI 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 - -`MoqVideo` gains `preference: Int32`, defaulting to `0`. Regenerate the bindings and rebuild against 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. - -### A broadcast reference waits for its sibling's announcement (#6254) - -Resolving a rendition's `broadcast` reference, through `MoqBroadcastConsumer.resolve(reference:epoch:)` 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 the task. -Regenerate the bindings for the changed `resolve` checksum. - -### 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; the memberwise initializer defaults it to `nil`. -`MoqBroadcastConsumer.resolve(reference:)` becomes `resolve(rendition:)`, taking the catalog `MoqRendition` and reading its `broadcast` and `epoch` from it. -Resolving, and the rendition-based consumers, throw `MoqError.Mux` for a rendition whose broadcast is proven to serve another epoch. -Regenerate the Swift sources and rebuild the matching XCFramework, then rebuild callers. - -### 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 +### Follow the core API migration guide -Documentation on `MoqAudioProducer::write` and `finish` changes their UniFFI metadata checksums. -Regenerate the Swift sources and rebuild the matching XCFramework, then rebuild callers. -The method signatures and behavior are unchanged. +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](../MIGRATE.md) has its own migration guide.