Skip to main content

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.

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:

  1. Built-in Components: EchoPillarboxWrapper and EchoPillarboxVideo handle all the complexity
  2. Single Player Architecture: One PlayerAPI instance manages the video stream. Can be used for both PreviewPlayers and Players!
  3. Automatic Synchronization: Components automatically sync and style video elements
  4. Simple Integration: Just wrap your existing Video component 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 mediaStreamController to capture a canvas element
  • The specific code required for canvas broadcasting

For foundational concepts, please review:

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:

  1. Detects the main Video component within the same EchoPillarboxWrapper
  2. Automatically accesses its MediaStream
  3. Creates a second video element with the same stream
  4. Applies blur and scale effects
  5. 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:

  1. Video - Video element component for the main video
  2. EchoPillarboxWrapper - Container component that manages the echo pillarbox layout
  3. EchoPillarboxVideo - 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

  1. Use one PlayerAPI instance - let EchoPillarboxVideo handle synchronization
  2. Wrap with PlayerAPIProvider - place it around the EchoPillarboxWrapper
  3. Dispose properly - clean up the player in useEffect cleanup
  4. Customize via props - use className and style on EchoPillarboxWrapper
  5. 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 };