Problem
libmoq currently provides no way for a C/C++ publisher to learn that a subscriber has requested a track. The C header states this directly:
The C API does not expose dynamic origin handlers. (moq.h, moq_origin_request)
As a result, a publisher cannot use the C API to start and stop track production based on subscriber demand. Producing every track continuously may be acceptable for a desktop capture tool, but on a battery-powered embedded device, running an encoder and its capture pipeline with no viewers wastes power.
Delivery already follows demand: an unsubscribed track does not consume uplink bandwidth for media delivery. Production cannot follow that demand through the C API, because the application receives no notification when a track is requested or no longer needed.
Approach
Expose the existing moq-net request path through the C ABI:
broadcast::Producer::dynamic() → register a dynamic track request handler
Dynamic::poll_requested_track() → invoke an on_request callback with the requested track name
track::Request::accept() / reject() → accept the request as a media track or reject it
track::Request::poll_unused() → invoke an on_unused callback so the publisher can stop encoding when the track is no longer needed
An illustrative API sketch, following the existing handle and callback style:
int32_t moq_publish_on_request(
uint32_t broadcast,
void (*on_request)(void *user_data, uint32_t request,
const char *name, uintptr_t name_len),
void *user_data);
int32_t moq_publish_request_accept(
uint32_t request,
const char *format, uintptr_t format_len,
const uint8_t *init, uintptr_t init_size,
uint32_t *out_track);
int32_t moq_publish_request_reject(
uint32_t request,
int32_t error_code);
int32_t moq_publish_on_unused(
uint32_t track,
void (*on_unused)(void *user_data),
void *user_data);
In this sketch, moq_publish_request_accept returns a status code and writes the accepted track handle to out_track.
The publisher would use on_request to initiate encoder startup, then call moq_publish_request_accept once the codec configuration is ready. It would use the returned track handle to publish media and register on_unused, allowing it to stop the encoder when the track is no longer needed.
The exact signatures and handle lifecycle are open to discussion. Callback registration should also account for a track becoming unused before on_unused is registered, so that the notification is not missed.
Impact
- C API additions only; no wire protocol or public Rust API changes are expected.
- Existing publishers retain their current behavior unless they opt in by registering a dynamic track request handler.
Alternatives
- Polling
moq_session_stats: This exposes only QUIC connection counters (bytes_sent, RTT, etc.), not the per-track subscription counts available in moq-net's stats. Traffic counters also cannot indicate when to start producing a track that has never produced any media.
- Out-of-band signalling through the application's own control channel: This works, but duplicates demand state already managed by the relay and requires deployment-specific integration.
Happy to send a PR if this approach looks reasonable.
(Written by Claude Opus 5)
Problem
libmoqcurrently provides no way for a C/C++ publisher to learn that a subscriber has requested a track. The C header states this directly:As a result, a publisher cannot use the C API to start and stop track production based on subscriber demand. Producing every track continuously may be acceptable for a desktop capture tool, but on a battery-powered embedded device, running an encoder and its capture pipeline with no viewers wastes power.
Delivery already follows demand: an unsubscribed track does not consume uplink bandwidth for media delivery. Production cannot follow that demand through the C API, because the application receives no notification when a track is requested or no longer needed.
Approach
Expose the existing
moq-netrequest path through the C ABI:broadcast::Producer::dynamic()→ register a dynamic track request handlerDynamic::poll_requested_track()→ invoke anon_requestcallback with the requested track nametrack::Request::accept()/reject()→ accept the request as a media track or reject ittrack::Request::poll_unused()→ invoke anon_unusedcallback so the publisher can stop encoding when the track is no longer neededAn illustrative API sketch, following the existing handle and callback style:
In this sketch,
moq_publish_request_acceptreturns a status code and writes the accepted track handle toout_track.The publisher would use
on_requestto initiate encoder startup, then callmoq_publish_request_acceptonce the codec configuration is ready. It would use the returned track handle to publish media and registeron_unused, allowing it to stop the encoder when the track is no longer needed.The exact signatures and handle lifecycle are open to discussion. Callback registration should also account for a track becoming unused before
on_unusedis registered, so that the notification is not missed.Impact
Alternatives
moq_session_stats: This exposes only QUIC connection counters (bytes_sent, RTT, etc.), not the per-track subscription counts available inmoq-net's stats. Traffic counters also cannot indicate when to start producing a track that has never produced any media.Happy to send a PR if this approach looks reasonable.
(Written by Claude Opus 5)