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
3 changes: 2 additions & 1 deletion doc/concept/hang.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ In Rust the catalog owns the lifetime: `catalog.json_stream(track, config)` (or
`json_snapshot` / `binary_snapshot` / `binary_stream`) writes the entry and
retracts it when the producer drops. Read the config from `catalog.json.tracks`
or `catalog.binary.tracks`, then pair its name and config with
`moq_mux::catalog::Entry::new` to subscribe. In the browser, read the same map,
`moq_mux::catalog::Entry::new` to subscribe. In C, `moq_publish_json_*` and
`moq_publish_binary_*` do the same, retracting on `_finish`. In the browser, read the same map,
subscribe by name, and hand the track to `@moq/json` or `@moq/binary`.

## Container
Expand Down
2 changes: 1 addition & 1 deletion doc/lib/c/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ and `target/include/moq.h`.
- **Server.** `moq_server_listen` binds before it returns (a bad address or certificate fails there) and hands each incoming session to `on_request` as a request handle. Read `moq_session_request_path` and `_query` to route and authenticate, then `moq_session_request_accept` (a session handle, with origins like `moq_session_connect`) or `moq_session_request_reject` with an HTTP-style code (401 and 403 become the protocol's unauthorized close). An accepted session reports `1` once SETUP completes and never reconnects. `moq_server_addr` reports an ephemeral port and `moq_server_fingerprints` the hashes a client pins for a `tls_generate` certificate. `moq_server_close` stops listening; its terminal callback fires once the sockets are released.
- **Demand.** A watcher on a published track (`moq_publish_track_demand`, `moq_publish_media_demand`, `moq_encode_video_demand`, `moq_encode_audio_demand`) calls `on_demand` with `MOQ_DEMAND_USED` or `MOQ_DEMAND_UNUSED` right away and again on every change, so an encoder on a battery-powered device runs only while someone is watching. The first call is the current state, so a track that went unused before the watcher existed still reports it. `moq_publish_demand_cancel` stops it; the terminal callback still fires. A container has no single demand and is refused. Demand follows the last real subscriber: an origin that served the track drops its source copy on the unused edge and keeps only the finished groups it already cached warm for 30 seconds, so the cache linger does not delay the unused edge.
- **Requests.** `moq_publish_dynamic` serves subscriptions to tracks the broadcast never declared: each arrives as a request handle, read its name with `moq_track_request_name`, then `moq_track_request_accept` (a raw track handle), `moq_track_request_video` / `_audio` (the media handle `moq_publish_video` / `_audio` return), or `moq_track_request_abort` with an application code the subscriber sees. Without a live handler an unknown name is refused. `moq_publish_track_dynamic` does the same for fetches of groups a track no longer has cached, delivered as `moq_group_request_*` (`sequence`, `priority`, `frame_start`); `moq_group_request_accept` starts the producer at `frame_start` so written frames keep their group indices. Register it with `moq_track_request_dynamic` before accepting a track that was itself requested by a fetch, so that pending group survives the transition. Both handlers stop with `moq_publish_dynamic_cancel`.
- **Everything the bindings can do** ([list](/lib/#what-every-binding-can-do)): media publish and consume with the catalog managed for you, raw pixels and PCM with the codec inside (`moq_encode_video`, `moq_encode_audio`, and the `moq_decode_*` mirrors), raw tracks with timestamps and datagrams, JSON snapshot and stream tracks, group fetch, catalog sections, shared video properties, and stalled hints. The three advertising operations are `moq_origin_create_broadcast` (unannounced producer, invisible to everyone), `moq_publish_announce` / `moq_publish_unannounce` (exact-path advertisement), and `moq_origin_dynamic` (a claim over a path prefix and everything beneath it; `""` for everything). A route is a capability, not an inventory. `moq_origin_announced` takes a literal prefix and an optional relative pattern filter; `moq_announce_update.prefix` stays relative to the origin, while `captures` reports what each wildcard matched when `has_captures` is true. Paths with a `.`-prefixed segment below the prefix are [hidden](/concept/moq-lite#hidden-broadcasts); name the dot segment in `prefix` to list them.
- **Everything the bindings can do** ([list](/lib/#what-every-binding-can-do)): media publish and consume with the catalog managed for you, raw pixels and PCM with the codec inside (`moq_encode_video`, `moq_encode_audio`, and the `moq_decode_*` mirrors), raw tracks with timestamps and datagrams, JSON and binary data tracks (snapshot or stream, each advertised in the catalog for as long as it lives), group fetch, catalog sections, shared video properties, and stalled hints. The three advertising operations are `moq_origin_create_broadcast` (unannounced producer, invisible to everyone), `moq_publish_announce` / `moq_publish_unannounce` (exact-path advertisement), and `moq_origin_dynamic` (a claim over a path prefix and everything beneath it; `""` for everything). A route is a capability, not an inventory. `moq_origin_announced` takes a literal prefix and an optional relative pattern filter; `moq_announce_update.prefix` stays relative to the origin, while `captures` reports what each wildcard matched when `has_captures` is true. Paths with a `.`-prefixed segment below the prefix are [hidden](/concept/moq-lite#hidden-broadcasts); name the dot segment in `prefix` to list them.

```c
moq_client_config config;
Expand Down
166 changes: 151 additions & 15 deletions rs/libmoq/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,22 @@ pub struct moq_json_stream_config {
pub compression: bool,
}

/// Options for a binary data track, in either mode.
///
/// The mode is fixed by which constructor is called ([moq_publish_binary_snapshot] or
/// [moq_publish_binary_stream]), so it is not in here.
#[repr(C)]
#[allow(non_camel_case_types)]
pub struct moq_binary_config {
/// DEFLATE-compress each payload, advertised in the catalog entry.
pub compression: bool,

/// The payloads' media type (e.g. `image/jpeg`), or NULL to leave it unstated.
pub mime: *const c_char,
/// Length of `mime` in bytes.
pub mime_len: usize,
}

/// A JSON value delivered by a consumer callback.
#[repr(C)]
#[allow(non_camel_case_types)]
Expand Down Expand Up @@ -2902,10 +2918,12 @@ pub extern "C" fn moq_publish_group_abort(group: u32, error_code: u16) -> i32 {
/// Create a JSON snapshot track (lossy latest-value) on a broadcast.
///
/// Values published via [moq_publish_json_snapshot_update] reach subscribers as a single latest
/// state; a late joiner only sees the newest. Advertise the track in the catalog with
/// [moq_publish_catalog_section] if consumers should discover it.
/// state; a late joiner only sees the newest. The track is advertised in the broadcast's catalog
/// under `json.tracks.<name>` with `mode: snapshot` (and `compression: deflate` when set), and the
/// entry is retired when the track finishes or fails, so consumers discover it with no extra call.
///
/// Returns a non-zero handle to the JSON producer on success, or a negative code on failure.
/// Returns a non-zero handle to the JSON producer on success, or a negative code on failure,
/// including a mux error when the catalog already carries an entry named `name`.
///
/// # Safety
/// - The caller must ensure `name` is a valid pointer to `name_len` bytes and `config` a valid pointer.
Expand All @@ -2920,13 +2938,9 @@ pub unsafe extern "C" fn moq_publish_json_snapshot(
let broadcast = ffi::parse_id(broadcast)?;
let name = unsafe { ffi::parse_str(name, name_len)? };
let config = unsafe { config.as_ref() }.ok_or(Error::InvalidPointer)?;
let mut producer = moq_json::snapshot::Config::default();
producer.delta_ratio = config.delta_ratio;
producer.compression = if config.compression {
moq_json::Compression::Deflate
} else {
moq_json::Compression::None
};
let producer = moq_mux::json::Config::default()
.with_compression(config.compression)
.with_delta_ratio(config.delta_ratio);
State::lock().publish.json_snapshot(broadcast, name, producer)
})
}
Expand Down Expand Up @@ -2962,8 +2976,11 @@ pub extern "C" fn moq_publish_json_snapshot_finish(json: u32) -> i32 {
/// Create a JSON stream track (lossless append-log) on a broadcast.
///
/// Every record appended via [moq_publish_json_stream_append] is preserved and delivered in order.
/// The track is advertised in the broadcast's catalog under `json.tracks.<name>` with
/// `mode: stream`, for as long as the track lives.
///
/// Returns a non-zero handle to the JSON stream producer on success, or a negative code on failure.
/// Returns a non-zero handle to the JSON stream producer on success, or a negative code on failure,
/// including a mux error when the catalog already carries an entry named `name`.
///
/// # Safety
/// - The caller must ensure `name` is a valid pointer to `name_len` bytes and `config` a valid pointer.
Expand All @@ -2978,10 +2995,7 @@ pub unsafe extern "C" fn moq_publish_json_stream(
let broadcast = ffi::parse_id(broadcast)?;
let name = unsafe { ffi::parse_str(name, name_len)? };
let config = unsafe { config.as_ref() }.ok_or(Error::InvalidPointer)?;
let mut producer = moq_json::stream::Config::default();
if config.compression {
producer.compression = moq_json::Compression::Deflate;
}
let producer = moq_mux::json::Config::default().with_compression(config.compression);
State::lock().publish.json_stream(broadcast, name, producer)
})
}
Expand Down Expand Up @@ -3013,6 +3027,128 @@ pub extern "C" fn moq_publish_json_stream_finish(stream: u32) -> i32 {
})
}

/// Parse a [moq_binary_config] into the mux's binary track config.
///
/// # Safety
/// - `config` must be a valid pointer, and its `mime` a valid pointer to `mime_len` bytes when not NULL.
unsafe fn binary_config(config: *const moq_binary_config) -> Result<moq_mux::binary::Config, Error> {
let config = unsafe { config.as_ref() }.ok_or(Error::InvalidPointer)?;
let mut binary = moq_mux::binary::Config::default().with_compression(config.compression);
if let Some(mime) = unsafe { ffi::parse_str_optional(config.mime, config.mime_len)? } {
binary = binary.with_mime(mime);
}
Ok(binary)
}

/// Create a binary snapshot track (lossy latest-value) on a broadcast: each payload supersedes the
/// last, and a late joiner only sees the newest, e.g. the latest thumbnail of a camera.
///
/// The track is advertised in the broadcast's catalog under `binary.tracks.<name>` with
/// `mode: snapshot` (plus `mime` and `compression` when set), and the entry is retired when the
/// track finishes or fails.
///
/// Returns a non-zero handle to the binary producer on success, or a negative code on failure,
/// including a mux error when the catalog already carries an entry named `name`.
///
/// # Safety
/// - The caller must ensure `name` is a valid pointer to `name_len` bytes and `config` a valid pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn moq_publish_binary_snapshot(
broadcast: u32,
name: *const c_char,
name_len: usize,
config: *const moq_binary_config,
) -> i32 {
ffi::enter(move || {
let broadcast = ffi::parse_id(broadcast)?;
let name = unsafe { ffi::parse_str(name, name_len)? };
let config = unsafe { binary_config(config)? };
State::lock().publish.binary_snapshot(broadcast, name, config)
})
}

/// Publish a new payload to a binary snapshot track, superseding the last.
///
/// Returns a zero on success, or a negative code on failure.
///
/// # Safety
/// - The caller must ensure `payload` is a valid pointer to `payload_len` bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn moq_publish_binary_snapshot_update(
binary: u32,
payload: *const u8,
payload_len: usize,
) -> i32 {
ffi::enter(move || {
let binary = ffi::parse_id(binary)?;
let payload = unsafe { ffi::parse_slice(payload, payload_len)? };
State::lock().publish.binary_snapshot_update(binary, payload)
})
}

/// Finish a binary snapshot track and retire its catalog entry. No more payloads can be published.
///
/// Returns a zero on success, or a negative code on failure.
#[unsafe(no_mangle)]
pub extern "C" fn moq_publish_binary_snapshot_finish(binary: u32) -> i32 {
ffi::enter(move || {
let binary = ffi::parse_id(binary)?;
State::lock().publish.binary_snapshot_finish(binary)
})
}

/// Create a binary stream track (lossless append-log) on a broadcast: every payload is preserved
/// and delivered in order.
///
/// The track is advertised in the broadcast's catalog under `binary.tracks.<name>` with
/// `mode: stream` (plus `mime` and `compression` when set), for as long as the track lives.
///
/// Returns a non-zero handle to the binary stream producer on success, or a negative code on
/// failure, including a mux error when the catalog already carries an entry named `name`.
///
/// # Safety
/// - The caller must ensure `name` is a valid pointer to `name_len` bytes and `config` a valid pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn moq_publish_binary_stream(
broadcast: u32,
name: *const c_char,
name_len: usize,
config: *const moq_binary_config,
) -> i32 {
ffi::enter(move || {
let broadcast = ffi::parse_id(broadcast)?;
let name = unsafe { ffi::parse_str(name, name_len)? };
let config = unsafe { binary_config(config)? };
State::lock().publish.binary_stream(broadcast, name, config)
})
}

/// Append one payload to a binary stream track.
///
/// Returns a zero on success, or a negative code on failure.
///
/// # Safety
/// - The caller must ensure `payload` is a valid pointer to `payload_len` bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn moq_publish_binary_stream_append(stream: u32, payload: *const u8, payload_len: usize) -> i32 {
ffi::enter(move || {
let stream = ffi::parse_id(stream)?;
let payload = unsafe { ffi::parse_slice(payload, payload_len)? };
State::lock().publish.binary_stream_append(stream, payload)
})
}

/// Finish a binary stream track and retire its catalog entry. No more payloads can be appended.
///
/// Returns a zero on success, or a negative code on failure.
#[unsafe(no_mangle)]
pub extern "C" fn moq_publish_binary_stream_finish(stream: u32) -> i32 {
ffi::enter(move || {
let stream = ffi::parse_id(stream)?;
State::lock().publish.binary_stream_finish(stream)
})
}

/// Create a catalog consumer for a broadcast.
///
/// `on_catalog` is invoked with a positive catalog ID for each catalog update
Expand Down
Loading
Loading