iptv-stream-web/index.php

411 lines
16 KiB
PHP

<?php
session_start();
// Admin configuration - Change this to a secure random string
define('ADMIN_CODE', 'dodgers2024streamAdm1nC0d3!xyz789'); // Change this!
// Check if user is admin
$isAdmin = false;
if (isset($_GET['admin']) && $_GET['admin'] === ADMIN_CODE) {
$_SESSION['is_admin'] = true;
}
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
// Generate or retrieve user ID
if (!isset($_SESSION['user_id'])) {
$_SESSION['user_id'] = substr(uniqid(), -6); // 6 character unique ID
}
// Simple file-based storage
$chatFile = 'chat_messages.json';
$viewersFile = 'active_viewers.json';
$bannedFile = 'banned_users.json';
$maxMessages = 100; // Keep last 100 messages
// Clean up old viewers (inactive for more than 10 seconds)
function cleanupViewers() {
global $viewersFile;
$viewers = file_exists($viewersFile) ? json_decode(file_get_contents($viewersFile), true) : [];
$currentTime = time();
$activeViewers = [];
foreach ($viewers as $viewer) {
if ($currentTime - $viewer['last_seen'] < 10) {
$activeViewers[] = $viewer;
}
}
file_put_contents($viewersFile, json_encode($activeViewers));
return count($activeViewers);
}
// Handle API requests for stream status
if (isset($_GET['api']) && $_GET['api'] === 'stream_status') {
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
$streamUrl = 'http://38.64.28.91:23456/stream.m3u8';
$online = false;
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "User-Agent: Mozilla/5.0\r\n",
'timeout' => 5 // Quick check
]
]);
$content = @file_get_contents($streamUrl, false, $context);
if ($content !== false && !empty($content)) {
// Check if it looks like a valid m3u8
if (str_starts_with($content, '#EXTM3U')) {
$online = true;
}
}
echo json_encode(['online' => $online]);
exit;
}
// Handle proxy requests for the stream
if (isset($_GET['proxy']) && $_GET['proxy'] === 'stream') {
$streamUrl = 'http://38.64.28.91:23456/stream.m3u8';
// Set appropriate headers for m3u8 content
header('Content-Type: application/vnd.apple.mpegurl');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
header('Access-Control-Allow-Headers: Range');
// Fetch and output the m3u8 content
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "User-Agent: Mozilla/5.0\r\n",
'timeout' => 10
]
]);
$content = @file_get_contents($streamUrl, false, $context);
if ($content !== false) {
// Parse and update the m3u8 content to use our proxy for .ts segments
$lines = explode("\n", $content);
$updatedContent = [];
foreach ($lines as $line) {
$line = trim($line);
if (!empty($line) && !str_starts_with($line, '#')) {
// This is a .ts segment URL
if (strpos($line, 'http') === 0) {
// Absolute URL
$updatedContent[] = '?proxy=segment&url=' . urlencode($line);
} else {
// Relative URL
$baseUrl = 'http://38.64.28.91:23456/';
$updatedContent[] = '?proxy=segment&url=' . urlencode($baseUrl . $line);
}
} else {
$updatedContent[] = $line;
}
}
echo implode("\n", $updatedContent);
} else {
http_response_code(500);
echo "Failed to fetch stream";
}
exit;
}
// Handle proxy requests for .ts segments
if (isset($_GET['proxy']) && $_GET['proxy'] === 'segment' && isset($_GET['url'])) {
$segmentUrl = urldecode($_GET['url']);
// Validate URL to prevent abuse
if (strpos($segmentUrl, 'http://38.64.28.91:23456/') !== 0) {
http_response_code(403);
exit;
}
header('Content-Type: video/mp2t');
header('Access-Control-Allow-Origin: *');
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "User-Agent: Mozilla/5.0\r\n",
'timeout' => 10
]
]);
$content = @file_get_contents($segmentUrl, false, $context);
if ($content !== false) {
echo $content;
} else {
http_response_code(500);
}
exit;
}
// Handle chat actions
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
header('Content-Type: application/json');
// Admin actions
if ($_POST['action'] === 'delete_message' && $isAdmin && isset($_POST['message_id'])) {
$messages = file_exists($chatFile) ? json_decode(file_get_contents($chatFile), true) : [];
$messages = array_filter($messages, function($msg) {
return $msg['id'] !== $_POST['message_id'];
});
$messages = array_values($messages); // Re-index array
file_put_contents($chatFile, json_encode($messages));
echo json_encode(['success' => true]);
exit;
}
if ($_POST['action'] === 'clear_chat' && $isAdmin) {
file_put_contents($chatFile, json_encode([]));
echo json_encode(['success' => true]);
exit;
}
if ($_POST['action'] === 'ban_user' && $isAdmin && isset($_POST['user_id'])) {
$banned = file_exists($bannedFile) ? json_decode(file_get_contents($bannedFile), true) : [];
if (!in_array($_POST['user_id'], $banned)) {
$banned[] = $_POST['user_id'];
file_put_contents($bannedFile, json_encode($banned));
}
echo json_encode(['success' => true]);
exit;
}
if ($_POST['action'] === 'heartbeat') {
$userId = $_SESSION['user_id'];
$nickname = isset($_POST['nickname']) ? htmlspecialchars(substr($_POST['nickname'], 0, 20)) : 'Anonymous';
$viewers = file_exists($viewersFile) ? json_decode(file_get_contents($viewersFile), true) : [];
// Update or add viewer
$found = false;
foreach ($viewers as &$viewer) {
if ($viewer['user_id'] === $userId) {
$viewer['last_seen'] = time();
$viewer['nickname'] = $nickname;
$viewer['is_admin'] = $isAdmin;
$found = true;
break;
}
}
if (!$found) {
$viewers[] = [
'user_id' => $userId,
'nickname' => $nickname,
'last_seen' => time(),
'is_admin' => $isAdmin
];
}
file_put_contents($viewersFile, json_encode($viewers));
$viewerCount = cleanupViewers();
echo json_encode(['success' => true, 'viewer_count' => $viewerCount]);
exit;
}
if ($_POST['action'] === 'send' && isset($_POST['message']) && isset($_POST['nickname'])) {
$nickname = htmlspecialchars(substr($_POST['nickname'], 0, 20));
$message = htmlspecialchars(substr($_POST['message'], 0, 1000));
$userId = $_SESSION['user_id'];
// Check if user is banned
$banned = file_exists($bannedFile) ? json_decode(file_get_contents($bannedFile), true) : [];
if (in_array($userId, $banned)) {
echo json_encode(['success' => false, 'error' => 'You are banned from chat']);
exit;
}
if (!empty($nickname) && !empty($message)) {
$messages = file_exists($chatFile) ? json_decode(file_get_contents($chatFile), true) : [];
$newMessage = [
'id' => uniqid(),
'user_id' => $userId,
'nickname' => $nickname,
'message' => $message,
'timestamp' => time(),
'time' => date('M j, H:i'),
'is_admin' => $isAdmin
];
array_push($messages, $newMessage);
// Keep only last N messages
if (count($messages) > $maxMessages) {
$messages = array_slice($messages, -$maxMessages);
}
file_put_contents($chatFile, json_encode($messages));
echo json_encode(['success' => true, 'message' => $newMessage]);
} else {
echo json_encode(['success' => false, 'error' => 'Invalid input']);
}
exit;
}
if ($_POST['action'] === 'fetch') {
$lastId = isset($_POST['last_id']) ? $_POST['last_id'] : '';
$messages = file_exists($chatFile) ? json_decode(file_get_contents($chatFile), true) : [];
// Find new messages only
$newMessages = [];
$foundLast = empty($lastId);
foreach ($messages as $msg) {
if ($foundLast) {
$newMessages[] = $msg;
}
if ($msg['id'] === $lastId) {
$foundLast = true;
}
}
// If lastId wasn't found, return all messages (initial load or refresh)
if (!$foundLast && !empty($lastId)) {
$newMessages = $messages;
}
// Get viewer count
$viewerCount = cleanupViewers();
// Determine if we should send all messages (for initial load or after admin actions)
$sendAllMessages = empty($lastId) || !$foundLast;
echo json_encode([
'success' => true,
'messages' => $newMessages,
'all_messages' => $sendAllMessages ? $messages : null,
'message_count' => count($messages),
'viewer_count' => $viewerCount,
'is_admin' => $isAdmin
]);
exit;
}
if ($_POST['action'] === 'get_user_id') {
echo json_encode(['success' => true, 'user_id' => $_SESSION['user_id'], 'is_admin' => $isAdmin]);
exit;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dodgers Stream Theater</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="https://vjs.zencdn.net/8.6.1/video-js.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/main.css?v=1.4.2">
</head>
<body>
<main class="theater" role="main">
<section class="theater__video-section" id="videoSection" aria-label="Video Player">
<header class="video-player__header">
<div class="video-player__header-left">
<h1 class="stream-logo">DODGERS STREAM</h1>
<div class="stream-stats">
<span class="stream-stats__status" role="status">WAITING...</span>
<span class="stream-stats__viewer-count" id="viewerCount" aria-live="polite">0 viewers</span>
</div>
</div>
<div class="video-player__header-controls">
<button class="stream-stats__refresh-btn" data-action="manual-refresh" title="Manual Stream Refresh" aria-label="Refresh stream">
🔄 Refresh
</button>
<select class="video-player__quality-selector" id="qualitySelector" style="display:none;" aria-label="Video quality">
<option value="auto">Auto Quality</option>
<option value="1080p">1080p</option>
<option value="720p">720p</option>
<option value="480p">480p</option>
</select>
<button class="video-player__toggle-chat-btn" data-action="toggle-chat" aria-expanded="false" aria-controls="chatSection">
<span id="chatToggleText">Hide Chat</span>
<span class="video-player__notification-badge" id="notificationBadge" aria-label="New messages">0</span>
</button>
</div>
</header>
<div class="video-wrapper">
<video
id="video-player"
class="video-js vjs-default-skin vjs-big-play-centered"
controls
preload="auto"
loop
>
<source src="?proxy=stream" type="application/x-mpegURL">
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to a web browser that
supports HTML5 video.
</p>
</video>
</div>
</div>
<aside class="theater__chat-section" id="chatSection" aria-label="Live Chat">
<header class="chat__header">
<h2 class="chat__header-title">Live Chat</h2>
<div class="chat__admin-controls" id="adminControls" style="display:none;">
<button class="chat__admin-btn" data-action="clear-chat" aria-label="Clear all chat messages">Clear</button>
</div>
</header>
<section class="chat__user-info" aria-labelledby="user-info-heading">
<h3 id="user-info-heading" class="sr-only">User Information</h3>
<div class="chat__user-id-display">
Your ID: <span class="chat__user-id-badge" id="userId">Loading...</span>
</div>
<div class="chat__nickname-input">
<input type="text" id="nickname" placeholder="Choose a nickname..." maxlength="20" autocomplete="off" aria-label="Enter your nickname">
</div>
</section>
<div class="chat__connection-status" aria-live="assertive">
<span class="chat__connection-indicator" id="statusDot" role="status" aria-label="Connection status"></span>
<span id="statusText">Connected</span>
</div>
<section class="chat__messages" id="chatMessages" aria-live="polite" aria-label="Chat messages" role="log" aria-atomic="false">
<div class="chat__empty-state">No messages yet. Be the first to say hello! 👋</div>
</section>
<div class="chat__typing-indicator" id="typingIndicator" aria-live="assertive">
Someone is typing<span>.</span><span>.</span><span>.</span>
</div>
<section class="chat__input" aria-labelledby="chat-input-heading">
<h3 id="chat-input-heading" class="sr-only">Send Message</h3>
<div class="chat__input-group">
<input type="text" id="messageInput" placeholder="Type a message..." maxlength="1000" autocomplete="off" aria-label="Type your message">
<button data-action="send-message" aria-label="Send message">Send</button>
</div>
</section>
</aside>
</main>
<!-- Toast Notification -->
<div class="toast" id="toast"></div>
<script src="https://vjs.zencdn.net/8.6.1/video.min.js"></script>
<script defer src="assets/js/app.js?v=1.4.4"></script>
<script defer src="assets/js/api.js?v=1.4.4"></script>
<script defer src="assets/js/ui-controls.js?v=1.4.4"></script>
<script defer src="assets/js/chat.js?v=1.4.4"></script>
<script defer src="assets/js/video-player.js?v=1.4.4"></script>
</body>
</html>