Files
Atomcms-edit/app/Services/StreamMonitoringService.php
T
2026-05-09 17:32:17 +02:00

234 lines
6.6 KiB
PHP
Executable File

<?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());
}
}