For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Log inSign up
Support
GuidesReferenceClick-to-Call
GuidesReferenceClick-to-Call
  • Getting Started
    • Overview
    • Authentication
    • RxJS Primer
    • Migrate from v3
  • Web Components
    • Overview
    • Click-to-Call
    • Theming
    • Customization
  • Build Voice & Video Apps
    • Overview
    • Outbound Calls
    • Inbound Calls
    • Device Management
    • Screen Sharing
    • Call Controls
    • Layouts
    • Messaging & Chat
  • Manage Resources
    • Overview
    • Users
    • Address Book
    • Client Preferences
    • Capabilities
  • Deploy
    • Overview
    • Framework Integration
    • SSR & Next.js
    • Troubleshooting
LogoLogoSignalWire Docs
Log inSign up
Support
On this page
  • Pick a layout
  • Render the layout
  • Draw overlays
  • Re-shuffles
  • Capability gating
  • Reference
Build Voice & Video Apps

Layouts & Participant Views

|View as Markdown|Open in Claude|
Was this page helpful?
Edit this page
Previous

Messaging & Chat

Next
Built with

For multi-party video rooms, the SignalWire platform composes every participant’s camera into a single mixed video stream — call.remoteStream$ emits that stream. A layout is the rule the server uses to composite it: grid, presenter + thumbnails, picture-in-picture, etc. Each room has a list of layouts the server allows; clients pick one, optionally pin who goes in which slot, and read back where everyone ended up.

The server composes the video. The client picks the composition and draws overlays (name tags, mute icons, click targets) on top using percentage-based layer coordinates.

Pick a layout

Wire a picker to layouts$ (available options), layout$ (current selection), and setLayout() (mutator):

1call.layouts$.subscribe((names) => {
2 layoutPicker.innerHTML = names
3 .map((n) => `<option value="${n}">${n}</option>`)
4 .join("");
5});
6
7call.layout$.subscribe((current) => {
8 layoutPicker.value = current ?? "";
9});
10
11layoutPicker.onchange = () => call.setLayout(layoutPicker.value, {});

The available layout names are server-defined — they depend on the room’s configuration. setLayout rejects with InvalidParams if you pass a name that isn’t in layouts$, so either bind from the picker options (as above) or validate up front.

The empty {} second argument means “let the server place participants automatically.” To pin specific members into specific slots:

1await call.setLayout("presenter", {
2 [presenterId]: "reserved-0", // big slot
3 [guestId]: "reserved-1", // sidebar
4});

Slot names (reserved-0, reserved-1, auto, standard-0, …) are defined per-layout — see VideoPosition. Members not in the map are auto-placed. The local user can request their own position too with call.self.setPosition(), gated by capabilities.self.position.

Render the layout

Attach the mixed stream to a single <video> element:

1<video id="room" autoplay playsinline></video>
1call.remoteStream$.subscribe((s) => roomVideo.srcObject = s);

The stream already contains every participant arranged by the current layout — don’t render per-participant <video> tags.

Draw overlays

Overlays (name tags, mute icons, click hotspots, speaking borders) go on top of the single video. layoutLayers$ emits per-participant boxes with percentage coordinates (0–100) relative to the room canvas, so overlays scale with the video element regardless of resolution.

1call.layoutLayers$.subscribe((layers) => {
2 for (const layer of layers) {
3 overlay(layer.member_id).style.cssText = `
4 left: ${layer.x}%;
5 top: ${layer.y}%;
6 width: ${layer.width}%;
7 height: ${layer.height}%;
8 `;
9 }
10});

See LayoutLayer for the full layer shape (z-index, visibility, reservation slot, etc.).

For per-tile UI, each Participant has its own position$ scoped to that member — simpler than filtering layoutLayers$ on every emission.

Re-shuffles

layout$ and layoutLayers$ re-emit whenever the server re-composites: setLayout calls, members joining or leaving under auto-layout, the server promoting a raised hand. Subscriptions stay live; overlays follow automatically.

Capability gating

ActionCapability
Pick a different layoutSelfCapabilities.setLayout$
Set your own positioncapabilities.self.position
Set another member’s positioncapabilities.member.position

Hide the picker / position controls when the capability is false. See Capabilities.

Reference

  • layouts$ · layout$ · layoutLayers$ — what to subscribe to
  • setLayout() — switch the composition / pin slots
  • Participant.position$ · Participant.setPosition() — per-member position
  • LayoutLayer · VideoPosition — data shapes
  • SelfCapabilities.setLayout$ — capability gate