Build a video meeting
Set up a Zoom-style small-group call with real OpenRTC rooms and media
Try a real room
The call below uses OpenRTC({ apiKey }), rtc.rooms.join(), and
connection.media.addTrack() against the configured OpenRTC service. It
requests your camera and microphone only after you select Join. Open this
page in a second browser or device, use the same room code and app key, and
watch the remote tile appear. Peer tiles come only from admitted OpenRTC
connections; there is no simulated local-tab fallback.
Enable JavaScript to join the interactive video room. Camera and microphone access is requested by the demo when you join.
The hosted docs demo uses its own public rooms key, configured for the docs origin. In a local or self-hosted build without that key, enter your public OpenRTC key. Register the exact page origin shown above in your app's Developer Portal Security settings, enable capability rooms with client-open room creation, and set a room capacity and credit budget that admit your test. A public API key is browser-safe; never put a server secret or product auth assertion in this page. The docs demo key is scoped to this demo app and cannot be reused as your product identity.
This is a small-group mesh meeting. The demo requests at most eight members,
including the local participant; the portal manifest and server admission can
set a lower ceiling. Each participant sends a microphone and camera track to
each connected peer, so upload and encode work grow with the peer count. Use
Live broadcasting for one-to-many viewing. A larger
meeting with server-mixed audio/video, recording, moderation, or waiting rooms
requires additional product services; changing maxPeers does not add those
features or bypass admission limits.
Set up your own app
- Create an app in the Developer Portal
and copy its public
pk_test_…orpk_live_…key. - Enable capability rooms and client-open room creation. Keep membership ephemeral for calls. Choose a reviewed room limit and budget for the expected simultaneous callers.
- Add each exact browser origin to the app allowlist, for example
https://meet.example.comandhttp://localhost:5173during development. The origin includes the scheme and port, but no path. Apk_test_…key does not select an internal OpenRTC staging service. - Serve the app over HTTPS or localhost. Check
getUserMedia, WebCodecs, browser track processing/rendering support, and device permissions before showing an enabled Join control. Test with two independent browsers or devices on different networks.
Install the published package in a standalone web app:
pnpm add openrtc
Keep the public key in a browser-public build variable such as
VITE_OPENRTC_API_KEY. The default SDK target is the managed production
OpenRTC platform for both development and deployed consumer apps.
Join and publish
The following is the central part of the live demo. It uses a separate
MediaStream for each peer, so one peer's tracks cannot overwrite another's.
The room code is a rendezvous name, not an access-control secret. Use
access: 'authenticated' with your registered assertion provider when your
product needs an invite list, roles, or a private meeting policy.
import { OpenRTC } from 'openrtc';
const rtc = OpenRTC({ apiKey: import.meta.env.VITE_OPENRTC_API_KEY });
const local = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
localVideo.srcObject = local; // HTMLVideoElement with autoplay, muted, playsinline
const room = await rtc.rooms.join(roomCode, {
access: 'capability',
membership: 'ephemeral',
identity: 'session',
architecture: 'mesh',
payload: 'reliable',
maxPeers: 8,
});
const remoteStreams = new Map<string, MediaStream>();
const stopConnections = room.onConnection(async (connection) => {
const remote = new MediaStream();
remoteStreams.set(connection.peerId, remote);
addRemoteVideoTile(connection.peerId, remote); // autoplay, playsinline; not muted
const stopTracks = connection.media.onTrack(({ track }) => {
for (const old of remote.getTracks()) {
if (old.kind === track.kind) remote.removeTrack(old);
}
remote.addTrack(track);
});
connection.onClose(() => {
stopTracks();
remoteStreams.delete(connection.peerId);
removeRemoteVideoTile(connection.peerId);
});
for (const track of local.getTracks()) {
await connection.media.addTrack(track);
}
});
onConnection() includes current and future logical peers. Do not redial or
republish on a carrier change: OpenRTC owns physical route recovery and
reopens active publications. Render peer arrival, media, and removal as UI
events. The onTrack() event gives an ordinary MediaStreamTrack; attach it
to an HTML media element. A remote audio-only participant still needs an
unmuted playback element.
Controls and cleanup
Set the local track's enabled property to mute or hide it for every peer.
Keep the returned MediaSender if you need replaceTrack() for camera
switching or screen share, stop() to end a publication, or getStats() for
local media diagnostics. A screen-share track should also be stopped when the
browser's sharing session ends. For example:
const microphone = local.getAudioTracks()[0];
microphone.enabled = false; // mute across this room's peer publications
// On Leave: stop observing, release hardware, retire room, close the client.
stopConnections();
local.getTracks().forEach((track) => track.stop());
await room.leave();
await rtc.close();
Do not use a BroadcastChannel, a second RTCPeerConnection, polling, or an
app-level retry loop to make the room appear connected. Those would give the
UI a different membership or media lifecycle from the admitted OpenRTC room.
For API details and codec support, see
Media, streams, and datagrams and
Rooms API.