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.

This guide will help you build a complete working example with both a subscriber and publisher using Moqtail.

Prerequisites

Before you begin, make sure you have:
  • Node.js 18 or later installed
  • A running MOQtail relay server (see Relay Server Setup)
  • WebTransport-enabled browser (Chrome/Edge recommended)

Installation

First, install the Moqtail package:
npm install moqtail

Building a Subscriber

Let’s start by creating a subscriber that consumes live video content.
1

Establish WebTransport connection

Connect to your relay server using WebTransport:
const url = 'https://localhost:4433'
const webTransport = new WebTransport(url)
await webTransport.ready
2

Initialize the MOQtail client

Create a client with role configuration:
import { MOQtailClient, ClientSetup, RoleType } from 'moqtail/client'

const clientSetup = new ClientSetup(
  [0],  // Supported versions (Draft 14)
  new SetupParameters()
    .withRole(RoleType.Subscriber)
    .withDeliveryTimeout(5000)
)

const client = await MOQtailClient.new(clientSetup, webTransport)
3

Subscribe to a track

Subscribe to live content and process incoming objects:
import { Subscribe, FilterType, FullTrackName } from 'moqtail/model'

// Subscribe to latest content
const subscribe = Subscribe.newLatestObject(
  client.nextClientRequestId,
  1n, // trackAlias
  FullTrackName.tryNew('live/conference', 'video'),
  1n  // subscriberId
)

const result = await client.subscribe(subscribe)

if (result instanceof SubscribeError) {
  console.error('Subscription failed:', result.reasonPhrase)
  return
}

// Process the stream
const reader = result.getReader()
while (true) {
  const { done, value: object } = await reader.read()
  if (done) break
  
  console.log(`Received object ${object.objectId} from group ${object.groupId}`)
  // Process the media data in object.payload
  processMediaFrame(object.payload)
}

Building a Publisher

Now let’s create a publisher that produces live video content.
1

Connect and initialize

Set up the WebTransport connection and client with publisher role:
const webTransport = new WebTransport('https://localhost:4433')
await webTransport.ready

const clientSetup = new ClientSetup(
  [0],
  new SetupParameters().withRole(RoleType.Publisher)
)

const client = await MOQtailClient.new(clientSetup, webTransport)
2

Create a content source

Set up a live content source with a stream of objects:
import { LiveTrackSource, MoqtObject, Location } from 'moqtail'

// Create a stream of video frames
const frameStream = new ReadableStream({
  async start(controller) {
    let groupId = 0n
    let objectId = 0n
    
    // Simulate video encoding (replace with real encoder)
    const interval = setInterval(() => {
      const payload = encodeVideoFrame()
      const object = MoqtObject.newWithPayload(
        new Location(groupId, objectId),
        payload,
        1  // publisherPriority
      )
      
      controller.enqueue(object)
      objectId++
      
      // Start new group every 30 frames (keyframe interval)
      if (objectId % 30n === 0n) {
        groupId++
        objectId = 0n
      }
    }, 33) // ~30fps
    
    // Cleanup on stream cancellation
    return () => clearInterval(interval)
  }
})

const contentSource = new LiveTrackSource(frameStream)
3

Create and register the track

Define your track and add it to the client:
import { Track, ObjectForwardingPreference } from 'moqtail'

const videoTrack: Track = {
  fullTrackName: FullTrackName.tryNew('live/conference', 'video'),
  trackAlias: 1n,
  forwardingPreference: ObjectForwardingPreference.Subgroup,
  contentSource: contentSource
}

client.addOrUpdateTrack(videoTrack)
4

Announce the namespace

Publish your namespace so subscribers can discover your tracks:
import { PublishNamespace, Tuple } from 'moqtail/model'

const announce = new PublishNamespace(
  client.nextClientRequestId,
  Tuple.tryNew(['live', 'conference'])
)

const result = await client.publishNamespace(announce)

if (result instanceof PublishNamespaceError) {
  console.error('Failed to announce:', result.reasonPhrase)
  return
}

console.log('Successfully publishing namespace!')

Complete Example

Here’s a complete working example that combines both patterns:
import { MOQtailClient, ClientSetup, RoleType, SetupParameters } from 'moqtail/client'
import { Subscribe, FullTrackName, SubscribeError } from 'moqtail/model'

async function createSubscriber() {
  // Connect to relay
  const webTransport = new WebTransport('https://localhost:4433')
  await webTransport.ready
  
  // Initialize client
  const clientSetup = new ClientSetup(
    [0],
    new SetupParameters()
      .withRole(RoleType.Subscriber)
      .withDeliveryTimeout(5000)
  )
  
  const client = await MOQtailClient.new(clientSetup, webTransport)
  
  // Subscribe to latest content
  const subscribe = Subscribe.newLatestObject(
    client.nextClientRequestId,
    1n,
    FullTrackName.tryNew('live/conference', 'video'),
    1n
  )
  
  const result = await client.subscribe(subscribe)
  
  if (result instanceof SubscribeError) {
    throw new Error(`Subscription failed: ${result.reasonPhrase}`)
  }
  
  // Process incoming objects
  const reader = result.getReader()
  
  try {
    while (true) {
      const { done, value: object } = await reader.read()
      if (done) break
      
      console.log(`Received object ${object.objectId} from group ${object.groupId}`)
      // Process media data
      await decodeAndRender(object.payload)
    }
  } finally {
    reader.releaseLock()
  }
}

createSubscriber().catch(console.error)
import { MOQtailClient, ClientSetup, RoleType, SetupParameters } from 'moqtail/client'
import { 
  PublishNamespace, 
  Tuple, 
  FullTrackName, 
  PublishNamespaceError 
} from 'moqtail/model'
import { 
  Track, 
  LiveTrackSource, 
  MoqtObject, 
  Location, 
  ObjectForwardingPreference 
} from 'moqtail'

async function createPublisher() {
  // Connect to relay
  const webTransport = new WebTransport('https://localhost:4433')
  await webTransport.ready
  
  // Initialize client
  const clientSetup = new ClientSetup(
    [0],
    new SetupParameters().withRole(RoleType.Publisher)
  )
  
  const client = await MOQtailClient.new(clientSetup, webTransport)
  
  // Create live content stream
  let groupId = 0n
  let objectId = 0n
  
  const frameStream = new ReadableStream({
    start(controller) {
      const interval = setInterval(() => {
        const payload = captureAndEncodeFrame()
        const object = MoqtObject.newWithPayload(
          new Location(groupId, objectId),
          payload,
          1
        )
        
        controller.enqueue(object)
        objectId++
        
        // New GOP every 30 frames
        if (objectId % 30n === 0n) {
          groupId++
          objectId = 0n
        }
      }, 33) // 30fps
      
      return () => clearInterval(interval)
    }
  })
  
  // Create and register track
  const videoTrack: Track = {
    fullTrackName: FullTrackName.tryNew('live/conference', 'video'),
    trackAlias: 1n,
    forwardingPreference: ObjectForwardingPreference.Subgroup,
    contentSource: new LiveTrackSource(frameStream)
  }
  
  client.addOrUpdateTrack(videoTrack)
  
  // Announce namespace
  const announce = new PublishNamespace(
    client.nextClientRequestId,
    Tuple.tryNew(['live', 'conference'])
  )
  
  const result = await client.publishNamespace(announce)
  
  if (result instanceof PublishNamespaceError) {
    throw new Error(`Failed to announce: ${result.reasonPhrase}`)
  }
  
  console.log('Successfully publishing live content!')
}

createPublisher().catch(console.error)

Next Steps

Core Concepts

Understand tracks, objects, and the MOQT protocol

Publisher Guide

Learn advanced publishing patterns

Subscriber Guide

Master subscription and fetching strategies

API Reference

Explore the complete API documentation

Troubleshooting

Make sure:
  • Your relay server is running on the correct port
  • You have valid TLS certificates (see WebTransport Setup)
  • Your browser trusts the certificates (for development, install the CA)
  • The browser supports WebTransport (Chrome/Edge 97+)
Check that:
  • The publisher has announced the namespace
  • The track name matches exactly (namespace and track name)
  • The publisher’s track is active and generating objects
  • Network connectivity between client and relay is stable
This is expected behavior for datagram delivery. To ensure ordering:
  • Use ObjectForwardingPreference.Subgroup for ordered delivery
  • Implement a playout buffer (see Playout Buffer)
  • Sort objects by their Location (groupId, objectId)