logger = VLogger::getInstance(); $this->db = VDatabase::getInstance(); $this->queue = new VQueueManager(); $this->loadSRSConfig(); } /** * Create live stream * @param int $userId User ID * @param array $streamData Stream configuration * @return array Stream creation result */ public function createStream($userId, $streamData) { try { $streamKey = $this->generateStreamKey(); $stream = [ 'stream_key' => $streamKey, 'user_id' => $userId, 'title' => $streamData['title'] ?? 'Live Stream', 'description' => $streamData['description'] ?? '', 'category' => $streamData['category'] ?? 'general', 'privacy' => $streamData['privacy'] ?? 'public', 'status' => 'created', 'rtmp_url' => "rtmp://{$this->srsHost}:{$this->srsRtmpPort}/live/{$streamKey}", 'hls_url' => "http://{$this->srsHost}:{$this->srsHttpPort}/live/{$streamKey}.m3u8", 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ]; $streamId = $this->db->doInsert('db_live_streams', $stream); if (!$streamId) { throw new Exception('Failed to create stream record'); } $this->logger->info('Live stream created', [ 'stream_id' => $streamId, 'stream_key' => $streamKey, 'user_id' => $userId ]); return [ 'success' => true, 'stream_id' => $streamId, 'stream_key' => $streamKey, 'rtmp_url' => $stream['rtmp_url'], 'hls_url' => $stream['hls_url'], 'stream_data' => $stream ]; } catch (Exception $e) { $this->logger->error('Failed to create live stream', [ 'user_id' => $userId, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Start live stream * @param string $streamKey Stream key * @return array Start result */ public function startStream($streamKey) { try { $stream = $this->getStreamByKey($streamKey); if (!$stream) { throw new Exception('Stream not found'); } // Update stream status $this->db->doUpdate('db_live_streams', 'id', [ 'status' => 'live', 'started_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ], $stream['id']); // Notify subscribers $this->notifyStreamStart($stream); $this->logger->info('Live stream started', [ 'stream_key' => $streamKey, 'stream_id' => $stream['id'] ]); return [ 'success' => true, 'message' => 'Stream started successfully' ]; } catch (Exception $e) { $this->logger->error('Failed to start stream', [ 'stream_key' => $streamKey, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Stop live stream * @param string $streamKey Stream key * @param bool $saveRecording Whether to save recording * @return array Stop result */ public function stopStream($streamKey, $saveRecording = true) { try { $stream = $this->getStreamByKey($streamKey); if (!$stream) { throw new Exception('Stream not found'); } // Update stream status $updateData = [ 'status' => 'ended', 'ended_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ]; if ($saveRecording) { // Queue recording processing job $this->queue->enqueue('StreamRecordingJob', [ 'stream_id' => $stream['id'], 'stream_key' => $streamKey, 'user_id' => $stream['user_id'] ], 'video_processing', 0, 1); $updateData['recording_queued'] = 1; } $this->db->doUpdate('db_live_streams', 'id', $updateData, $stream['id']); $this->logger->info('Live stream stopped', [ 'stream_key' => $streamKey, 'stream_id' => $stream['id'], 'save_recording' => $saveRecording ]); return [ 'success' => true, 'message' => 'Stream stopped successfully', 'recording_queued' => $saveRecording ]; } catch (Exception $e) { $this->logger->error('Failed to stop stream', [ 'stream_key' => $streamKey, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Get live streams * @param array $filters Filters * @param int $limit Limit * @param int $offset Offset * @return array Streams */ public function getLiveStreams($filters = [], $limit = 20, $offset = 0) { try { $whereClause = "WHERE 1=1"; $params = []; if (isset($filters['status'])) { $whereClause .= " AND status = ?"; $params[] = $filters['status']; } if (isset($filters['user_id'])) { $whereClause .= " AND user_id = ?"; $params[] = $filters['user_id']; } if (isset($filters['category'])) { $whereClause .= " AND category = ?"; $params[] = $filters['category']; } $query = "SELECT ls.*, au.usr_user, au.usr_avatar FROM db_live_streams ls JOIN db_accountuser au ON ls.user_id = au.usr_id {$whereClause} ORDER BY ls.created_at DESC LIMIT ? OFFSET ?"; $params[] = $limit; $params[] = $offset; $result = $this->db->doQuery($query, $params); $streams = []; while ($row = $this->db->doFetch($result)) { $streams[] = $this->formatStreamData($row); } return [ 'success' => true, 'streams' => $streams, 'total' => $this->getStreamCount($filters) ]; } catch (Exception $e) { $this->logger->error('Failed to get live streams', [ 'filters' => $filters, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Get stream statistics * @param string $streamKey Stream key * @return array Statistics */ public function getStreamStats($streamKey) { try { $stream = $this->getStreamByKey($streamKey); if (!$stream) { throw new Exception('Stream not found'); } // Get viewer count from SRS API $viewerCount = $this->getSRSViewerCount($streamKey); // Get chat message count $chatCount = $this->getChatMessageCount($stream['id']); // Calculate duration $duration = 0; if ($stream['started_at']) { $endTime = $stream['ended_at'] ? strtotime($stream['ended_at']) : time(); $duration = $endTime - strtotime($stream['started_at']); } return [ 'success' => true, 'stats' => [ 'viewer_count' => $viewerCount, 'chat_messages' => $chatCount, 'duration' => $duration, 'status' => $stream['status'], 'started_at' => $stream['started_at'], 'ended_at' => $stream['ended_at'] ] ]; } catch (Exception $e) { $this->logger->error('Failed to get stream stats', [ 'stream_key' => $streamKey, 'error' => $e->getMessage() ]); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Private helper methods */ private function loadSRSConfig() { // Load SRS configuration from database or config file $this->srsConfig = [ 'host' => $this->srsHost, 'rtmp_port' => $this->srsRtmpPort, 'http_port' => $this->srsHttpPort, 'api_port' => $this->srsApiPort ]; } private function generateStreamKey() { return uniqid('stream_', true) . '_' . time(); } private function getStreamByKey($streamKey) { $query = "SELECT * FROM db_live_streams WHERE stream_key = ?"; $result = $this->db->doQuery($query, [$streamKey]); return $this->db->doFetch($result); } private function notifyStreamStart($stream) { // Get user's followers $query = "SELECT follower_id FROM db_user_follows WHERE user_id = ?"; $result = $this->db->doQuery($query, [$stream['user_id']]); while ($row = $this->db->doFetch($result)) { // Create notification $notification = [ 'user_id' => $row['follower_id'], 'type' => 'live_stream_started', 'title' => 'Live Stream Started', 'message' => "User is now live streaming: {$stream['title']}", 'data' => json_encode([ 'stream_id' => $stream['id'], 'stream_key' => $stream['stream_key'], 'streamer' => $stream['user_id'] ]), 'created_at' => date('Y-m-d H:i:s'), 'read_status' => 0 ]; $this->db->doInsert('db_notifications', $notification); } } private function getSRSViewerCount($streamKey) { try { // Call SRS API to get viewer count $url = "http://{$this->srsHost}:{$this->srsApiPort}/api/v1/streams/"; $response = file_get_contents($url); if ($response) { $data = json_decode($response, true); // Parse SRS response to get viewer count for specific stream // This is a simplified implementation return $data['streams'][$streamKey]['clients'] ?? 0; } return 0; } catch (Exception $e) { return 0; } } private function getChatMessageCount($streamId) { try { $query = "SELECT COUNT(*) as count FROM db_stream_chat WHERE stream_id = ?"; $result = $this->db->doQuery($query, [$streamId]); $row = $this->db->doFetch($result); return (int)($row['count'] ?? 0); } catch (Exception $e) { return 0; } } private function getStreamCount($filters) { try { $whereClause = "WHERE 1=1"; $params = []; if (isset($filters['status'])) { $whereClause .= " AND status = ?"; $params[] = $filters['status']; } if (isset($filters['user_id'])) { $whereClause .= " AND user_id = ?"; $params[] = $filters['user_id']; } $query = "SELECT COUNT(*) as count FROM db_live_streams {$whereClause}"; $result = $this->db->doQuery($query, $params); $row = $this->db->doFetch($result); return (int)($row['count'] ?? 0); } catch (Exception $e) { return 0; } } private function formatStreamData($row) { return [ 'id' => $row['id'], 'stream_key' => $row['stream_key'], 'title' => $row['title'], 'description' => $row['description'], 'category' => $row['category'], 'privacy' => $row['privacy'], 'status' => $row['status'], 'rtmp_url' => $row['rtmp_url'], 'hls_url' => $row['hls_url'], 'created_at' => $row['created_at'], 'started_at' => $row['started_at'], 'ended_at' => $row['ended_at'], 'user' => [ 'id' => $row['user_id'], 'username' => $row['usr_user'], 'avatar' => $row['usr_avatar'] ] ]; } }