Skip to content

Publishing Streams

This guide covers how to publish media to MoQ relays using @moq/publish.

Web Component

The simplest way to publish:

html
<script type="module">
    import "@moq/publish/element";
</script>

<hang-publish
    url="https://relay.example.com/anon"
    path="room/alice"
    device="camera"
    audio video controls>
    <video muted autoplay></video>
</hang-publish>

Attributes

AttributeTypeDefaultDescription
urlstringrequiredRelay server URL
pathstringrequiredBroadcast path
devicestring"camera""camera" or "screen"
audiobooleanfalseEnable audio
videobooleanfalseEnable video
controlsbooleanfalseShow controls

Events

typescript
const publish = document.querySelector("hang-publish") as HangPublish;

publish.addEventListener("start", () => {
    console.log("Publishing started");
});

publish.addEventListener("stop", () => {
    console.log("Publishing stopped");
});

publish.addEventListener("error", (e) => {
    console.error("Error:", e.detail);
});

JavaScript API

For more control:

typescript
import * as Hang from "@moq/hang";

const connection = new Hang.Connection("https://relay.example.com/anon");

const publish = new Hang.Publish.Broadcast(connection, {
    enabled: true,
    name: "alice",
    video: {
        enabled: true,
        device: "camera",
        bitrate: 2_500_000,
        framerate: 30,
    },
    audio: {
        enabled: true,
        bitrate: 128_000,
    },
});

// Access the local preview
publish.video.media.subscribe((stream) => {
    if (stream) {
        previewVideo.srcObject = stream;
    }
});

Device Selection

Camera

typescript
const publish = new Hang.Publish.Broadcast(connection, {
    name: "stream",
    video: {
        enabled: true,
        device: "camera",
    },
});

Screen Share

typescript
const publish = new Hang.Publish.Broadcast(connection, {
    name: "stream",
    video: {
        enabled: true,
        device: "screen",
    },
});

Switching Devices

typescript
// Switch from camera to screen
publish.video.device.set("screen");

// Switch back to camera
publish.video.device.set("camera");

Specific Camera Selection

typescript
// Get available devices
const devices = await navigator.mediaDevices.enumerateDevices();
const cameras = devices.filter(d => d.kind === "videoinput");

// Select specific camera by deviceId
publish.video.deviceId.set(cameras[1].deviceId);

Quality Settings

Video Quality

typescript
const publish = new Hang.Publish.Broadcast(connection, {
    name: "stream",
    video: {
        enabled: true,
        width: 1920,
        height: 1080,
        framerate: 30,
        bitrate: 5_000_000,
    },
});

// Change bitrate dynamically
publish.video.bitrate.set(2_500_000);

// Change framerate
publish.video.framerate.set(15);

Audio Quality

typescript
const publish = new Hang.Publish.Broadcast(connection, {
    name: "stream",
    audio: {
        enabled: true,
        bitrate: 128_000,
        sampleRate: 48000,
        channels: 2,
    },
});

Codec Selection

typescript
const publish = new Hang.Publish.Broadcast(connection, {
    name: "stream",
    video: {
        enabled: true,
        codec: "avc1.640028", // H.264 High Profile
        // codec: "hev1.1.6.L93.B0", // H.265
        // codec: "vp09.00.10.08", // VP9
        // codec: "av01.0.04M.08", // AV1
    },
    audio: {
        enabled: true,
        codec: "opus", // or "mp4a.40.2" for AAC
    },
});

Enable/Disable Tracks

typescript
// Disable video (audio only)
publish.video.enabled.set(false);

// Re-enable video
publish.video.enabled.set(true);

// Mute audio
publish.audio.enabled.set(false);

Publishing State

typescript
// Check if publishing
publish.enabled.subscribe((isEnabled) => {
    console.log("Publishing:", isEnabled);
});

// Start publishing
publish.enabled.set(true);

// Stop publishing
publish.enabled.set(false);

Error Handling

typescript
publish.error.subscribe((error) => {
    if (error) {
        console.error("Publish error:", error);

        if (error.code === "PERMISSION_DENIED") {
            showMessage("Camera/microphone access denied");
        } else if (error.code === "NOT_SUPPORTED") {
            showMessage("WebRTC not supported");
        }
    }
});

Permission Handling

typescript
// Request permissions before publishing
try {
    await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    // Permissions granted, safe to publish
    publish.enabled.set(true);
} catch (error) {
    if (error.name === "NotAllowedError") {
        showMessage("Please allow camera and microphone access");
    }
}

React Integration

tsx
import { useEffect, useRef, useState } from "react";
import "@moq/publish/element";
import type HangPublish from "@moq/publish/element";

function Publisher({ url, path }) {
    const publishRef = useRef<HangPublish>(null);
    const [isPublishing, setIsPublishing] = useState(false);

    const togglePublish = () => {
        const publish = publishRef.current;
        if (publish) {
            publish.enabled.set(!isPublishing);
            setIsPublishing(!isPublishing);
        }
    };

    return (
        <div>
            <hang-publish
                ref={publishRef}
                url={url}
                path={path}
                audio video>
                <video muted autoplay style={{ width: "100%" }} />
            </hang-publish>

            <button onClick={togglePublish}>
                {isPublishing ? "Stop" : "Start"} Publishing
            </button>
        </div>
    );
}

SolidJS Integration

Use @moq/publish/ui for the SolidJS UI overlay. The <hang-publish-ui> element wraps a nested <hang-publish>:

html
<script type="module">
    import "@moq/publish/element";
    import "@moq/publish/ui";
</script>

<hang-publish-ui>
    <hang-publish url="https://relay.example.com/anon" path="room/alice" audio video>
        <video muted autoplay></video>
    </hang-publish>
</hang-publish-ui>

Authentication

Include JWT token in the URL:

html
<hang-publish
    url="https://relay.example.com/room/123?jwt=eyJhbGciOiJIUzI1NiIs..."
    path="alice"
    audio video>
</hang-publish>

See Authentication for token generation.

Next Steps

Licensed under MIT or Apache-2.0