MOQX (moqx v0.10.0)

Copy Markdown View Source

Elixir Media over QUIC library.

Protocol code is built on top of a small transport adapter boundary so that native QUIC and deterministic support transports can share the same contract.

Summary

Types

Object delivery selected for a published track.

Protocol-neutral reason for publisher-initiated subscription completion.

Option accepted by subscribe/3.

Relative object boundary requested when a subscription begins.

Functions

Accepts one pending inbound publisher subscription.

Registers a retained catalog track under a ready publication.

Registers a track under an active publication.

Cancels one discovery, withdrawing its reported broadcasts before DiscoveryDone.

Gracefully closes the selected protocol connection.

Connects to an endpoint using one explicitly selected protocol implementation.

Discovers matching Lite05 broadcasts using a literal path prefix.

Finishes every active delivery and withdraws a namespace publication.

Finishes one accepted publisher subscription without withdrawing its track or namespace publication.

Advertises a namespace through the selected protocol implementation.

Publishes a complete catalog snapshot on a handle returned by add_catalog/3.

Publishes a complete group containing zero objects on a registered Lite track.

Publishes one object on a registered track.

Rejects one pending inbound publisher subscription.

Rejects one pending track metadata request, without affecting sibling requests or subscription authorization. Lite05 sends only the rejection code on wire; textual reasons stay local. Decided, cancelled and foreign requests return :stale_track_request.

Subscribes to a protocol-neutral track address.

Returns the default native QUIC transport implementation.

Ends an active subscription and sends the selected protocol's unsubscribe message.

Updates an active subscription's draft-neutral filter and delivery parameters.

Withdraws one registered track while keeping its publication and siblings active.

Types

publication_delivery()

@type publication_delivery() :: :subgroup | :datagram

Object delivery selected for a published track.

published_subscription_status()

@type published_subscription_status() ::
  :internal_error
  | :unauthorized
  | :track_ended
  | :subscription_ended
  | :going_away
  | :expired
  | :too_far_behind
  | :malformed_track
  | :update_failed

Protocol-neutral reason for publisher-initiated subscription completion.

published_track_option()

@type published_track_option() ::
  {:retention, :live | :latest | :all}
  | {:delivery, publication_delivery()}
  | {:timescale, pos_integer()}
  | {:publisher_priority, 0..255}
  | {:publisher_max_latency, non_neg_integer()}

Option accepted by add_track/4.

subscription_option()

@type subscription_option() ::
  {:profile, MOQX.Profile.t()}
  | {:max_catalog_bytes, pos_integer()}
  | {:max_catalog_encoded_bytes, pos_integer()}
  | {:start, subscription_start()}
  | {:filter, MOQX.SubscriptionFilter.t()}
  | {:priority, 0..255}
  | {:group_order, :ascending | :descending}
  | {:delivery_timeout, pos_integer()}
  | {:parameters, [MOQX.SubscriptionParameter.t()]}

Option accepted by subscribe/3.

subscription_start()

@type subscription_start() :: :next_object | :next_group

Relative object boundary requested when a subscription begins.

:next_object starts after the publisher's current largest object and is the compatibility default. :next_group waits for the first object in a later group. A selected protocol returns an error when it cannot represent the requested policy.

subscription_update_option()

@type subscription_update_option() ::
  {:start, subscription_start()}
  | {:filter, MOQX.SubscriptionFilter.t()}
  | {:priority, 0..255}
  | {:delivery_timeout, pos_integer()}
  | {:forward, boolean()}
  | {:new_group, non_neg_integer()}
  | {:parameters, [MOQX.SubscriptionParameter.t()]}

Option accepted by update_subscription/3.

withdraw_track_error()

@type withdraw_track_error() ::
  :unknown_published_track
  | :wrong_client_published_track
  | :unsupported_completion_status
  | :invalid_track_completion
  | :timeout
  | {:connection_closed, term()}
  | {:transport_action_failed, term()}

Error returned by withdraw_track/3.

Functions

accept_subscription(client, request, published_track_or_options, options \\ [])

Accepts one pending inbound publisher subscription.

Pass an existing PublishedTrack to attach another subscriber to a registered track; the result contains its PublishedSubscription handle. Pass track options instead to register the requested track reactively and accept its first subscription without sending a separate publisher-initiated PUBLISH; that result contains both the new track and subscription handles.

add_catalog(client, publication, options)

@spec add_catalog(MOQX.Client.t(), MOQX.Publication.t(), keyword()) ::
  {:ok, MOQX.PublishedTrack.t()} | {:error, term()}

Registers a retained catalog track under a ready publication.

Requires an explicit :profile. HANG supports compression: :none (default, catalog.json) and :deflate (catalog.json.z). Register both to serve both forms. Each handle has independent update numbering and retains its latest snapshot for late subscribers. CMSF uses its profile's conventional name.

add_track(client, publication, track, options \\ [])

@spec add_track(MOQX.Client.t(), MOQX.Publication.t(), binary(), [
  published_track_option()
]) ::
  {:ok, MOQX.PublishedTrack.t()} | {:error, term()}

Registers a track under an active publication.

:delivery defaults to :subgroup. The selected protocol rejects a delivery mode it cannot represent. MoQ Lite draft-05 additionally requires a positive :timescale and accepts :publisher_priority and :publisher_max_latency for its immutable TRACK_INFO.

Lite registration commits locally and returns a usable track even if one of its pending metadata replies fails. Each reply is attempted independently: PublicationTrackRequestDone reports :registered only after transport admission, or :reply_failed with its error. Neither outcome proves peer delivery or authorizes a subscription. Reactive accept_subscription/3 uses the same isolated metadata-reply behavior without skipping admission actions.

cancel_discovery(client, discovery)

@spec cancel_discovery(MOQX.Client.t(), MOQX.Discovery.t()) :: :ok | {:error, term()}

Cancels one discovery, withdrawing its reported broadcasts before DiscoveryDone.

close(client, options \\ [])

@spec close(MOQX.Client.t(), keyword()) :: :ok | {:error, term()}

Gracefully closes the selected protocol connection.

connect(endpoint, options)

@spec connect(binary() | URI.t(), keyword()) ::
  {:ok, MOQX.Client.t()} | {:error, term()}

Connects to an endpoint using one explicitly selected protocol implementation.

discover(client, prefix, options \\ [])

@spec discover(MOQX.Client.t(), binary(), keyword()) ::
  {:ok, MOQX.Discovery.t()} | {:error, term()}

Discovers matching Lite05 broadcasts using a literal path prefix.

BroadcastAvailable events enumerate initial matches, followed by DiscoveryReady; additions and withdrawals then continue live. This does not subscribe to track catalogs. max_broadcasts defaults to 1024; exceeding it ends only that discovery. Replacement advertisements emit withdrawal with reason :replaced followed by availability. An unknown withdrawal terminates the discovery with :invalid_announcement, clearing all its broadcasts. Other protocols return :unsupported_operation.

finish_publication(client, publication, options \\ [])

@spec finish_publication(MOQX.Client.t(), MOQX.Publication.t(), keyword()) ::
  :ok | {:error, term()}

Finishes every active delivery and withdraws a namespace publication.

Pending controlled subscription requests are cancelled before established subscriptions and published tracks complete. The selected protocol withdraws the namespace only after those per-request completion boundaries.

finish_subscription(client, published_subscription, options \\ [])

@spec finish_subscription(
  MOQX.Client.t(),
  MOQX.PublishedSubscription.t(),
  status: published_subscription_status(),
  reason: binary()
) :: :ok | {:error, term()}

Finishes one accepted publisher subscription without withdrawing its track or namespace publication.

The selected implementation maps the protocol-neutral :status atom to its native PUBLISH_DONE code. The default is :subscription_ended.

publish(client, namespace, options \\ [])

@spec publish(MOQX.Client.t(), [binary()], keyword()) ::
  {:ok, MOQX.Publication.t()} | {:error, term()}

Advertises a namespace through the selected protocol implementation.

Lite05 supports missing_track_metadata: :reject | :controlled (default :reject). Controlled metadata demand emits PublicationTrackRequested when a peer asks for an absent track under this publication. track_metadata_timeout defaults to 5,000 milliseconds (integer, 0..4_294_967_295); max_pending_track_metadata defaults to 128 (positive integer, per publication). The decision window begins on request receipt. Invalid bounds return :invalid_track_metadata_options before advertising. Other protocols reject these options with :unsupported_operation.

The connection's events_to recipient owns provisioning. Registering the requested track through add_track/4 or reactive accept_subscription/3 resolves all its pending metadata requests; reject_track_request/3 rejects only one. Terminal paths emit PublicationTrackRequestDone once, including :reply_failed with the transport error when a metadata reply fails. Stream cleanup is best-effort and cannot suppress the request's terminal outcome. Unknown publications, default-rejected requests and capacity overflow reset immediately without allocating application handles. Deadline, cancellation, publication finish and connection close invalidate pending handles. Owner exit closes the whole connection and its resources, not merely one publication.

Metadata demand does not change inbound_subscriptions: controlled media subscriptions still need explicit acceptance. No implicit track factory, application retry, or subscription approval is performed.

publish_catalog(client, track, catalog)

@spec publish_catalog(MOQX.Client.t(), MOQX.PublishedTrack.t(), MOQX.Catalog.t()) ::
  :ok | {:error, term()}

Publishes a complete catalog snapshot on a handle returned by add_catalog/3.

Groups increase from zero independently per handle. Every update is one object, immediately finished, and retained for late subscribers. A failed validation does not consume a group number. This is transport admission; CatalogReceived at a receiver proves delivery.

publish_empty_group(client, track, group_id)

@spec publish_empty_group(MOQX.Client.t(), MOQX.PublishedTrack.t(), non_neg_integer()) ::
  :ok | {:error, term()}

Publishes a complete group containing zero objects on a registered Lite track.

Unlike an object with an empty payload, this sends only a group header and FIN. group_id must be in 0..4_611_686_018_427_387_902, leaving room for the exclusive SUBSCRIBE_END bound. A new group must have a greater ID than the previous published group, and an open non-empty group must first be finished with an object whose end_of_group? is true. These publication-wide checks also apply to publish_object/3, even with zero subscribers. Multiple objects within an open group keep consecutive object IDs starting at zero.

An empty group has no timestamp. Subsequent groups may use earlier or later timestamps; MOQX never interprets or rewrites codec epochs. HANG consumers own decoder reset and any cross-group ordering. Receiver events preserve arrival order, not global group order (see MOQX.Event.SubgroupEnded).

Only matching subscriber ranges receive the group; completion counts it in the exclusive END bound. retention: :latest retains this header-only group in place of the previous single-object snapshot, so late subscribers do not receive stale pre-discontinuity media. This does not add an archive or multi-object group cache; other retention modes retain their existing behavior.

Invalid IDs return :invalid_group_id, non-increasing IDs return :invalid_group_sequence, and an open group returns :unfinished_group. Foreign-client handles return :wrong_client_published_track; removed tracks return :unknown_published_track (or :unknown_publication once the entire publication has ended). A replacement track starts a fresh sequence. Other protocol implementations return {:error, :unsupported_operation}. Success means backend admission, not peer delivery or decoder reset.

publish_object(client, track, object)

@spec publish_object(MOQX.Client.t(), MOQX.PublishedTrack.t(), MOQX.Object.t()) ::
  :ok | {:error, term()}

Publishes one object on a registered track.

reject_subscription(client, request, rejection)

@spec reject_subscription(
  MOQX.Client.t(),
  MOQX.PublicationSubscriptionRequest.t(),
  MOQX.SubscriptionRejection.t()
) :: :ok | {:error, term()}

Rejects one pending inbound publisher subscription.

reject_track_request(client, request, rejection)

@spec reject_track_request(
  MOQX.Client.t(),
  MOQX.PublicationTrackRequest.t(),
  MOQX.SubscriptionRejection.t()
) :: :ok | {:error, term()}

Rejects one pending track metadata request, without affecting sibling requests or subscription authorization. Lite05 sends only the rejection code on wire; textual reasons stay local. Decided, cancelled and foreign requests return :stale_track_request.

subscribe(client, track, options \\ [])

@spec subscribe(MOQX.Client.t(), MOQX.TrackRef.t(), [subscription_option()]) ::
  {:ok, MOQX.Subscription.t()} | {:error, term()}

Subscribes to a protocol-neutral track address.

:profile defaults to :none: even catalog-named tracks emit opaque ObjectReceived events. Select :cloudflare_cmsf, :moqtail_cmsf, or :hang explicitly for CatalogReceived snapshots. The byte-limit options default to 1 MiB each. HANG catalog.json.z selects raw DEFLATE; other HANG track names select plain JSON. Malformed updates emit CatalogFailed on this handle and preserve the last valid snapshot; a newer valid group can recover. HANG snapshots replace the whole catalog, report added/removed/changed track refs, and ignore older groups. Duplicate groups and nonzero object IDs are errors. Profile selection is immutable for the subscription lifetime.

The :start option accepts :next_object or :next_group and defaults to :next_object. Protocol implementations map that application policy to their native subscription filter and reject unsupported policies explicitly.

transport()

@spec transport() :: module()

Returns the default native QUIC transport implementation.

unsubscribe(client, subscription)

@spec unsubscribe(MOQX.Client.t(), MOQX.Subscription.t()) :: :ok | {:error, term()}

Ends an active subscription and sends the selected protocol's unsubscribe message.

update_subscription(client, subscription, options)

@spec update_subscription(
  MOQX.Client.t(),
  MOQX.Subscription.t(),
  [subscription_update_option()]
) :: :ok | {:error, term()}

Updates an active subscription's draft-neutral filter and delivery parameters.

withdraw_track(client, track, options \\ [])

@spec withdraw_track(
  MOQX.Client.t(),
  MOQX.PublishedTrack.t(),
  status: published_subscription_status(),
  reason: binary()
) :: :ok | {:error, withdraw_track_error()}

Withdraws one registered track while keeping its publication and siblings active.

The track is unavailable to new subscribers before this call returns. The selected protocol maps :status to its native terminal code. The default status is :track_ended.