Skip to content

libmoq: no dynamic track requests in the C API, so a publisher must produce every track up front and keep every encoder running #3678

Description

@pwrwpw

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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    questBeing tracked/planned in a quest. See `quest/`

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions