Documentation Index Fetch the complete documentation index at: https://mintlify.com/moq-dev/moq/llms.txt
Use this file to discover all available pages before exploring further.
@moq/hang provides media-specific encoding and decoding on top of @moq/lite, with WebCodecs integration for handling audio and video.
Installation
npm install @moq/hang @moq/lite
Version : 0.2.0
License : MIT OR Apache-2.0
Repository : github:moq-dev/moq
Dependencies : @moq/lite, @moq/signals, zod, libavjs polyfill, SVTA CML
Overview
Hang provides the media layer for MoQ, handling:
Catalog : JSON track describing available media tracks and their codec properties
Container : Encoding/decoding of timestamped codec bitstreams
WebCodecs Integration : Work with VideoEncoder, AudioEncoder, VideoDecoder, AudioDecoder
Exports
import * as Catalog from "@moq/hang/catalog" ;
import * as Container from "@moq/hang/container" ;
import * as Moq from "@moq/hang/Moq" ;
import * as Signals from "@moq/hang/Signals" ;
The library exports:
Catalog - Catalog format for describing tracks
Container - Container format for framing codec data
Moq - Re-export of @moq/lite
Signals - Re-export of @moq/signals
Catalog
The catalog is a JSON object describing the available tracks and their properties.
import type { Catalog } from "@moq/hang/catalog" ;
const catalog : Catalog = {
version: 1 ,
tracks: [
{
name: "video" ,
kind: "video" ,
codec: "avc1.64001f" , // H.264 High Profile
width: 1920 ,
height: 1080 ,
framerate: 30 ,
bitrate: 5000000
},
{
name: "audio" ,
kind: "audio" ,
codec: "opus" ,
sampleRate: 48000 ,
channels: 2 ,
bitrate: 128000
}
]
};
Publishing a Catalog
import * as Catalog from "@moq/hang/catalog" ;
import { Track } from "@moq/lite" ;
const catalogTrack = new Track ( ".catalog" );
const catalogData = Catalog . encode ({
version: 1 ,
tracks: [
{ name: "video" , kind: "video" , codec: "avc1.64001f" },
{ name: "audio" , kind: "audio" , codec: "opus" }
]
});
const group = catalogTrack . appendGroup ();
await group . write ( catalogData );
await group . close ();
Reading a Catalog
import * as Catalog from "@moq/hang/catalog" ;
// Read catalog track
const catalogTrack = await subscriber . subscribe ({ name: ".catalog" });
for await ( const group of catalogTrack . groups ()) {
for await ( const frame of group . frames ()) {
const catalog = Catalog . decode ( frame );
console . log ( "Available tracks:" , catalog . tracks );
}
}
Container
The container format handles timestamped codec bitstreams.
Each frame in the container consists of:
Timestamp (microseconds)
Codec-specific bitstream data
Encoding Video Frames
import * as Container from "@moq/hang/container" ;
import { Track } from "@moq/lite" ;
const videoTrack = new Track ( "video" );
// Create a VideoEncoder
const encoder = new VideoEncoder ({
output : async ( chunk , metadata ) => {
// Encode the chunk with timestamp
const data = Container . encodeVideo ({
timestamp: chunk . timestamp ,
type: chunk . type , // "key" or "delta"
data: new Uint8Array ( chunk . byteLength ),
metadata: metadata
});
// Write to track
const group = videoTrack . appendGroup ();
await group . write ( data );
await group . close ();
},
error : ( e ) => console . error ( "Encode error:" , e )
});
encoder . configure ({
codec: "avc1.64001f" ,
width: 1920 ,
height: 1080 ,
bitrate: 5_000_000 ,
framerate: 30
});
// Encode frames
const frame = new VideoFrame ( videoElement , { timestamp: 0 });
encoder . encode ( frame );
frame . close ();
Decoding Video Frames
import * as Container from "@moq/hang/container" ;
const decoder = new VideoDecoder ({
output : ( frame ) => {
// Display the decoded frame
ctx . drawImage ( frame , 0 , 0 );
frame . close ();
},
error : ( e ) => console . error ( "Decode error:" , e )
});
// Configure from catalog
decoder . configure ({
codec: catalogTrack . codec ,
codedWidth: catalogTrack . width ,
codedHeight: catalogTrack . height
});
// Decode frames from track
for await ( const group of videoTrack . groups ()) {
for await ( const frame of group . frames ()) {
const decoded = Container . decodeVideo ( frame );
const chunk = new EncodedVideoChunk ({
type: decoded . type ,
timestamp: decoded . timestamp ,
data: decoded . data
});
decoder . decode ( chunk );
}
}
Encoding Audio Frames
import * as Container from "@moq/hang/container" ;
const audioEncoder = new AudioEncoder ({
output : async ( chunk , metadata ) => {
const data = Container . encodeAudio ({
timestamp: chunk . timestamp ,
type: chunk . type ,
data: new Uint8Array ( chunk . byteLength ),
metadata: metadata
});
const group = audioTrack . appendGroup ();
await group . write ( data );
await group . close ();
},
error : ( e ) => console . error ( "Audio encode error:" , e )
});
audioEncoder . configure ({
codec: "opus" ,
sampleRate: 48000 ,
numberOfChannels: 2 ,
bitrate: 128000
});
Decoding Audio Frames
const audioDecoder = new AudioDecoder ({
output : ( frame ) => {
// Play the decoded audio frame
// ...
frame . close ();
},
error : ( e ) => console . error ( "Audio decode error:" , e )
});
audioDecoder . configure ({
codec: "opus" ,
sampleRate: 48000 ,
numberOfChannels: 2
});
for await ( const group of audioTrack . groups ()) {
for await ( const frame of group . frames ()) {
const decoded = Container . decodeAudio ( frame );
const chunk = new EncodedAudioChunk ({
type: decoded . type ,
timestamp: decoded . timestamp ,
data: decoded . data
});
audioDecoder . decode ( chunk );
}
}
WebCodecs Support
Hang is designed to work with the WebCodecs API:
VideoEncoder / VideoDecoder
AudioEncoder / AudioDecoder
VideoFrame / AudioData
EncodedVideoChunk / EncodedAudioChunk
Supported Codecs
Video:
H.264 (avc1)
H.265 (hev1)
VP8
VP9
AV1
Audio:
Utilities
The util export provides helper functions:
import * as Util from "@moq/hang/util" ;
// Timestamp utilities
const microseconds = Util . nowMicroseconds ();
const milliseconds = Util . microsToMillis ( microseconds );
// Codec utilities
const codecInfo = Util . parseCodec ( "avc1.64001f" );
Browser Support
WebCodecs is supported in:
Chrome/Edge 94+
Opera 80+
Safari 16.4+ (limited codec support)
Node.js Support
For Node.js, use libavjs polyfill (automatically included):
import "@kixelated/libavjs-webcodecs-polyfill" ;
Complete Example
Here’s a complete example publishing video with catalog:
import * as Connection from "@moq/lite/Connection" ;
import { Broadcast , Track } from "@moq/lite" ;
import * as Catalog from "@moq/hang/catalog" ;
import * as Container from "@moq/hang/container" ;
// Connect and publish
const conn = await Connection . connect ({ url: "https://relay.quic.video" });
const broadcast = await conn . publish ( "my-stream" );
// Publish catalog
const catalogTrack = new Track ( ".catalog" );
await broadcast . announce ( catalogTrack );
const catalogData = Catalog . encode ({
version: 1 ,
tracks: [{ name: "video" , kind: "video" , codec: "avc1.64001f" }]
});
const catalogGroup = catalogTrack . appendGroup ();
await catalogGroup . write ( catalogData );
await catalogGroup . close ();
// Publish video
const videoTrack = new Track ( "video" );
await broadcast . announce ( videoTrack );
const encoder = new VideoEncoder ({
output : async ( chunk ) => {
const data = Container . encodeVideo ({
timestamp: chunk . timestamp ,
type: chunk . type ,
data: new Uint8Array ( chunk . byteLength )
});
const group = videoTrack . appendGroup ();
await group . write ( data );
await group . close ();
},
error : ( e ) => console . error ( e )
});
encoder . configure ({
codec: "avc1.64001f" ,
width: 1920 ,
height: 1080
});
Next Steps
@moq/watch Use the watch component to display media
@moq/publish Use the publish component to capture media
@moq/lite Learn about the underlying protocol
WebCodecs API Learn about WebCodecs