# MoQT inspector

> Watch the control messages and data streams of one relay session, and measure a track switch.

The MoQT inspector connects the browser SDK to one relay and one namespace,
subscribes the tracks you pick, and shows what crosses the wire. Open it when
a track does not play, when the relay refuses a request, or when you want a
number for what a rendition switch costs before you change the player.

The screen has four parts:

| Part | What it shows |
| ---- | ------------- |
| Session strip | Connection state, transport, dialect, uptime, the transport's send-rate estimate and RTT when the browser exposes them, and the object rate, bytes, open streams and resets across all tracks. |
| Tracks | The tracks the catalog lists, with a Subscribe button per track, the track-switch experiment, and a thumbnail preview. |
| Data tab | Per-track counters and timing for every data stream the relay opened. The screen opens on this tab. |
| Messages tab | Every control message the SDK sent or received, decoded into fields, in arrival order. |

The SDK that the inspector drives is the same `moqt.js` bundle the latency
console and the player load. The records come from the SDK's session trace,
so the fields shown are the fields the session acted on.

## Connect to a relay and a namespace

Fill the form at the top of the screen and press **Connect**:

| Field | Expected value |
| ----- | -------------- |
| Relay (QUIC/WT) | The WebTransport URL of the relay. `https://` only. The default is the public relay of this console's brand. |
| Namespace | `stream/<org id>/<input id>`. The **Live input** selector fills it from one of your live inputs. |
| Token | Optional. A relay capability appended to the connection URL. |
| Catalog track | The track subscribed first. Its JSON object lists the tracks of the namespace. Default `.catalog`. |

The relay URL and the namespace are written to the page URL (`?relay=&ns=`),
so a working setup is a link you can share.

**Catalog URL** is optional and is for an asset. A VOD asset publishes no
catalog object on the relay: its catalog, with the init segment of each
rendition and the relay URL and token the player uses, is the asset's
playback catalog endpoint
(`https://streams.<brand>/api/streams/playback/<playback id>/catalog`).
Paste it and press **Load catalog**: the Tracks panel fills, and the relay,
token and namespace fields are filled from the catalog when they are empty.

Press **Disconnect** to close every subscription and the session.

## Session strip

| Value | Meaning |
| ----- | ------- |
| State | Not connected, Connecting, Connected, Reconnecting, Disconnected, or Connection failed, with the SDK's reason when there is one. |
| Transport | WebTransport (QUIC) or WebSocket, and the MoQT dialect the session negotiated. |
| Send-rate estimate | The transport's own estimate of the path capacity, in kbps, read once a second. |
| RTT | The smoothed round-trip time of the QUIC connection, in ms. |
| Transport stats | Reads "not exposed by this browser" in place of the two values above when the browser has no `WebTransport.getStats()`. Chrome does not expose it. |
| Received | Objects per second and kbps across all tracks, with the payload bytes since Connect. The Data tab has the object count per track. |
| Streams open | Data streams open now across all tracks, and how many the relay reset. |
| Messages | Control messages traced since Connect. |

## Preview

A muted thumbnail plays one video track, so the picture confirms what the
counters say without taking the screen. Press **Play** on a video track to
subscribe it and feed its objects to the player; the init segment comes from
the catalog (the catalog object of a live input, or a loaded catalog URL), or
from the first object that carries one in band. With a track already playing,
**Switch** on another track runs a measured rendition switch (see Track
switch) and continues playback on the new track. The status line shows the
fragments appended, where the init segment came from, the decoded size and
the seconds buffered ahead. **Stop** releases the player.

## Tracks

The inspector subscribes the catalog track as soon as the session is up. The
first catalog object fills the list: video tracks first, then audio and other
tracks, each with its codec, resolution and bitrate when the catalog carries
them. A loaded catalog URL fills the same list before you connect.

- **Play** (video tracks) plays the track in the preview; **Switch** moves the
  preview to it.
- **Subscribe** opens a subscription from the live edge. **Unsubscribe**
  closes it.
- A track the catalog does not list can be subscribed by name with the field
  under the list.
- **Filter** sets where every new subscription starts: the largest object
  (the live edge), the next group, or the start of the track (group 0, object
  0). A VOD asset is served from the relay's retained window only from the
  start; the live edge of a VOD track whose replay is over is after its end.
  The catalog track is always subscribed from the largest object.
- The **Subscribed** list shows objects received and the last group id per
  subscription.

Every subscription shows in the Messages tab as a SUBSCRIBE and its answer,
and in the Data tab as its streams.

## Messages

One row per control message: time, direction (an up arrow is sent, a down
arrow is received), the message name, the request id of the stream it rode on,
its wire size, and its decoded fields. Click a row to expand the fields.

Filters:

- **Sent** and **Received** toggle a direction.
- The type selector keeps one message type.
- The text filter matches the message name or any decoded field value.
- **Follow** keeps the newest message in view. Scrolling up turns it off.

**Clear** empties the list. The list keeps the newest 2000 messages.

## Data

One card per track, keyed by the track alias the relay assigned and named from
the SUBSCRIBE that opened it. The card's header shows the object rate, the
kbps, and a sparkline of the kbps over the last 60 seconds.

| Counter | Meaning |
| ------- | ------- |
| Streams | Data streams the relay opened for the track, and how many are still open. |
| Groups, Subgroups, Objects, Payload | Distinct group ids, distinct subgroups, objects, and payload bytes received. |
| Last group/object | The newest location received. |
| Stream errors | Streams that ended with a reset or a parse error. |
| Resets | Of the stream errors, streams the relay reset, and the code of the last reset. |
| Skipped groups | Group ids jumped over between the highest group seen and a newer one. |
| Skipped objects | Object ids jumped over inside one stream. |
| Duplicates | Objects already received on the track, which two overlapping subscriptions produce. |

Timing per track (average, minimum, maximum):

- **Interarrival**: time between consecutive objects.
- **Jitter**: the RFC 3550 estimator against the objects' capture timestamps
  when the publisher stamps them; otherwise how much consecutive intervals
  differ.
- **Latency (capture → receive)**: from the capture timestamp to the receive
  time, when the publisher stamps a wall-clock capture time.
- **Path**: when the relays stamp hop timestamps on the objects, one line per
  segment of the path: publisher to the first relay (when the objects also
  carry a capture time), each relay's dwell, each link between relays, and
  the last relay to this viewer. The clocks are the machines' own, so a
  segment between two machines carries their clock offset.
- **Stream open → first object**: from a data stream's header to its first
  object.
- **Stream lifetime**: from a stream's header to its end, for streams that
  ended.
- **Per group**: the objects, the payload bytes, and the time from the first
  to the last object of a group, for groups whose stream ended.

**Reset counters** starts every card from zero.

## Track switch

A viewer can change rendition three ways. The experiment runs one switch and
measures it:

- **make-before-break** subscribes the new track, waits for its first object,
  then closes the old one. There is no hole; the two tracks overlap.
- **break-before-make** closes the old track, then subscribes the new one.
  There is no overlap; the viewer sees a hole until the first object.
- **switch-from** sends one SUBSCRIBE that carries the `SWITCH_FROM`
  parameter (soft mode) naming the old subscription. The relay drains the old
  track to the group before the new track's first group, then ends it with
  PUBLISH_DONE. The viewer sends one request and closes nothing. The new
  track starts at the group after the old track's last group, as the player
  does. This needs a
  relay that accepts `SWITCH_FROM`; a relay that does not closes the session
  over the unknown parameter.

Pick **From** (a subscribed track), **To** (a catalog track) and a mode, then
press **Run switch**. Each run adds a row:

| Column | Meaning |
| ------ | ------- |
| First object | Time from the switch to the new track's first object. |
| Overlap / hole | How long both tracks delivered at once, or how long neither did. For switch-from, also when the relay ended the old subscription relative to the new track's first object. |
| Groups | The old track's last group id and the new track's first. `contiguous` means the new track started at the next group; `skipped` or `repeated` give the gap. |

A switch that gets no object from the new track within 10 seconds is reported
as a timeout; the old subscription is left as the mode left it. A switch-from
that gets no PUBLISH_DONE for the old track within 3 seconds reports the old
track as not ended.

## Limits

- The inspector observes a subscriber session. It does not publish.
- Jitter and latency need capture timestamps on the objects. A publisher that
  does not stamp them gets interarrival only, and jitter from interval
  variation.
- The send-rate estimate and RTT come from the browser's WebTransport
  statistics. Chrome has no `WebTransport.getStats()`, so it shows
  "not exposed" in their place.
- Path segments need hop timestamps on the objects, which the relays stamp
  only when their hop-latency extension is on.
- Switch-from needs a relay that accepts the `SWITCH_FROM` parameter.
