Skip to main content

Tutorial - Twitch-Style Host Dashboard

Use this tutorial when your product is host-first and needs moderation, requests, and recording controls in one workspace.

Outcome

By the end, you will have:

  • a production-safe host join and room control boundary
  • a dashboard-style host shell around MediaSFU runtime logic
  • a clear split between runtime workflow and UI composition

Visual target

Twitch-style MediaSFU host dashboard preview

Use this as the first layout target: a persistent host sidebar, a large stage region, live moderation controls, recording status, and a right-side queue. MediaSFU should continue to own room state and workflow while your host dashboard owns presentation.

Scope and assumptions

This tutorial assumes:

  • your secure backend create and join flow already works
  • you understand basic room join behavior
  • you now need stronger host tooling than a default room shell

Read this before starting:

  1. Choose your UI mode
  2. Secure backend proxy
  3. Build style guide
  4. Custom component replacement
  5. Media lifecycle

Step 1: Decide your integration depth

For most teams, start with:

  • framework SDK plus customComponent

Use Headless mode only when you need a fully bespoke shell from the first visible screen.

Two valid angles for the same product shape

This tutorial's main implementation uses returnUI={true} with customComponent, but the same host dashboard can also be built with returnUI={false}.

AngleWhat you keepUse it when
returnUI={true} + customComponentMediaSFU entry flow and runtime shell, with your dashboard rendered as the visible workspaceYou want a custom host dashboard without taking ownership of every visible step from first paint.
returnUI={false} + headless shellYour own dashboard mounted next to a headless MediaSFU instance via sourceParametersYou want zero stock room UI visible and need app-level routing, panels, or shell behavior around the host workspace.

Headless angle for this same dashboard:

<>
<MediasfuGeneric
connectMediaSFU={true}
returnUI={false}
noUIPreJoinOptions={noUIPreJoinOptions}
sourceParameters={sourceParameters}
updateSourceParameters={setSourceParameters}
createMediaSFURoom={createMediaSFURoom}
joinMediaSFURoom={joinMediaSFURoom}
/>
<TwitchHostDashboard parameters={sourceParameters} />
</>

Choose the true angle when your dashboard mainly replaces the main room surface. Choose the false angle when the rest of your app shell, moderation layout, or navigation stack must stay fully app-owned. The Tutorial - Support AI Console page shows the headless wiring pattern in more detail.

Step 2: Define host dashboard regions

A practical first layout uses four regions:

  • stage region for active speaker or stream focus
  • moderation queue for requests and admissions
  • host control panel for permissions, recording, and room actions
  • chat or event log panel for live state and interaction

Keep runtime actions connected to MediaSFU helpers and state.

Copy/paste starter: React host dashboard shell

Drop this into a React app after installing mediasfu-reactjs. It replaces the visible room shell with a host dashboard while MediaSFU keeps managing the room workflow.

Create src/TwitchStyleHostRoom.tsx, paste the component below, and keep your backend /api/mediasfu/create-room and /api/mediasfu/join-room routes forwarding to the same upstream MediaSFU Cloud rooms URL from Secure backend proxy.

import { useMemo } from 'react';
import {
MediasfuGeneric,
type CreateMediaSFURoomOptions,
type JoinMediaSFURoomOptions,
} from 'mediasfu-reactjs';

type RoomResult = {
data: Record<string, unknown> | null;
success: boolean;
};

async function createMediaSFURoom({ payload }: { payload: CreateMediaSFURoomOptions }): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/create-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});

return { data: await response.json(), success: response.ok };
}

async function joinMediaSFURoom({ payload }: { payload: JoinMediaSFURoomOptions }): Promise<RoomResult> {
const response = await fetch('/api/mediasfu/join-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});

return { data: await response.json(), success: response.ok };
}

function TwitchHostDashboard({ parameters }: { parameters: any }) {
const participants = parameters.participants ?? [];
const waitingList = parameters.waitingList ?? [];
const requests = parameters.requests ?? [];

return (
<main style={{ minHeight: '100vh', display: 'grid', gridTemplateColumns: '1fr 340px', background: '#111827', color: '#f9fafb' }}>
<section style={{ padding: 24, display: 'grid', gap: 16 }}>
<div style={{ minHeight: 360, borderRadius: 8, background: '#020617', display: 'grid', placeItems: 'center' }}>
<div style={{ textAlign: 'center' }}>
<h1 style={{ margin: 0 }}>Live stage</h1>
<p>{parameters.roomName ?? 'Room connecting...'}</p>
</div>
</div>

<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
<button onClick={() => parameters.clickVideo?.({ parameters })}>Toggle camera</button>
<button onClick={() => parameters.clickAudio?.({ parameters })}>Toggle mic</button>
<button onClick={() => parameters.clickScreenShare?.({ parameters })}>Share screen</button>
<button onClick={() => parameters.launchRecording?.({ parameters })}>Recording</button>
</div>
</section>

<aside style={{ borderLeft: '1px solid #334155', padding: 20, display: 'grid', gap: 20, alignContent: 'start' }}>
<section>
<h2>Participants</h2>
<p>{participants.length} connected</p>
</section>

<section>
<h2>Waiting room</h2>
<p>{waitingList.length} waiting</p>
</section>

<section>
<h2>Requests</h2>
<p>{requests.length} open requests</p>
</section>
</aside>
</main>
);
}

export function TwitchStyleHostRoom() {
const noUIPreJoinOptions = useMemo(
() => ({
action: 'create',
eventType: 'webinar',
userName: 'Host',
capacity: 100,
duration: 120,
}),
[]
);

return (
<MediasfuGeneric
connectMediaSFU={true}
returnUI={true}
noUIPreJoinOptions={noUIPreJoinOptions}
createMediaSFURoom={createMediaSFURoom}
joinMediaSFURoom={joinMediaSFURoom}
customComponent={TwitchHostDashboard}
/>
);
}

Render it from your app entry:

import { TwitchStyleHostRoom } from './TwitchStyleHostRoom';

export default function App() {
return <TwitchStyleHostRoom />;
}

What you should see first:

  • a stage region for the host view
  • a right-side dashboard for participants, waiting room, and requests
  • host actions still routed through MediaSFU runtime helpers
About the control buttons

The inline buttons in this starter are intentionally low-level. In customComponent, parameters is the live MediaSFU helper bundle: room state, modal togglers, and action helpers all travel through it. Use direct calls like clickVideo or clickAudio for simple toggles, but do not assume every surface must be rebuilt this way.

Recording is usually better handled by importing RecordingModal or ModernRecordingModal, or by restyling uiOverrides.recordingModal, while keeping the stock MediaSFU flow underneath. Read Custom component replacement, Media lifecycle, and UI overrides before polishing the control bar.

Step 3: Implement host-critical flows first

Prioritize these flows before visual polish:

  1. host can create and join securely
  2. host can admit or reject requests
  3. host can mute or control role-sensitive actions
  4. host can start and stop recording
  5. host sees reliable participant and room state changes

Step 4: Add lifecycle-aware media controls

As the dashboard grows, keep produce and consume behavior explicit:

  • stage render rules
  • participant card promotion and demotion
  • active-speaker and pinned layouts
  • fallback behavior when streams drop

Reference:

Step 5: Validate the live room behavior

Before launch:

  • test request and permission flows with multiple roles
  • test recording transitions under reconnect conditions
  • test participant churn and stage reassignment
  • test host controls under packet loss and latency

When to move lower than UI SDKs

Move to mediasfu-shared only when:

  • your dashboard needs lower-level runtime hooks that UI SDK helpers do not expose cleanly
  • your team is building its own wrapper layer across frameworks

If your existing host dashboard works with customComponent, stay there.

What to build next

After this tutorial is stable:

  1. Add translation and subtitle workflows.
  2. Add whiteboard or screenboard synchronization where needed.
  3. Add AI or support-console escalation patterns if your product direction requires it.