moqtail-ts
    Preparing search index...

    Class MOQtailClient

    Represents a Media Over QUIC Transport (MOQT) client session.

    Use MOQtailClient.new to establish a connection and perform MOQT operations such as subscribing to tracks, fetching historical data, announcing tracks for publication, and managing session lifecycle.

    Once initialized, the client provides high-level methods for MOQT requests and publishing. If a protocol violation occurs, the client will terminate and must be re-initialized.

    const client = await MOQtailClient.new({ url, supportedVersions: [0xff00000b] });
    const result = await client.subscribe({
    fullTrackName,
    filterType: FilterType.LatestObject,
    forward: true,
    groupOrder: GroupOrder.Original,
    priority: 0
    });
    if (!(result instanceof SubscribeError)) {
    for await (const object of result.stream) {
    // Consume MOQT objects
    }
    }
    const client = await MOQtailClient.new({ url, supportedVersions: [0xff00000b] });
    const announceResult = await client.announce(["camera", "main"]);
    if (!(announceResult instanceof AnnounceError)) {
    // Ready to publish objects under this namespace
    }
    await client.disconnect();
    
    Index

    Properties

    announcedNamespaces: Set<Tuple> = ...

    Track namespaces this client has successfully announced (received ANNOUNCE_OK). Source of truth for deciding what to UNANNOUNCE on teardown or targeted withdrawal.(future optimization: prefix trie).

    controlStream: ControlStream

    Outgoing / incoming control message bidirectional stream wrapper.

    controlStreamTimeoutMs?: number

    Timeout (ms) for control stream read operations; undefined => no explicit timeout.

    dataStreamTimeoutMs?: number

    Timeout (ms) applied to reading incoming data streams; undefined => no explicit timeout.

    maxRequestId?: bigint

    Optional highest request id allowed (enforced externally / via configuration).

    onDataReceived?: (
        data: FetchHeader | FetchObject | SubgroupObject | SubgroupHeader,
    ) => void

    Invoked for each decoded data object/header arriving on a uni stream (fetch or subgroup). Use to process or display incoming media/data. Informational event.

    onDataSent?: (
        data: FetchHeader | FetchObject | SubgroupObject | SubgroupHeader,
    ) => void

    Invoked after enqueuing each outbound data object/header. Reserved for future use. Informational event.

    onError?: (er: unknown) => void

    General-purpose error callback for surfaced exceptions not thrown to caller synchronously. Use to log or display errors. Error handler.

    onGoaway?: (msg: GoAway) => void

    Fired on GOAWAY reception signaling graceful session wind-down. Use to prepare for disconnect or cleanup. Lifecycle handler.

    onMessageReceived?: (msg: ControlMessage) => void

    Invoked upon receiving each inbound control message before handling. Use for logging or debugging. Informational event.

    onMessageSent?: (msg: ControlMessage) => void

    Invoked after each outbound control message is sent. Use for logging or analytics. Informational event.

    onSessionTerminated?: (reason?: unknown) => void

    Fired exactly once when the client transitions to terminated (disconnect). Use to clean up resources or notify user. Lifecycle handler.

    onTrackAnnounced?: (msg: Announce) => void

    Fired when an ANNOUNCE control message is processed for a track namespace. Use to update UI or trigger discovery logic. Discovery event.

    onTrackUnannounced?: (msg: Unannounce) => void

    Fired when an UNANNOUNCE control message is processed for a namespace. Use to remove tracks from UI or stop discovery. Discovery event.

    onWebTransportFail?: () => void

    Fired if the underlying WebTransport session fails (ready → closed prematurely). Use to log or alert on transport errors. Lifecycle/error handler.

    peerSubscribeAnnounces: Set<Tuple> = ...

    Namespace prefixes (tuples) the peer has requested announce notifications for via SUBSCRIBE_ANNOUNCES. Used to decide which locally issued ANNOUNCE messages should be forwarded (future optimization: prefix trie).

    publications: Map<bigint, SubscribePublication | FetchPublication> = ...

    Active publications (SUBSCRIBE or FETCH) keyed by requestId to manage object stream controllers and lifecycle. Subset / specialization view of requests.

    requests: Map<bigint, MOQtailRequest> = ...

    All in‑flight request objects keyed by requestId (SUBSCRIBE, FETCH, ANNOUNCE, etc). Facilitates lookup when responses / data arrive. Entries are removed on completion or error.

    subscribedAnnounces: Set<Tuple> = ...

    Namespace prefixes this client has subscribed to (issued SUBSCRIBE_ANNOUNCES). Enables automatic filtering of incoming ANNOUNCE / UNANNOUNCE. Maintained locally; no dedupe of overlapping / shadowing prefixes yet.

    subscriptions: Map<bigint, SubscribeRequest> = ...

    Active SUBSCRIBE request wrappers keyed by track alias for rapid alias -> subscription resolution during incoming unidirectional data handling.

    trackAliasMap: TrackAliasMap = ...

    Bidirectional alias <-> full track name mapping to reconstruct metadata for incoming objects that reference aliases only.

    trackSources: Map<string, Track> = ...

    Locally registered track definitions keyed by full track name string. Populated via addOrUpdateTrack. Does not imply the track has been announced or has active publications.

    webTransport: WebTransport

    Underlying WebTransport session (set after successful construction in MOQtailClient.new).

    Accessors

    Methods

    • Registers or updates a Track definition for local publishing or serving.

      A Track describes a logical media/data stream, identified by a unique name and namespace.

      • If trackSource.live is present, the track can be served to subscribers in real-time.
      • If trackSource.past is present, the track can be fetched for historical data.
      • If both are present, the track supports both live and historical access.

      Parameters

      Returns void

      void

      : MOQtailError If the client has been destroyed.

      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
      const videoTrack = stream.getVideoTracks()[0];

      // Convert video frames to MoqtObject instances using your chosen scheme (e.g. WARP, CMAF, etc.)
      // This part is application-specific and not provided by MOQtail:
      const liveReadableStream: ReadableStream<MoqtObject> = ...

      // Register the track for live subscription
      client.addOrUpdateTrack({
      fullTrackName: { namespace: ["camera"], name: "main" },
      forwardingPreference: ObjectForwardingPreference.Latest,
      trackSource: { live: liveReadableStream },
      publisherPriority: 0 // highest priority
      });

      // For a hybrid track (live + past):
      import { MemoryObjectCache } from './track/object_cache';
      const cache = new MemoryObjectCache(); // Caches are not yet fully supported
      client.addOrUpdateTrack({
      fullTrackName: { namespace: ["camera"], name: "main" },
      forwardingPreference: ObjectForwardingPreference.Latest,
      trackSource: { live: liveReadableStream, past: cache },
      publisherPriority: 8
      });
    • Declare (publish) a track namespace to the peer so subscribers using matching prefixes (via MOQtailClient.subscribeAnnounces) can discover and begin subscribing/fetching its tracks.

      Typical flow (publisher side):

      1. Prepare / register one or more Track objects locally (see MOQtailClient.addOrUpdateTrack).
      2. Call announce(namespace) once per namespace prefix to expose those tracks.
      3. Later, call MOQtailClient.unannounce when no longer publishing under that namespace.

      Parameter semantics:

      • trackNamespace: Tuple representing the namespace prefix (e.g. ["camera","main"]). All tracks whose full names start with this tuple are considered within the announce scope.
      • parameters: Optional VersionSpecificParameters; omitted => default instance.

      Returns: AnnounceOk on success (namespace added to announcedNamespaces) or AnnounceError explaining refusal.

      Use cases:

      • Make a camera or sensor namespace available before any objects are pushed.
      • Dynamically expose a newly created room / session namespace.
      • Re-announce after reconnect to repopulate discovery state.

      Parameters

      Returns Promise<AnnounceError | AnnounceOk>

      MOQtailError If client is destroyed.

      InternalError Transport/control failure while sending or awaiting response (client disconnects first).

      • Duplicate announce detection is TODO (currently a second call will still send another ANNOUNCE; receiver behavior may vary).
      • Successful announces are tracked in announcedNamespaces; manual removal occurs via MOQtailClient.unannounce.
      • Discovery subscribers (those who issued MOQtailClient.subscribeAnnounces) will receive the resulting Announce message.
      const res = await client.announce(["camera","main"])
      if (res instanceof AnnounceOk) {
      // ready to publish objects under tracks with this namespace prefix
      }
      const params = new VersionSpecificParameters().setSomeExtensionFlag(true)
      const resp = await client.announce(["room","1234"], params)
    • Send an AnnounceCancel to abort a previously issued ANNOUNCE before (or after) the peer fully processes it.

      Use when an announce was sent prematurely (e.g. validation failed locally, namespace no longer needed) and you want to retract it without waiting for normal announce lifecycle or before publishing any objects.

      Parameter semantics:

      • msg: Pre-constructed AnnounceCancel referencing the original announce request id / namespace (builder provided elsewhere).

      Behavior:

      • Simply forwards the control frame; does not modify announcedNamespaces (call MOQtailClient.unannounce for local bookkeeping removal).
      • Safe to send even if the announce already succeeded; peer may ignore duplicates per spec guidance.

      Parameters

      Returns Promise<void>

      MOQtailError If client is destroyed.

      InternalError Wrapped transport/control send failure (client disconnects first) then rethrows.

      Use in tandem with internal tracking if you want to prevent subsequent object publication until a new announce is issued.

      const announceResp = await client.announce(["camera","temp"]) // wrong namespace
      // Assume you kept the original announce requestId (e.g. from AnnounceRequest)
      const cancelMsg = new AnnounceCancel(announceResp.requestId as bigint)
      await client.announceCancel(cancelMsg)
    • Gracefully terminates this MOQtailClient session and releases underlying WebTransport resources.

      Parameters

      • Optionalreason: unknown

        Optional application-level reason (string or error) recorded and wrapped in an InternalError passed to the onSessionTerminated callback.

      Returns Promise<void>

      Promise that resolves once shutdown logic completes. Subsequent calls are safe no-ops.

      await client.disconnect();
      
      await client.disconnect('user logout');
      
      await client.disconnect();
      await client.disconnect(); // no error
      window.addEventListener('beforeunload', () => {
      client.disconnect('page unload');
      });
    • One-shot retrieval of a bounded object span, optionally anchored to an existing subscription, returning a stream of MoqtObjects.

      Choose a fetch type via typeAndProps.type:

      • StandAlone: Historical slice of a specific FullTrackName independent of active subscriptions.
      • Relative: Range relative to the JOINING subscription's current (largest) location; use when you want "N groups back" from live.
      • Absolute: Absolute group/object offsets tied to an existing subscription (stable anchor) even if that subscription keeps forwarding.

      Field highlights (in FetchOptions):

      • priority: 0 (highest) .. 255 (lowest); out-of-range rejected; non-integers rounded by caller expectation.
      • groupOrder: GroupOrder.Original to preserve publisher order; or reorder ascending/descending if supported by server.
      • typeAndProps: Discriminated union carrying parameters specific to each fetch mode (see examples).
      • parameters: Optional version-specific extension block.

      Returns either a FetchError (refusal / invalid request at protocol level) or { requestId, stream } whose stream ends naturally after the bounded range completes (no explicit cancel needed for normal completion).

      Use cases:

      • Grab a historical window for scrubbing UI while a separate live subscription tails.
      • Late joiner fetching a short back-buffer then discarding the stream.
      • Analytics batch job pulling a fixed slice without subscribing long-term.

      Parameters

      Returns Promise<FetchError | { requestId: bigint; stream: ReadableStream<MoqtObject> }>

      MOQtailError If client is destroyed.

      ProtocolViolationError Priority out of [0-255] or missing/invalid joining subscription id for Relative/Absolute.

      InternalError Transport/control failure (the client disconnects first) then rethrows original error.

      • Relative / Absolute require an existing active SUBSCRIBE joiningRequestId; if not found a ProtocolViolationError is thrown.
      • Result stream is finite; reader close occurs automatically when last object delivered.
      • Use MOQtailClient.fetchCancel only for early termination (not yet fully implemented: see TODO in code).
      const r = await client.fetch({
      priority: 64,
      groupOrder: GroupOrder.Original,
      typeAndProps: {
      type: FetchType.StandAlone,
      props: { fullTrackName, startLocation, endLocation }
      }
      })
      if (!(r instanceof FetchError)) {
      for await (const obj of r.stream as any) {
      // consume objects then stream ends automatically
      }
      }
      const sub = await client.subscribe({ fullTrackName, filterType: FilterType.LatestObject, forward: true, groupOrder: GroupOrder.Original, priority: 0 })
      if (!(sub instanceof SubscribeError)) {
      const slice = await client.fetch({
      priority: 32,
      groupOrder: GroupOrder.Original,
      typeAndProps: { type: FetchType.Relative, props: { joiningRequestId: sub.requestId, joiningStart: 0n } }
      })
      }
    • Request early termination of an in‑flight FETCH identified by its requestId.

      Use when the consumer no longer needs the remaining objects (user scrubbed away, UI panel closed, replaced by a new fetch). Sends a FetchCancel control frame if the id currently maps to an active fetch; otherwise silent no-op (idempotent).

      Parameter semantics:

      Current behavior / limitations:

      • Data stream closure after cancel is TODO (objects may still arrive briefly).
      • Unknown / already finished request: ignored without error.
      • Only targets FETCH requests (not subscriptions).

      Parameters

      • requestId: number | bigint

      Returns Promise<void>

      MOQtailError If client is destroyed.

      InternalError Failure while sending the cancel (client disconnects first).

      Follow-up improvement planned: actively close associated readable stream controller immediately upon acknowledgment.

      const r = await client.fetch({ priority: 32, groupOrder: GroupOrder.Original, typeAndProps: { type: FetchType.StandAlone, props: { fullTrackName, startLocation, endLocation } } })
      if (!(r instanceof FetchError)) {
      // user navigated away
      await client.fetchCancel(r.requestId)
      }
      await client.fetchCancel(456n)
      await client.fetchCancel(456n) // no error
    • Removes a previously registered Track from this client's local catalog.

      This deletes the in-memory entry inserted via MOQtailClient.addOrUpdateTrack, so future lookups by its Track.fullTrackName will fail. Does not automatically:

      • Send an Unannounce (call MOQtailClient.unannounce separately if you want to inform peers)
      • Cancel active subscriptions or fetches (they continue until normal completion)
      • Affect already-sent objects.

      If the track was not present, the call is a silent no-op (idempotent removal).

      Parameters

      • track: Track

        The exact Track instance (its canonical name is used as the key).

      Returns void

      : MOQtailError If the client has been destroyed.

      // Register a track
      client.addOrUpdateTrack(track);

      // Later, when no longer publishing:
      client.removeTrack(track);

      // Optionally, inform peers that the namespace is no longer available:
      await client.unannounce(track.fullTrackName.namespace);
    • Subscribes to a track and returns a stream of MoqtObjects matching the requested window and relay forwarding mode.

      • forward: true tells the relay to forward objects to this subscriber as they arrive.
      • forward: false means the relay subscribes upstream but buffers objects locally, not forwarding them to you.
      • filterType: AbsoluteStart lets you specify a start position in the future; the stream waits for that object. If the start location is < the latest object observed at the publisher then it behaves as filterType: LatestObject
      • filterType: AbsoluteRange lets you specify a start and end group, both of should be in the future; the stream waits for those objects. If the start location is < the latest object observed at the publisher then it behaves as filterType: LatestObject.

      The method returns either a SubscribeError (on refusal) or an object with the subscription requestId and a ReadableStream of MoqtObjects. Use the requestId for MOQtailClient.unsubscribe or MOQtailClient.subscribeUpdate. Use the stream to decode and display objects.

      Parameters

      Returns Promise<
          | SubscribeError
          | { requestId: bigint; stream: ReadableStream<MoqtObject> },
      >

      Either a SubscribeError or { requestId, stream } for consuming objects.

      : MOQtailError If the client is destroyed.

      : ProtocolViolationError If required fields are missing or inconsistent.

      : InternalError On transport/protocol failure (disconnect is triggered before rethrow).

      const result = await client.subscribe({
      fullTrackName,
      filterType: FilterType.LatestObject,
      forward: true,
      groupOrder: GroupOrder.Original,
      priority: 32
      });
      if (!(result instanceof SubscribeError)) {
      for await (const obj of result.stream) {
      // decode and display obj
      }
      }
      const result = await client.subscribe({
      fullTrackName,
      filterType: FilterType.AbsoluteRange,
      startLocation: futureStart,
      endGroup: futureEnd,
      forward: true,
      groupOrder: GroupOrder.Original,
      priority: 128
      });
    • Narrows or updates an active subscription window and/or relay forwarding behavior.

      Use this to:

      • Move the start of the subscription forward (trim history or future window).
      • Move the end group earlier (shorten the window).
      • Change relay forwarding (forward: false stops forwarding new objects, true resumes).
      • Adjust subscriber priority.

      Only narrowing is allowed: you cannot move the start earlier or the end group later than the original subscription. Forwarding and priority can be changed at any time.

      Parameters

      Returns Promise<void>

      Promise that resolves when the update control frame is sent.

      :MOQtailError If the client is destroyed.

      :ProtocolViolationError If the update would widen the window (earlier start, later end group, or invalid ordering).

      :InternalError On transport/control failure (disconnect is triggered before rethrow).

      • Only applies to active SUBSCRIBE requests; ignored if the request is not a subscription.
      • Omitting a parameter (e.g. priority) leaves the previous value unchanged.
      • Setting forward: false stops relay forwarding new objects after the current window drains.
      • Safe to call multiple times; extra calls with unchanged bounds have no effect.
      await client.subscribeUpdate({ requestId, startLocation: laterLoc, endGroup, forward: true, priority });
      
      await client.subscribeUpdate({ requestId, startLocation: origStart, endGroup: cutoffGroup, forward: false, priority });
      
      await client.subscribeUpdate({ requestId, startLocation: currentStart, endGroup: currentEnd, forward: true, priority: 200 });
      
    • Withdraw a previously announced namespace so new subscribers no longer discover its tracks.

      Use when shutting down publishing for a logical scope (camera offline, room closed, session ended). Removes the namespace from announcedNamespaces locally and sends an Unannounce control frame.

      Parameter semantics:

      Behavior:

      • Does not delete locally registered Track objects (they remain in trackSources).
      • Does not forcibly end active subscriptions that were already established; peers simply stop discovering it for new ones.
      • Silent if the namespace was not currently recorded (idempotent style).

      Parameters

      Returns Promise<void>

      MOQtailError If client is destroyed before sending.

      (rethrows original error) Any lower-level failure while sending results in a disconnect (unwrapped TODO: future wrap with InternalError for consistency).

      Peers that issued MOQtailClient.subscribeAnnounces for a matching prefix should receive the resulting Unannounce. Consider calling this before MOQtailClient.disconnect to give consumers prompt notice.

      await client.unannounce(["camera","main"])
      
      await client.unannounce(["camera","main"]) // first time
      await client.unannounce(["camera","main"]) // no error, already removed
    • Stops an active subscription identified by its original SUBSCRIBE requestId.

      Sends an Unsubscribe control frame if the subscription is still active. If the id is unknown or already cleaned up, the call is a silent no-op (hence multiple calls are idempotent).

      Use this when you no longer want incoming objects for a track (e.g. user navigated away, switching quality). Canceling the consumer stream reader does not auto-unsubscribe; call this explicitly for prompt cleanup.

      Parameters

      Returns Promise<void>

      Promise that resolves when the unsubscribe control frame is sent.

      :MOQtailError If the client is destroyed.

      :InternalError Wrapped lower-level failure while attempting to send (session will be disconnected first).

      • Only targets SUBSCRIBE requests, not fetches. Passing a fetch request id is ignored (no-op).
      • Safe to call multiple times; extra calls have no effect.
      const sub = await client.subscribe({ fullTrackName, filterType: FilterType.LatestObject, forward: true, groupOrder: GroupOrder.Original, priority: 0 });
      if (!(sub instanceof SubscribeError)) {
      // ...consume objects...
      await client.unsubscribe(sub.requestId);
      }
      await client.unsubscribe(123n);
      await client.unsubscribe(123n); // no error
    • Establishes a new MOQtailClient session over WebTransport and performs the MOQT setup handshake.

      Returns Promise<MOQtailClient>

      Promise resolving to a ready MOQtailClient instance.

      :ProtocolViolationError If the server sends an unexpected or invalid message during setup.

      const client = await MOQtailClient.new({
      url: 'https://relay.example.com/transport',
      supportedVersions: [0xff00000b]
      });
      const client = await MOQtailClient.new({
      url,
      supportedVersions: [0xff00000b],
      setupParameters: new SetupParameters().addMaxRequestId(1000),
      transportOptions: { congestionControl: 'default' },
      dataStreamTimeoutMs: 5000,
      controlStreamTimeoutMs: 2000,
      callbacks: {
      onMessageSent: msg => console.log('Sent:', msg),
      onMessageReceived: msg => console.log('Received:', msg),
      onSessionTerminated: reason => console.warn('Session ended:', reason)
      }
      });