Skip to main content

Stream Events

Monitor and respond to real-time stream events to create interactive and responsive video experiences. Handle stream lifecycle, viewer activities, broadcast status changes, and technical events.

Overview

social.plus Video SDK provides comprehensive event handling for live streaming applications. Subscribe to events to update UI, trigger notifications, handle errors, and create interactive features.

Event Categories

  • Stream Lifecycle - Start, stop, pause, resume events
  • Viewer Activity - Join, leave, engagement events
  • Broadcast Status - Connection, quality, technical events
  • Interactive Features - Chat, reactions, polls, screen sharing
  • Recording Events - Start, stop, processing, completion
  • Error Handling - Connection failures, stream errors, recovery

Event Types

Stream Lifecycle Events

EventDescriptionPayload
stream.createdStream session created{ streamId, title, scheduledTime }
stream.startedStream went live{ streamId, startTime, quality }
stream.pausedStream temporarily paused{ streamId, pauseTime, reason }
stream.resumedStream resumed after pause{ streamId, resumeTime }
stream.endedStream stopped{ streamId, endTime, duration, viewerCount }
stream.deletedStream session deleted{ streamId, deletedAt }

Viewer Activity Events

EventDescriptionPayload
viewer.joinedViewer joined stream{ streamId, viewerId, joinTime, viewerCount }
viewer.leftViewer left stream{ streamId, viewerId, leaveTime, duration, viewerCount }
viewer.milestoneViewer count milestone{ streamId, count, milestone, timestamp }
viewer.engagementViewer interaction{ streamId, viewerId, action, timestamp }

Broadcast Status Events

EventDescriptionPayload
broadcast.connectedBroadcaster connected{ streamId, quality, bitrate, fps }
broadcast.disconnectedBroadcaster disconnected{ streamId, reason, timestamp }
broadcast.reconnectingAttempting to reconnect{ streamId, attempt, maxAttempts }
broadcast.quality_changedStream quality changed{ streamId, oldQuality, newQuality, reason }
broadcast.errorBroadcasting error occurred{ streamId, error, severity, timestamp }

Interactive Events

EventDescriptionPayload
chat.messageChat message sent{ streamId, message, sender, timestamp }
reaction.addedViewer reaction added{ streamId, reaction, viewerId, timestamp }
poll.createdPoll created in stream{ streamId, poll, createdBy, timestamp }
poll.votedVote cast in poll{ streamId, pollId, option, voterId, timestamp }
screen.sharedScreen sharing started{ streamId, sharerDetails, timestamp }

Recording Events

EventDescriptionPayload
recording.startedRecording began{ streamId, recordingId, startTime, format }
recording.stoppedRecording ended{ streamId, recordingId, endTime, duration }
recording.processingRecording being processed{ streamId, recordingId, progress, estimatedTime }
recording.completedRecording ready{ streamId, recordingId, url, duration, size }
recording.failedRecording failed{ streamId, recordingId, error, timestamp }

Platform Implementation

Swift Event Handling
import SocialVideo

class StreamEventHandler: VideoStreamDelegate {
    
    func setupEventListeners() {
        // Stream lifecycle events
        VideoStreamManager.shared.onStreamStarted = { streamData in
            self.handleStreamStarted(streamData)
        }
        
        VideoStreamManager.shared.onStreamEnded = { streamData in
            self.handleStreamEnded(streamData)
        }
        
        // Viewer activity events
        VideoStreamManager.shared.onViewerJoined = { viewerData in
            self.updateViewerCount(viewerData.viewerCount)
            self.showViewerJoinedAnimation(viewerData.viewer)
        }
        
        // Broadcast status events
        VideoStreamManager.shared.onBroadcastError = { error in
            self.handleBroadcastError(error)
        }
        
        // Recording events
        VideoStreamManager.shared.onRecordingStatusChanged = { status in
            self.updateRecordingUI(status)
        }
    }
    
    private func handleStreamStarted(_ streamData: StreamData) {
        DispatchQueue.main.async {
            self.liveIndicator.isHidden = false
            self.liveIndicator.startAnimating()
            self.viewerCountLabel.text = "0 viewers"
            
            // Send analytics event
            Analytics.track("stream_started", properties: [
                "stream_id": streamData.id,
                "quality": streamData.quality
            ])
        }
    }
    
    private func handleStreamEnded(_ streamData: StreamData) {
        DispatchQueue.main.async {
            self.liveIndicator.isHidden = true
            self.showStreamEndedScreen(streamData)
            
            // Show stream statistics
            self.presentStreamStats(
                duration: streamData.duration,
                viewerCount: streamData.maxViewers,
                engagement: streamData.engagementRate
            )
        }
    }
    
    private func handleBroadcastError(_ error: BroadcastError) {
        DispatchQueue.main.async {
            switch error.severity {
            case .critical:
                self.showCriticalErrorAlert(error)
            case .warning:
                self.showWarningBanner(error)
            case .info:
                self.logInfo(error.message)
            }
        }
    }
}

Advanced Event Patterns

Event Aggregation

// Aggregate multiple events for batch processing
class EventAggregator {
  private events: StreamEvent[] = [];
  private timer: NodeJS.Timeout | null = null;
  
  addEvent(event: StreamEvent): void {
    this.events.push(event);
    
    // Batch process events every 5 seconds
    if (!this.timer) {
      this.timer = setTimeout(() => {
        this.processEvents();
        this.timer = null;
      }, 5000);
    }
  }
  
  private processEvents(): void {
    // Group events by type
    const eventGroups = this.events.reduce((groups, event) => {
      const type = event.type;
      if (!groups[type]) groups[type] = [];
      groups[type].push(event);
      return groups;
    }, {} as Record<string, StreamEvent[]>);
    
    // Process each group
    Object.entries(eventGroups).forEach(([type, events]) => {
      this.processBatchedEvents(type, events);
    });
    
    // Clear processed events
    this.events = [];
  }
}

Event Filtering

// Filter events based on conditions
class EventFilter {
  private filters: Map<string, (event: StreamEvent) => boolean> = new Map();
  
  addFilter(name: string, filter: (event: StreamEvent) => boolean): void {
    this.filters.set(name, filter);
  }
  
  shouldProcessEvent(event: StreamEvent): boolean {
    // Check all filters
    for (const [name, filter] of this.filters) {
      if (!filter(event)) {
        console.log(`Event filtered out by ${name}`);
        return false;
      }
    }
    return true;
  }
}

// Usage
const eventFilter = new EventFilter();

// Only process events for active streams
eventFilter.addFilter('active_streams', (event) => {
  return activeStreamIds.includes(event.streamId);
});

// Skip viewer join events during high traffic
eventFilter.addFilter('high_traffic', (event) => {
  if (event.type === 'viewer.joined' && currentViewerCount > 1000) {
    return false;
  }
  return true;
});

Event Persistence

// Store events for replay or analysis
class EventStore {
  private events: StoredEvent[] = [];
  
  async storeEvent(event: StreamEvent): Promise<void> {
    const storedEvent: StoredEvent = {
      ...event,
      id: generateId(),
      timestamp: Date.now(),
      processed: false
    };
    
    // Store in database
    await this.database.events.create(storedEvent);
    this.events.push(storedEvent);
  }
  
  async getEventsForStream(streamId: string): Promise<StoredEvent[]> {
    return this.database.events.find({ streamId });
  }
  
  async replayEvents(streamId: string, fromTimestamp: number): Promise<void> {
    const events = await this.getEventsForStream(streamId);
    const replayEvents = events.filter(e => e.timestamp >= fromTimestamp);
    
    for (const event of replayEvents) {
      await this.processEvent(event);
    }
  }
}

Error Handling

Comprehensive Error Management

class StreamErrorHandler {
  private errorHandlers: Map<string, (error: StreamError) => void> = new Map();
  
  constructor() {
    this.setupDefaultHandlers();
  }
  
  private setupDefaultHandlers(): void {
    // Connection errors
    this.errorHandlers.set('connection.failed', (error) => {
      this.handleConnectionError(error);
    });
    
    // Stream errors
    this.errorHandlers.set('stream.error', (error) => {
      this.handleStreamError(error);
    });
    
    // Viewer errors
    this.errorHandlers.set('viewer.error', (error) => {
      this.handleViewerError(error);
    });
  }
  
  private handleConnectionError(error: StreamError): void {
    // Attempt automatic recovery
    if (error.recoverable) {
      this.attemptRecovery(error);
    } else {
      // Show error to user
      this.showErrorMessage('Connection lost. Please check your internet connection.');
    }
  }
  
  private async attemptRecovery(error: StreamError): Promise<void> {
    const maxRetries = 3;
    let retryCount = 0;
    
    while (retryCount < maxRetries) {
      try {
        await this.reconnect();
        this.showSuccessMessage('Connection restored');
        return;
      } catch (retryError) {
        retryCount++;
        await this.delay(2000 * retryCount); // Exponential backoff
      }
    }
    
    // Recovery failed
    this.showErrorMessage('Unable to restore connection. Please restart the stream.');
  }
}

Testing Events

Event Testing Utilities

// Mock event generator for testing
class MockEventGenerator {
  private streamId: string;
  private intervalId: NodeJS.Timeout | null = null;
  
  constructor(streamId: string) {
    this.streamId = streamId;
  }
  
  startSimulation(): void {
    this.intervalId = setInterval(() => {
      this.generateRandomEvent();
    }, 2000);
  }
  
  stopSimulation(): void {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  }
  
  private generateRandomEvent(): void {
    const eventTypes = [
      'viewer.joined',
      'viewer.left', 
      'chat.message',
      'reaction.added',
      'quality.changed'
    ];
    
    const randomType = eventTypes[Math.floor(Math.random() * eventTypes.length)];
    const mockEvent = this.createMockEvent(randomType);
    
    // Emit the mock event
    VideoStreamManager.getInstance().emit(randomType, mockEvent);
  }
  
  private createMockEvent(type: string): StreamEvent {
    switch (type) {
      case 'viewer.joined':
        return {
          type,
          streamId: this.streamId,
          viewerId: `viewer-${Date.now()}`,
          viewerCount: Math.floor(Math.random() * 1000),
          timestamp: Date.now()
        };
        
      case 'chat.message':
        return {
          type,
          streamId: this.streamId,
          message: this.generateRandomMessage(),
          sender: `user-${Math.floor(Math.random() * 100)}`,
          timestamp: Date.now()
        };
        
      default:
        return {
          type,
          streamId: this.streamId,
          timestamp: Date.now()
        };
    }
  }
}

Performance Considerations

Event Throttling: Use throttling for high-frequency events like viewer activity to prevent UI performance issues.

Event Throttling

// Throttle high-frequency events
class EventThrottler {
  private lastEventTime: Map<string, number> = new Map();
  private throttleIntervals: Map<string, number> = new Map();
  
  constructor() {
    // Set throttle intervals for different event types
    this.throttleIntervals.set('viewer.joined', 1000); // 1 second
    this.throttleIntervals.set('viewer.left', 1000);
    this.throttleIntervals.set('quality.changed', 5000); // 5 seconds
  }
  
  shouldProcessEvent(event: StreamEvent): boolean {
    const eventKey = `${event.type}-${event.streamId}`;
    const now = Date.now();
    const lastTime = this.lastEventTime.get(eventKey) || 0;
    const throttleInterval = this.throttleIntervals.get(event.type) || 0;
    
    if (now - lastTime >= throttleInterval) {
      this.lastEventTime.set(eventKey, now);
      return true;
    }
    
    return false;
  }
}

Best Practices

Event Handling Strategy

  • Prioritize Critical Events - Stream start/end, connection failures
  • Batch Non-Critical Events - Viewer activity, chat messages
  • Implement Retry Logic - For failed event processing
  • Use Event Aggregation - Reduce processing overhead
  • Store Important Events - For analytics and replay

Performance Optimization

  • Throttle High-Frequency Events - Prevent UI blocking
  • Debounce Rapid Events - Batch similar events
  • Use Background Processing - For non-UI critical events
  • Implement Event Queuing - Handle event bursts
  • Monitor Event Volume - Track processing performance

Error Recovery

  • Graceful Degradation - Continue operation when non-critical events fail
  • Automatic Retry - For transient failures
  • User Feedback - Clear error messages and recovery options
  • Fallback Mechanisms - Alternative event sources when primary fails

Next Steps

  1. Push Notifications - Implement notification delivery
  2. Broadcasting Setup - Configure event-driven broadcasting
  3. Platform Guides: Implementation details:
  1. Troubleshooting - Debug event handling issues
Event Volume: High-traffic streams can generate thousands of events per minute. Implement appropriate throttling, batching, and filtering to maintain performance.