Skip to main content

Lifecycle Management

Proper lifecycle management ensures optimal performance, resource efficiency, and a smooth user experience. This guide covers stream lifecycle, application state management, and resource cleanup strategies across all platforms.

Stream Lifecycle Overview

Stream States and Transitions

Understanding stream states is crucial for proper lifecycle management:
enum StreamState {
    IDLE = 'idle',           // Created but not started
    CONNECTING = 'connecting', // Establishing connection
    LIVE = 'live',           // Currently broadcasting
    RECONNECTING = 'reconnecting', // Attempting to reconnect
    ENDED = 'ended',         // Broadcast stopped
    RECORDED = 'recorded',   // Available for playback
    ERROR = 'error',         // Error state
    CLEANUP = 'cleanup'      // Resources being cleaned up
}

class StreamLifecycleManager {
    private streamState: StreamState = StreamState.IDLE;
    private stateListeners: ((state: StreamState) => void)[] = [];
    private resources: Map<string, any> = new Map();
    
    async createStream(config: StreamConfig): Promise<Stream> {
        this.setState(StreamState.IDLE);
        
        const stream = await this.streamRepository.createStream(config);
        this.allocateResources(stream.streamId);
        
        return stream;
    }
    
    async startBroadcast(streamId: string): Promise<void> {
        this.setState(StreamState.CONNECTING);
        
        try {
            await this.establishConnection(streamId);
            await this.initializeBroadcast(streamId);
            this.setState(StreamState.LIVE);
            this.startHeartbeat(streamId);
        } catch (error) {
            this.setState(StreamState.ERROR);
            throw error;
        }
    }
    
    async stopBroadcast(streamId: string): Promise<void> {
        this.setState(StreamState.ENDED);
        
        await this.stopHeartbeat();
        await this.finalizeBroadcast(streamId);
        
        // Wait for processing to complete
        await this.waitForProcessing(streamId);
        this.setState(StreamState.RECORDED);
    }
    
    async cleanup(streamId: string): Promise<void> {
        this.setState(StreamState.CLEANUP);
        
        await this.releaseResources(streamId);
        await this.clearCache(streamId);
        
        this.setState(StreamState.IDLE);
    }
    
    private setState(newState: StreamState) {
        const previousState = this.streamState;
        this.streamState = newState;
        
        console.log(`Stream state: ${previousState}${newState}`);
        this.notifyStateListeners(newState);
    }
}

Application Lifecycle Integration

Platform-Specific Lifecycle Handling

import UIKit

class StreamLifecycleManager: NSObject {
    private var activeStreams: [String: StreamSession] = [:]
    
    override init() {
        super.init()
        setupAppLifecycleObservers()
    }
    
    private func setupAppLifecycleObservers() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(appWillResignActive),
            name: UIApplication.willResignActiveNotification,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(appDidEnterBackground),
            name: UIApplication.didEnterBackgroundNotification,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(appWillEnterForeground),
            name: UIApplication.willEnterForegroundNotification,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(appDidBecomeActive),
            name: UIApplication.didBecomeActiveNotification,
            object: nil
        )
    }
    
    @objc private func appWillResignActive() {
        // Prepare for transition to background
        for (streamId, session) in activeStreams {
            session.prepareForBackground()
        }
    }
    
    @objc private func appDidEnterBackground() {
        // Handle background transition
        for (streamId, session) in activeStreams {
            if session.isLive {
                session.enableBackgroundMode()
            } else {
                session.pause()
            }
        }
    }
    
    @objc private func appWillEnterForeground() {
        // Prepare for foreground return
        for (streamId, session) in activeStreams {
            session.prepareForForeground()
        }
    }
    
    @objc private func appDidBecomeActive() {
        // Resume normal operation
        for (streamId, session) in activeStreams {
            session.resume()
        }
    }
}

class StreamSession {
    var isLive: Bool = false
    private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
    
    func prepareForBackground() {
        // Reduce resource usage
        reduceQuality()
        
        // Start background task if streaming
        if isLive {
            backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
                self?.endBackgroundTask()
            }
        }
    }
    
    func enableBackgroundMode() {
        // Enable background streaming
        let backgroundConfig = StreamConfig(
            quality: .low,
            frameRate: 15,
            enableBackgroundOptimizations: true
        )
        
        applyConfiguration(backgroundConfig)
    }
    
    private func endBackgroundTask() {
        if backgroundTask != .invalid {
            UIApplication.shared.endBackgroundTask(backgroundTask)
            backgroundTask = .invalid
        }
    }
}

Resource Management

Memory and Resource Cleanup

class StreamResourceManager {
    private resourcePool = new Map<string, ResourceInfo>();
    private memoryThreshold = 150; // MB
    private cleanupInterval?: NodeJS.Timeout;
    
    initialize() {
        this.startMemoryMonitoring();
        this.setupPeriodicCleanup();
    }
    
    private startMemoryMonitoring() {
        setInterval(() => {
            const memoryUsage = this.getCurrentMemoryUsage();
            
            if (memoryUsage > this.memoryThreshold) {
                console.warn(`High memory usage: ${memoryUsage}MB`);
                this.performMemoryCleanup();
            }
        }, 10000); // Check every 10 seconds
    }
    
    allocateResources(streamId: string, config: StreamConfig): ResourceAllocation {
        const resources: ResourceAllocation = {
            videoBuffer: this.allocateVideoBuffer(config.resolution),
            audioBuffer: this.allocateAudioBuffer(config.audioConfig),
            encoderContext: this.allocateEncoder(config),
            networkBuffers: this.allocateNetworkBuffers(),
            tempFiles: this.allocateTempFiles(streamId)
        };
        
        this.resourcePool.set(streamId, {
            allocation: resources,
            allocatedAt: new Date(),
            lastUsed: new Date()
        });
        
        return resources;
    }
    
    releaseResources(streamId: string): void {
        const resourceInfo = this.resourcePool.get(streamId);
        if (!resourceInfo) return;
        
        const { allocation } = resourceInfo;
        
        // Release video resources
        allocation.videoBuffer?.release();
        allocation.audioBuffer?.release();
        allocation.encoderContext?.dispose();
        
        // Clean up network buffers
        allocation.networkBuffers.forEach(buffer => buffer.release());
        
        // Delete temporary files
        allocation.tempFiles.forEach(file => this.deleteFile(file));
        
        this.resourcePool.delete(streamId);
        
        // Force garbage collection hint
        this.requestGarbageCollection();
    }
    
    private performMemoryCleanup(): void {
        console.log('Performing memory cleanup...');
        
        // Clean up unused resources
        this.cleanupUnusedResources();
        
        // Optimize active resources
        this.optimizeActiveResources();
        
        // Clear caches
        this.clearCaches();
        
        // Request garbage collection
        this.requestGarbageCollection();
    }
    
    private cleanupUnusedResources(): void {
        const now = new Date();
        const unusedThreshold = 5 * 60 * 1000; // 5 minutes
        
        this.resourcePool.forEach((resourceInfo, streamId) => {
            const timeSinceLastUse = now.getTime() - resourceInfo.lastUsed.getTime();
            
            if (timeSinceLastUse > unusedThreshold) {
                console.log(`Cleaning up unused resources for stream ${streamId}`);
                this.releaseResources(streamId);
            }
        });
    }
}

Connection Management

class ConnectionLifecycleManager {
    private connections = new Map<string, Connection>();
    private heartbeatInterval?: NodeJS.Timeout;
    private reconnectAttempts = new Map<string, number>();
    
    async createConnection(streamId: string, config: ConnectionConfig): Promise<Connection> {
        const connection = new Connection(streamId, config);
        
        connection.onDisconnect(() => this.handleDisconnection(streamId));
        connection.onError((error) => this.handleConnectionError(streamId, error));
        
        await connection.connect();
        this.connections.set(streamId, connection);
        
        this.startHeartbeat(streamId);
        return connection;
    }
    
    async closeConnection(streamId: string): Promise<void> {
        const connection = this.connections.get(streamId);
        if (!connection) return;
        
        this.stopHeartbeat(streamId);
        await connection.gracefulDisconnect();
        this.connections.delete(streamId);
        this.reconnectAttempts.delete(streamId);
    }
    
    private startHeartbeat(streamId: string): void {
        const connection = this.connections.get(streamId);
        if (!connection) return;
        
        const heartbeatId = setInterval(async () => {
            try {
                await connection.sendHeartbeat();
            } catch (error) {
                console.warn(`Heartbeat failed for ${streamId}:`, error);
                this.handleConnectionError(streamId, error);
            }
        }, 30000); // 30 second heartbeat
        
        connection.setHeartbeatId(heartbeatId);
    }
    
    private stopHeartbeat(streamId: string): void {
        const connection = this.connections.get(streamId);
        if (connection) {
            connection.clearHeartbeat();
        }
    }
    
    private async handleDisconnection(streamId: string): Promise<void> {
        console.log(`Connection lost for stream ${streamId}`);
        
        const maxReconnectAttempts = 5;
        const currentAttempts = this.reconnectAttempts.get(streamId) || 0;
        
        if (currentAttempts < maxReconnectAttempts) {
            await this.attemptReconnection(streamId);
        } else {
            console.error(`Max reconnection attempts reached for ${streamId}`);
            this.onReconnectionFailed(streamId);
        }
    }
    
    private async attemptReconnection(streamId: string): Promise<void> {
        const attempts = this.reconnectAttempts.get(streamId) || 0;
        this.reconnectAttempts.set(streamId, attempts + 1);
        
        const delay = Math.min(1000 * Math.pow(2, attempts), 30000); // Exponential backoff, max 30s
        
        console.log(`Reconnecting ${streamId} in ${delay}ms (attempt ${attempts + 1})`);
        
        setTimeout(async () => {
            try {
                const connection = this.connections.get(streamId);
                if (connection) {
                    await connection.reconnect();
                    this.reconnectAttempts.set(streamId, 0); // Reset on success
                    console.log(`Reconnection successful for ${streamId}`);
                }
            } catch (error) {
                console.error(`Reconnection failed for ${streamId}:`, error);
                this.handleDisconnection(streamId); // Try again
            }
        }, delay);
    }
}

Error Recovery and Resilience

Graceful Degradation

class FailureRecoveryManager {
    private recoveryStrategies = new Map<string, RecoveryStrategy>();
    private failureHistory = new Map<string, FailureInfo[]>();
    
    registerRecoveryStrategy(errorType: string, strategy: RecoveryStrategy): void {
        this.recoveryStrategies.set(errorType, strategy);
    }
    
    async handleFailure(streamId: string, error: StreamError): Promise<boolean> {
        this.recordFailure(streamId, error);
        
        const strategy = this.recoveryStrategies.get(error.type);
        if (!strategy) {
            console.error(`No recovery strategy for error type: ${error.type}`);
            return false;
        }
        
        const maxAttempts = this.getMaxRecoveryAttempts(streamId, error.type);
        let attempt = 0;
        
        while (attempt < maxAttempts) {
            try {
                await this.wait(this.getRetryDelay(attempt));
                
                const recovered = await strategy.execute(streamId, error, attempt);
                if (recovered) {
                    console.log(`Recovery successful for ${streamId} after ${attempt + 1} attempts`);
                    this.clearFailureHistory(streamId, error.type);
                    return true;
                }
                
                attempt++;
            } catch (recoveryError) {
                console.error(`Recovery attempt ${attempt + 1} failed:`, recoveryError);
                attempt++;
            }
        }
        
        console.error(`Recovery failed for ${streamId} after ${maxAttempts} attempts`);
        return false;
    }
    
    private recordFailure(streamId: string, error: StreamError): void {
        const failures = this.failureHistory.get(streamId) || [];
        failures.push({
            error,
            timestamp: new Date(),
            recoveryAttempted: false
        });
        
        // Keep only recent failures (last hour)
        const oneHourAgo = Date.now() - 60 * 60 * 1000;
        const recentFailures = failures.filter(f => f.timestamp.getTime() > oneHourAgo);
        
        this.failureHistory.set(streamId, recentFailures);
    }
    
    private getMaxRecoveryAttempts(streamId: string, errorType: string): number {
        const failures = this.failureHistory.get(streamId) || [];
        const recentFailuresOfType = failures.filter(f => f.error.type === errorType);
        
        // Reduce attempts for repeated failures
        if (recentFailuresOfType.length > 3) {
            return 1; // Only one attempt for frequently failing streams
        }
        
        return 3; // Default attempts
    }
    
    private getRetryDelay(attempt: number): number {
        // Exponential backoff with jitter
        const baseDelay = 1000; // 1 second
        const maxDelay = 30000; // 30 seconds
        const exponentialDelay = baseDelay * Math.pow(2, attempt);
        const jitter = Math.random() * 1000; // Add up to 1 second jitter
        
        return Math.min(exponentialDelay + jitter, maxDelay);
    }
}

// Recovery strategies for different error types
class NetworkRecoveryStrategy implements RecoveryStrategy {
    async execute(streamId: string, error: StreamError, attempt: number): Promise<boolean> {
        console.log(`Attempting network recovery for ${streamId} (attempt ${attempt + 1})`);
        
        // Step 1: Check network connectivity
        const isConnected = await this.checkNetworkConnectivity();
        if (!isConnected) {
            throw new Error('No network connectivity');
        }
        
        // Step 2: Test connection to streaming server
        const serverReachable = await this.testServerConnectivity();
        if (!serverReachable) {
            throw new Error('Streaming server unreachable');
        }
        
        // Step 3: Attempt to reestablish stream
        return await this.reestablishStream(streamId);
    }
    
    private async checkNetworkConnectivity(): Promise<boolean> {
        try {
            const response = await fetch('https://httpbin.org/get', { 
                method: 'HEAD',
                timeout: 5000 
            });
            return response.ok;
        } catch {
            return false;
        }
    }
    
    private async reestablishStream(streamId: string): Promise<boolean> {
        // Implementation specific to your streaming setup
        // This would reconnect to the streaming service
        return true; // Placeholder
    }
}

Performance Optimization

Resource Optimization

class PerformanceOptimizer {
    private performanceMetrics = new Map<string, PerformanceMetric[]>();
    
    async optimizeStream(streamId: string): Promise<void> {
        const metrics = await this.gatherMetrics(streamId);
        const optimizations = this.analyzeMetrics(metrics);
        
        await this.applyOptimizations(streamId, optimizations);
    }
    
    private analyzeMetrics(metrics: PerformanceMetrics): Optimization[] {
        const optimizations: Optimization[] = [];
        
        // CPU optimization
        if (metrics.cpuUsage > 70) {
            optimizations.push({
                type: 'cpu',
                action: 'reduce_quality',
                priority: 'high'
            });
        }
        
        // Memory optimization
        if (metrics.memoryUsage > 150) {
            optimizations.push({
                type: 'memory',
                action: 'cleanup_resources',
                priority: 'high'
            });
        }
        
        // Network optimization
        if (metrics.packetLoss > 5) {
            optimizations.push({
                type: 'network',
                action: 'reduce_bitrate',
                priority: 'medium'
            });
        }
        
        return optimizations;
    }
    
    private async applyOptimizations(streamId: string, optimizations: Optimization[]): Promise<void> {
        // Sort by priority
        optimizations.sort((a, b) => this.getPriorityValue(b.priority) - this.getPriorityValue(a.priority));
        
        for (const optimization of optimizations) {
            try {
                await this.executeOptimization(streamId, optimization);
            } catch (error) {
                console.error(`Failed to apply optimization ${optimization.type}:`, error);
            }
        }
    }
}

Next Steps

Now that you understand lifecycle management, explore these advanced topics: For lifecycle-related troubleshooting, see our Platform Issues Guide.