btw: MoQ is under active development. The APIs and protocols are still evolving and will change. Most of this documentation is AI generated until things get more stable.

Skip to content

Web Components

@moq/hang provides Web Components for easy integration into any web page or framework.

Why Web Components?

  • Framework agnostic - Works with React, Vue, Solid, or vanilla JS
  • Easy integration - Just import and use like HTML
  • Encapsulated - Shadow DOM for style isolation
  • Reactive - Automatically update when attributes change

Loading From a CDN (No Bundler)

For quick demos or embeds on a static page, both @moq/watch and @moq/publish can be loaded straight from esm.sh with no build step. esm.sh serves each package as a browser-ready ESM module and rewrites bare imports (like @moq/hang, @moq/net) to other esm.sh URLs, so it loads in the browser without an import map:

html
<script type="module">
    import "https://esm.sh/@moq/watch/element";
    import "https://esm.sh/@moq/publish/element";
</script>

<moq-watch url="https://relay.example.com/anon" name="room/alice.hang">
    <canvas></canvas>
</moq-watch>

Pin a version range in the URL for production, e.g. https://esm.sh/@moq/watch@0.2/element. jsDelivr's +esm endpoint (https://cdn.jsdelivr.net/npm/@moq/watch/element.js/+esm) works the same way if you prefer it.

This is the fastest way to try MoQ in a blog post or demo page, but for real apps you should install the packages and use a bundler. You'll get tree-shaking, offline dev, and no dependency on a third-party CDN's availability.

Available Components

<moq-publish>

Publish camera/microphone or screen as a MoQ broadcast.

Attributes:

  • url (required) - Relay server URL
  • name (required) - Broadcast name
  • source - "camera", "screen", or "file"
  • muted - Disable audio capture (boolean)
  • invisible - Disable video capture (boolean)
  • simulcast - Also publish a lower-resolution video/sd rendition (a fraction of the source resolution) alongside video/hd (boolean)

Example:

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

<moq-publish
    url="https://relay.example.com/anon"
    name="room/alice.hang"
    source="camera" simulcast>
    <!-- Optional preview element -->
    <video muted autoplay style="width: 100%"></video>
</moq-publish>

<moq-watch>

Subscribe to and render a MoQ broadcast.

Attributes:

  • url (required) - Relay server URL
  • name (required) - Broadcast name
  • controls - Show playback controls (boolean)
  • paused - Pause playback (boolean)
  • muted - Mute audio (boolean)
  • volume - Audio volume (0-1, default: 1)

Example:

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

<moq-watch
    url="https://relay.example.com/anon"
    name="room/alice.hang"
    volume="0.8"
    controls>
    <!-- Optional canvas for video rendering -->
    <canvas style="width: 100%"></canvas>
</moq-watch>

<moq-watch-support>

Display browser support information for watching streams.

Attributes:

  • show - "always", "warning", "error", or "never" (default: "warning")
  • details - show detailed codec information

Example:

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

<!-- Show only when a polyfill/fallback is needed -->
<moq-watch-support show="warning"></moq-watch-support>

<moq-publish-support>

Display browser support information for publishing streams.

Attributes:

  • show - "always", "warning", "error", or "never" (default: "warning")
  • details - show detailed codec information

Example:

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

<!-- Show only when a polyfill/fallback is needed -->
<moq-publish-support show="warning"></moq-publish-support>

Using JavaScript Properties

HTML attributes are strings, but JavaScript properties are typed and reactive:

typescript
// Get element reference
const watch = document.querySelector("moq-watch") as MoqWatch;

// Set properties (reactive)
watch.volume.set(0.8);
watch.muted.set(false);
watch.paused.set(true);

// Subscribe to changes
watch.volume.subscribe((vol) => {
    console.log("Volume changed:", vol);
});

// Get current value
const currentVolume = watch.volume.get();

Reactive Properties

All properties are signals from @moq/signals:

typescript
import MoqWatch from "@moq/watch/element";

const watch = document.querySelector("moq-watch") as MoqWatch;

// These are all reactive signals:
watch.volume    // Signal<number>
watch.muted     // Signal<boolean>
watch.paused    // Signal<boolean>
watch.url       // Signal<string>
watch.name      // Signal<string>

Framework Integration

React

tsx
import { useEffect, useRef } from "react";
import "@moq/watch/element";

function VideoPlayer({ url, name }) {
    const ref = useRef<MoqWatch>(null);

    useEffect(() => {
        if (ref.current) {
            ref.current.volume.set(0.8);
        }
    }, []);

    return (
        <moq-watch
            ref={ref}
            url={url}
            name={name}
            controls>
            <canvas />
        </moq-watch>
    );
}

SolidJS

Use @moq/watch/ui and @moq/publish/ui for ready-made UI overlays, or use Web Components directly:

tsx
import "@moq/watch/element";

function VideoPlayer(props) {
    return (
        <moq-watch
            url={props.url}
            name={props.name}
            controls>
            <canvas />
        </moq-watch>
    );
}

Vue

vue
<template>
    <moq-watch
        :url="url"
        :name="name"
        controls>
        <canvas />
    </moq-watch>
</template>

<script>
import "@moq/watch/element";

export default {
    props: ["url", "name"],
};
</script>

Styling

Web Components use Shadow DOM, so global styles won't apply. Use CSS custom properties (variables) or style child elements:

html
<style>
moq-watch::part(video) {
    border-radius: 8px;
}

moq-watch canvas {
    width: 100%;
    border-radius: 8px;
}
</style>

<moq-watch url="..." name="..." controls>
    <canvas style="width: 100%; border-radius: 8px;"></canvas>
</moq-watch>

Tree-Shaking

To prevent tree-shaking from removing component registrations, explicitly import with /element suffix:

typescript
// Correct
import "@moq/watch/element";

// May be tree-shaken (don't use)
import "@moq/watch";

TypeScript Support

Full TypeScript support with type definitions:

typescript
import MoqWatch from "@moq/watch/element";
import MoqPublish from "@moq/publish/element";

const watch: MoqWatch = document.querySelector("moq-watch")!;
const publish: MoqPublish = document.querySelector("moq-publish")!;

Events

Components emit custom events:

typescript
const watch = document.querySelector("moq-watch") as MoqWatch;

watch.addEventListener("play", () => {
    console.log("Playback started");
});

watch.addEventListener("pause", () => {
    console.log("Playback paused");
});

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

Browser Compatibility

Requires modern browser features:

  • WebTransport - Chromium-based browsers (Chrome, Edge, Brave)
  • WebCodecs - For media encoding/decoding
  • WebAudio - For audio playback

Supported browsers:

  • Chrome 97+
  • Edge 97+
  • Brave (recent versions)

Experimental support:

  • Firefox (behind flag)
  • Safari (future support planned)

Production Deployment

For production, you'll want to:

  1. Use a production relay (moq-relay)
  2. Set up proper authentication
  3. Use a bundler, see examples for Vite, Webpack, esbuild, and more.

NOTE both of these libraries are intended for client-side. However, @moq/net can run on the server side using Deno or a WebTransport polyfill. Don't even try to run @moq/hang on the server side or you'll run into a ton of issues, especially with Next.js.

Next Steps

Licensed under MIT or Apache-2.0