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

Namespaces in MOQT provide hierarchical organization for tracks, similar to file system directories. Publishers announce namespaces to make their tracks discoverable by subscribers. The namespace system enables efficient filtering and routing of content across the network.
A namespace is a tuple of string segments (e.g., ['live', 'conference', 'room42']) that forms the first part of a track’s full name.

Namespace Structure

Namespaces are represented by the Tuple class:
import { Tuple } from 'moqtail-ts'

// Create from string path
const namespace = Tuple.fromUtf8Path('live/conference/room42')
console.log(namespace.fields) // ['live', 'conference', 'room42']

// Create from array
const namespace2 = Tuple.tryNew(['media', 'camera', 'main'])

// Convert back to path
console.log(namespace.toUtf8Path()) // 'live/conference/room42'
  • Namespace must have 1-32 segments
  • Empty segments are allowed except at start/end
  • Total serialized length (namespace + track name) must be under 4096 bytes

Full Track Names

A full track name combines namespace and name:
import { FullTrackName, Tuple } from 'moqtail-ts'

// Using string path (namespace/name)
const trackName = FullTrackName.tryNew('live/conference', 'video')

// Using Tuple
const namespace = Tuple.tryNew(['live', 'conference'])
const trackName2 = FullTrackName.tryNew(namespace, 'audio')

// String representation
console.log(trackName.toString())
// Output: live/conference:766964656f (name in hex)

Naming Best Practices

1

Use Hierarchical Structure

Organize namespaces to reflect your application hierarchy:
// Good: hierarchical and descriptive
'app/feature/resource'
'live/event/stadium-a'
'vod/movies/action'
'sensors/building-1/floor-3'

// Avoid: flat structure
'video-stream-42'
'data'
2

Consistent Separators

Always use / as the separator:
// Good
FullTrackName.tryNew('media/conference/room', 'video')

// Avoid mixing separators
// 'media-conference/room' or 'media.conference.room'
3

Descriptive Names

Use clear, self-documenting names:
// Good
FullTrackName.tryNew('broadcast/sports/game-123', 'video-high')
FullTrackName.tryNew('broadcast/sports/game-123', 'video-low')
FullTrackName.tryNew('broadcast/sports/game-123', 'audio-english')

// Avoid abbreviations
FullTrackName.tryNew('bc/sp/g123', 'v-h')

Publishing Namespaces

Before subscribers can discover your tracks, you must announce the namespace:
import { MOQtailClient } from 'moqtail-ts'

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

// Announce namespace (array form)
const result = await client.publishNamespace(['live', 'conference'])

if (result instanceof PublishNamespaceError) {
  console.error(`Failed to announce: ${result.reasonPhrase}`)
} else {
  console.log('Namespace announced successfully')
}

Announcement Workflow

1

Register Tracks

Add all tracks under the namespace first:
const videoTrack: Track = {
  fullTrackName: FullTrackName.tryNew('live/conference', 'video'),
  forwardingPreference: ObjectForwardingPreference.Subgroup,
  trackSource: { live: videoSource },
  publisherPriority: 0
}

const audioTrack: Track = {
  fullTrackName: FullTrackName.tryNew('live/conference', 'audio'),
  forwardingPreference: ObjectForwardingPreference.Datagram,
  trackSource: { live: audioSource },
  publisherPriority: 8
}

client.addOrUpdateTrack(videoTrack)
client.addOrUpdateTrack(audioTrack)
2

Announce Namespace

Make the namespace discoverable:
await client.publishNamespace(['live', 'conference'])
3

Handle Subscriptions

Subscribers can now discover and subscribe to tracks under this namespace.

Namespace Discovery

Subscribers discover namespaces by subscribing to namespace prefixes:
// Subscriber code
import { SubscribeNamespace, Tuple } from 'moqtail-ts'

// Subscribe to all 'live' namespaces
await client.subscribeNamespace(Tuple.tryNew(['live']))

// Client will receive announcements for:
// - live/conference
// - live/sports
// - live/music
// etc.

Publisher-Side Handlers

Publishers can track namespace subscription requests:
const client = await MOQtailClient.new({
  url: 'https://relay.example.com',
  supportedVersions: [0xff00000b],
  callbacks: {
    onPeerSubscribeNamespace: (msg) => {
      console.log('Peer subscribed to namespace:', msg.namespace.toUtf8Path())
    }
  }
})

Stopping Announcements

When you’re done publishing a namespace:
import { PublishNamespaceDone } from 'moqtail-ts'

// Stop announcing namespace
await client.publishNamespaceDone(['live', 'conference'])
Stopping an announcement notifies subscribers that tracks under this namespace are no longer available, but doesn’t cancel active subscriptions immediately.

Complete Publisher Example

Here’s a complete example with multiple tracks under a namespace:
import {
  MOQtailClient,
  FullTrackName,
  Track,
  LiveTrackSource,
  ObjectForwardingPreference,
  MoqtObject
} from 'moqtail-ts'

class ConferencePublisher {
  private client?: MOQtailClient
  private namespace: string[]
  
  constructor(
    private roomId: string
  ) {
    this.namespace = ['conference', roomId]
  }
  
  async start(relayUrl: string) {
    // 1. Connect to relay
    this.client = await MOQtailClient.new({
      url: relayUrl,
      supportedVersions: [0xff00000b],
      callbacks: {
        onPeerSubscribeNamespace: (msg) => {
          console.log('Subscriber interested in:', msg.namespace.toUtf8Path())
        },
        onNamespaceDone: (msg) => {
          console.log('Namespace ended:', msg.namespace.toUtf8Path())
        }
      }
    })
    
    // 2. Create tracks
    const tracks = await this.createTracks()
    
    // 3. Register all tracks
    for (const track of tracks) {
      this.client.addOrUpdateTrack(track)
    }
    
    // 4. Announce namespace
    const result = await this.client.publishNamespace(this.namespace)
    
    if (result instanceof PublishNamespaceError) {
      throw new Error(`Failed to announce namespace: ${result.reasonPhrase}`)
    }
    
    console.log(`Publishing namespace: ${this.namespace.join('/')}`)
  }
  
  private async createTracks(): Promise<Track[]> {
    // Get media streams
    const mediaStream = await navigator.mediaDevices.getUserMedia({
      video: { width: 1920, height: 1080 },
      audio: true
    })
    
    // Create video track
    const videoStream = this.createVideoStream(mediaStream.getVideoTracks()[0])
    const videoTrack: Track = {
      fullTrackName: FullTrackName.tryNew(this.namespace.join('/'), 'video'),
      forwardingPreference: ObjectForwardingPreference.Subgroup,
      trackSource: { live: new LiveTrackSource(videoStream) },
      publisherPriority: 0
    }
    
    // Create audio track
    const audioStream = this.createAudioStream(mediaStream.getAudioTracks()[0])
    const audioTrack: Track = {
      fullTrackName: FullTrackName.tryNew(this.namespace.join('/'), 'audio'),
      forwardingPreference: ObjectForwardingPreference.Datagram,
      trackSource: { live: new LiveTrackSource(audioStream) },
      publisherPriority: 8
    }
    
    // Create metadata track (chat, participant list, etc.)
    const metadataStream = this.createMetadataStream()
    const metadataTrack: Track = {
      fullTrackName: FullTrackName.tryNew(this.namespace.join('/'), 'metadata'),
      forwardingPreference: ObjectForwardingPreference.Subgroup,
      trackSource: { live: new LiveTrackSource(metadataStream) },
      publisherPriority: 32
    }
    
    return [videoTrack, audioTrack, metadataTrack]
  }
  
  private createVideoStream(track: MediaStreamTrack): ReadableStream<MoqtObject> {
    // Implementation from live-streaming.mdx
    return new ReadableStream<MoqtObject>({/* ... */})
  }
  
  private createAudioStream(track: MediaStreamTrack): ReadableStream<MoqtObject> {
    // Implementation from live-streaming.mdx
    return new ReadableStream<MoqtObject>({/* ... */})
  }
  
  private createMetadataStream(): ReadableStream<MoqtObject> {
    return new ReadableStream<MoqtObject>({
      start: (controller) => {
        // Emit periodic metadata updates
        setInterval(() => {
          const metadata = JSON.stringify({
            timestamp: Date.now(),
            participants: this.getParticipants(),
            messages: this.getRecentMessages()
          })
          
          const object = MoqtObject.newWithPayload(
            FullTrackName.tryNew(this.namespace.join('/'), 'metadata'),
            new Location(Date.now(), 0n),
            32,
            ObjectForwardingPreference.Subgroup,
            null,
            null,
            new TextEncoder().encode(metadata)
          )
          
          controller.enqueue(object)
        }, 1000)
      }
    })
  }
  
  async stop() {
    if (!this.client) return
    
    // Announce namespace is done
    await this.client.publishNamespaceDone(this.namespace)
    
    // Disconnect
    await this.client.disconnect()
    
    console.log(`Stopped publishing namespace: ${this.namespace.join('/')}`)
  }
  
  private getParticipants() {
    // Return current participants
    return []
  }
  
  private getRecentMessages() {
    // Return recent chat messages
    return []
  }
}

// Usage
const publisher = new ConferencePublisher('room-42')
await publisher.start('https://relay.example.com')

// Later, when conference ends
await publisher.stop()

Hierarchical Namespace Management

Organize complex applications with namespace hierarchies:
// Multi-tenant application
class MultiTenantPublisher {
  async publishForTenant(tenantId: string, applicationId: string) {
    const client = await MOQtailClient.new({/* ... */})
    
    // Announce top-level namespace for tenant
    await client.publishNamespace([tenantId])
    
    // Announce application namespace
    await client.publishNamespace([tenantId, applicationId])
    
    // Add tracks under application namespace
    const videoTrack: Track = {
      fullTrackName: FullTrackName.tryNew(
        `${tenantId}/${applicationId}/streams`,
        'video-main'
      ),
      // ...
    }
    
    client.addOrUpdateTrack(videoTrack)
    
    // Subscribers can discover:
    // - All content: subscribeNamespace([tenantId])
    // - Specific app: subscribeNamespace([tenantId, applicationId])
    // - Specific streams: subscribeNamespace([tenantId, applicationId, 'streams'])
  }
}

Error Handling

Handle namespace announcement failures:
import { PublishNamespaceError } from 'moqtail-ts'

const result = await client.publishNamespace(['live', 'conference'])

if (result instanceof PublishNamespaceError) {
  switch (result.errorCode) {
    case PublishNamespaceErrorCode.Forbidden:
      console.error('Not authorized to publish this namespace')
      break
    case PublishNamespaceErrorCode.InvalidNamespace:
      console.error('Invalid namespace format')
      break
    default:
      console.error(`Announcement failed: ${result.reasonPhrase}`)
  }
} else {
  console.log('Namespace announced successfully')
}

Best Practices

Namespace Granularity

  • Use 2-4 levels for most applications
  • Too shallow: poor filtering (['content'])
  • Too deep: hard to manage (['app', 'region', 'datacenter', 'rack', 'server', 'process', 'stream'])
  • Just right: ['app', 'feature', 'resource']

Track Organization

Group related tracks under the same namespace:
// Good: related tracks together
'conference/room42''video', 'audio', 'chat'

// Avoid: unrelated tracks
'conference''room42-video', 'room42-audio', 'room99-video'

Lifecycle Management

// Announce when ready
await client.publishNamespace(namespace)

// Track active namespaces
const activeNamespaces = new Set<string>()
activeNamespaces.add(namespace.join('/'))

// Clean up on shutdown
for (const ns of activeNamespaces) {
  await client.publishNamespaceDone(ns.split('/'))
}

Discovery Optimization

Announce at appropriate levels for discovery:
// Announce both parent and specific namespaces
await client.publishNamespace(['live'])           // For broad discovery
await client.publishNamespace(['live', 'sports']) // For specific discovery

Next Steps

Creating Tracks

Learn the fundamentals of track creation

Live Streaming

Publish real-time content to subscribers