Echo Pillarbox
Add a blurred video background behind portrait or non-standard aspect ratio videos.
The "echo pillarbox" effect is a popular video presentation technique used by platforms like TikTok to create a more visually appealing display for portrait or non-standard aspect ratio videos. Instead of showing black bars (pillarboxing) on the sides of the video, a blurred and zoomed version of the same video fills the background, creating an immersive "echo" effect.
- React
This implementation uses built-in components from @video/video-client-react to easily create the echo pillarbox effect without manual styling or MediaStream management.
The echo pillarbox implementation uses:
- Built-in Components:
EchoPillarboxWrapperandEchoPillarboxVideohandle all the complexity - Single Player Architecture: One
PlayerAPIinstance manages the video stream. Can be used for both PreviewPlayers and Players! - Automatic Synchronization: Components automatically sync and style video elements
- Simple Integration: Just wrap your existing
Videocomponent with the echo pillarbox components
Prerequisites
This guide assumes you have a basic understanding of setting up an <PreviewPlayer/> with @video/video-client-react. We will be focusing on the key differences for creating an echo pillarbox, specifically:
- How to configure the
mediaStreamControllerto capture a canvas element - The specific code required for canvas broadcasting
For foundational concepts, please review:
- The
usePreviewPlayeranduseAuthClienthooks. - The
useCallControlscustom hook. - The
PreviewPlayercomponent for device controls.
Key Concepts Explained
Component-Based Architecture
The echo pillarbox effect uses built-in components that handle all complexity automatically:
EchoPillarboxWrapper:
- Container component that manages layout
- Handles positioning and overflow
- Accepts standard props (className, style)
EchoPillarboxVideo:
- Automatically creates blurred background video
- Syncs MediaStream from the main Video component
- No props required - works automatically
Video:
- Standard video component from
@video/video-client-react - Positioned on top when inside EchoPillarboxWrapper
- Displays sharp, centered video
Automatic MediaStream Synchronization
The EchoPillarboxVideo component:
- Detects the main
Videocomponent within the sameEchoPillarboxWrapper - Automatically accesses its MediaStream
- Creates a second video element with the same stream
- Applies blur and scale effects
- Keeps everything in sync
Single PlayerAPI Pattern
Unlike manual implementations, this approach uses:
- One PlayerAPI instance - managed by
usePreviewPlayer - One PlayerAPIProvider - wraps all video components
- Automatic synchronization - handled by EchoPillarboxVideo
- Simpler cleanup - only one player to dispose
Echo Pillarbox Preview Player Component
Imports
From the @video/video-client-react package you'll need to import:
- Context providers for managing API states
- UI components for video display and controls
- EchoPillarboxWrapper and EchoPillarboxVideo - Built-in components for the echo effect
- Hooks for preview player and authentication
Key imports explained:
Video- Video element component for the main videoEchoPillarboxWrapper- Container component that manages the echo pillarbox layoutEchoPillarboxVideo- Component that automatically creates the blurred background
import { components, context, hooks, types } from "@video/video-client-react";
// Import context providers for managing different API states
const { MediaStreamControllerAPIProvider, PlayerAPIProvider } = context;
// Import UI components for video controls and display
const {
Video,
AudioSourceSelect,
VideoSourceSelect,
AspectRatioSelect,
ToggleMicButton,
ToggleCameraButton,
EchoPillarboxWrapper, // Container component that manages echo pillarbox layout
EchoPillarboxVideo, // Component that automatically creates blurred background video
} = components;
const { usePreviewPlayer, useAuthClient } = hooks;
Video Ref
This should look familiar to all other Player and PreviewPlayer implementations, but want to call out that only one ref is needed for the Echo Pillarbox.
// Ref for the main video element
// Note: EchoPillarboxVideo handles the blurred background internally, so no separate ref needed
const videoRef = useRef<HTMLVideoElement>(null); // Main video element (centered, object-fit: contain)
Cleanup
Render UI
Now comes the simple part - wrapping your video with the echo pillarbox components!
return (
<MediaStreamControllerAPIProvider mediaStreamControllerAPI={mediaStreamController}>
<PlayerAPIProvider playerAPI={previewPlayer}>
<div className="flex flex-row w-full h-full">
{/* Echo Pillarbox Container - TikTok Style
EchoPillarboxWrapper is a built-in component that manages the echo pillarbox layout.
It handles:
- Relative positioning for layered video elements
- Overflow management to hide blur edges
- Container sizing and responsive behavior
You can customize it with className and style props as needed. */}
<EchoPillarboxWrapper className="relative flex-1 bg-black" style={{ minHeight: '100%', maxWidth: '66.666%' }}>
{/* Blurred Background Video Layer
EchoPillarboxVideo is a built-in component that automatically:
- Creates a blurred background video element
- Syncs the MediaStream from the main Video component
- Applies blur filter and scale transform
- Uses object-fit: cover to fill the container
- Positions itself as the background layer (z-index: 0)
No props required - it automatically detects and syncs with the Video component. */}
<EchoPillarboxVideo/>
{/* Main Video Layer
The standard Video component from @video/video-client-react.
When placed inside EchoPillarboxWrapper:
- Automatically positioned on top of EchoPillarboxVideo (z-index: 10)
- Uses object-fit: contain to maintain aspect ratio
- Centered within the container
- Displays sharp, unblurred video content
The pillarbox areas (sides of the video) show the blurred background. */}
<Video ref={videoRef} />
</EchoPillarboxWrapper>
{/* Controls Panel - Contains all user controls for the video encoder */}
<div className="flex flex-col m-4">
{/* Toggle controls for microphone and camera */}
<div className="flex flex-row my-2 gap-2">
<ToggleMicButton />
<ToggleCameraButton />
</div>
{/* Device and aspect ratio selection controls */}
<div className="flex flex-col my-2 gap-2">
<AudioSourceSelect />
<VideoSourceSelect />
<AspectRatioSelect />
</div>
{/* Call and broadcast management controls */}
{renderControls()}
</div>
</div>
</PlayerAPIProvider>
</MediaStreamControllerAPIProvider>
);
Full Component Code
// EchoPillarboxPreviewPlayer.tsx
//
// OVERVIEW:
// This component demonstrates how to create a TikTok-style "echo pillarbox" effect
// where a blurred background version of the video fills the pillarbox areas
// around the main video content, creating a more visually appealing presentation
// for portrait or non-standard aspect ratio videos.
//
// KEY CONCEPTS:
// 1. ECHO PILLARBOX COMPONENT ARCHITECTURE
// - Uses built-in EchoPillarboxWrapper and EchoPillarboxVideo components
// - EchoPillarboxWrapper: Container component that manages layout and styling
// - EchoPillarboxVideo: Automatically creates the blurred background layer
// - Video component: Renders the main centered video with proper aspect ratio
// - Both video elements share the same MediaStream from PlayerAPI
//
// 2. COMPONENT-BASED RENDERING
// - EchoPillarboxWrapper handles the container styling and layout
// - EchoPillarboxVideo automatically creates and syncs the blurred background
// - Video component is wrapped in PlayerAPIProvider for proper media management
// - All styling and MediaStream synchronization is handled internally
//
// 3. SIMPLIFIED IMPLEMENTATION
// - No manual MediaStream sharing required
// - No manual styling for layering and blur effects
// - Built-in components handle all the complexity
// - Just wrap Video component in EchoPillarboxWrapper with EchoPillarboxVideo
//
// HOW TO IMPLEMENT IN YOUR APPLICATION:
// 1. Import EchoPillarboxWrapper and EchoPillarboxVideo from @video/video-client-react
// 2. Wrap your Video component in PlayerAPIProvider as usual
// 3. Wrap both in EchoPillarboxWrapper
// 4. Add EchoPillarboxVideo as a sibling to the Video component
// 5. EchoPillarboxVideo will automatically create the blurred background
// 6. The wrapper handles all layout, positioning, and styling
//
// STYLING NOTES:
// - EchoPillarboxWrapper accepts className and style props for customization
// - The wrapper automatically handles relative positioning and overflow
// - Background blur and scaling is handled by EchoPillarboxVideo
// - Main video centering and aspect ratio is handled automatically
import React, { memo, useRef, useEffect } from "react";
import { components, context, hooks, types } from "@video/video-client-react";
// Import context providers for managing different API states
const { MediaStreamControllerAPIProvider, PlayerAPIProvider } = context;
// Import UI components for video controls and display
const {
Video,
AudioSourceSelect,
VideoSourceSelect,
AspectRatioSelect,
ToggleMicButton,
ToggleCameraButton,
EchoPillarboxWrapper, // Container component that manages echo pillarbox layout
EchoPillarboxVideo, // Component that automatically creates blurred background video
} = components;
const { usePreviewPlayer, useAuthClient } = hooks;
import {useCallControls} from "../../components/CallControls";
interface EchoPillarboxPreviewPlayerProps {
backendEndpoint: string; // Backend API endpoint URL
token: string; // Authentication token
streamKey: string; // Stream key
cbBroadcast: (broadcast: types.BroadcastAPI | null) => void;
cbCallId: (callId: string) => void;
}
function EchoPillarboxPreviewPlayer({ backendEndpoint, token, streamKey, cbBroadcast, cbCallId }: EchoPillarboxPreviewPlayerProps): JSX.Element {
// Ref for the main video element
// Note: EchoPillarboxVideo handles the blurred background internally, so no separate ref needed
const videoRef = useRef<HTMLVideoElement>(null); // Main video element (centered, object-fit: contain)
// Initialize preview player and media stream controller
// This hook provides the main player for the centered video
const { mediaStreamController, previewPlayer } = usePreviewPlayer({});
// Initialize authentication client with token refresher
const authClient = useAuthClient(token);
// Configure call options for creating a new call
const callOptions: types.CallOptions = {
streamKey, // Unique stream identifier
backendEndpoints: [backendEndpoint], // Backend server endpoints
auth: authClient, // Authentication client
user: {
userId: crypto.randomUUID(), // Unique user ID
displayName: "John Doe" // Display name for the user
},
}
const broadcastOptions = {streamName: "default"};
const { renderControls, call, setCall, setBroadcast, broadcast } = useCallControls({callOptions, broadcastOptions, type: "owner"});
useEffect(() => {
cbBroadcast(broadcast);
}, [broadcast, cbBroadcast]);
useEffect(() => {
cbCallId(call?.id ?? "");
}, [call, cbCallId]);
// Cleanup on unmount
useEffect(() => {
return () => {
if (call != null) {
call.dispose("Disposed by useEffect return");
setCall(null);
}
};
}, [call]);
useEffect(() => {
return () => {
if (broadcast != null) {
broadcast.dispose("Disposed by useEffect return");
setBroadcast(null);
}
};
}, [broadcast]);
useEffect(() => {
return () => {
if (previewPlayer != null) {
previewPlayer.dispose("Disposed by useEffect return");
}
}
}, [previewPlayer]);
useEffect(() => {
return () => {
if (mediaStreamController != null) {
mediaStreamController.dispose("Disposed by useEffect return");
}
}
}, [mediaStreamController]);
// Wait for all required resources to be initialized before rendering
if (!mediaStreamController || !previewPlayer || !authClient) {
return <div className="w-full h-full bg-gray-200" />;
}
return (
<MediaStreamControllerAPIProvider mediaStreamControllerAPI={mediaStreamController}>
<PlayerAPIProvider playerAPI={previewPlayer}>
<div className="flex flex-row w-full h-full">
{/* Echo Pillarbox Container - TikTok Style
EchoPillarboxWrapper is a built-in component that manages the echo pillarbox layout.
It handles:
- Relative positioning for layered video elements
- Overflow management to hide blur edges
- Container sizing and responsive behavior
You can customize it with className and style props as needed. */}
<EchoPillarboxWrapper className="relative flex-1 bg-black" style={{ minHeight: '100%', maxWidth: '66.666%' }}>
{/* Blurred Background Video Layer
EchoPillarboxVideo is a built-in component that automatically:
- Creates a blurred background video element
- Syncs the MediaStream from the main Video component
- Applies blur filter and scale transform
- Uses object-fit: cover to fill the container
- Positions itself as the background layer (z-index: 0)
No props required - it automatically detects and syncs with the Video component. */}
<EchoPillarboxVideo/>
{/* Main Video Layer
The standard Video component from @video/video-client-react.
When placed inside EchoPillarboxWrapper:
- Automatically positioned on top of EchoPillarboxVideo (z-index: 10)
- Uses object-fit: contain to maintain aspect ratio
- Centered within the container
- Displays sharp, unblurred video content
The pillarbox areas (sides of the video) show the blurred background. */}
<Video ref={videoRef} />
</EchoPillarboxWrapper>
{/* Controls Panel - Contains all user controls for the video encoder */}
<div className="flex flex-col m-4">
{/* Toggle controls for microphone and camera */}
<div className="flex flex-row my-2 gap-2">
<ToggleMicButton />
<ToggleCameraButton />
</div>
{/* Device and aspect ratio selection controls */}
<div className="flex flex-col my-2 gap-2">
<AudioSourceSelect />
<VideoSourceSelect />
<AspectRatioSelect />
</div>
{/* Call and broadcast management controls */}
{renderControls()}
</div>
</div>
</PlayerAPIProvider>
</MediaStreamControllerAPIProvider>
);
}
export default memo(EchoPillarboxPreviewPlayer);
Best Practices
- Use one PlayerAPI instance - let EchoPillarboxVideo handle synchronization
- Wrap with PlayerAPIProvider - place it around the EchoPillarboxWrapper
- Dispose properly - clean up the player in useEffect cleanup
- Customize via props - use className and style on EchoPillarboxWrapper
- No manual styling needed - components handle layering automatically
Full Code
Echo Pillarbox Preview Player Components
// EchoPillarboxPreviewPlayer.tsx
//
// OVERVIEW:
// This component demonstrates how to create a TikTok-style "echo pillarbox" effect
// where a blurred background version of the video fills the pillarbox areas
// around the main video content, creating a more visually appealing presentation
// for portrait or non-standard aspect ratio videos.
//
// KEY CONCEPTS:
// 1. ECHO PILLARBOX COMPONENT ARCHITECTURE
// - Uses built-in EchoPillarboxWrapper and EchoPillarboxVideo components
// - EchoPillarboxWrapper: Container component that manages layout and styling
// - EchoPillarboxVideo: Automatically creates the blurred background layer
// - Video component: Renders the main centered video with proper aspect ratio
// - Both video elements share the same MediaStream from PlayerAPI
//
// 2. COMPONENT-BASED RENDERING
// - EchoPillarboxWrapper handles the container styling and layout
// - EchoPillarboxVideo automatically creates and syncs the blurred background
// - Video component is wrapped in PlayerAPIProvider for proper media management
// - All styling and MediaStream synchronization is handled internally
//
// 3. SIMPLIFIED IMPLEMENTATION
// - No manual MediaStream sharing required
// - No manual styling for layering and blur effects
// - Built-in components handle all the complexity
// - Just wrap Video component in EchoPillarboxWrapper with EchoPillarboxVideo
//
// HOW TO IMPLEMENT IN YOUR APPLICATION:
// 1. Import EchoPillarboxWrapper and EchoPillarboxVideo from @video/video-client-react
// 2. Wrap your Video component in PlayerAPIProvider as usual
// 3. Wrap both in EchoPillarboxWrapper
// 4. Add EchoPillarboxVideo as a sibling to the Video component
// 5. EchoPillarboxVideo will automatically create the blurred background
// 6. The wrapper handles all layout, positioning, and styling
//
// STYLING NOTES:
// - EchoPillarboxWrapper accepts className and style props for customization
// - The wrapper automatically handles relative positioning and overflow
// - Background blur and scaling is handled by EchoPillarboxVideo
// - Main video centering and aspect ratio is handled automatically
import React, { memo, useRef, useEffect } from "react";
import { components, context, hooks, types } from "@video/video-client-react";
// Import context providers for managing different API states
const { MediaStreamControllerAPIProvider, PlayerAPIProvider } = context;
// Import UI components for video controls and display
const {
Video,
AudioSourceSelect,
VideoSourceSelect,
AspectRatioSelect,
ToggleMicButton,
ToggleCameraButton,
EchoPillarboxWrapper, // Container component that manages echo pillarbox layout
EchoPillarboxVideo, // Component that automatically creates blurred background video
} = components;
const { usePreviewPlayer, useAuthClient } = hooks;
import {useCallControls} from "../../components/CallControls";
interface EchoPillarboxPreviewPlayerProps {
backendEndpoint: string; // Backend API endpoint URL
token: string; // Authentication token
streamKey: string; // Stream key
cbBroadcast: (broadcast: types.BroadcastAPI | null) => void;
cbCallId: (callId: string) => void;
}
function EchoPillarboxPreviewPlayer({ backendEndpoint, token, streamKey, cbBroadcast, cbCallId }: EchoPillarboxPreviewPlayerProps): JSX.Element {
// Ref for the main video element
// Note: EchoPillarboxVideo handles the blurred background internally, so no separate ref needed
const videoRef = useRef<HTMLVideoElement>(null); // Main video element (centered, object-fit: contain)
// Initialize preview player and media stream controller
// This hook provides the main player for the centered video
const { mediaStreamController, previewPlayer } = usePreviewPlayer({});
// Initialize authentication client with token refresher
const authClient = useAuthClient(token);
// Configure call options for creating a new call
const callOptions: types.CallOptions = {
streamKey, // Unique stream identifier
backendEndpoints: [backendEndpoint], // Backend server endpoints
auth: authClient, // Authentication client
user: {
userId: crypto.randomUUID(), // Unique user ID
displayName: "John Doe" // Display name for the user
},
}
const broadcastOptions = {streamName: "default"};
const { renderControls, call, setCall, setBroadcast, broadcast } = useCallControls({callOptions, broadcastOptions, type: "owner"});
useEffect(() => {
cbBroadcast(broadcast);
}, [broadcast, cbBroadcast]);
useEffect(() => {
cbCallId(call?.id ?? "");
}, [call, cbCallId]);
// Cleanup on unmount
useEffect(() => {
return () => {
if (call != null) {
call.dispose("Disposed by useEffect return");
setCall(null);
}
};
}, [call]);
useEffect(() => {
return () => {
if (broadcast != null) {
broadcast.dispose("Disposed by useEffect return");
setBroadcast(null);
}
};
}, [broadcast]);
useEffect(() => {
return () => {
if (previewPlayer != null) {
previewPlayer.dispose("Disposed by useEffect return");
}
}
}, [previewPlayer]);
useEffect(() => {
return () => {
if (mediaStreamController != null) {
mediaStreamController.dispose("Disposed by useEffect return");
}
}
}, [mediaStreamController]);
// Wait for all required resources to be initialized before rendering
if (!mediaStreamController || !previewPlayer || !authClient) {
return <div className="w-full h-full bg-gray-200" />;
}
return (
<MediaStreamControllerAPIProvider mediaStreamControllerAPI={mediaStreamController}>
<PlayerAPIProvider playerAPI={previewPlayer}>
<div className="flex flex-row w-full h-full">
{/* Echo Pillarbox Container - TikTok Style
EchoPillarboxWrapper is a built-in component that manages the echo pillarbox layout.
It handles:
- Relative positioning for layered video elements
- Overflow management to hide blur edges
- Container sizing and responsive behavior
You can customize it with className and style props as needed. */}
<EchoPillarboxWrapper className="relative flex-1 bg-black" style={{ minHeight: '100%', maxWidth: '66.666%' }}>
{/* Blurred Background Video Layer
EchoPillarboxVideo is a built-in component that automatically:
- Creates a blurred background video element
- Syncs the MediaStream from the main Video component
- Applies blur filter and scale transform
- Uses object-fit: cover to fill the container
- Positions itself as the background layer (z-index: 0)
No props required - it automatically detects and syncs with the Video component. */}
<EchoPillarboxVideo/>
{/* Main Video Layer
The standard Video component from @video/video-client-react.
When placed inside EchoPillarboxWrapper:
- Automatically positioned on top of EchoPillarboxVideo (z-index: 10)
- Uses object-fit: contain to maintain aspect ratio
- Centered within the container
- Displays sharp, unblurred video content
The pillarbox areas (sides of the video) show the blurred background. */}
<Video ref={videoRef} />
</EchoPillarboxWrapper>
{/* Controls Panel - Contains all user controls for the video encoder */}
<div className="flex flex-col m-4">
{/* Toggle controls for microphone and camera */}
<div className="flex flex-row my-2 gap-2">
<ToggleMicButton />
<ToggleCameraButton />
</div>
{/* Device and aspect ratio selection controls */}
<div className="flex flex-col my-2 gap-2">
<AudioSourceSelect />
<VideoSourceSelect />
<AspectRatioSelect />
</div>
{/* Call and broadcast management controls */}
{renderControls()}
</div>
</div>
</PlayerAPIProvider>
</MediaStreamControllerAPIProvider>
);
}
export default memo(EchoPillarboxPreviewPlayer);
Supporting Components
The following components are used by EchoPillarbox Preview Player and are documented in Set Up A Livestream Video:
// CallControls.tsx
/**
* CallControls Component & Hook
*
* OVERVIEW:
* This file provides both a component and a custom hook for managing
* call and broadcast state with automatic UI controls. It's a powerful
* abstraction that handles all the complexity of call/broadcast lifecycle.
*
* WHAT IT DOES FOR YOU:
* - Manages call and broadcast state automatically
* - Renders the right buttons based on current state
* - Handles state transitions (no call → call → broadcasting)
* - Provides callbacks for cleanup and notifications
* - Supports multiple usage patterns (owner, participant, broadcast-only)
*
* KEY BENEFITS:
* - No need to manually track call/broadcast state
* - No need to conditionally render buttons yourself
* - No need to write call creation/disposal logic
* - Consistent UX across your application
* - Easy to extend and customize
*
* USAGE PATTERNS:
* 1. "owner": Creates and hosts calls (shows CreateCallButton)
* 2. "participant": Joins existing calls (shows JoinCallButton)
* 3. "broadcast-controls-only": Only manages broadcast (requires existing call)
*
* @example As a hook:
* ```tsx
* const { renderControls, call, broadcast } = useCallControls({
* callOptions: { streamKey, auth, ... },
* broadcastOptions: { streamName: 'default' },
* type: 'owner'
* });
*
* return <div>{renderControls()}</div>;
* ```
*
* @example As a component:
* ```tsx
* <CallControls
* callOptions={{ streamKey, auth, ... }}
* broadcastOptions={{ streamName: 'default' }}
* type="owner"
* />
* ```
*/
import React, { useState } from "react";
import { components, types, context } from "@video/video-client-react";
/**
* EXTRACT PRE-BUILT BUTTON COMPONENTS
*
* These components handle button UI and click logic:
* - CreateCallButton: Creates a new call
* - JoinCallButton: Joins an existing call by ID
* - EndCallButton: Ends the current call
* - StartBroadcastButton: Starts broadcasting to a call
* - EndBroadcastButton: Stops broadcasting
*
* All buttons automatically handle loading states, errors, and cleanup.
*/
const { CreateCallButton, EndCallButton, StartBroadcastButton, EndBroadcastButton, JoinCallButton } = components;
/**
* EXTRACT CONTEXT PROVIDERS
*
* These make call and broadcast instances available to button components:
* - CallAPIProvider: Shares call instance with children
* - BroadcastAPIProvider: Shares broadcast instance with children
*/
const { CallAPIProvider, BroadcastAPIProvider } = context;
interface CallControlsOptions {
callOptions?: types.CallOptions;
broadcastOptions: types.BroadcastOptions;
type: 'owner' | 'participant' | 'broadcast-controls-only';
call?: types.CallAPI | null;
}
type CallState = 'no-call-owner' | 'no-call-participant' | 'call-no-broadcast' | 'call-with-broadcast' | 'start-broadcast-only' | 'end-broadcast-only';
interface CallControlsReturn {
call: types.CallAPI | null;
broadcast: types.BroadcastAPI | null;
setCall: (call: types.CallAPI | null) => void;
setBroadcast: (broadcast: types.BroadcastAPI | null) => void;
state: CallState;
renderControls: () => React.ReactElement;
}
/**
* Utility function to determine the current call/broadcast state
*/
function getCallState(call: types.CallAPI | null, broadcast: types.BroadcastAPI | null, type: 'owner' | 'participant' | 'broadcast-controls-only'): CallState {
switch (type) {
case 'owner':
if (call == null) {
return 'no-call-owner';
}
if (broadcast == null) {
return 'call-no-broadcast';
}
return 'call-with-broadcast';
case 'participant':
if (call == null) {
return 'no-call-participant';
}
if (broadcast == null) {
return 'call-no-broadcast';
}
return 'call-with-broadcast';
case 'broadcast-controls-only':
if (call == null) {
throw new Error('Call is required when type is broadcast-controls-only');
}
if (broadcast == null) {
return 'start-broadcast-only';
}
return 'end-broadcast-only';
default:
// This should never happen if TypeScript types are correct
const _exhaustive: never = type;
throw new Error(`Unknown type: ${_exhaustive}`);
}
}
/**
* Custom hook for managing broadcaster call and broadcast state with controls
*
* @param options - Call and broadcast configuration options
* @returns Object containing state, setters, and a render function for controls
*
* @example
* ```tsx
* function MyComponent() {
* const { renderControls } = useBroadcasterCallControls({
* callOptions: { streamKey: '...', auth: authClient, ... },
* broadcastOptions: { streamName: 'default' }
* });
*
* return <div>{renderControls()}</div>;
* }
* ```
*/
function useCallControls(options: CallControlsOptions): CallControlsReturn {
const { callOptions, broadcastOptions } = options;
const [call, setCall] = useState<types.CallAPI | null>(options.call ?? null);
const [broadcast, setBroadcast] = useState<types.BroadcastAPI | null>(null);
const state = getCallState(call, broadcast, options.type);
/**
* Renders the appropriate controls based on current call/broadcast state
*/
const renderControls = (): React.ReactElement => {
switch (state) {
case 'no-call-owner':
if (callOptions == null) {
throw new Error('Call options are required');
}
// No active call - show CreateCallButton
return (
<CreateCallButton
callOptions={callOptions}
setCall={setCall}
/>
);
case 'no-call-participant':
if (callOptions == null || callOptions.callId == null) {
throw new Error('Call options and call ID are required');
}
// No active call - show JoinCallButton
return <JoinCallButton callId={callOptions.callId} joinCallOptions={callOptions} setCall={setCall} />;
case 'call-no-broadcast':
// Call active, no broadcast - show EndCallButton and StartBroadcastButton
return (
<CallAPIProvider callAPI={call!}>
<EndCallButton onDisposed={() => setCall(null)} />
<StartBroadcastButton
broadcastOptions={broadcastOptions}
setBroadcast={setBroadcast}
/>
</CallAPIProvider>
);
case 'call-with-broadcast':
// Call and broadcast active - show EndCallButton and EndBroadcastButton
return (
<CallAPIProvider callAPI={call!}>
<EndCallButton onDisposed={() => setCall(null)} />
<BroadcastAPIProvider broadcastAPI={broadcast!}>
<EndBroadcastButton onDisposed={() => setBroadcast(null)} />
</BroadcastAPIProvider>
</CallAPIProvider>
);
case 'start-broadcast-only':
return (
<CallAPIProvider callAPI={call!}>
<StartBroadcastButton broadcastOptions={broadcastOptions} setBroadcast={setBroadcast} />
</CallAPIProvider>
);
case 'end-broadcast-only':
return (
<CallAPIProvider callAPI={call!}>
<BroadcastAPIProvider broadcastAPI={broadcast!}>
<EndBroadcastButton onDisposed={() => setBroadcast(null)} />
</BroadcastAPIProvider>
</CallAPIProvider>
);
default:
// Exhaustive check - TypeScript will error if we miss a case
const _exhaustive: CallState = state;
return _exhaustive;
}
};
return {
call,
broadcast,
setCall,
setBroadcast,
state,
renderControls,
};
}
/**
* Component wrapper for the broadcaster call controls hook
* Manages call and broadcast state internally
*/
function CallControls(options: CallControlsOptions): React.ReactElement {
const { renderControls } = useCallControls(options);
return renderControls();
}
export default CallControls;
export { useCallControls, getCallState };
export type { CallControlsOptions, CallControlsReturn, CallState };