Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/moqtail/moqtail/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The subscribe() method enables you to consume real-time streaming content from publishers. Subscribe operations are optimized for live delivery with intelligent stream management and automatic cancellation of outdated content during network congestion.
Subscribe operations are designed for live streaming scenarios where you want to receive content as it’s being produced. For historical or static content, use the fetch method instead.

Transport Mechanisms

Subscribe operations support multiple transport mechanisms for optimal performance:

Datagrams

Low-latency delivery where occasional packet loss is acceptable

Multiple Streams

Each group (GOP) delivered in a separate stream for better prioritization

Stream Cancellation

The library implements intelligent stream cancellation on both sides:
  • Publisher Side: Automatically cancels streams for older groups when bandwidth is limited
  • Subscriber Side: Cancels streams for groups that are no longer needed due to latency constraints
This ensures subscribers always receive the most recent content with minimal latency, automatically dropping outdated frames during network congestion.

Creating a Subscription

1

Initialize the client

First, create a MOQtail client connection:
const client = await MOQtailClient.new({
  url: 'https://relay.example.com/transport',
  supportedVersions: [0xff00000b]
});
2

Configure subscription parameters

Create a subscription request with your desired filter type:
import { FilterType, GroupOrder } from 'moqtail';

const result = await client.subscribe({
  fullTrackName: FullTrackName.tryNew('live/conference', 'video'),
  filterType: FilterType.LatestObject,
  forward: true,
  groupOrder: GroupOrder.Original,
  priority: 0 // Highest priority (0-255)
});
3

Handle subscription response

Check if the subscription was successful:
if (result instanceof SubscribeError) {
  console.error(`Subscription failed: ${result.errorReason.phrase}`);
  // Handle error based on error code
  switch (result.errorCode) {
    case SubscribeErrorCode.InvalidRange:
      // Adjust range and retry
      break;
    case SubscribeErrorCode.TrackDoesNotExist:
      console.error('Track not found');
      break;
    default:
      console.error(`Unknown error: ${result.errorReason.phrase}`);
  }
} else {
  // Success - result contains requestId and stream
  const { requestId, stream } = result;
  console.log(`Subscribed successfully with ID: ${requestId}`);
}
4

Process incoming objects

Consume objects from the stream:
const reader = stream.getReader();

try {
  while (true) {
    const { done, value: object } = await reader.read();
    if (done) break;

    // Process each object
    console.log(`Received object ${object.objectId} from group ${object.groupId}`);
    processObject(object);
  }
} finally {
  reader.releaseLock();
}

Filter Types

The filterType parameter determines where the subscription starts:

LatestObject

Subscribe to the most recent available object and continue receiving new objects:
const result = await client.subscribe({
  fullTrackName,
  filterType: FilterType.LatestObject,
  forward: true,
  groupOrder: GroupOrder.Original,
  priority: 32
});
This is the most common filter type for live streaming applications where you want to join at the current position.

NextGroupStart

Wait for the next group boundary before receiving objects:
const result = await client.subscribe({
  fullTrackName,
  filterType: FilterType.NextGroupStart,
  forward: true,
  groupOrder: GroupOrder.Original,
  priority: 32
});

AbsoluteStart

Start from a specific location (group and object ID):
import { Location } from 'moqtail';

const result = await client.subscribe({
  fullTrackName,
  filterType: FilterType.AbsoluteStart,
  startLocation: new Location(100n, 0n), // Group 100, Object 0
  forward: true,
  groupOrder: GroupOrder.Original,
  priority: 32
});
If the start location is less than the latest object at the publisher, the subscription behaves as LatestObject.

AbsoluteRange

Subscribe to a specific range of groups:
const result = await client.subscribe({
  fullTrackName,
  filterType: FilterType.AbsoluteRange,
  startLocation: new Location(100n, 0n),
  endGroup: 200n, // Stop at group 200
  forward: true,
  groupOrder: GroupOrder.Original,
  priority: 32
});

Processing Objects

Each MoqtObject has a status indicating its type:
import { ObjectStatus } from 'moqtail';

function processObject(object: MoqtObject) {
  switch (object.objectStatus) {
    case ObjectStatus.Normal:
      // Regular data object with payload
      if (object.payload) {
        processMediaData(object.payload);
      }
      break;
      
    case ObjectStatus.ObjectDoesNotExist:
      // Object was not available
      console.warn(`Missing object ${object.objectId} in group ${object.groupId}`);
      break;
      
    case ObjectStatus.GroupDoesNotExist:
      // Entire group was not available
      console.warn(`Missing group ${object.groupId}`);
      break;
      
    case ObjectStatus.EndOfGroup:
      // Marks the end of a group (GOP)
      finalizeGroup(object.groupId);
      break;
      
    case ObjectStatus.EndOfTrack:
      // Marks the end of the track
      console.log('Track ended');
      finalizeTrack();
      break;
  }
}

Managing Subscriptions

Updating a Subscription

You can update an active subscription to change its range or forwarding behavior:
await client.subscribeUpdate({
  subscriptionRequestId: requestId,
  startLocation: new Location(150n, 0n), // Move start forward
  endGroup: 250n, // Update end group
  forward: true,
  priority: 64 // Change priority
});
You can only narrow the subscription window, not expand it. Moving the start earlier or end later will result in an error.

Unsubscribing

Stop receiving objects by unsubscribing:
await client.unsubscribe(requestId);
Canceling the stream reader does NOT automatically unsubscribe. Always call unsubscribe() explicitly for proper cleanup.

Complete Example

Here’s a complete example of subscribing to a live video track:
import { MOQtailClient, FilterType, GroupOrder, SubscribeError } from 'moqtail';

async function subscribeLiveVideo() {
  // Initialize client
  const client = await MOQtailClient.new({
    url: 'https://relay.example.com/transport',
    supportedVersions: [0xff00000b]
  });

  // Subscribe to live video
  const result = await client.subscribe({
    fullTrackName: FullTrackName.tryNew('live/conference', 'video'),
    filterType: FilterType.LatestObject,
    forward: true,
    groupOrder: GroupOrder.Original,
    priority: 0 // Highest priority
  });

  if (result instanceof SubscribeError) {
    console.error(`Failed to subscribe: ${result.errorReason.phrase}`);
    return;
  }

  const { requestId, stream } = result;
  console.log(`Subscribed successfully with ID: ${requestId}`);

  // Process the stream
  const reader = stream.getReader();
  try {
    while (true) {
      const { done, value: object } = await reader.read();
      if (done) break;

      if (object.objectStatus === ObjectStatus.Normal && object.payload) {
        // Decode and render video frame
        await decodeAndRenderFrame(object);
      }
    }
  } finally {
    reader.releaseLock();
    await client.unsubscribe(requestId);
  }
}

Priority Management

Priority values range from 0 (highest) to 255 (lowest):
// Critical live video
const videoResult = await client.subscribe({
  fullTrackName: videoTrackName,
  filterType: FilterType.LatestObject,
  forward: true,
  groupOrder: GroupOrder.Original,
  priority: 0 // Highest priority
});

// Lower priority audio
const audioResult = await client.subscribe({
  fullTrackName: audioTrackName,
  filterType: FilterType.LatestObject,
  forward: true,
  groupOrder: GroupOrder.Original,
  priority: 32
});

// Background data
const dataResult = await client.subscribe({
  fullTrackName: dataTrackName,
  filterType: FilterType.LatestObject,
  forward: true,
  groupOrder: GroupOrder.Original,
  priority: 128 // Lower priority
});

Next Steps

Fetching Historical Content

Learn how to retrieve historical data

Playout Buffer

Implement smooth media playback