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
Available Components
<hang-publish>
Publish camera/microphone or screen as a MoQ broadcast.
Attributes:
url(required) - Relay server URLpath(required) - Broadcast path/namedevice- "camera" or "screen" (default: "camera")audio- Enable audio capture (boolean)video- Enable video capture (boolean)controls- Show publishing controls (boolean)
Example:
<script type="module">
import "@moq/publish/element";
</script>
<hang-publish
url="https://relay.example.com/anon"
path="room/alice"
device="camera"
audio video controls>
<!-- Optional preview element -->
<video muted autoplay style="width: 100%"></video>
</hang-publish><hang-watch>
Subscribe to and render a MoQ broadcast.
Attributes:
url(required) - Relay server URLpath(required) - Broadcast path/namecontrols- Show playback controls (boolean)paused- Pause playback (boolean)muted- Mute audio (boolean)volume- Audio volume (0-1, default: 1)
Example:
<script type="module">
import "@moq/watch/element";
</script>
<hang-watch
url="https://relay.example.com/anon"
path="room/alice"
volume="0.8"
controls>
<!-- Optional canvas for video rendering -->
<canvas style="width: 100%"></canvas>
</hang-watch><hang-support>
Display browser support information.
Attributes:
mode- "publish" or "watch"show- "always", "partial", or "never" (default: "partial")
Example:
<script type="module">
import "@moq/hang/support/element";
</script>
<!-- Show only when publishing is not supported -->
<hang-support mode="publish" show="partial"></hang-support>Using JavaScript Properties
HTML attributes are strings, but JavaScript properties are typed and reactive:
// Get element reference
const watch = document.querySelector("hang-watch") as HangWatch;
// 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:
import { HangWatch } from "@moq/watch/element";
const watch = document.querySelector("hang-watch") as HangWatch;
// These are all reactive signals:
watch.volume // Signal<number>
watch.muted // Signal<boolean>
watch.paused // Signal<boolean>
watch.url // Signal<string>
watch.path // Signal<string>Framework Integration
React
import { useEffect, useRef } from "react";
import "@moq/watch/element";
function VideoPlayer({ url, path }) {
const ref = useRef<HangWatch>(null);
useEffect(() => {
if (ref.current) {
ref.current.volume.set(0.8);
}
}, []);
return (
<hang-watch
ref={ref}
url={url}
path={path}
controls>
<canvas />
</hang-watch>
);
}SolidJS
Use @moq/watch/ui and @moq/publish/ui for SolidJS UI overlays, or use Web Components directly:
import "@moq/watch/element";
function VideoPlayer(props) {
return (
<hang-watch
url={props.url}
path={props.path}
controls>
<canvas />
</hang-watch>
);
}Vue
<template>
<hang-watch
:url="url"
:path="path"
controls>
<canvas />
</hang-watch>
</template>
<script>
import "@moq/watch/element";
export default {
props: ["url", "path"],
};
</script>Styling
Web Components use Shadow DOM, so global styles won't apply. Use CSS custom properties (variables) or style child elements:
<style>
hang-watch::part(video) {
border-radius: 8px;
}
hang-watch canvas {
width: 100%;
border-radius: 8px;
}
</style>
<hang-watch url="..." path="..." controls>
<canvas style="width: 100%; border-radius: 8px;"></canvas>
</hang-watch>Tree-Shaking
To prevent tree-shaking from removing component registrations, explicitly import with /element suffix:
// Correct
import "@moq/watch/element";
// May be tree-shaken (don't use)
import "@moq/watch";TypeScript Support
Full TypeScript support with type definitions:
import type HangWatch from "@moq/watch/element";
import type HangPublish from "@moq/publish/element";
const watch: HangWatch = document.querySelector("hang-watch")!;
const publish: HangPublish = document.querySelector("hang-publish")!;Events
Components emit custom events:
const watch = document.querySelector("hang-watch") as HangWatch;
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:
- Use a production relay (moq-relay)
- Set up proper authentication
- Use a bundler like Vite
Currently, you need to use a bundler and Vite is the only supported option for @moq/watch and @moq/publish. It makes me very sad and we're working on a more universal solution, contributions welcome!
NOTE both of these libraries are intended for client-side. However, @moq/lite 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
- Learn about @moq/hang
- Use @moq/lite for custom protocols
- View code examples