caprid_relay
From the caprid-relay package: import caprid_relay
Relay Python SDK.
Owns the Relay product API and authentication convention, built on moq_core. The default
transport is native QUIC through aioquic (caprid_relay.quic), which reaches a relay natively with no
browser; create_transport swaps in another implementation of the same seam.
The public names are the rows of spec/vocabulary.md, pinned by tests/test_exports.py. From
the core this package re-exports exactly what @caprid/relay re-exports, plus Closed; every
other protocol type (location filters, fetch, track status, the constants) is imported from
moq_core, a declared dependency.
Functions
Section titled “Functions”connect_relay_publisher
Section titled “connect_relay_publisher”async def connect_relay_publisher( url: str | None = None, *, namespace: Sequence[str], get_token: TokenProvider | None = None, cancellation: CancellationToken | None = None, connect_timeout: float = DEFAULT_CONNECT_TIMEOUT, create_transport: RelayTransportFactory | None = None,) -> RelayPublisherOpen a dedicated Relay publisher connection.
Subscribers use connect_relay_subscriber; publishing does not share their transport. One
publisher connection serves one namespace and any number of tracks. A relay must solicit the
namespace and subscribe upstream before data can flow. With no url, the connection dials
DEFAULT_ENDPOINT rooted at the token’s root claim, which get_token must then supply.
connect_relay_subscriber
Section titled “connect_relay_subscriber”async def connect_relay_subscriber( url: str | None = None, *, get_token: TokenProvider | None = None, cancellation: CancellationToken | None = None, connect_timeout: float = DEFAULT_CONNECT_TIMEOUT, request_timeout: float = DEFAULT_REQUEST_TIMEOUT, create_transport: RelayTransportFactory | None = None,) -> RelaySubscriberOpen a Relay connection for subscribing to tracks.
One deadline (connect_timeout) covers token resolution and both handshakes. cancellation
controls the connection’s entire lifetime. An explicit jwt query parameter on url takes
precedence over get_token, matching the Relay authentication convention; an empty one is a
ValueError. With no url, the connection dials DEFAULT_ENDPOINT rooted at the token’s
root claim, which get_token must then supply. Publishing uses a separate connection; see
connect_relay_publisher.
connect_supervised_publisher
Section titled “connect_supervised_publisher”async def connect_supervised_publisher( url: str | None = None, *, namespace: Sequence[str], get_token: TokenProvider | None = None, cancellation: CancellationToken | None = None, connect_timeout: float = DEFAULT_CONNECT_TIMEOUT, create_transport: RelayTransportFactory | None = None, reconnect: SupervisionBackoff | None = None, rotate_before: float | None = None, on_generation: Callable[[PublisherGeneration], None] | None = None, on_status: Callable[[SupervisedStatus, BaseException | None], None] | None = None,) -> SupervisedPublisherOpen a publisher that reconnects: each connection serves namespace plus a fresh generation
field, and the writers SupervisedPublisher.register_track returns outlive the connections.
Returns once the first generation is up. A first attempt that fails on its arguments, or because
the relay refused the credentials (UNAUTHORIZED), raises that failure; any other failure is
retried with backoff (reconnect, from 0.5 s doubling up to 10 s), bounded only by
cancellation, whose reason is raised. The token must grant namespace, the root, since every
generation publishes one field below it. on_generation runs once each generation is up and
every track is bound to it; on_status on every status change, with what ended the previous
connection or failed the last attempt. rotate_before (seconds, default 60) is how long before
the connection’s credential expires (RelayPublisher.expires_at) the next generation is
brought up to replace it, make-before-break; never earlier than halfway through the time left.
With no url, every generation dials the endpoint the first one rooted at its token’s root. See
spec/behavior.md, “Supervised connections”; connect_relay_publisher never reconnects.
connect_supervised_subscriber
Section titled “connect_supervised_subscriber”async def connect_supervised_subscriber( url: str | None = None, *, get_token: TokenProvider | None = None, cancellation: CancellationToken | None = None, connect_timeout: float = DEFAULT_CONNECT_TIMEOUT, request_timeout: float = DEFAULT_REQUEST_TIMEOUT, create_transport: RelayTransportFactory | None = None, reconnect: SupervisionBackoff | None = None, rotate_before: float | None = None, on_status: Callable[[SupervisedStatus, BaseException | None], None] | None = None,) -> SupervisedSubscriberOpen a subscriber that reconnects and keeps every wanted track subscribed across connections, publisher restarts and generations.
Returns once the first connection is up. A first attempt that fails on its arguments, or because
the relay refused the credentials (UNAUTHORIZED), raises that failure; any other failure is
retried with backoff (reconnect, from 0.5 s doubling up to 10 s), bounded only by
cancellation, whose reason is raised. on_status runs on every status change, with what
ended the previous connection or failed the last attempt. rotate_before (seconds, default 60)
is how long before the connection’s credential expires (RelaySubscriber.expires_at) a
replacement connection is opened, make-before-break; never earlier than halfway through the time
left. With no url, every connection dials the endpoint the first one rooted at its token’s
root. See spec/behavior.md, “Supervised connections”; connect_relay_subscriber never
reconnects or resubscribes.
read_json
Section titled “read_json”def read_json(obj: MoqtObject) -> AnyParse one UTF-8 JSON payload without schema validation; invalid input raises ValueError.
read_text
Section titled “read_text”def read_text(obj: MoqtObject) -> strDecode one complete object as UTF-8; malformed bytes raise UnicodeDecodeError.
Classes
Section titled “Classes”CancellationToken
Section titled “CancellationToken”class CancellationTokenCancel every connection that was handed this token, whether still connecting or established.
Attributes
cancelled:bool(property) — Whethercancelhas been called.reason:BaseException | None(property) — The exception recorded bycancel;Noneuntil then.
CancellationToken.cancel
Section titled “CancellationToken.cancel”def cancel(self, reason: BaseException | None = None) -> NoneCancel once. Later calls are ignored. Registered callbacks run synchronously, in order.
CancellationToken.throw_if_cancelled
Section titled “CancellationToken.throw_if_cancelled”def throw_if_cancelled(self) -> NoneRaise the recorded reason if the token has been cancelled; return otherwise.
CancellationToken.add_callback
Section titled “CancellationToken.add_callback”def add_callback(self, callback: Callable[[BaseException], None]) -> NoneRun callback with the reason when the token is cancelled. No-op once already cancelled.
CancellationToken.remove_callback
Section titled “CancellationToken.remove_callback”def remove_callback(self, callback: Callable[[BaseException], None]) -> NoneUnregister a callback added with add_callback. No-op if it is not registered.
Closed
Section titled “Closed”class ClosedHow a session or connection ended. error is None for a clean close.
Attributes
error:BaseException | None
ConnectingTransport
Section titled “ConnectingTransport”class ConnectingTransportWhat a transport factory hands back: the transport, an awaitable for its handshake, and its framing.
Attributes
transport:MoqtTransportready:Awaitable[None]framing:Framing—"native-quic"makes the connection send the endpoint’s PATH and AUTHORITY in SETUP (TA-1, TA-6).
Goaway
Section titled “Goaway”class GoawayGOAWAY (§9.2): the peer intends to close the session soon and asks for no new requests.
Attributes
kind:Literal['goaway']new_session_uri:str— Where to reconnect; empty means the current URI. A client always sends it empty.timeout:int— Milliseconds until the peer closes the session;0when it gave none.
GoawayEvent
Section titled “GoawayEvent”class GoawayEventRequest relocation details; no automatic retry.
Attributes
value:Goawaytype:Literal['goaway']
KeyValuePair
Section titled “KeyValuePair”class KeyValuePairA generic MOQT Key-Value-Pair.
Even types carry a varint value, odd types a byte field. Used for Setup Options and Track/Object
Properties (message parameters use per-type encodings instead; see MessageParameters).
Attributes
type:intvalue:int | bytes
MessageParameters
Section titled “MessageParameters”class MessageParametersDecoded message parameters.
Unlike Setup Options, message parameters use per-type encodings (uint8 / varint / length-prefixed), so the codec is registry-driven. Per §9.20 an unknown parameter type is a PROTOCOL_VIOLATION (parameters cannot be skipped; the block is bounded by count, not length).
fill_parameters (§9.20.15) asks the publisher to deliver a fill range on a fetch stream
alongside the subscription; its presence alone requests the fill. It is a nested parameter scope:
only the fill-scoped subset (Table 7) is legal inside, and omitted entries inherit the
subscription’s own values.
Attributes
-
object_delivery_timeout:int | None -
subgroup_delivery_timeout:int | None -
rendezvous_timeout:int | None -
fill_timeout:int | None -
authorization_tokens:list[bytes] | None— Serialized Token structures (§9.20.2). May repeat. -
expires:int | None -
largest_object:Location | None -
forward:int | None—0or1. -
subscriber_priority:int | None -
location_filter:LocationFilter | None -
group_order:GroupOrder | None -
fill_parameters:MessageParameters | None -
new_group_request:int | None -
range_filters:list[RangeFilter] | None -
track_namespace_prefix:TrackNamespace | None -
include_properties:int | None— INCLUDE_PROPERTIES (§9.20.21):0asks the responder to send empty Track Properties. Default1. -
max_age:int | None— MAX_AGE (0x915C6, the MoQ Timestamp extension; not a draft-22 parameter).The subscriber’s max age in milliseconds, 0 to 2^53-1. A publisher that honors it skips, for this subscription only, a group once a newer group supersedes it and either that successor arrived at the publisher’s hop at least this long ago or the group’s media time trails the live edge by at least this much; the newest group never expires. moq-dev main turns it into the subscription’s
max_delay. A session sends it on SUBSCRIBE and REQUEST_UPDATE only when the peer’s SETUP declaredSETUP_OPTION.MAX_AGE, and never on FETCH, TRACK_STATUS or SUBSCRIBE_NAMESPACE; seeMoqtSession.max_age_supported.
MoqtObject
Section titled “MoqtObject”class MoqtObjectAn object delivered on a subscription’s subgroup stream.
Attributes
group_id:intobject_id:intsubgroup_id:intpriority:int | None— Publisher priority;Noneinherits the subscription’s priority.status:ObjectStatusproperties:list[KeyValuePair]payload:bytes
NamespaceEvent
Section titled “NamespaceEvent”class NamespaceEventOne change in which namespaces under the prefix are announced.
Attributes
type:Literal['announced', 'unannounced']—announced: a publisher announced the namespace, or it was already announced when the request was accepted.unannounced: its announce was withdrawn. Each namespace alternates between the two; the relay’s repeats are not passed on.namespace:tuple[str, ...]— The full namespace: the prefix’s fields followed by the rest, without splitting or joining.
NamespaceEvents
Section titled “NamespaceEvents”class NamespaceEvents(BoundedEvents[NamespaceEvent])Single-consumer iterator owning one namespace request, never the shared connection.
Create one with caprid_relay.RelaySubscriber.namespace_events and use async with around
async for. At most capacity events are retained; overflow discards the queue, cancels the
request and raises SubscriptionOverflowError on reads. A relay rejection raises
SubscriptionRequestError, the connection ending SubscriptionConnectionClosedError;
a relay that finishes the request, close(), or break ends iteration cleanly.
ObjectEvent
Section titled “ObjectEvent”class ObjectEventAn object, with its payload and protocol metadata.
Attributes
value:MoqtObjecttype:Literal['object']
PublishDone
Section titled “PublishDone”class PublishDonePUBLISH_DONE (§9.9): the publisher finished a subscription.
status_code is one of the PUBLISH_DONE status codes; stream_count is how many data streams
it opened for the subscription, so a subscriber can tell when all of them have arrived.
Attributes
kind:Literal['publish-done']status_code:intstream_count:int | None—Nonewhen the publisher could not count its streams (the 2^64-1 sentinel, §9.9).reason:str
PublisherDoneEvent
Section titled “PublisherDoneEvent”class PublisherDoneEventPublisher completion; trailing objects may still arrive.
Attributes
value:PublishDonetype:Literal['publisher_done']
PublisherGeneration
Section titled “PublisherGeneration”class PublisherGenerationOne publisher generation: a connection and the namespace it serves.
Attributes
generation:str— The generation field: start time in fixed-width base 36, then four random characters, so it sorts by age.namespace:tuple[str, ...]— The namespace this generation serves: the root followed bygeneration.
RelayAwaitable
Section titled “RelayAwaitable”class RelayAwaitable(Generic[_T])A fact the SDK settles once: a subscription’s subscribed or ended, a publisher’s
announced, or a connection’s closed.
Await it as often as needed. Each waiter is isolated: a task cancelled while awaiting, or an
asyncio.wait_for that times out, never touches the underlying result, where handing out the
future itself would let one impatient waiter destroy the signal for every later one. done(),
result(), exception() and add_done_callback() read the result directly and allocate
nothing, so polling is free. It is not an asyncio.Future: it cannot be cancelled or
set. Wrap it with asyncio.ensure_future for an API that needs a future.
RelayAwaitable.done
Section titled “RelayAwaitable.done”def done(self) -> boolWhether the result is in.
RelayAwaitable.result
Section titled “RelayAwaitable.result”def result(self) -> _TThe result; raises what awaiting would, or asyncio.InvalidStateError while pending.
RelayAwaitable.exception
Section titled “RelayAwaitable.exception”def exception(self) -> BaseException | NoneThe exception awaiting would raise, None for a plain result.
RelayAwaitable.add_done_callback
Section titled “RelayAwaitable.add_done_callback”def add_done_callback( self, callback: Callable[[asyncio.Future[_T]], object], *, context: contextvars.Context | None = None,) -> NoneCall callback with the settled future once the result is in.
RelayAwaitable.remove_done_callback
Section titled “RelayAwaitable.remove_done_callback”def remove_done_callback(self, callback: Callable[[asyncio.Future[_T]], object]) -> intForget a callback; returns how many registrations were removed.
RelayCredential
Section titled “RelayCredential”class RelayCredentialA token together with when the relay stops honoring it.
moq-relay ends a session at its grant’s deadline, which is the token’s expiry, and a live session cannot take a new token; supervised connections replace theirs before then.
Attributes
-
token:str— The token, carried in the endpoint’sjwtquery parameter. An empty one sends no token. -
expires_at:datetime— When the relay ends a session the token admitted: the JWT’sexp, or the grant’s deadline if earlier.Must be timezone-aware.
RelayGroupWriter
Section titled “RelayGroupWriter”class RelayGroupWriterOne open multi-object group on a track. Create one with RelayTrackWriter.open_group.
Objects are appended in order with increasing object IDs starting at 0, so the first object is
the group’s random-access point in LOC/MSF terms. close() FINs the stream, which is how a
complete group is announced; abort() resets it unless it already closed cleanly. A group that
fails part-way is reset, and the track stays open for the next group.
Attributes
group_idclosed:bool(property) — True once the group was FINed or reset; no further objects can be written.object_count:int(property) — Objects accepted by the transport so far.
RelayGroupWriter.write_text
Section titled “RelayGroupWriter.write_text”async def write_text( self, text: str, *, properties: Sequence[KeyValuePair] | None = None, status: ObjectStatus = 'normal',) -> NoneEncode UTF-8 text and write one object with the same semantics as write.
RelayGroupWriter.write_json
Section titled “RelayGroupWriter.write_json”async def write_json( self, value: object, *, properties: Sequence[KeyValuePair] | None = None, status: ObjectStatus = 'normal',) -> NoneSerialize JSON and write one object; serialization errors leave the writer usable.
RelayGroupWriter.write
Section titled “RelayGroupWriter.write”async def write( self, payload: bytes, *, properties: Sequence[KeyValuePair] | None = None, status: ObjectStatus = 'normal',) -> NoneAppend one object. Returns when the transport accepts it. Bytes are copied.
Properties are encoded as supplied; the relay may transform them before delivery.
Each call has its own write_timeout deadline; expiry resets the group and leaves the
track open. status is only meaningful for an empty payload (an end-of-group or end-of-track marker).
On the group’s first object, a 0x02 or 0x06 property sets the group’s delivery timeout
instead of the track’s; on a later object it is only sent. An object older than the
group’s OBJECT_DELIVERY_TIMEOUT when it reaches the transport raises DELIVERY_TIMEOUT
and resets the group.
RelayGroupWriter.close
Section titled “RelayGroupWriter.close”async def close(self) -> NoneFIN within write_timeout; expiry resets the group. Idempotent once closed cleanly.
RelayGroupWriter.abort
Section titled “RelayGroupWriter.abort”def abort(self, reason: BaseException | None = None) -> NoneReset the group’s stream; objects already sent may be incomplete at the peer.
After a clean close() this has no effect: the FIN already ended the stream, and a reset
after complete delivery is ignorable (draft-ietf-moq-transport-22 section 11.3.2).
RelayPublisher
Section titled “RelayPublisher”class RelayPublisherA connection that can only publish, serving one namespace. Create one with connect_relay_publisher.
Subscribing uses a separate connection; see connect_relay_subscriber.
Attributes
-
expires_at:datetime | None(property) — When the relay will end this connection for its credential’s expiry, as an aware UTC datetime.The
RelayCredential’sexpires_at, or the JWT’sexpclaim, read without verifying it;Nonewhen neither is known. The relay closes the session then withUNAUTHORIZEDin phaseconnection; open a replacement before it. Supervised connections do. -
namespace:list[str](property) — The namespace every track on this connection is published under. -
announced:RelayAwaitable[None](property) — Settles once the namespace announce has reached the wire on a solicitation that covers it.That is no earlier than the first
register_track, and it is the strongest fact the publisher has: the relay acknowledges an announce with nothing and routes subscribes for the namespace about a round trip later, so a subscribe that races it is rejected (request error 16unroutableon moq-relay 0.15.8 and later; seespec/behavior.md). It stays pending while no solicitation covers the namespace, where the first write reportsNAMESPACE_NOT_SOLICITED, and the connection’s closure terminates it if that comes first: it raises the closure’s error, or aCLOSEDRelayErrorafter a clean shutdown. Every waiter is isolated, as forclosed. -
closed:RelayAwaitable[Closed](property) — Resolves when the connection shuts down. Errors are returned as data; this never raises.Every waiter is isolated: a timed-out
asyncio.wait_foror a cancelled task leaves the result intact for every other waiter.
RelayPublisher.register_track
Section titled “RelayPublisher.register_track”def register_track( self, track: str, *, track_properties: Sequence[KeyValuePair] | None = None, write_timeout: float = DEFAULT_WRITE_TIMEOUT,) -> RelayTrackWriterRegister a track this publisher will serve and return its writer.
Nothing is sent: the relay learns of the track when a subscriber asks for it, and bytes move
when the writer’s write() or open_group() runs, after waiting for that relay demand.
No capture starts and no media catalog is published. Track names are unique per publisher
connection, including after their writer closes.
track_properties is encoded as supplied; the SDK adds no default timescale. It rides in
every SUBSCRIBE_OK (and TRACK_STATUS_OK), so it must encode to at most 65525 bytes, hold at most 64 pairs (the
relay’s limit) and keep 0x0E at most 255 and 0x22 at 1 or 2 (draft-22); otherwise the relay
would refuse every subscriber, so register_track raises ValueError instead.
The relay may add, drop, or rewrite properties before delivery to subscribers; see README.
Properties 0x02 (OBJECT_DELIVERY_TIMEOUT) and 0x06 (SUBGROUP_DELIVERY_TIMEOUT), in
milliseconds, are also the publisher’s delivery timeouts (draft-ietf-moq-transport-22
section 5.2), which the writer enforces; 0 means none, and values above 2147483647 are clamped.
write_timeout (seconds) bounds relay demand and the transport write together.
RelayPublisher.close
Section titled “RelayPublisher.close”async def close(self) -> NoneEnd all tracks, retract the namespace, and wait for transport shutdown. Idempotent.
Before the session ends it waits, for up to the largest write_timeout among the tracks,
for the relay to acknowledge the groups that completed and each track’s end; any still
unacknowledged after that may be lost, and the close still succeeds. Cancelling the caller
does not stop the shutdown.
RelayPublisher.aenter
Section titled “RelayPublisher.aenter”async def __aenter__(self) -> SelfRelaySubscriber
Section titled “RelaySubscriber”class RelaySubscriberA connection that can only subscribe. Create one with connect_relay_subscriber.
Publishing uses a separate connection and token; see connect_relay_publisher.
Attributes
-
closed:RelayAwaitable[Closed](property) — Resolves once for local shutdown, peer closure, or failure. It never raises.Every waiter is isolated: a timed-out
asyncio.wait_foror a cancelled task leaves the result intact for every other waiter. -
expires_at:datetime | None(property) — When the relay will end this connection for its credential’s expiry, as an aware UTC datetime.The
RelayCredential’sexpires_at, or the JWT’sexpclaim, read without verifying it;Nonewhen neither is known. The relay closes the session then withUNAUTHORIZEDin phaseconnection; open a replacement before it. Supervised connections do. -
max_age_supported:bool(property) — Whether the relay’s SETUP declared subscriber max age (the MoQ Timestamp extension’s MAX_AGE, 0x915C4).When it did, a
parameters.max_ageonsubscribe,subscribe_switchable,subscribe_events,update()ortrack_source.subscribereaches it. WhileFalsethe SDK leavesmax_ageoff every request, as the extension requires. moq-dev main declares it; moq-relay 0.17.x does not. Fixed for the connection’s life. -
track_source:MoqTrackSource(property) — This connection seen through the genericMoqTrackSourcecontract, for integrations such as players.subscribe,fetch, andtrack_statusin protocol terms, plusclosed. It lists no tracks and cannot close the connection; the application that opened the connection owns shutdown.
RelaySubscriber.subscribe
Section titled “RelaySubscriber.subscribe”def subscribe( self, namespace: Sequence[str], track: str, handlers: RelaySubscriptionHandlers | None = None, *, parameters: MessageParameters | None = None,) -> RelaySubscriptionAsk the relay for a track’s objects, in any payload format, without discovering or loading a catalog.
Returns at once with a cancellable handle whose subscribed settles when the relay answers
and whose ended settles once the subscription can no longer deliver;
handlers.on_subscribed and handlers.on_error report the same answer as callbacks.
namespace fields are sent verbatim, without splitting or joining on slashes.
parameters carries protocol subscription controls such as the location filter and priority.
RelaySubscriber.subscribe_switchable
Section titled “RelaySubscriber.subscribe_switchable”def subscribe_switchable( self, namespace: Sequence[str], track: str, handlers: SwitchableHandlers | None = None, *, parameters: MessageParameters | None = None,) -> SwitchableSubscriptionSubscribe to a track through a handle that can move to another track in the same namespace make-before-break.
SwitchableSubscription.switch_to opens the new subscription while this one keeps
delivering, and cuts over once the new one’s object passes until. Use it to change layers
(simulcast, ABR), or to change a subscription’s filter or priority, which update() cannot
do against moq-relay. Handlers see only the active subscription, with its track name.
RelaySubscriber.subscribe_events
Section titled “RelaySubscriber.subscribe_events”def subscribe_events( self, namespace: Sequence[str], track: str, *, parameters: MessageParameters | None = None, capacity: int = 64, cancellation: CancellationToken | None = None,) -> SubscriptionEventsCreate a bounded event iterator. Use async with around async for.
Owns only this request. publisher_done permits trailing objects; it is not EOF.
RelaySubscriber.namespace_events
Section titled “RelaySubscriber.namespace_events”def namespace_events( self, prefix: Sequence[str], *, capacity: int = 64, cancellation: CancellationToken | None = None,) -> NamespaceEventsDiscover which namespaces under prefix are announced, as a bounded event iterator.
Same ownership rules as subscribe_events: the iterator owns one SUBSCRIBE_NAMESPACE
request, and close(), leaving async with or cancellation cancels it. It first yields
announced for each namespace already announced, then one event per announce and
withdrawal; the relay sends no marker between the two. A relay rejection ends it with
SubscriptionRequestError, the connection ending with
SubscriptionConnectionClosedError, a full queue with
SubscriptionOverflowError; a relay that finishes the request ends it cleanly, after
the queued events and an unannounced for each namespace still announced. See
spec/behavior.md, “Discovery”, for the timing. prefix follows the namespace rules; one that
equals, contains or falls under a prefix this connection is already discovering raises a
RelayError with code PREFIX_OVERLAP before anything is sent.
RelaySubscriber.close
Section titled “RelaySubscriber.close”def close(self) -> NoneClose this connection and all requests on it. Idempotent.
RelaySubscriber.aenter
Section titled “RelaySubscriber.aenter”async def __aenter__(self) -> SelfRelaySubscription
Section titled “RelaySubscription”class RelaySubscription(Subscription)The handle caprid_relay.RelaySubscriber.subscribe returns.
The core handle (request_id, update(), cancel()) plus two awaitables. subscribed
settles when the relay answers: it resolves with the acceptance info on_subscribed receives
and raises SubscriptionRequestError when the relay rejected the request or it timed out,
SubscriptionConnectionClosedError when the connection ended first, or the reason given to
cancel() (a CLOSED RelayError when none was given). ended settles once,
when the subscription can no longer deliver, with a SubscriptionEnd; it never raises.
Publisher completion settles neither, and neither implies exactly-once or gap-free delivery.
It subclasses the core handle so subscribe() keeps returning a Subscription; every
inherited member is overridden and the core’s own slots are never set.
Attributes
-
subscribed:RelayAwaitable[SubscribeOk](property) — Settles when the relay answers. See the class docstring for what it raises.Every waiter is isolated: cancelling one, as
asyncio.wait_fordoes on timeout, leaves the subscription’s readiness intact for every other. -
ended:RelayAwaitable[SubscriptionEnd](property) — Settles once, when the subscription can no longer deliver, with how it ended. Never raises.Every waiter is isolated, as for
subscribed. -
request_id:int(property) — The Request ID the SUBSCRIBE carried.Raises when the subscription ended before its request was sent (the connection had already ended): the error it ended with, or a
CLOSEDRelayError.
RelaySubscription.update
Section titled “RelaySubscription.update”def update(self, parameters: MessageParameters) -> NoneSend REQUEST_UPDATE, or queue it in order until the relay accepts the subscription.
RelaySubscription.cancel
Section titled “RelaySubscription.cancel”def cancel(self, reason: object = None) -> NoneRelaySubscriptionHandlers
Section titled “RelaySubscriptionHandlers”class RelaySubscriptionHandlersCallbacks for one subscription made through RelaySubscriber.subscribe.
Each is optional and runs synchronously as the protocol layer delivers the event; the SDK adds
no queue. The names say what happened between this connection and the relay. The borrowed
RelaySubscriber.track_source keeps the core’s protocol names (on_ok, on_done).
Attributes
-
on_subscribed:Callable[[SubscribeOk], None] | None— The relay accepted the subscription (SUBSCRIBE_OK) and will forward objects.infocarries the track alias, the relay’s parameters (largest_objectwhen the track already has content), and the track properties as the relay sends them, which can differ from what the publisher declared. -
on_error:Callable[[RequestError], None] | None— The relay answered the request with REQUEST_ERROR.It rejected the subscription, or the request failed before acceptance (request timeout, or the request stream failing). A REQUEST_ERROR can also arrive after acceptance, for example when the relay refuses an
update(), and it ends the subscription then too (issue #94). Connection shutdown and GOAWAY never arrive here: a pending subscription ends throughsubscribedandended, and an accepted one surfaces them as subgroup resets and connection closure. -
on_object:Callable[[MoqtObject], None] | None— One object arrived. Payloads are uninterpreted bytes. Objects can arrive afteron_publisher_done. -
on_subgroup_end:Callable[[SubgroupEnd], None] | None— A subgroup stream finished cleanly (FIN). -
on_subgroup_reset:Callable[[SubgroupReset], None] | None— A subgroup stream was reset; objects from it may be missing.Called for every reset the relay forwards. The relay may drop a reset for a subgroup it had not begun forwarding, so an abandoned group can arrive as nothing at all, only a gap in group IDs.
-
on_publisher_done:Callable[[PublishDone], None] | None— The publisher finished with the track (PUBLISH_DONE).A control notification, not the end of the subscription: objects still in transit can arrive after it, and the request stays open until
cancel()or connection shutdown. The SDK does not resubscribe, and resubscribing cannot bring back a finished track: against moq-relay, a subscribe after the publisher has gone is refused (request error 16unroutable), and one while its namespace stays announced replays the track’s cached last group and another done one round trip later. Back off, and do not resubscribe to a track the publisher ended. A done that follows this subscription’s ownupdate()is different: moq-relay ends the subscription on any update, the track is still live, and resubscribing is how to apply the change. After a publisher crash the done arrives only at the relay’s QUIC idle timeout, about 11 s later on moq-relay 0.17.0. See spec/relay-interop.md, “Publisher restarts”. -
on_update_ok:Callable[[], None] | None— The relay acknowledged anupdate()on this subscription (REQUEST_OK).moq-relay never does: it ends the subscription with PUBLISH_DONE on any update (spec/behavior.md, relay interop deviation 4).
-
on_state_notify:Callable[[MessageParameters], None] | None— The publisher changed the subscription’s state on its own (PUBLISH_STATE_NOTIFY).The parameters carry only what changed. Informational.
-
on_goaway:Callable[[Goaway], None] | None— GOAWAY on this request stream: the relay wants this request re-issued, possibly elsewhere.A GOAWAY on the connection itself ends the connection instead; observe
closedfor that.
RelayTrackWriter
Section titled “RelayTrackWriter”class RelayTrackWriterOne registered track on a publisher connection. Create one with RelayPublisher.register_track.
Registration sends nothing; write and open_group send, after waiting for relay demand.
The behaviour behind this class is the track’s write slot (see CONTEXT.md and the “Write
phases and interruptions” table in spec/behavior.md).
Attributes
-
name -
has_demand:bool(property) — Whether the relay currently has an upstream subscription for this track, so a write would proceed at once.Demand is the relay’s own subscription, shared by every downstream viewer and ended when the relay chooses: it is neither a viewer count nor a liveness signal. Always
Falseonce the track has ended, whatever the relay asks for; readclosedto tell a track that ended apart from one the relay has not subscribed to. -
closed:bool(property) — True once the application closed the track or the publisher connection ended.Individual write failures reset their stream and leave the track open.
RelayTrackWriter.demand_changes
Section titled “RelayTrackWriter.demand_changes”def demand_changes(self) -> AsyncIterator[bool]Iterate relay demand: the current state first, then one value per relay subscription change.
Yields True when the relay subscribes or replaces its subscription (so True can
repeat) and False when its subscription ends, and stops once the track ends, however it
ended. This is how a catalog re-sends its document on every relay subscription. Values are
per relay subscription, never per downstream viewer.
RelayTrackWriter.close
Section titled “RelayTrackWriter.close”def close(self) -> NoneEnd the track and cancel an outstanding write or group. The publisher connection stays open.
RelayTrackWriter.write_text
Section titled “RelayTrackWriter.write_text”async def write_text( self, text: str, *, properties: Sequence[KeyValuePair] | None = None,) -> NoneEncode UTF-8 text and write one object with the same semantics as write.
RelayTrackWriter.write_json
Section titled “RelayTrackWriter.write_json”async def write_json( self, value: object, *, properties: Sequence[KeyValuePair] | None = None,) -> NoneSerialize JSON and write one object; serialization errors leave the writer usable.
RelayTrackWriter.write
Section titled “RelayTrackWriter.write”async def write( self, payload: bytes, *, properties: Sequence[KeyValuePair] | None = None,) -> NoneSend one independent object/group.
Waits for relay demand (see has_demand). Returns when the transport accepts the complete group, not when a
downstream viewer acknowledges it. Only one write or group may be pending; await it before
writing again. Bytes are copied. Failure resets this group and leaves the track usable
while the connection is open. Data may already have reached the relay; choose the next
object explicitly (no automatic retry).
Properties are encoded as supplied; the relay may transform them before delivery. A 0x02 or
0x06 property sets this group’s delivery timeout instead of the track’s. An object older
than its OBJECT_DELIVERY_TIMEOUT when it would reach the transport, counted from this call and
including the wait for demand, raises DELIVERY_TIMEOUT: the data is stale, so write fresh
data rather than this object again.
RelayTrackWriter.open_group
Section titled “RelayTrackWriter.open_group”async def open_group( self, payload: bytes | None = None, *, properties: Sequence[KeyValuePair] | None = None,) -> RelayGroupWriterOpen a multi-object group: one stream carrying objects 0, 1, 2, … until close().
Waits for relay demand, for room under the connection’s settling-group limit and for the
stream within write_timeout. Only one write or group may be pending per track. If the
relay’s subscription changes or ends while the group is open, the group is reset and its
writer raises; the track stays open, so start the next group at the next random-access point.
With payload, the group opens with it as object 0 and returns once the transport takes it
(object_count is 1). Its delivery timeout counts from this call, so every wait before the
stream opens counts (draft-ietf-moq-transport-22 section 5.2), and a 0x02 or 0x06 property in
properties sets the group’s delivery timeouts. If it expires this raises
DELIVERY_TIMEOUT and the group is reset. Without payload there is no object to time,
so only write_timeout bounds the wait; properties without payload is a TypeError.
RequestError
Section titled “RequestError”class RequestErrorREQUEST_ERROR (§9.4): the peer refused a request.
The session also reports a request that fails on its stream (FIN before any response, a reset, or the response timeout) in this shape, so a consumer has one failure path.
Attributes
kind:Literal['request-error']error_code:intretry_interval:intreason:strredirect:Redirect | None
StateNotifyEvent
Section titled “StateNotifyEvent”class StateNotifyEventPublisher-initiated subscription state changes.
Attributes
value:MessageParameterstype:Literal['state_notify']
SubgroupEndEvent
Section titled “SubgroupEndEvent”class SubgroupEndEventClean FIN of one subgroup, not the whole subscription.
Attributes
value:SubgroupEndtype:Literal['subgroup_end']
SubgroupResetEvent
Section titled “SubgroupResetEvent”class SubgroupResetEventA subgroup reset; objects may be missing.
Attributes
value:SubgroupResettype:Literal['subgroup_reset']
SubscribeOk
Section titled “SubscribeOk”class SubscribeOkSUBSCRIBE_OK (§9.7): the publisher accepted a subscription.
track_alias is the alias its data streams carry; parameters and track_properties are
as the publisher sent them.
Attributes
kind:Literal['subscribe-ok']track_alias:intparameters:MessageParameterstrack_properties:list[KeyValuePair]
SubscribedEvent
Section titled “SubscribedEvent”class SubscribedEventSubscription acceptance.
Attributes
value:SubscribeOktype:Literal['subscribed']
Subscription
Section titled “Subscription”class SubscriptionA live subscription request.
MOQT has no UNSUBSCRIBE message: teardown is the request stream’s lifecycle (§6.4.2.3).
Attributes
request_id:int(property) — The Request ID the SUBSCRIBE carried (§6.4.2.1).
Subscription.update
Section titled “Subscription.update”def update(self, parameters: MessageParameters) -> NoneModify the subscription (REQUEST_UPDATE, §9.5).
Subscription.cancel
Section titled “Subscription.cancel”def cancel(self, reason: object = None) -> NoneTear the subscription down.
SubscriptionEnd
Section titled “SubscriptionEnd”class SubscriptionEndHow a subscription ended.
kind is the lifecycle name; cause is what happened on the wire or locally; accepted
says whether SUBSCRIBE_OK had arrived; error is the moq_core.RequestError for a
request error, the connection’s failure (None after a clean shutdown) for closure, and the
caller’s reason for a cancel.
Attributes
kind:SubscriptionEndKindcause:Literal['request-error', 'connection-closed', 'cancelled']accepted:boolerror:object
SubscriptionEvents
Section titled “SubscriptionEvents”class SubscriptionEvents(BoundedEvents[SubscriptionEvent])Single-consumer iterator. Use async with for deterministic cleanup on break/errors.
Create one with caprid_relay.RelaySubscriber.subscribe_events. At most capacity events are
retained, including lifecycle events. Overflow discards the queue, cancels only this request and
raises on reads. No transport backpressure is implied. Publisher completion is an event, never
EOF. Explicit close produces EOF; failures raise.
SubscriptionHandlers
Section titled “SubscriptionHandlers”class SubscriptionHandlersCallbacks for one subscription. Every one is optional and must be synchronous.
An async callback raises TypeError at subscribe time; a callback that returns an awaitable
has it discarded with a RuntimeWarning. Relay’s subscribe_events() is the async way in.
Attributes
-
on_ok:Callable[[SubscribeOk], None] | None— The publisher accepted the subscription. -
on_error:Callable[[RequestError], None] | None— The request failed: a REQUEST_ERROR, or a stream failure or timeout in the same shape. -
on_object:Callable[[MoqtObject], None] | None— An object arrived on one of the subscription’s data streams. -
on_subgroup_end:Callable[[SubgroupEnd], None] | None— A subgroup stream finished cleanly. -
on_subgroup_reset:Callable[[SubgroupReset], None] | None— A subgroup stream was reset; seeSubgroupReset. -
on_done:Callable[[PublishDone], None] | None— PUBLISH_DONE arrived: the publisher finished the subscription. -
on_update_ok:Callable[[], None] | None— Response to a REQUEST_UPDATE sent on this subscription. -
on_state_notify:Callable[[MessageParameters], None] | None— PUBLISH_STATE_NOTIFY (§9.10): the publisher changed subscription state on its own.The parameters carry only what changed, with LARGEST_OBJECT marking where. Informational; the session sends no reply.
-
on_goaway:Callable[[Goaway], None] | None— GOAWAY on this request stream: re-issue the request (possibly elsewhere).
SupervisedPublisher
Section titled “SupervisedPublisher”class SupervisedPublisherA publisher that reconnects. Create one with connect_supervised_publisher.
Each connection is a new generation with its own namespace; tracks are registered once and rebound to every generation.
Attributes
generation:PublisherGeneration | None(property) — The live generation, orNonebetween connections and after closing.status:SupervisedStatus(property) — Where the supervisor stands; seeSupervisedStatus.closed:RelayAwaitable[Closed](property) — Resolves once the supervisor stops, with the failure that stopped it or the cancellation’s reason aserror(Noneafterclose). Never raises.
SupervisedPublisher.register_track
Section titled “SupervisedPublisher.register_track”def register_track( self, track: str, *, track_properties: Sequence[KeyValuePair] | None = None, write_timeout: float = DEFAULT_WRITE_TIMEOUT,) -> RelayTrackWriterRegister a track every generation serves, and return the writer that outlives the generations.
The same rules as RelayPublisher.register_track, checked at once: track names are
unique per supervised publisher, including after their writer closes. Writes wait for demand
on the live generation; while none is up they raise RECONNECTING, and an operation the
connection’s end interrupts raises RECONNECTING too, never CLOSED, so a frame writer
treats it as an interruption and resumes at the next keyframe.
SupervisedPublisher.rotate
Section titled “SupervisedPublisher.rotate”def rotate(self) -> NoneReplace the live generation make-before-break, as happens on its own rotate_before before the
credential expires.
The next generation connects, with a fresh token, and every track is registered on it. Each track’s writer then moves to it at its next write or group, once the track has demand there or has none on the old one. The old generation closes once every track has moved, after 10 seconds at most. A no-op while no generation is up.
SupervisedPublisher.close
Section titled “SupervisedPublisher.close”async def close(self) -> NoneStop reconnecting and close the live generation as RelayPublisher.close does. Idempotent.
SupervisedPublisher.aenter
Section titled “SupervisedPublisher.aenter”async def __aenter__(self) -> SelfSupervisedSubscriber
Section titled “SupervisedSubscriber”class SupervisedSubscriberA subscriber that reconnects and keeps wanted tracks subscribed. Create one with
connect_supervised_subscriber.
Attributes
status:SupervisedStatus(property) — Where the supervisor stands; seeSupervisedStatus.closed:RelayAwaitable[Closed](property) — Resolves once the supervisor stops, with the failure that stopped it or the cancellation’s reason aserror(Noneafterclose). Never raises.
SupervisedSubscriber.want
Section titled “SupervisedSubscriber.want”def want( self, key: str, namespace: Sequence[str], track: str, handlers: WantHandlers | None = None, *, parameters: MessageParameters | None = None, generations: bool = False, stale_after: float | None = None, retry: SupervisionBackoff | None = None, until: Until = 'first-object', drain: float = 0.0,) -> NoneKeep a subscription to a track open until unwant(key).
generations=True makes namespace a supervised publisher’s root: the want follows the
newest generation announced one field below it, and the subscriber’s token must allow
discovering the root. stale_after (seconds) resubscribes when no object arrived for that
long, for tracks that are never silent while live. parameters is sent on every SUBSCRIBE;
retry is the backoff between resubscriptions (from 0.25 s, doubling up to 8 s).
A move (a newer generation, or a re-want with another track or parameters) is made before the
break, as switch_to makes one: the current subscription keeps delivering until an object
of the replacement passes until ("first-object", the default, or a synchronous
predicate), and on_switched reports the cutover. Compare the object’s own timeline with
what was delivered, never group IDs: each generation and track numbers its own groups.
drain (seconds, default 0) keeps the old subscription delivering its in-flight groups
that long after the cutover.
Wanting a key again with the same namespace and generations updates the want in place:
the handlers, retry, stale_after, until and drain are swapped, and another
track or parameters moves the subscription. Another namespace or generations replaces the
want. Arguments are checked at once.
SupervisedSubscriber.unwant
Section titled “SupervisedSubscriber.unwant”def unwant(self, key: str) -> NoneCancel a want’s subscription and forget it. Unknown keys are ignored.
SupervisedSubscriber.rotate
Section titled “SupervisedSubscriber.rotate”def rotate(self) -> NoneReplace the connection make-before-break, as happens on its own rotate_before before the
credential expires.
A new connection opens, with a fresh token, and every want moves its subscription to it as a move would, keeping the old one delivering until the cutover. A want whose target is unchanged cuts over on the first object the old one did not deliver, with no callback: nothing is lost or repeated. The old connection closes once every want has moved, after 10 seconds at most. A no-op while no connection is up.
SupervisedSubscriber.close
Section titled “SupervisedSubscriber.close”def close(self) -> NoneStop reconnecting, cancel every want and close the connection. Idempotent.
SupervisedSubscriber.aenter
Section titled “SupervisedSubscriber.aenter”async def __aenter__(self) -> SelfSupervisionBackoff
Section titled “SupervisionBackoff”class SupervisionBackoffThe delay before each retry: it starts at initial_delay and doubles up to max_delay, with jitter.
Both are seconds; None takes the default for what is being retried.
Attributes
initial_delay:float | None— The delay before the first retry, in seconds.max_delay:float | None— The longest delay between retries, in seconds.
SwitchableHandlers
Section titled “SwitchableHandlers”class SwitchableHandlersCallbacks for a switchable subscription.
Each runs synchronously, for the active subscription’s events only, and receives the active track’s name as its last argument. A pending subscription reaches no handler before its cutover.
Attributes
on_subscribed:Callable[[SubscribeOk, str], None] | None— The first subscription accepted (SUBSCRIBE_OK). A later subscription’s acceptance arrives with its cutover, inon_switched.on_error:Callable[[RequestError, str], None] | None— The active subscription ended with REQUEST_ERROR: the first one was rejected, or an accepted one was refused later. The switchable has ended. A pending subscription’s rejection fails itsswitch_toinstead.on_object:Callable[[MoqtObject, str], None] | None— One object from the active subscription. Payloads are uninterpreted bytes.on_subgroup_end:Callable[[SubgroupEnd, str], None] | None— One of the active subscription’s subgroup streams finished cleanly (FIN).on_subgroup_reset:Callable[[SubgroupReset, str], None] | None— One of the active subscription’s subgroup streams was reset; objects from it may be missing.on_publisher_done:Callable[[PublishDone, str], None] | None— The active track’s publisher finished with it (PUBLISH_DONE). Not an ending, as onRelaySubscriptionHandlers.on_publisher_done; a pending switch carries on.on_state_notify:Callable[[MessageParameters, str], None] | None— The publisher changed the active subscription’s state (PUBLISH_STATE_NOTIFY).on_goaway:Callable[[Goaway, str], None] | None— GOAWAY on the active subscription’s request stream. Informational; nothing is retried.on_switched:Callable[[SwitchedInfo], None] | None— A cutover:to_trackis now active andfrom_trackhas been cancelled (or is draining). Runs before the first object ofto_track, which is the one that passeduntil. Decoders drop state that assumed continuity here.
SwitchableSubscription
Section titled “SwitchableSubscription”class SwitchableSubscriptionThe handle caprid_relay.RelaySubscriber.subscribe_switchable returns.
Handlers see one subscription at a time, the active one. switch_to moves them to another
track in the same namespace, or the same track with other parameters, make-before-break.
Attributes
-
track:str(property) — The active subscription’s track name. -
pending:str | None(property) — The pending subscription’s track name, while a switch is pending. -
subscribed:RelayAwaitable[SubscribeOk](property) — Settles with the acceptance info of the first subscription to be active and accepted.That is the first one, or the one a switch made before the first one was answered. Raises as
RelaySubscription.subscribeddoes if the switchable ends before that. -
ended:RelayAwaitable[SubscriptionEnd](property) — Settles once with the active subscription’s ending, when it ends other than by a cutover, or whencancelruns. Any pending switch is cancelled with it. Never raises.
SwitchableSubscription.switch_to
Section titled “SwitchableSubscription.switch_to”def switch_to( self, track: str, *, parameters: MessageParameters | None = None, until: Until = 'first-object', drain: float = 0.0, cancellation: CancellationToken | None = None,) -> RelayAwaitable[SwitchedInfo]Subscribe to track in the same namespace and cut over once one of its objects passes until.
The active subscription keeps delivering until then, and is cancelled at the cutover. The
track may be the active one, to change parameters. parameters are not inherited from the
subscription being replaced: omitted means none.
until is "first-object" (the default), which promotes on the new subscription’s first
object, or a synchronous predicate that promotes on the first object it accepts. Objects
before the cutover are discarded, except those of groups later than the one that cuts over
(arrived early, out of order), which follow it. Compare the object’s own timeline (a timestamp property,
say) with what the active subscription delivered, so the cutover repeats media rather than
skipping it: group IDs are no such timeline, since each track numbers its own groups. A new
subscription can also start with a stale cached group, or lose its first object through
another relay (issue #243); a timeline predicate absorbs both.
drain keeps the old subscription open that many seconds after the cutover, from 0 to
MAX_TIMEOUT (2147483.647, as for every SDK deadline). Groups arrive
out of order across streams, most of all through another relay, so an older group of the old
track can still be in flight at the cutover. While it drains, its objects, subgroup ends and
resets from groups no later than the largest it had delivered still reach the handlers, with
the old track’s name, after on_switched; everything else from it is discarded.
cancellation abandons the switch while it is pending: the new subscription is cancelled
and the active one stays. A newer switch_to also cancels a pending one, which raises
CLOSED. The returned awaitable raises as RelaySubscription.subscribed does if the new
subscription ends before its cutover, the token’s reason when it is cancelled, or what
until raised; the active subscription carries on. Raises at once for an invalid track or
options, a closed connection, or an ended switchable.
SwitchableSubscription.cancel
Section titled “SwitchableSubscription.cancel”def cancel(self, reason: object = None) -> NoneCancel the active subscription and any pending one. Idempotent.
SwitchedInfo
Section titled “SwitchedInfo”class SwitchedInfoWhat on_switched receives and SwitchableSubscription.switch_to resolves with.
Attributes
from_track:str— The track that was active before the cutover.to_track:str— The track now active; the same asfrom_trackwhen only the parameters changed.subscribed:SubscribeOk— The new subscription’s acceptance info.
UpdateOkEvent
Section titled “UpdateOkEvent”class UpdateOkEventAcknowledgement of a subscription update.
Attributes
value:Nonetype:Literal['update_ok']
WantHandlers
Section titled “WantHandlers”class WantHandlersThe callbacks a want delivers to. Like RelaySubscriptionHandlers, they must be synchronous.
Attributes
on_subscribed:Callable[[SubscribeOk, tuple[str, ...]], None] | None— A subscription was accepted. The second argument is the namespace it was made on: the generation’s, when following one. A move’s replacement reports its acceptance inon_switchedinstead.on_object:Callable[[MoqtObject], None] | None— One object, from whichever subscription is current.on_subgroup_end:Callable[[SubgroupEnd], None] | None— A subgroup stream ended cleanly.on_subgroup_reset:Callable[[SubgroupReset], None] | None— A subgroup stream was reset.on_publisher_done:Callable[[PublishDone], None] | None— The relay sent PUBLISH_DONE on the current subscription; the want resubscribes after its backoff.on_restarted:Callable[[RestartReason, tuple[str, ...]], None] | None— An accepted subscription ended and the want will replace it: drop decoder state that assumed continuity. Receives the reason and the namespace the ended subscription was made on. Called before any object of the replacement; the gap is never filled.on_switched:Callable[[WantSwitchedInfo], None] | None— A move’s cutover: the want now delivers from theto_subscription, and thefrom_one was cancelled or drains its in-flight groups. Runs before the replacement’s first object, the one that passeduntil. Decoders drop state that assumed continuity here; noon_restartedoron_subscribedruns for a move.on_error:Callable[[BaseException], None] | None— A failure the want does not retry, after which it stops and is removed: a REQUEST_ERROR no wait fixes (credentials, an unsupported or malformed request) as aSubscriptionRequestError, or discovery refusing the root.
WantSwitchedInfo
Section titled “WantSwitchedInfo”class WantSwitchedInfoWhat on_switched receives at a want’s cutover.
Attributes
from_namespace:tuple[str, ...]— The namespace the want delivered from before the cutover: the generation’s, when following one.from_track:str— The track the want delivered from before the cutover.to_namespace:tuple[str, ...]— The namespace it delivers from now.to_track:str— The track it delivers from now.subscribed:SubscribeOk— The new subscription’s acceptance info.
Protocols
Section titled “Protocols”MoqTrackSource
Section titled “MoqTrackSource”class MoqTrackSource(Protocol)The subscribe / fetch / track-status subset of a session, plus closure observation, without shutdown.
Attributes
closed:asyncio.Future[Closed](property) — Resolves when the owning connection ends; an error denotes failure.
MoqTrackSource.subscribe
Section titled “MoqTrackSource.subscribe”def subscribe( self, options: MoqtSubscribeOptions, handlers: SubscriptionHandlers | None = None,) -> SubscriptionSubscribe to a track, as MoqtSession.subscribe does; the caller owns the request.
MoqTrackSource.fetch
Section titled “MoqTrackSource.fetch”def fetch( self, options: MoqtSubscribeOptions, handlers: FetchHandlers | None = None,) -> FetchHandleFetch a range of a track, as MoqtSession.fetch does; the caller owns the request.
MoqTrackSource.track_status
Section titled “MoqTrackSource.track_status”def track_status( self, options: MoqtSubscribeOptions, handlers: TrackStatusHandlers | None = None,) -> NoneAsk for a track’s status, as MoqtSession.track_status does.
MoqtTransport
Section titled “MoqtTransport”class MoqtTransport(Protocol)Structural subset of a WebTransport session the session drivers need.
MoqtTransport.incoming_unidirectional_streams
Section titled “MoqtTransport.incoming_unidirectional_streams”def incoming_unidirectional_streams(self) -> AsyncIterator[ReceiveStream]Iterate peer-initiated unidirectional streams until the transport ends.
MoqtTransport.incoming_bidirectional_streams
Section titled “MoqtTransport.incoming_bidirectional_streams”def incoming_bidirectional_streams(self) -> AsyncIterator[BidirectionalStream]Iterate peer-initiated bidirectional streams until the transport ends.
MoqtTransport.create_unidirectional_stream
Section titled “MoqtTransport.create_unidirectional_stream”async def create_unidirectional_stream(self) -> SendStreamOpen a unidirectional stream to the peer.
MoqtTransport.create_bidirectional_stream
Section titled “MoqtTransport.create_bidirectional_stream”async def create_bidirectional_stream(self) -> BidirectionalStreamOpen a bidirectional stream to the peer.
MoqtTransport.close
Section titled “MoqtTransport.close”def close(self, close_code: int = 0, reason: str = '') -> NoneClose the session. Idempotent; a transport that already ended ignores it.
MoqtTransport.wait_closed
Section titled “MoqtTransport.wait_closed”async def wait_closed(self) -> NoneReturn once the session ended cleanly (either side); raise the error that ended it otherwise.
Exceptions
Section titled “Exceptions”Cancelled
Section titled “Cancelled”class Cancelled(Exception)The default reason recorded when a token is cancelled without one.
RelayError
Section titled “RelayError”class RelayError(Exception)An SDK failure with a stable code and origin phase; neither promises safe replay.
Branch on code (see spec/behavior.md), never on the message. The underlying exception,
when there is one, is chained as __cause__.
Attributes
code:RelayErrorCodephase:RelayErrorPhase
SubscriptionConnectionClosedError
Section titled “SubscriptionConnectionClosedError”class SubscriptionConnectionClosedError(RelayError)The connection ended, without proving that subscription delivery was drained.
SubscriptionOverflowError
Section titled “SubscriptionOverflowError”class SubscriptionOverflowError(RelayError)A stalled consumer exceeded the event capacity; the subscription was cancelled.
SubscriptionRequestError
Section titled “SubscriptionRequestError”class SubscriptionRequestError(RelayError)Request rejection, timeout, or request failure, with the protocol details preserved.
Attributes
request_error
LocationFilter
Section titled “LocationFilter”LocationFilter: TypeAlias = ...Which Objects a subscription or fetch covers.
Draft-22 opens the LOCATION_FILTER value with a Location Filter Type that selects the fields after it; draft-20 length-prefixes up to four varints and the number present selects the meaning.
RestartReason
Section titled “RestartReason”RestartReason: TypeAlias = ...Why a want replaced its subscription.
publisher-done: the relay sent PUBLISH_DONE. stale: no object arrived for stale_after.
generation: the current generation was withdrawn with none newer, or a move to a newer one
could not be made before the break. connection:
the subscriber connection ended and was replaced. terminated: the relay ended the subscription
with REQUEST_ERROR after accepting it.
SubscriptionEndKind
Section titled “SubscriptionEndKind”SubscriptionEndKind: TypeAlias = Literal['rejected', 'terminated', 'cancelled']The lifecycle name for how a subscription ended.
rejected: the relay answered the request with REQUEST_ERROR before accepting it (including the
request timeout). terminated: an accepted subscription can no longer deliver (the connection
ended, or a REQUEST_ERROR arrived after acceptance, which is how a refused update() is answered,
issue #94), or the connection ended before any answer. cancelled: the application cancelled it.
SubscriptionEvent
Section titled “SubscriptionEvent”SubscriptionEvent: TypeAlias = ...Typed callback arrivals; no ordering across streams or drain guarantee is implied.
SupervisedStatus
Section titled “SupervisedStatus”SupervisedStatus: TypeAlias = Literal['connecting', 'live', 'reconnecting', 'closed']Where a supervised connection stands: connecting until the first connection is up, live
while one is, reconnecting between connections, and closed once the application closed it,
its cancellation token was cancelled, or a first attempt failed in a way that is not retried.
Constants
Section titled “Constants”DEFAULT_ENDPOINT
Section titled “DEFAULT_ENDPOINT”DEFAULT_ENDPOINT = 'https://relay.mux.global'The default Relay endpoint pinned in spec/protocol.json.
RelayErrorCode
Section titled “RelayErrorCode”RelayErrorCode = ...RelayErrorPhase
Section titled “RelayErrorPhase”RelayErrorPhase = ...RelayTransportFactory
Section titled “RelayTransportFactory”RelayTransportFactory = Callable[[str, Sequence[str]], ConnectingTransport]Supply a transport for (url, protocols).
protocols are the MOQT protocol IDs to offer, in preference order: moqt-22, then moqt-20
as a fallback. A transport reports the one the peer selected as protocol_id (TA-8).
Native QUIC on aioquic (caprid_relay.quic.connect_quic) by default; tests supply in-memory pairs.