Skip to content

Instantly share code, notes, and snippets.

@mondain
Created November 5, 2025 01:07
Show Gist options
  • Select an option

  • Save mondain/ea9cdaeb279322ecad7a5e2233b67bd5 to your computer and use it in GitHub Desktop.

Select an option

Save mondain/ea9cdaeb279322ecad7a5e2233b67bd5 to your computer and use it in GitHub Desktop.
Red5 MOQ Relay - Publish and Automatic Subscribe Flow

MoQ Relay: Publish and Automatic Subscribe Flow

This document describes the message sequence for publishing a track and automatic internal subscription in the red5-moq-relay server.

Sequence Diagram

%%{init: {'theme':'base', 'themeVariables': {'fontSize':'16px', 'fontFamily':'arial'}}}%%
sequenceDiagram
    participant Publisher as Publisher Client
    participant Control as MoQ Control Handler
    participant Relay as MoqRelayManager
    participant Internal as RecordingInternalSubscriber
    participant Data as MoQ Data Handler

    Note over Publisher,Internal: Phase 1: Session Setup (not shown)
    Note over Publisher,Internal: CLIENT_SETUP ↔ SERVER_SETUP exchange

    rect rgb(120, 160, 220)
        Note over Publisher,Internal: Phase 2: Namespace/Track Publication
        Publisher->>Control: PUBLISH_NAMESPACE(namespace="red5")
        Control->>Relay: registerPublishedNamespace()
        Relay-->>Internal: onNamespacePublished() notification
        Control->>Publisher: PUBLISH_NAMESPACE_OK

        Publisher->>Control: PUBLISH(trackNamespace="red5", trackName="catalog", alias=555704345)
        Control->>Relay: onTrackPublished(track)
        Relay-->>Internal: onTrackAvailable() notification
        Note right of Internal: Creates recording file<br/>red5_catalog_*.moq
        Control->>Publisher: PUBLISH_OK(alias=555704345, forward=1)
    end

    rect rgb(230, 160, 100)
        Note over Publisher,Internal: Phase 3: Internal Subscriber Triggers Data Flow
        Internal->>Relay: subscribeToTrack(track)
        Relay->>Control: sendSubscribeToPublisher()
        Control->>Publisher: SUBSCRIBE(id=2000006, namespace="red5", track="catalog", filter=LATEST_GROUP)
        Note right of Publisher: Publisher prepares to send data
        Publisher->>Control: SUBSCRIBE_OK(id=2000006, alias=555704345)
    end

    rect rgb(100, 190, 170)
        Note over Publisher,Internal: Phase 4: Data Delivery
        Publisher->>Data: Open unidirectional stream (stream_id=2)
        Publisher->>Data: SUBGROUP_HEADER(type=SUBGROUP_ZERO_ID, alias=1, group=4, subgroup=0)
        Note right of Data: Auto-creates track if needed<br/>based on namespace subscription

        Publisher->>Data: OBJECT(objectIdDelta=0, payload=128426 bytes)
        Data->>Relay: relayObject(object)
        Relay->>Internal: handleObject(object)
        Note right of Internal: Writes object to recording:<br/>group=4, object=1, 128426 bytes

        Publisher->>Data: OBJECT(objectIdDelta=0, payload=5120 bytes)
        Data->>Relay: relayObject(object)
        Relay->>Internal: handleObject(object)
        Note right of Internal: Writes to recording:<br/>group=4, object=2, 5120 bytes
    end

    rect rgb(200, 150, 220)
        Note over Publisher,Internal: Phase 5: Additional Tracks
        Publisher->>Control: PUBLISH(trackName="track-1", alias=1)
        Control->>Relay: tryAutoCreateTrackFromNamespaceSubscription()
        Note right of Relay: Auto-creates track from<br/>announced namespace
        Relay-->>Internal: onTrackAvailable() notification
        Control->>Publisher: PUBLISH_OK(alias=1, forward=1)

        Internal->>Relay: subscribeToTrack("track-1")
        Relay->>Control: sendSubscribeToPublisher()
        Control->>Publisher: SUBSCRIBE(id=2000007, track="track-1", filter=LATEST_GROUP)
        Publisher->>Control: SUBSCRIBE_OK(id=2000007, alias=1)

        Note over Publisher,Data: Data delivery continues as in Phase 4
    end
Loading

Message Details

Phase 1: Session Setup

  • CLIENT_SETUP: Publisher initiates session with supported versions and role
  • SERVER_SETUP: Server responds with negotiated version (e.g., draft-14)

Phase 2: Namespace/Track Publication

PUBLISH_NAMESPACE (0x06)

Message Type: 0x06
Track Namespace Prefix: tuple["red5"]
Parameters: 0

PUBLISH_NAMESPACE_OK (0x07)

Message Type: 0x07
Track Namespace Prefix: tuple["red5"]

PUBLISH (0x1D) - Draft-14 Publisher-Initiated Subscription

Message Type: 0x1D
Request ID: 8 (varint)
Track Namespace: tuple["red5"]
Track Name: "catalog" (length-prefixed string)
Track Alias: 555704345 (varint)
Subscriber Priority: 128 (u8)
Group Order: ASCENDING (0x01)
Forward: 1 (u8) - how many hops to forward subscription requests
Filter Type: LATEST_GROUP (0x01)
Parameters: 0

PUBLISH_OK (0x1E)

Message Type: 0x1E
Request ID: 8 (varint)
Track Alias: 555704345 (varint) - NOT included in response (already known)
Forward: 1 (u8)
Publisher Priority: 128 (u8)
Group Order: ASCENDING (0x01)
Filter Type: LATEST_GROUP (0x01)
Parameters: 0

Phase 3: Internal Subscriber Triggers Data Flow

SUBSCRIBE (0x03) - Sent by Relay to Publisher

Message Type: 0x03
Subscribe ID: 2000006 (varint) - unique ID for this subscription
Track Namespace: tuple["red5"]
Track Name: "catalog" (length-prefixed string)
Subscriber Priority: 128 (u8)
Group Order: PUBLISHER_PREFERENCE (0x00)
Filter Type: LATEST_GROUP (0x01)
Parameters: 0

Note: Draft-14 removed the Forward field from SUBSCRIBE. It only exists in PUBLISH/PUBLISH_OK.

SUBSCRIBE_OK (0x04)

Message Type: 0x04
Subscribe ID: 2000006 (varint)
Expires: 0 (varint) - no expiration
Group Order: ASCENDING (0x01)
Content Exists: true (0x01)
Largest Group ID: 4 (varint, optional)
Largest Object ID: 1 (varint, optional)

Phase 4: Data Delivery

SUBGROUP_HEADER (0x10) - On Unidirectional Data Stream

Stream Type: SUBGROUP_ZERO_ID (0x10)
Track Alias: 1 (varint)
Group ID: 4 (varint)
Subgroup ID: 0 (implicitly 0 for type 0x10)
Publisher Priority: 0 (u8)

Subgroup Header Types (draft-14 Section 10.4.2):

  • 0x10: SUBGROUP_ZERO_ID - subgroup ID = 0, no extensions
  • 0x11: SUBGROUP_ZERO_ID_EXT - subgroup ID = 0, with extensions
  • 0x12: SUBGROUP_FIRST_OBJECT_ID - subgroup ID = first object's ID
  • 0x14: SUBGROUP_ID - explicit subgroup ID field
  • 0x18-0x1D: Same as above but with end-of-group marker

OBJECT (Delta-Encoded) - On Same Stream After SUBGROUP_HEADER

Object ID Delta: 0 (varint) - first object, so delta = absolute ID
Payload Length: 128426 (varint)
Payload: [128426 bytes]

Delta Encoding Rules (draft-14 Section 10.4.2):

  • First object: objectId = objectIdDelta
  • Subsequent objects: objectId = previousObjectId + objectIdDelta + 1

Example:

Object 1: delta=0  → objectId = 0
Object 2: delta=0  → objectId = 0 + 0 + 1 = 1
Object 3: delta=1  → objectId = 1 + 1 + 1 = 3 (skipped object 2)

Phase 5: Track Auto-Creation

When a publisher sends data for a track not explicitly registered:

  1. Data arrives with unknown track alias
  2. Relay looks up announced namespaces for the publisher session
  3. Auto-creates track: namespace="red5", trackName="track-{alias}"
  4. Registers track in TrackRegistry
  5. Notifies internal subscribers (RecordingInternalSubscriber, etc.)
  6. Internal subscriber subscribes → Relay sends SUBSCRIBE to publisher
  7. Publisher responds with SUBSCRIBE_OK
  8. Data flows as normal

Key Components

MoqRelayManager

Central coordinator for:

  • Registering published namespaces and tracks
  • Managing subscriptions (both external and internal)
  • Forwarding objects from publishers to subscribers
  • Sending SUBSCRIBE messages to publishers for internal subscribers

Internal Subscribers

Built-in subscribers that automatically subscribe to tracks:

  1. RecordingInternalSubscriber

    • Records all objects to .moq files
    • Format: recordings/{namespace}_{track}_{timestamp}.moq
    • Can be disabled via recording.internal.subscriber.enabled=false
  2. Other Internal Subscribers (future)

    • Metrics collection
    • Transcoding
    • CDN replication

Control vs Data Streams

Control Stream (Bidirectional Stream 0 or 4):

  • PUBLISH_NAMESPACE, PUBLISH, SUBSCRIBE, etc.
  • Always bidirectional
  • One per session
  • For WebTransport: Stream 0 = HTTP/3 CONNECT handshake, Stream 4 = MoQ control

Data Streams (Unidirectional):

  • SUBGROUP_HEADER + OBJECT messages
  • One stream per subgroup (draft-14)
  • Publisher-initiated
  • Carries media payload

Draft-14 Specific Features

Publisher-Initiated Subscriptions (PUBLISH)

  • Publisher announces track availability via PUBLISH instead of waiting for SUBSCRIBE
  • Relay/subscriber can send SUBSCRIBE to request data delivery
  • Enables "push" model where publisher controls when to start sending

Subgroup Streams

  • Replaces legacy STREAM_HEADER_TRACK and STREAM_HEADER_GROUP
  • 12 variants (0x10-0x1D) encoding subgroup ID, extensions, and end-of-group
  • Delta-encoded object IDs for efficiency

Namespace Subscriptions

  • SUBSCRIBE_NAMESPACE: Subscribe to all tracks under a namespace prefix
  • Server automatically creates subscriptions when new tracks are published
  • Simplifies subscription management for large track sets (e.g., conference rooms)

Configuration

Internal subscribers can be configured in server.properties:

# Enable/disable recording internal subscriber
recording.internal.subscriber.enabled=true

# Recording output directory
recording.output.dir=recordings/

# Recording file format: moq, mp4, etc.
recording.format=moq

Error Handling

If track alias is unknown when data arrives:

  1. Relay attempts auto-creation from announced namespaces
  2. If no namespace announced → objects dropped + error logged
  3. If namespace exists → track auto-created as namespace/track-{alias}

This auto-creation enables:

  • Simplified publisher implementation (just send data with alias)
  • Namespace-based authorization (announce namespace first, then send any track)
  • Dynamic track discovery without pre-registration

References

  • draft-ietf-moq-transport-14: Main MoQ Transport specification
  • Section 9.13-9.15: PUBLISH/PUBLISH_OK/PUBLISH_ERROR messages
  • Section 10.4.2: Subgroup stream format and delta encoding
  • Section 9.7: SUBSCRIBE message format
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment