You've already forked Atomcms-edit
Initial commit
This commit is contained in:
Executable
+233
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
readonly class StreamMonitoringService
|
||||
{
|
||||
private const string CACHE_KEY = 'radio_stream_status';
|
||||
|
||||
private const int CACHE_DURATION_MINUTES = 1;
|
||||
|
||||
private const string SETTINGS_CACHE_KEY = 'radio_monitor_settings';
|
||||
|
||||
private const int SETTINGS_CACHE_DURATION = 60;
|
||||
|
||||
private string $streamUrl;
|
||||
|
||||
private int $timeout;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$settings = $this->getSettings();
|
||||
$this->streamUrl = $settings['radio_stream_url'] ?? '';
|
||||
$this->timeout = (int) ($settings['radio_monitoring_timeout'] ?? 5);
|
||||
}
|
||||
|
||||
public function getStatus(): array
|
||||
{
|
||||
return Cache::remember(self::CACHE_KEY, now()->addMinutes(self::CACHE_DURATION_MINUTES), fn (): array => $this->fetchStatus());
|
||||
}
|
||||
|
||||
private function fetchStatus(): array
|
||||
{
|
||||
$status = [
|
||||
'online' => false,
|
||||
'listeners' => 0,
|
||||
'bitrate' => 0,
|
||||
'format' => null,
|
||||
'server_type' => null,
|
||||
'last_check' => now()->toIso8601String(),
|
||||
'error' => null,
|
||||
];
|
||||
|
||||
if ($this->streamUrl === '') {
|
||||
$status['error'] = 'No stream URL configured';
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::timeout($this->timeout)
|
||||
->withOptions([
|
||||
'verify' => false,
|
||||
'allow_redirects' => true,
|
||||
])
|
||||
->head($this->streamUrl);
|
||||
|
||||
if ($response->successful()) {
|
||||
$status['online'] = true;
|
||||
$status['listeners'] = $this->extractListeners($response);
|
||||
$status['bitrate'] = $this->extractBitrate($response);
|
||||
$status['format'] = $this->extractFormat($response);
|
||||
$status['server_type'] = $this->detectServerType($response);
|
||||
} else {
|
||||
$status['error'] = 'Stream returned status: ' . $response->status();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$status['error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
private function extractListeners(Response $response): int
|
||||
{
|
||||
$headers = [
|
||||
'x-listeners',
|
||||
'x-audiocast-listeners',
|
||||
'icecast-listeners',
|
||||
'listeners',
|
||||
];
|
||||
|
||||
foreach ($headers as $header) {
|
||||
$value = $response->header($header);
|
||||
if ($value !== null && is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function extractBitrate(Response $response): int
|
||||
{
|
||||
$headers = ['x-bitrate', 'bitrate'];
|
||||
|
||||
foreach ($headers as $header) {
|
||||
$value = $response->header($header);
|
||||
if ($value !== null && is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function extractFormat(Response $response): ?string
|
||||
{
|
||||
$contentType = $response->header('content-type');
|
||||
|
||||
if ($contentType === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match (true) {
|
||||
str_contains($contentType, 'mp3') => 'MP3',
|
||||
str_contains($contentType, 'ogg') => 'OGG',
|
||||
str_contains($contentType, 'aac') => 'AAC',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
private function detectServerType(Response $response): ?string
|
||||
{
|
||||
$server = $response->header('server');
|
||||
|
||||
if ($server !== null) {
|
||||
$serverLower = strtolower($server);
|
||||
|
||||
return match (true) {
|
||||
str_contains($serverLower, 'icecast') => 'Icecast',
|
||||
str_contains($serverLower, 'shoutcast') => 'Shoutcast',
|
||||
str_contains($serverLower, 'azurecast') => 'AzureCast',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
$headers = $response->headers();
|
||||
|
||||
return isset($headers['x-audiocast-server']) ? 'AzureCast' : null;
|
||||
}
|
||||
|
||||
public function getUptime(): array
|
||||
{
|
||||
$uptime = $this->getSetting('radio_last_online');
|
||||
|
||||
if ($uptime === null) {
|
||||
return [
|
||||
'was_ever_online' => false,
|
||||
'last_online' => null,
|
||||
'uptime_percentage' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$lastOnline = Carbon::parse($uptime);
|
||||
$now = now();
|
||||
$diff = $lastOnline->diff($now);
|
||||
|
||||
$totalMinutes = $diff->days * 24 * 60 + $diff->h * 60 + $diff->i;
|
||||
|
||||
return [
|
||||
'was_ever_online' => true,
|
||||
'last_online' => $lastOnline->toIso8601String(),
|
||||
'downtime_minutes' => $totalMinutes,
|
||||
'last_online_human' => $lastOnline->diffForHumans(),
|
||||
];
|
||||
}
|
||||
|
||||
public function recordOnlineStatus(bool $isOnline): void
|
||||
{
|
||||
if ($isOnline) {
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => 'radio_last_online'],
|
||||
['value' => now()->toIso8601String()],
|
||||
);
|
||||
|
||||
$this->incrementStat('radio_total_online_minutes');
|
||||
}
|
||||
|
||||
$this->incrementStat('radio_total_checks');
|
||||
}
|
||||
|
||||
private function incrementStat(string $key): void
|
||||
{
|
||||
$current = (int) ($this->getSetting($key) ?? 0);
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => $key],
|
||||
['value' => (string) ($current + 1)],
|
||||
);
|
||||
}
|
||||
|
||||
public function getStats(): array
|
||||
{
|
||||
$totalChecks = (int) ($this->getSetting('radio_total_checks') ?? 0);
|
||||
$totalOnlineMinutes = (int) ($this->getSetting('radio_total_online_minutes') ?? 0);
|
||||
|
||||
$uptimePercentage = $totalChecks > 0 ? round(($totalOnlineMinutes / max($totalChecks, 1)) * 100, 2) : 0;
|
||||
|
||||
return [
|
||||
'current_status' => $this->getStatus(),
|
||||
'uptime' => $this->getUptime(),
|
||||
'total_checks' => $totalChecks,
|
||||
'total_online_minutes' => $totalOnlineMinutes,
|
||||
'uptime_percentage' => $uptimePercentage,
|
||||
];
|
||||
}
|
||||
|
||||
private function getSetting(string $key): ?string
|
||||
{
|
||||
return $this->getSettings()[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|null>
|
||||
*/
|
||||
private function getSettings(): array
|
||||
{
|
||||
return Cache::remember(self::SETTINGS_CACHE_KEY, self::SETTINGS_CACHE_DURATION, fn (): array => WebsiteSetting::whereIn('key', [
|
||||
'radio_stream_url',
|
||||
'radio_monitoring_timeout',
|
||||
'radio_last_online',
|
||||
'radio_total_checks',
|
||||
'radio_total_online_minutes',
|
||||
])->pluck('value', 'key')->toArray());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user