Building Your First App
Create a complete streaming application with Playcast
1 Project Setup
âąī¸
5 minutes
Let's create a new Playcast streaming application from scratch. We'll use the Nx workspace to generate a new app with all the necessary dependencies.
terminal
# Clone the Playcast repository git clone https://github.com/your-org/playcast-apps.git cd playcast-apps # Install dependencies npm install # Generate a new streaming application nx generate @nrwl/react:app my-streaming-app # Navigate to the new app cd apps/my-streaming-app
Setup Checklist
Repository cloned successfully
Dependencies installed
New app generated
Development server starts
đ
Expected Output
â
Repository cloned
â
Dependencies installed (247 packages)
â
Generated my-streaming-app
â
App created in apps/my-streaming-app
đ Next: Run 'nx serve my-streaming-app' to start development
2 Playcast Client Integration
âąī¸
10 minutes
Now we'll integrate the Playcast client into our application. This involves setting up the core client, configuration, and basic connection handling.
playcast-client.ts
app.tsx
types.ts
// apps/my-streaming-app/src/lib/playcast-client.ts
import { PlaycastClient, StreamConfig, ConnectionState } from '@playcast/core';
import { StreamingService } from '@playcast/streaming';
import { EventEmitter } from 'events';
export class MyStreamingClient extends EventEmitter {
private client: PlaycastClient;
private streamingService: StreamingService;
private currentStream: any = null;
constructor() {
super();
// Initialize the Playcast client
this.client = new PlaycastClient({
apiKey: process.env['NX_PLAYCAST_API_KEY'] || 'demo-key',
environment: 'development',
debug: true,
endpoints: {
api: 'http://localhost:3333',
streaming: 'http://localhost:8080',
webrtc: 'ws://localhost:9090'
}
});
// Initialize streaming service
this.streamingService = new StreamingService(this.client);
// Set up event handlers
this.setupEventHandlers();
}
private setupEventHandlers() {
this.client.on('connection-state', (state: ConnectionState) => {
console.log('Connection state:', state);
this.emit('connection-change', state);
});
this.streamingService.on('stream-started', (stream) => {
console.log('Stream started:', stream.id);
this.currentStream = stream;
this.emit('stream-started', stream);
});
this.streamingService.on('stream-error', (error) => {
console.error('Stream error:', error);
this.emit('stream-error', error);
});
}
async connect(): Promise<boolean> {
try {
await this.client.connect();
return true;
} catch (error) {
console.error('Connection failed:', error);
return false;
}
}
async createStream(config: StreamConfig): Promise<string | null> {
try {
const stream = await this.streamingService.createStream({
quality: config.quality || 'HD',
codec: config.codec || 'H264',
bitrate: config.bitrate || '5mbps',
resolution: config.resolution || '1920x1080',
frameRate: config.frameRate || 60
});
return stream.id;
} catch (error) {
console.error('Failed to create stream:', error);
return null;
}
}
async stopStream(): Promise<void> {
if (this.currentStream) {
await this.streamingService.stopStream(this.currentStream.id);
this.currentStream = null;
}
}
disconnect(): void {
this.client.disconnect();
}
}
// apps/my-streaming-app/src/app/app.tsx
import React, { useEffect, useState } from 'react';
import { MyStreamingClient } from '../lib/playcast-client';
import { StreamingComponent } from './components/streaming-component';
import { ConnectionStatus } from './components/connection-status';
const streamingClient = new MyStreamingClient();
export function App() {
const [isConnected, setIsConnected] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
// Set up client event listeners
streamingClient.on('connection-change', (state) => {
setIsConnected(state === 'connected');
});
streamingClient.on('stream-error', (error) => {
setError(error.message);
});
// Initial connection attempt
connectToPlaycast();
return () => {
streamingClient.disconnect();
};
}, []);
const connectToPlaycast = async () => {
setIsLoading(true);
setError(null);
try {
const connected = await streamingClient.connect();
if (!connected) {
setError('Failed to connect to Playcast services');
}
} catch (error) {
setError('Connection error occurred');
} finally {
setIsLoading(false);
}
};
return (
<div className="app-container">
<header className="app-header">
<h1>My Streaming App</h1>
<ConnectionStatus
isConnected={isConnected}
isLoading={isLoading}
onRetry={connectToPlaycast}
/>
</header>
<main className="app-main">
{error && (
<div className="error-banner">
<span>â {error}</span>
<button onClick={() => setError(null)}>Dismiss</button>
</div>
)}
{isConnected ? (
<StreamingComponent client={streamingClient} />
) : (
<div className="connecting-state">
<h2>Connecting to Playcast...</h2>
<p>Please wait while we establish a connection.</p>
</div>
)}
</main>
</div>
);
}
export default App;
// apps/my-streaming-app/src/types/index.ts
export interface StreamConfig {
quality?: 'HD' | '4K' | 'SD';
codec?: 'H264' | 'H265' | 'VP9';
bitrate?: string;
resolution?: string;
frameRate?: number;
}
export interface StreamState {
id: string | null;
status: 'idle' | 'connecting' | 'streaming' | 'error';
quality: string;
bitrate: string;
latency: number;
}
export interface ConnectionState {
status: 'disconnected' | 'connecting' | 'connected' | 'error';
latency: number;
lastError: string | null;
}
export interface StreamingComponentProps {
client: MyStreamingClient;
}
export interface ConnectionStatusProps {
isConnected: boolean;
isLoading: boolean;
onRetry: () => void;
}
Integration Checklist
Playcast client configured
Event handlers implemented
App component updated
Type definitions added
3 User Interface Components
âąī¸
15 minutes
Create the user interface components for your streaming application, including connection status, stream controls, and the video display area.
streaming-component.tsx
connection-status.tsx
stream-controls.tsx
// apps/my-streaming-app/src/app/components/streaming-component.tsx
import React, { useState, useRef, useEffect } from 'react';
import { MyStreamingClient } from '../../lib/playcast-client';
import { StreamControls } from './stream-controls';
import { StreamState } from '../../types';
interface StreamingComponentProps {
client: MyStreamingClient;
}
export const StreamingComponent: React.FC<StreamingComponentProps> = ({ client }) => {
const videoRef = useRef<HTMLVideoElement>(null);
const [streamState, setStreamState] = useState<StreamState>({
id: null,
status: 'idle',
quality: 'HD',
bitrate: '5mbps',
latency: 0
});
useEffect(() => {
client.on('stream-started', (stream) => {
setStreamState(prev => ({
...prev,
id: stream.id,
status: 'streaming'
}));
// Attach video stream to video element
if (videoRef.current && stream.mediaStream) {
videoRef.current.srcObject = stream.mediaStream;
}
});
client.on('stream-error', () => {
setStreamState(prev => ({
...prev,
status: 'error'
}));
});
return () => {
client.removeAllListeners('stream-started');
client.removeAllListeners('stream-error');
};
}, [client]);
const handleStartStream = async (config: any) => {
setStreamState(prev => ({ ...prev, status: 'connecting' }));
const streamId = await client.createStream(config);
if (!streamId) {
setStreamState(prev => ({ ...prev, status: 'error' }));
}
};
const handleStopStream = async () => {
await client.stopStream();
setStreamState(prev => ({
...prev,
id: null,
status: 'idle'
}));
if (videoRef.current) {
videoRef.current.srcObject = null;
}
};
return (
<div className="streaming-container">
<div className="video-area">
<video
ref={videoRef}
className="stream-video"
autoPlay
muted
controls={false}
style={{
width: '100%',
height: '500px',
backgroundColor: '#000',
borderRadius: '8px'
}}
/>
{streamState.status === 'idle' && (
<div className="video-overlay">
<h3>Ready to Stream</h3>
<p>Click "Start Stream" to begin</p>
</div>
)}
{streamState.status === 'connecting' && (
<div className="video-overlay">
<div className="loading-spinner"></div>
<p>Connecting to stream...</p>
</div>
)}
{streamState.status === 'error' && (
<div className="video-overlay error">
<h3>Stream Error</h3>
<p>Failed to start the stream. Please try again.</p>
</div>
)}
</div>
<StreamControls
streamState={streamState}
onStartStream={handleStartStream}
onStopStream={handleStopStream}
/>
<div className="stream-info">
<div className="info-card">
<h4>Stream Status</h4>
<p>Status: {streamState.status}</p>
<p>Quality: {streamState.quality}</p>
<p>Bitrate: {streamState.bitrate}</p>
</div>
</div>
</div>
);
};
// apps/my-streaming-app/src/app/components/connection-status.tsx
import React from 'react';
interface ConnectionStatusProps {
isConnected: boolean;
isLoading: boolean;
onRetry: () => void;
}
export const ConnectionStatus: React.FC<ConnectionStatusProps> = ({
isConnected,
isLoading,
onRetry
}) => {
const getStatusColor = () => {
if (isLoading) return '#fbbf24'; // Yellow
if (isConnected) return '#10b981'; // Green
return '#ef4444'; // Red
};
const getStatusText = () => {
if (isLoading) return 'Connecting...';
if (isConnected) return 'Connected';
return 'Disconnected';
};
const getStatusIcon = () => {
if (isLoading) return 'âŗ';
if (isConnected) return 'â
';
return 'â';
};
return (
<div className="connection-status">
<div
className="status-indicator"
style={{
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.5rem 1rem',
borderRadius: '20px',
backgroundColor: 'rgba(31, 41, 55, 0.8)',
border: `2px solid ${getStatusColor()}`,
color: getStatusColor()
}}
>
<span>{getStatusIcon()}</span>
<span style={{ fontWeight: '600', fontSize: '0.875rem' }}>
{getStatusText()}
</span>
</div>
{!isConnected && !isLoading && (
<button
onClick={onRetry}
style={{
marginLeft: '1rem',
padding: '0.5rem 1rem',
borderRadius: '6px',
border: 'none',
backgroundColor: '#fbbf24',
color: '#111827',
fontWeight: '600',
cursor: 'pointer',
fontSize: '0.875rem'
}}
>
Retry
</button>
)}
</div>
);
};
// apps/my-streaming-app/src/app/components/stream-controls.tsx
import React, { useState } from 'react';
import { StreamState, StreamConfig } from '../../types';
interface StreamControlsProps {
streamState: StreamState;
onStartStream: (config: StreamConfig) => void;
onStopStream: () => void;
}
export const StreamControls: React.FC<StreamControlsProps> = ({
streamState,
onStartStream,
onStopStream
}) => {
const [config, setConfig] = useState<StreamConfig>({
quality: 'HD',
codec: 'H264',
bitrate: '5mbps',
resolution: '1920x1080',
frameRate: 60
});
const handleStartStream = () => {
onStartStream(config);
};
const isStreaming = streamState.status === 'streaming';
const isConnecting = streamState.status === 'connecting';
return (
<div className="stream-controls">
<div className="controls-section">
<h3>Stream Configuration</h3>
<div className="control-group">
<label>Quality:</label>
<select
value={config.quality}
onChange={(e) => setConfig({ ...config, quality: e.target.value as any })}
disabled={isStreaming || isConnecting}
>
<option value="SD">SD (720p)</option>
<option value="HD">HD (1080p)</option>
<option value="4K">4K (2160p)</option>
</select>
</div>
<div className="control-group">
<label>Codec:</label>
<select
value={config.codec}
onChange={(e) => setConfig({ ...config, codec: e.target.value as any })}
disabled={isStreaming || isConnecting}
>
<option value="H264">H.264</option>
<option value="H265">H.265</option>
<option value="VP9">VP9</option>
</select>
</div>
<div className="control-group">
<label>Bitrate:</label>
<select
value={config.bitrate}
onChange={(e) => setConfig({ ...config, bitrate: e.target.value })}
disabled={isStreaming || isConnecting}
>
<option value="2mbps">2 Mbps</option>
<option value="5mbps">5 Mbps</option>
<option value="10mbps">10 Mbps</option>
<option value="20mbps">20 Mbps</option>
</select>
</div>
</div>
<div className="action-buttons">
{!isStreaming && !isConnecting && (
<button
className="start-button"
onClick={handleStartStream}
style={{
padding: '1rem 2rem',
borderRadius: '8px',
border: 'none',
background: 'linear-gradient(135deg, #10b981, #059669)',
color: 'white',
fontWeight: 'bold',
cursor: 'pointer',
fontSize: '1rem'
}}
>
đŦ Start Stream
</button>
)}
{isConnecting && (
<button
disabled
style={{
padding: '1rem 2rem',
borderRadius: '8px',
border: 'none',
backgroundColor: '#6b7280',
color: 'white',
fontWeight: 'bold',
cursor: 'not-allowed',
fontSize: '1rem'
}}
>
âŗ Connecting...
</button>
)}
{isStreaming && (
<button
className="stop-button"
onClick={onStopStream}
style={{
padding: '1rem 2rem',
borderRadius: '8px',
border: 'none',
background: 'linear-gradient(135deg, #ef4444, #dc2626)',
color: 'white',
fontWeight: 'bold',
cursor: 'pointer',
fontSize: '1rem'
}}
>
âšī¸ Stop Stream
</button>
)}
</div>
</div>
);
};
đī¸ Component Preview
đŽ
Streaming Interface
Your components will create a complete streaming interface with video display, controls, and status indicators.
Step 1 of 6
đ
Achievement Unlocked!
First App Builder