![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/public_html/radio/ |
<?php
/**
* Public Live Radio Player
* Consumer-facing live radio streaming interface
*/
session_start();
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/includes/functions.php';
$pdo = getDBConnection();
if (!$pdo) {
die("Database connection failed. Please check your configuration.");
}
// Check if radio_streams table exists, if not, show helpful message
try {
$pdo->query("SELECT 1 FROM radio_streams LIMIT 1");
} catch (PDOException $e) {
// Table doesn't exist, show setup message
die("
<html>
<head><title>Setup Required</title></head>
<body style='font-family: Arial; padding: 2rem;'>
<h1>Database Setup Required</h1>
<p>The live streaming tables need to be created. Please run:</p>
<pre style='background: #f5f5f5; padding: 1rem; border-radius: 4px;'>
php /radio/migrations/add_live_streaming_tables.php
</pre>
<p>Or visit: <a href='/radio/migrations/add_live_streaming_tables.php'>/radio/migrations/add_live_streaming_tables.php</a></p>
</body>
</html>
");
}
// Get station ID from query parameter or default to first active station
$station_id = isset($_GET['station']) ? (int)$_GET['station'] : null;
if (!$station_id) {
// Get first active station with a live stream
try {
$stmt = $pdo->prepare("
SELECT rs.id, rs.station_name, rs.call_sign, rs.logo_url, rs.description,
rstr.id as stream_id, rstr.stream_url, rstr.is_live, rstr.listener_count
FROM radio_stations rs
LEFT JOIN radio_streams rstr ON rs.id = rstr.station_id
WHERE rs.subscription_status = 'active'
AND rs.is_active = 1
AND rstr.is_live = 1
ORDER BY rstr.listener_count DESC, rs.id ASC
LIMIT 1
");
$stmt->execute();
$station = $stmt->fetch();
} catch (PDOException $e) {
error_log("Error fetching station: " . $e->getMessage());
$station = null;
}
if ($station) {
$station_id = $station['id'];
$stream_id = $station['stream_id'];
} else {
// No live stream, show placeholder
$station = null;
$stream_id = null;
}
} else {
// Get specific station
try {
$stmt = $pdo->prepare("
SELECT rs.id, rs.station_name, rs.call_sign, rs.logo_url, rs.description,
rstr.id as stream_id, rstr.stream_url, rstr.is_live, rstr.listener_count
FROM radio_stations rs
LEFT JOIN radio_streams rstr ON rs.id = rstr.station_id
WHERE rs.id = ? AND rs.subscription_status = 'active' AND rs.is_active = 1
");
$stmt->execute([$station_id]);
$station = $stmt->fetch();
$stream_id = $station['stream_id'] ?? null;
} catch (PDOException $e) {
error_log("Error fetching station: " . $e->getMessage());
$station = null;
$stream_id = null;
}
}
// Get current track playing
$now_playing = null;
if ($stream_id) {
try {
$stmt = $pdo->prepare("
SELECT np.*, mt.title, mt.audio_url, mt.image_url, mt.duration,
u.name as artist_name, u.id as artist_id
FROM radio_now_playing np
JOIN music_tracks mt ON np.track_id = mt.id
LEFT JOIN users u ON mt.user_id = u.id
WHERE np.stream_id = ? AND np.ended_at IS NULL
ORDER BY np.started_at DESC
LIMIT 1
");
$stmt->execute([$stream_id]);
$now_playing = $stmt->fetch();
} catch (PDOException $e) {
error_log("Error fetching now playing: " . $e->getMessage());
$now_playing = null;
}
}
// Get upcoming tracks (queue)
$upcoming = [];
if ($stream_id) {
try {
$stmt = $pdo->prepare("
SELECT q.*, mt.title, mt.audio_url, mt.image_url, mt.duration,
u.name as artist_name, u.id as artist_id
FROM radio_stream_queue q
JOIN music_tracks mt ON q.track_id = mt.id
LEFT JOIN users u ON mt.user_id = u.id
WHERE q.stream_id = ? AND q.played_at IS NULL
ORDER BY q.priority DESC, q.vote_count DESC, q.queued_at ASC
LIMIT 10
");
$stmt->execute([$stream_id]);
$upcoming = $stmt->fetchAll();
} catch (PDOException $e) {
error_log("Error fetching queue: " . $e->getMessage());
$upcoming = [];
}
}
$page_title = $station ? $station['station_name'] . ' - Live Radio' : 'Live Radio - SoundStudioPro';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($page_title) ?></title>
<link rel="stylesheet" href="/assets/css/main.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%);
color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
min-height: 100vh;
padding-bottom: 2rem;
}
.live-radio-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.radio-header {
text-align: center;
margin-bottom: 3rem;
padding-top: 2rem;
}
.station-logo {
width: 120px;
height: 120px;
border-radius: 20px;
margin: 0 auto 1.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
font-weight: bold;
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3);
}
.station-logo img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 20px;
}
.station-name {
font-size: 2.5rem;
font-weight: 800;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.station-call-sign {
font-size: 1.2rem;
color: #a0aec0;
margin-bottom: 1rem;
}
.live-badge {
display: inline-block;
padding: 0.5rem 1.5rem;
background: #ef4444;
color: white;
border-radius: 50px;
font-size: 0.9rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
animation: pulse 2s ease-in-out infinite;
margin-bottom: 1rem;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.player-section {
background: rgba(26, 26, 26, 0.8);
border-radius: 24px;
padding: 3rem;
margin-bottom: 2rem;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
}
.now-playing {
display: flex;
align-items: center;
gap: 2rem;
margin-bottom: 2rem;
}
.track-artwork {
width: 200px;
height: 200px;
border-radius: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
flex-shrink: 0;
overflow: hidden;
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3);
}
.track-artwork img {
width: 100%;
height: 100%;
object-fit: cover;
}
.track-info {
flex: 1;
}
.track-title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.track-artist {
font-size: 1.3rem;
color: #a0aec0;
margin-bottom: 1rem;
}
.audio-player {
width: 100%;
margin-top: 1.5rem;
}
.audio-player audio {
width: 100%;
height: 50px;
}
.listener-count {
display: flex;
align-items: center;
gap: 0.5rem;
color: #a0aec0;
font-size: 1rem;
margin-top: 1rem;
}
.listener-count strong {
color: #667eea;
}
.voting-section {
background: rgba(26, 26, 26, 0.6);
border-radius: 20px;
padding: 2rem;
margin-top: 2rem;
}
.voting-section h3 {
font-size: 1.5rem;
margin-bottom: 1.5rem;
}
.upcoming-tracks {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}
.track-card {
background: rgba(26, 26, 26, 0.8);
border-radius: 16px;
padding: 1.5rem;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s;
cursor: pointer;
}
.track-card:hover {
transform: translateY(-5px);
border-color: rgba(102, 126, 234, 0.5);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.2);
}
.track-card-artwork {
width: 100%;
aspect-ratio: 1;
border-radius: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin-bottom: 1rem;
overflow: hidden;
}
.track-card-artwork img {
width: 100%;
height: 100%;
object-fit: cover;
}
.track-card-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.track-card-artist {
font-size: 0.9rem;
color: #a0aec0;
margin-bottom: 1rem;
}
.vote-button {
width: 100%;
padding: 0.75rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.vote-button:hover {
transform: scale(1.05);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
.vote-button.voted {
background: #48bb78;
}
.vote-count {
text-align: center;
color: #667eea;
font-weight: 600;
margin-top: 0.5rem;
}
.no-stream {
text-align: center;
padding: 4rem 2rem;
}
.no-stream h2 {
font-size: 2rem;
margin-bottom: 1rem;
}
.no-stream p {
color: #a0aec0;
font-size: 1.1rem;
}
@media (max-width: 768px) {
.now-playing {
flex-direction: column;
text-align: center;
}
.track-artwork {
width: 150px;
height: 150px;
}
.station-name {
font-size: 1.8rem;
}
.upcoming-tracks {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<?php include __DIR__ . '/../includes/header.php'; ?>
<div class="live-radio-container">
<?php if ($station && $stream_id): ?>
<div class="radio-header">
<?php if ($station['logo_url']): ?>
<div class="station-logo">
<img src="<?= htmlspecialchars($station['logo_url']) ?>" alt="<?= htmlspecialchars($station['station_name']) ?>">
</div>
<?php else: ?>
<div class="station-logo">
<?= strtoupper(substr($station['station_name'], 0, 2)) ?>
</div>
<?php endif; ?>
<div class="live-badge">🔴 LIVE</div>
<h1 class="station-name"><?= htmlspecialchars($station['station_name']) ?></h1>
<?php if ($station['call_sign']): ?>
<div class="station-call-sign"><?= htmlspecialchars($station['call_sign']) ?></div>
<?php endif; ?>
</div>
<div class="player-section">
<?php if ($now_playing): ?>
<div class="now-playing">
<div class="track-artwork">
<?php if ($now_playing['image_url']): ?>
<img src="<?= htmlspecialchars($now_playing['image_url']) ?>" alt="<?= htmlspecialchars($now_playing['title']) ?>">
<?php else: ?>
<div style="display: flex; align-items: center; justify-content: center; height: 100%; font-size: 3rem;">🎵</div>
<?php endif; ?>
</div>
<div class="track-info">
<h2 class="track-title"><?= htmlspecialchars($now_playing['title']) ?></h2>
<div class="track-artist">
<?php if ($now_playing['artist_name']): ?>
<a href="/artists.php?id=<?= $now_playing['artist_id'] ?>" style="color: #667eea; text-decoration: none;">
<?= htmlspecialchars($now_playing['artist_name']) ?>
</a>
<?php else: ?>
Unknown Artist
<?php endif; ?>
</div>
<?php if ($now_playing['audio_url']): ?>
<div class="audio-player">
<audio controls autoplay>
<source src="<?= htmlspecialchars($now_playing['audio_url']) ?>" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</div>
<?php endif; ?>
<div class="listener-count">
<span>👂</span>
<strong id="listener-count"><?= number_format($station['listener_count']) ?></strong>
<span>listeners</span>
</div>
</div>
</div>
<?php else: ?>
<div style="text-align: center; padding: 2rem;">
<h3 style="margin-bottom: 1rem;">Stream Starting Soon...</h3>
<p style="color: #a0aec0;">Waiting for the next track to begin.</p>
</div>
<?php endif; ?>
</div>
<?php if (count($upcoming) > 0): ?>
<div class="voting-section">
<h3>🎵 Vote for Next Track</h3>
<div class="upcoming-tracks">
<?php foreach ($upcoming as $track): ?>
<div class="track-card" data-track-id="<?= $track['track_id'] ?>">
<div class="track-card-artwork">
<?php if ($track['image_url']): ?>
<img src="<?= htmlspecialchars($track['image_url']) ?>" alt="<?= htmlspecialchars($track['title']) ?>">
<?php else: ?>
<div style="display: flex; align-items: center; justify-content: center; height: 100%; font-size: 2rem;">🎵</div>
<?php endif; ?>
</div>
<div class="track-card-title"><?= htmlspecialchars($track['title']) ?></div>
<div class="track-card-artist">
<?= htmlspecialchars($track['artist_name'] ?? 'Unknown Artist') ?>
</div>
<button class="vote-button" onclick="voteForTrack(<?= $track['track_id'] ?>, <?= $stream_id ?>)">
Vote
</button>
<div class="vote-count" id="votes-<?= $track['track_id'] ?>">
<?= $track['vote_count'] ?> votes
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php else: ?>
<div class="no-stream">
<h2>No Live Stream Available</h2>
<p>There are currently no active radio streams. Check back soon!</p>
</div>
<?php endif; ?>
</div>
<script>
const streamId = <?= $stream_id ?? 'null' ?>;
const stationId = <?= $station_id ?? 'null' ?>;
// Real-time updates using Server-Sent Events
if (streamId) {
const eventSource = new EventSource(`/radio/api/live/stream.php?stream_id=${streamId}`);
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === 'now_playing') {
// Update now playing track
location.reload(); // Simple refresh for now
}
if (data.type === 'listener_count') {
const countEl = document.getElementById('listener-count');
if (countEl) {
countEl.textContent = parseInt(data.count).toLocaleString();
}
}
if (data.type === 'vote_update') {
const voteEl = document.getElementById(`votes-${data.track_id}`);
if (voteEl) {
voteEl.textContent = `${data.vote_count} votes`;
}
}
};
eventSource.onerror = function(error) {
console.error('SSE error:', error);
// Reconnect after 5 seconds
setTimeout(() => {
eventSource.close();
location.reload();
}, 5000);
};
}
// Vote for track
function voteForTrack(trackId, streamId) {
fetch('/radio/api/live/vote.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
track_id: trackId,
stream_id: streamId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
const button = event.target;
button.classList.add('voted');
button.textContent = 'Voted ✓';
button.disabled = true;
const voteEl = document.getElementById(`votes-${trackId}`);
if (voteEl) {
voteEl.textContent = `${data.vote_count} votes`;
}
} else {
alert(data.error || 'Failed to vote');
}
})
.catch(error => {
console.error('Vote error:', error);
alert('Failed to vote. Please try again.');
});
}
// Track listener connection
if (streamId) {
fetch('/radio/api/live/listener.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
stream_id: streamId,
action: 'connect'
})
});
// Disconnect on page unload
window.addEventListener('beforeunload', () => {
navigator.sendBeacon('/radio/api/live/listener.php', JSON.stringify({
stream_id: streamId,
action: 'disconnect'
}));
});
}
</script>
<?php include __DIR__ . '/../includes/footer.php'; ?>
</body>
</html>