feat: Add comprehensive documentation suite and reorganize project structure
- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
This commit is contained in:
463
f_core/f_classes/class.livestreaming.php
Normal file
463
f_core/f_classes/class.livestreaming.php
Normal file
@@ -0,0 +1,463 @@
|
||||
<?php
|
||||
/*******************************************************************************************************************
|
||||
| Software Name : EasyStream
|
||||
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
||||
| Software Author : (c) Sami Ahmed
|
||||
|*******************************************************************************************************************
|
||||
|
|
||||
|*******************************************************************************************************************
|
||||
| This source file is subject to the EasyStream Proprietary License Agreement.
|
||||
|
|
||||
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|
||||
|*******************************************************************************************************************
|
||||
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
||||
|*******************************************************************************************************************/
|
||||
|
||||
defined('_ISVALID') or header('Location: /error');
|
||||
|
||||
/**
|
||||
* Live Streaming System with SRS Integration
|
||||
*/
|
||||
class VLiveStreaming
|
||||
{
|
||||
private $logger;
|
||||
private $db;
|
||||
private $queue;
|
||||
private $srsConfig;
|
||||
|
||||
// SRS default configuration
|
||||
private $srsHost = 'localhost';
|
||||
private $srsRtmpPort = 1935;
|
||||
private $srsHttpPort = 8080;
|
||||
private $srsApiPort = 1985;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->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']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user