使用说明
proxy.php?url=适用于苹果CMS超级播放器
<?php // 设置CORS头,允许跨域访问 header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization'); // 处理预检请求 if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit(); } // 获取参数 $url = isset($_GET['url']) ? trim($_GET['url']) : ''; $type = isset($_GET['type']) ? trim($_GET['type']) : 'auto'; $referer = isset($_GET['referer']) ? trim($_GET['referer']) : ''; $ua = isset($_GET['ua']) ? trim($_GET['ua']) : ''; // 验证URL if (empty($url)) { http_response_code(400); echo json_encode(['error' => '缺少URL参数']); exit(); } // URL解码处理 - 这是关键修复 $url = urldecode($url); // 验证URL格式 - 使用更宽松的验证 if (!filter_var($url, FILTER_VALIDATE_URL) && !preg_match('/^https?:\/\/[a-zA-Z0-9.-]+/', $url)) { http_response_code(400); echo json_encode(['error' => '无效的URL格式', 'url' => $url]); exit(); } // 安全检查 - 防止SSRF攻击 $parsed_url = parse_url($url); if (!$parsed_url || !isset($parsed_url['host'])) { http_response_code(400); echo json_encode(['error' => '无效的URL', 'parsed_url' => $parsed_url]); exit(); } // 检查是否为内网IP - 增加错误处理 $ip = gethostbyname($parsed_url['host']); if ($ip === $parsed_url['host']) { // DNS解析失败,但不一定是错误,可能是有效域名 // 继续执行,让curl去处理 } else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) { http_response_code(403); echo json_encode(['error' => '禁止访问内网资源', 'ip' => $ip]); exit(); } /** * 发送HTTP请求 - 简化版本 * @param string $url 请求URL * @param string $referer 来源 * @param string $userAgent 用户代理 * @return array */ function fetchContent($url, $referer = '', $userAgent = '') { $ch = curl_init(); // 基础配置 curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 3, CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 15, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_USERAGENT => $userAgent ?: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0' ]); // 设置请求头 $headers = [ 'Accept: */*', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8', 'Cache-Control: no-cache', 'Connection: keep-alive' ]; if ($referer) { $headers[] = 'Referer: ' . $referer; } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); // 获取响应头 curl_setopt($ch, CURLOPT_HEADER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $error = curl_error($ch); curl_close($ch); if ($response === false) { return [ 'success' => false, 'error' => $error ?: '请求失败', 'code' => 500 ]; } $headers = substr($response, 0, $headerSize); $body = substr($response, $headerSize); return [ 'success' => true, 'code' => $httpCode, 'headers' => $headers, 'body' => $body, 'contentType' => $contentType ]; } /** * 增强版M3U8文件内容处理 * 确保播放器从第一个片段开始播放 */ function processM3U8Content($content, $baseUrl) { $lines = explode("\n", $content); $baseUrlInfo = parse_url($baseUrl); $basePath = dirname($baseUrlInfo['path']); $baseScheme = $baseUrlInfo['scheme'] ?? 'http'; $baseHost = $baseUrlInfo['host']; $basePort = isset($baseUrlInfo['port']) ? ':' . $baseUrlInfo['port'] : ''; // 获取当前proxy.php的基本URL $proxyBase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']); foreach ($lines as &$line) { $line = trim($line); // 核心修复:强制从第一个片段开始 if (strpos($line, '#EXT-X-MEDIA-SEQUENCE:') === 0) { $line = '#EXT-X-MEDIA-SEQUENCE:0'; // 强制序号从0开始 continue; } // 处理EXT-X-KEY标签 if (strpos($line, '#EXT-X-KEY:') === 0) { if (preg_match('/URI="([^"]+)"/', $line, $matches)) { $keyUri = $matches[1]; // 处理密钥URI的相对路径 if (!preg_match('/^https?:\/\//', $keyUri)) { if (strpos($keyUri, '/') === 0) { $keyUri = $baseScheme . '://' . $baseHost . $basePort . $keyUri; } else { $keyUri = $baseScheme . '://' . $baseHost . $basePort . rtrim($basePath, '/') . '/' . ltrim($keyUri, '/'); } } // 将密钥URI也通过代理 $proxyKeyUri = $proxyBase . '/proxy.php?url=' . urlencode($keyUri); $line = str_replace($matches[1], $proxyKeyUri, $line); } continue; } // 跳过空行和注释行(除了EXT-X-KEY) if (empty($line) || strpos($line, '#') === 0) { continue; } // 处理普通TS文件或播放列表的相对路径 if (!preg_match('/^https?:\/\//', $line)) { if (strpos($line, '/') === 0) { $line = $baseScheme . '://' . $baseHost . $basePort . $line; } else { $line = $baseScheme . '://' . $baseHost . $basePort . rtrim($basePath, '/') . '/' . ltrim($line, '/'); } $line = $proxyBase . '/proxy.php?url=' . urlencode($line); } else { $line = $proxyBase . '/proxy.php?url=' . urlencode($line); } } return implode("\n", $lines); } /** * 获取文件扩展名 * @param string $url * @return string */ function getFileExtension($url) { $path = parse_url($url, PHP_URL_PATH); $extension = pathinfo($path, PATHINFO_EXTENSION); return strtolower($extension); } // 发送请求 $result = fetchContent($url, $referer, $ua); if (!$result['success']) { http_response_code($result['code']); echo json_encode(['error' => $result['error']]); exit(); } // 检查HTTP状态码 if ($result['code'] >= 400) { http_response_code($result['code']); echo json_encode(['error' => 'HTTP错误: ' . $result['code']]); exit(); } // 获取文件类型 $extension = getFileExtension($url); $contentType = $result['contentType'] ?: 'application/octet-stream'; // 检查是否是.key文件的请求(密钥文件) if ($extension === 'key') { // 直接返回密钥内容 header('Content-Type: application/octet-stream'); header('Content-Length: ' . strlen($result['body'])); header('Cache-Control: no-cache'); header('Access-Control-Allow-Origin: *'); echo $result['body']; exit(); } // 根据文件类型设置响应头 switch ($extension) { case 'm3u8': $contentType = 'application/vnd.apple.mpegurl'; break; case 'ts': $contentType = 'video/mp2t'; break; case 'mp4': $contentType = 'video/mp4'; break; case 'flv': $contentType = 'video/x-flv'; break; } // 设置响应头 header('Content-Type: ' . $contentType); header('Content-Length: ' . strlen($result['body'])); header('Cache-Control: public, max-age=300'); // 缓存5分钟 header('Access-Control-Allow-Origin: *'); // 如果是M3U8文件,处理其中的URL if ($extension === 'm3u8' || strpos($contentType, 'mpegurl') !== false) { $result['body'] = processM3U8Content($result['body'], $url); // 重新计算内容长度 header('Content-Length: ' . strlen($result['body'])); } // 输出内容 echo $result['body']; ?>
