You've already forked Atomcms-edit
234 lines
6.6 KiB
PHP
Executable File
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());
|
|
}
|
|
}
|