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