diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a7217b54..6f7d0aff 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1575,7 +1575,7 @@ $rServers = ServerRepository::getAll(); // 50+ call-sites | `$rConfig` | `ConfigReader::getAll()` | core + streaming | | `$rServers` | `ServerRepository::getAll()` | core + streaming | | `$rFFPROBE` | `FfmpegPaths::probe()` | core + streaming | -| `$rFFMPEG` | `FfmpegPaths::cpu()` | core + streaming | +| `$rFFMPEG_CPU` | `FfmpegPaths::cpu()` | core + streaming | | `$rFFMPEG_GPU` | `FfmpegPaths::gpu()` | core + streaming | | `$db` | `DatabaseFactory` | уже глобальная | @@ -1596,9 +1596,9 @@ public static function probeStream($url) { **Обновлённые файлы (54 замены в 20 файлах):** - `core/Init/LegacyInitializer.php` — добавлен `exportGlobals()`, вызов из `initCore()` + FFmpeg globals в `initStreaming()` - `streaming/Codec/FFprobeRunner.php` — `global $rSettings, $rFFPROBE` -- `streaming/Codec/SubtitleExtractor.php` — `global $rSettings, $rFFMPEG` +- `streaming/Codec/SubtitleExtractor.php` — `global $rSettings, $rFFMPEG_CPU` - `streaming/Health/ProcessChecker.php` — `global $rServers` -- `domain/Stream/StreamProcess.php` — 4 метода: `global $db, $rSettings, $rServers, $rFFMPEG, $rFFMPEG_GPU`; переименование `$rFFMPEGCPU`→`$rFFMPEG`, `$rFFMPEGGPU`→`$rFFMPEG_GPU` +- `domain/Stream/StreamProcess.php` — 4 метода: `global $db, $rSettings, $rServers, $rFFMPEG_CPU, $rFFMPEG_GPU`; переименование `$rFFMPEGCPU`→`$rFFMPEG_CPU`, `$rFFMPEGGPU`→`$rFFMPEG_GPU` - `domain/Stream/ConnectionTracker.php` — 5 методов: `global $rSettings, $rServers, $db` - `domain/Stream/PlaylistGenerator.php` — `global $db, $rSettings, $rServers` - `domain/Stream/StreamService.php` — `global $db, $rSettings` diff --git a/src/admin/api.php b/src/admin/api.php index 422d1ff0..0d481f24 100644 --- a/src/admin/api.php +++ b/src/admin/api.php @@ -4491,9 +4491,9 @@ if (isset($_SESSION['hash'])) { } if (RequestManager::getAll()['action'] != 'multi') { } - $rType = RequestManager::getAll()['type']; - $rRequestIDs = json_decode(RequestManager::getAll()['ids'], true); - $rSub = RequestManager::getAll()['sub']; + $rType = RequestManager::getAll()['type'] ?? ''; + $rRequestIDs = json_decode(RequestManager::getAll()['ids'] ?? '[]', true) ?: []; + $rSub = RequestManager::getAll()['sub'] ?? ''; if (count($rRequestIDs) != 0) { switch ($rType) { diff --git a/src/admin/record.php b/src/admin/record.php index 2a0b6834..a2a680b7 100644 --- a/src/admin/record.php +++ b/src/admin/record.php @@ -8,7 +8,7 @@ if (!isset($__viewMode)): goHome(); } - $rAvailableServers = $rServers = array(); + $rAvailableServers = array(); $rStream = $rProgramme = null; if (isset(RequestManager::getAll()['id'])) { diff --git a/src/core/Init/LegacyInitializer.php b/src/core/Init/LegacyInitializer.php index 8714f225..b9425525 100644 --- a/src/core/Init/LegacyInitializer.php +++ b/src/core/Init/LegacyInitializer.php @@ -104,7 +104,7 @@ class LegacyInitializer { // FFmpeg paths — export to globals (streaming context) $GLOBALS['rFFPROBE'] = FfmpegPaths::probe(); - $GLOBALS['rFFMPEG'] = FfmpegPaths::cpu(); + $GLOBALS['rFFMPEG_CPU'] = FfmpegPaths::cpu(); $GLOBALS['rFFMPEG_GPU'] = FfmpegPaths::gpu(); self::syncStreamingContainer(); @@ -125,7 +125,7 @@ class LegacyInitializer { * $rConfig — config.ini данные (ConfigReader) * $rServers — все серверы (ServerRepository) * $rFFPROBE — путь к ffprobe (FfmpegPaths) - * $rFFMPEG — путь к ffmpeg CPU (FfmpegPaths) + * $rFFMPEG_CPU — путь к ffmpeg CPU (FfmpegPaths) * $rFFMPEG_GPU — путь к ffmpeg GPU (FfmpegPaths) */ public static function exportGlobals(): void { @@ -134,7 +134,7 @@ class LegacyInitializer { $GLOBALS['rConfig'] = ConfigReader::getAll(); $GLOBALS['rServers'] = ServerRepository::getAll(); $GLOBALS['rFFPROBE'] = FfmpegPaths::probe(); - $GLOBALS['rFFMPEG'] = FfmpegPaths::cpu(); + $GLOBALS['rFFMPEG_CPU'] = FfmpegPaths::cpu(); $GLOBALS['rFFMPEG_GPU'] = FfmpegPaths::gpu(); } diff --git a/src/core/Process/ProcessManager.php b/src/core/Process/ProcessManager.php index 48894f08..192039b8 100644 --- a/src/core/Process/ProcessManager.php +++ b/src/core/Process/ProcessManager.php @@ -282,6 +282,10 @@ class ProcessManager { } // Write our PID + $lockDir = dirname($lockFile); + if (!is_dir($lockDir)) { + @mkdir($lockDir, 0775, true); + } file_put_contents($lockFile, getmypid()); return true; diff --git a/src/crons/streams.php b/src/crons/streams.php index eecad3d0..c9581642 100644 --- a/src/crons/streams.php +++ b/src/crons/streams.php @@ -209,8 +209,8 @@ function loadCron() { } if (!SettingsManager::getAll()['kill_rogue_ffmpeg']) { } else { - exec("ps aux | grep -v grep | grep '/*_.m3u8' | awk '{print \$2}'", $rFFMPEG); - foreach ($rFFMPEG as $rPID) { + exec("ps aux | grep -v grep | grep '/*_.m3u8' | awk '{print \$2}'", $rRoguePIDs); + foreach ($rRoguePIDs as $rPID) { if (!(is_numeric($rPID) && 0 < intval($rPID)) || in_array($rPID, $rActivePIDs)) { } else { echo 'Kill Roque PID: ' . $rPID . "\n"; diff --git a/src/crons/tmp.php b/src/crons/tmp.php index a74f59d9..ac0edb1c 100644 --- a/src/crons/tmp.php +++ b/src/crons/tmp.php @@ -8,6 +8,10 @@ if (posix_getpwuid(posix_geteuid())['name'] == 'xc_vm') { $rIdentifier = CRONS_TMP_PATH . md5(Encryption::generateUniqueCode(SettingsManager::getAll()['live_streaming_pass']) . __FILE__); ProcessManager::acquireCronLock($rIdentifier); foreach (array(TMP_PATH, CRONS_TMP_PATH, DIVERGENCE_TMP_PATH, FLOOD_TMP_PATH, MINISTRA_TMP_PATH, SIGNALS_TMP_PATH, LOGS_TMP_PATH) as $rTmpPath) { + if (!is_dir($rTmpPath)) { + @mkdir($rTmpPath, 0775, true); + continue; + } foreach (scandir($rTmpPath) as $rFile) { $fullPath = $rTmpPath . '/' . $rFile; diff --git a/src/domain/Server/ServerRepository.php b/src/domain/Server/ServerRepository.php index 38d098a7..bd1c8788 100644 --- a/src/domain/Server/ServerRepository.php +++ b/src/domain/Server/ServerRepository.php @@ -3,6 +3,9 @@ class ServerRepository { public static function getAll($rForce = false) { global $db, $rSettings; + if (!$rSettings) { + $rSettings = SettingsManager::getAll(); + } if (!$rForce) { $rCache = FileCache::getCache('servers', 10); if (!empty($rCache)) { diff --git a/src/domain/Stream/StreamProcess.php b/src/domain/Stream/StreamProcess.php index 515e6042..8d59c452 100644 --- a/src/domain/Stream/StreamProcess.php +++ b/src/domain/Stream/StreamProcess.php @@ -95,7 +95,7 @@ class StreamProcess { } public static function createChannelItem($rStreamID, $rSource) { - global $db, $rSettings, $rServers, $rFFMPEG, $rFFMPEG_GPU; + global $db, $rSettings, $rServers, $rFFMPEG_CPU, $rFFMPEG_GPU; $rStream = array(); $rLoopback = false; $db->query('SELECT * FROM `streams` t1 INNER JOIN `streams_types` t2 ON t2.type_id = t1.type AND t1.type = 3 LEFT JOIN `profiles` t4 ON t1.transcode_profile_id = t4.profile_id WHERE t1.direct_source = 0 AND t1.id = ?', $rStreamID); @@ -170,7 +170,7 @@ class StreamProcess { } } - $rCommand = ((isset($rStream['stream_info']['transcode_attributes']['gpu']) ? $rFFMPEG_GPU : $rFFMPEG)) . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -err_detect ignore_err {GPU} -fflags +genpts -async 1 -i {STREAM_SOURCE} {LOGO} '; + $rCommand = ((isset($rStream['stream_info']['transcode_attributes']['gpu']) ? $rFFMPEG_GPU : $rFFMPEG_CPU)) . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -err_detect ignore_err {GPU} -fflags +genpts -async 1 -i {STREAM_SOURCE} {LOGO} '; if (!array_key_exists('-acodec', $rStream['stream_info']['transcode_attributes'])) { $rStream['stream_info']['transcode_attributes']['-acodec'] = 'copy'; @@ -300,7 +300,7 @@ class StreamProcess { } public static function startMovie($rStreamID) { - global $db, $rSettings, $rServers, $rFFMPEG, $rFFMPEG_GPU; + global $db, $rSettings, $rServers, $rFFMPEG_CPU, $rFFMPEG_GPU; $rStream = array(); $rLoopback = false; $db->query('SELECT * FROM `streams` t1 INNER JOIN `streams_types` t2 ON t2.type_id = t1.type AND t2.live = 0 LEFT JOIN `profiles` t4 ON t1.transcode_profile_id = t4.profile_id WHERE t1.direct_source = 0 AND t1.id = ?', $rStreamID); @@ -403,7 +403,7 @@ class StreamProcess { $rInputCodec = '-c:v ' . $rFFProbeOutput['codecs']['video']['codec_name'] . '_cuvid'; } } - $rFFMPEG = ((isset($rStream['stream_info']['transcode_attributes']['gpu']) ? $rFFMPEG_GPU : $rFFMPEG)) . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -err_detect ignore_err {GPU} {FETCH_OPTIONS} -fflags +genpts -async 1 {READ_NATIVE} -i {STREAM_SOURCE} {LOGO} ' . $rSubtitlesImport; + $rFFMPEG = ((isset($rStream['stream_info']['transcode_attributes']['gpu']) ? $rFFMPEG_GPU : $rFFMPEG_CPU)) . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -err_detect ignore_err {GPU} {FETCH_OPTIONS} -fflags +genpts -async 1 {READ_NATIVE} -i {STREAM_SOURCE} {LOGO} ' . $rSubtitlesImport; $rMap = '-map 0 -copy_unknown '; if (!empty($rStream['stream_info']['custom_map'])) { $rMap = escapeshellcmd($rStream['stream_info']['custom_map']) . ' -copy_unknown '; @@ -506,7 +506,7 @@ class StreamProcess { } public static function startStream($rStreamID, $rFromCache = false, $rForceSource = null, $rLLOD = false, $rStartPos = 0) { - global $db, $rSettings, $rServers, $rFFMPEG, $rFFMPEG_GPU, $rFFPROBE; + global $db, $rSettings, $rServers, $rFFMPEG_CPU, $rFFMPEG_GPU, $rFFPROBE; $rSegmentSettings = array('seg_time' => intval($rSettings['seg_time']), 'seg_list_size' => intval($rSettings['seg_list_size']), 'seg_delete_threshold' => intval($rSettings['seg_delete_threshold'])); if (file_exists(STREAMS_PATH . $rStreamID . '_.pid')) { unlink(STREAMS_PATH . $rStreamID . '_.pid'); @@ -534,7 +534,7 @@ class StreamProcess { $rTimeout = intval($rAnalyseDuration / 1000000) + $rSettings['probe_extra_wait']; $rFFProbee = 'timeout ' . $rTimeout . ' ' . $rFFPROBE . ' {FETCH_OPTIONS} -probesize ' . $rProbesize . ' -analyzeduration ' . $rAnalyseDuration . ' {CONCAT} -i {STREAM_SOURCE} -v quiet -print_format json -show_streams -show_format'; - $rFetchOptions = array(); + $rFetchOptions = ''; $rLoopback = false; $rOffset = 0; @@ -789,11 +789,11 @@ class StreamProcess { $rGenPTS = '-fflags +genpts -async 1'; } else { if (is_array($rFFProbeOutput) && isset($rFFProbeOutput['codecs']['audio']['codec_name']) && in_array($rFFProbeOutput['codecs']['audio']['codec_name'], array('ac3', 'eac3')) && $rSettings['dts_legacy_ffmpeg']) { - $rFFMPEG = FFMPEG_BIN_40; + $rFFMPEG_CPU = FFMPEG_BIN_40; $rFFPROBE = FFPROBE_BIN_40; } - $rNoFix = ($rFFMPEG == FFMPEG_BIN_40 ? '-nofix_dts' : ''); + $rNoFix = ($rFFMPEG_CPU == FFMPEG_BIN_40 ? '-nofix_dts' : ''); $rGenPTS = $rNoFix . ' -start_at_zero -copyts -vsync 0 -correct_ts_overflow 0 -avoid_negative_ts disabled -max_interleave_delta 0'; } @@ -814,7 +814,7 @@ class StreamProcess { $rStream['stream_info']['transcode_attributes'] = array(); } - $rFFMPEG = ((isset($rStream['stream_info']['transcode_attributes']['gpu']) ? $rFFMPEG_GPU : $rFFMPEG)) . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -err_detect ignore_err ' . $rOptions . ' {GEN_PTS} {READ_NATIVE} -probesize ' . $rProbesize . ' -analyzeduration ' . $rAnalyseDuration . ' -progress "' . $rProgressURL . '" {CONCAT} -i {STREAM_SOURCE} {LOGO} '; + $rFFMPEG = ((isset($rStream['stream_info']['transcode_attributes']['gpu']) ? $rFFMPEG_GPU : $rFFMPEG_CPU)) . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -err_detect ignore_err ' . $rOptions . ' {GEN_PTS} {READ_NATIVE} -probesize ' . $rProbesize . ' -analyzeduration ' . $rAnalyseDuration . ' -progress "' . $rProgressURL . '" {CONCAT} -i {STREAM_SOURCE} {LOGO} '; if (!array_key_exists('-acodec', $rStream['stream_info']['transcode_attributes'])) { $rStream['stream_info']['transcode_attributes']['-acodec'] = 'copy'; @@ -829,7 +829,7 @@ class StreamProcess { } } else { $rStream['stream_info']['transcode_attributes'] = array(); - $rFFMPEG = ((stripos($rStream['stream_info']['custom_ffmpeg'], 'nvenc') !== false ? $rFFMPEG_GPU : $rFFMPEG)) . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -progress "' . $rProgressURL . '" ' . $rStream['stream_info']['custom_ffmpeg']; + $rFFMPEG = ((stripos($rStream['stream_info']['custom_ffmpeg'], 'nvenc') !== false ? $rFFMPEG_GPU : $rFFMPEG_CPU)) . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -progress "' . $rProgressURL . '" ' . $rStream['stream_info']['custom_ffmpeg']; } $rLLODOptions = ($rLLOD && !$rLoopback ? '-fflags nobuffer -flags low_delay -strict experimental' : ''); diff --git a/src/includes/cli/monitor.php b/src/includes/cli/monitor.php index ee4c2a76..04d49fdd 100644 --- a/src/includes/cli/monitor.php +++ b/src/includes/cli/monitor.php @@ -105,8 +105,8 @@ if (ProcessManager::isStreamRunning($rPID, $rStreamID)) { } if (SettingsManager::getAll()['kill_rogue_ffmpeg']) { - exec('ps aux | grep -v grep | grep \'/' . $rStreamID . '_.m3u8\' | awk \'{print $2}\'', $rFFMPEG); - foreach ($rFFMPEG as $rRoguePID) { + exec('ps aux | grep -v grep | grep \'/' . $rStreamID . '_.m3u8\' | awk \'{print $2}\'', $rRoguePIDs); + foreach ($rRoguePIDs as $rRoguePID) { if (is_numeric($rRoguePID) && intval($rRoguePID) > 0 && intval($rRoguePID) != intval($rPID)) { shell_exec('kill -9 ' . $rRoguePID . ';'); } diff --git a/src/includes/libs/Logger.php b/src/includes/libs/Logger.php index 1d158bcd..743378b0 100644 --- a/src/includes/libs/Logger.php +++ b/src/includes/libs/Logger.php @@ -143,6 +143,12 @@ final class Logger { 'env' => php_sapi_name() // SAPI name (cli, fpm-fcgi, etc.) ]; + // Ensure log directory exists + $logDir = dirname(self::$logFile); + if (!is_dir($logDir)) { + @mkdir($logDir, 0775, true); + } + // Write to file as base64-encoded JSON (easy to parse and prevents line corruption) file_put_contents( self::$logFile, diff --git a/src/streaming/Codec/SubtitleExtractor.php b/src/streaming/Codec/SubtitleExtractor.php index 73be6f9d..39add93f 100644 --- a/src/streaming/Codec/SubtitleExtractor.php +++ b/src/streaming/Codec/SubtitleExtractor.php @@ -2,9 +2,9 @@ class SubtitleExtractor { public static function extractSubtitle($rStreamID, $rSourceURL, $rIndex) { - global $rSettings, $rFFMPEG; + global $rSettings, $rFFMPEG_CPU; $rTimeout = 10; - $rCommand = 'timeout ' . $rTimeout . ' ' . $rFFMPEG . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -err_detect ignore_err -i "' . $rSourceURL . '" -map 0:s:' . intval($rIndex) . ' ' . VOD_PATH . intval($rStreamID) . '_' . intval($rIndex) . '.srt'; + $rCommand = 'timeout ' . $rTimeout . ' ' . $rFFMPEG_CPU . ' -y -nostdin -hide_banner -loglevel ' . (($rSettings['ffmpeg_warnings'] ? 'warning' : 'error')) . ' -err_detect ignore_err -i "' . $rSourceURL . '" -map 0:s:' . intval($rIndex) . ' ' . VOD_PATH . intval($rStreamID) . '_' . intval($rIndex) . '.srt'; exec($rCommand, $rOutput); if (file_exists(VOD_PATH . intval($rStreamID) . '_' . intval($rIndex) . '.srt')) { if (filesize(VOD_PATH . intval($rStreamID) . '_' . intval($rIndex) . '.srt') != 0) { diff --git a/src/www/admin/api.php b/src/www/admin/api.php index d3d24060..f7cfaba5 100644 --- a/src/www/admin/api.php +++ b/src/www/admin/api.php @@ -42,10 +42,10 @@ switch ($rAction) { case 'start': $rStreamIDs = array_map('intval', RequestManager::getAll()['stream_ids']); $rForce = (RequestManager::getAll()['force'] ?: false); - $rServers = (empty(RequestManager::getAll()['servers']) ? array_keys($rAllServers) : array_map('intval', RequestManager::getAll()['servers'])); + $rServerIDs = (empty(RequestManager::getAll()['servers']) ? array_keys($rAllServers) : array_map('intval', RequestManager::getAll()['servers'])); $rURLs = array(); - foreach ($rServers as $rServerID) { + foreach ($rServerIDs as $rServerID) { $rURLs[$rServerID] = array('url' => $rAllServers[$rServerID]['api_url_ip'] . '&action=vod', 'postdata' => array('function' => $rSubAction, 'stream_ids' => $rStreamIDs, 'force' => $rForce)); } CurlClient::getMultiCURL($rURLs); @@ -55,10 +55,10 @@ switch ($rAction) { case 'stop': $rStreamIDs = array_map('intval', RequestManager::getAll()['stream_ids']); - $rServers = (empty(RequestManager::getAll()['servers']) ? array_keys($rAllServers) : array_map('intval', RequestManager::getAll()['servers'])); + $rServerIDs = (empty(RequestManager::getAll()['servers']) ? array_keys($rAllServers) : array_map('intval', RequestManager::getAll()['servers'])); $rURLs = array(); - foreach ($rServers as $rServerID) { + foreach ($rServerIDs as $rServerID) { $rURLs[$rServerID] = array('url' => $rAllServers[$rServerID]['api_url_ip'] . '&action=vod', 'postdata' => array('function' => $rSubAction, 'stream_ids' => $rStreamIDs)); } CurlClient::getMultiCURL($rURLs); @@ -73,10 +73,10 @@ switch ($rAction) { switch ($rSubAction) { case 'start': $rStreamIDs = array_map('intval', RequestManager::getAll()['stream_ids']); - $rServers = (empty(RequestManager::getAll()['servers']) ? array_keys($rAllServers) : array_map('intval', RequestManager::getAll()['servers'])); + $rServerIDs = (empty(RequestManager::getAll()['servers']) ? array_keys($rAllServers) : array_map('intval', RequestManager::getAll()['servers'])); $rURLs = array(); - foreach ($rServers as $rServerID) { + foreach ($rServerIDs as $rServerID) { $rURLs[$rServerID] = array('url' => $rAllServers[$rServerID]['api_url_ip'] . '&action=stream', 'postdata' => array('function' => $rSubAction, 'stream_ids' => $rStreamIDs)); } CurlClient::getMultiCURL($rURLs); @@ -86,10 +86,10 @@ switch ($rAction) { case 'stop': $rStreamIDs = array_map('intval', RequestManager::getAll()['stream_ids']); - $rServers = (empty(RequestManager::getAll()['servers']) ? array_keys($rAllServers) : array_map('intval', RequestManager::getAll()['servers'])); + $rServerIDs = (empty(RequestManager::getAll()['servers']) ? array_keys($rAllServers) : array_map('intval', RequestManager::getAll()['servers'])); $rURLs = array(); - foreach ($rServers as $rServerID) { + foreach ($rServerIDs as $rServerID) { $rURLs[$rServerID] = array('url' => $rAllServers[$rServerID]['api_url_ip'] . '&action=stream', 'postdata' => array('function' => $rSubAction, 'stream_ids' => $rStreamIDs)); } CurlClient::getMultiCURL($rURLs); @@ -113,8 +113,8 @@ switch ($rAction) { $rStreams = $db->get_rows(true, 'stream_id', false, 'server_id'); $rOutput = array(); - foreach ($rStreams as $rStreamID => $rServers) { - $rOutput[$rStreamID] = array_keys($rServers); + foreach ($rStreams as $rStreamID => $rStreamServers) { + $rOutput[$rStreamID] = array_keys($rStreamServers); } echo json_encode($rOutput); @@ -125,8 +125,8 @@ switch ($rAction) { $rStreams = $db->get_rows(true, 'stream_id', false, 'server_id'); $rOutput = array(); - foreach ($rStreams as $rStreamID => $rServers) { - $rOutput[$rStreamID] = array_keys($rServers); + foreach ($rStreams as $rStreamID => $rStreamServers) { + $rOutput[$rStreamID] = array_keys($rStreamServers); } echo json_encode($rOutput);