Building Your First App

Create a complete streaming application with Playcast

âąī¸ 45 minutes
📋 Intermediate
đŸŽ¯ Hands-on Project
🎮 Streaming App

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