<?php
// 兼容 PHP 7.x 的 str_contains
if (!function_exists('str_contains')) {
    function str_contains($haystack, $needle) {
        return $needle !== '' && strpos($haystack, $needle) !== false;
    }
}

session_start();
error_reporting(E_ALL & ~E_NOTICE);

$login_timeout = 3600;
$password = 'admin123';

if (!isset($_SESSION['admin_logged']) || $_SESSION['admin_logged'] !== true) {
    if (isset($_POST['pass'])) {
        if ($_POST['pass'] === $password) {
            $_SESSION['admin_logged'] = true;
            $_SESSION['login_time'] = time();
            header('Location: ./'); exit;
        } else {
            $error = '密码错误';
        }
    }
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>管理员登录</title>
    <style>
        *{margin:0;padding:0;box-sizing:border-box;}
        body{background:#1e1e2f;color:#fff;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;}
        .login-box{background:#2a2a41;padding:40px;border-radius:12px;width:340px;box-shadow:0 8px 30px rgba(0,0,0,0.4);}
        .login-box h2{text-align:center;margin-bottom:25px;color:#c1c1ff;}
        .login-box input{width:100%;padding:14px;border-radius:8px;border:none;background:#373757;color:#fff;outline:none;margin-bottom:20px;font-size:16px;}
        .login-box button{width:100%;padding:14px;border-radius:8px;border:none;background:#6c63ff;color:#fff;cursor:pointer;font-size:16px;font-weight:600;}
        .error{color:#ff5555;text-align:center;margin-bottom:15px;font-size:15px;}
    </style>
</head>
<body>
    <div class="login-box">
        <h2>管理员登录</h2>
        <?php if(isset($error)) echo '<div class="error">'.$error.'</div>'; ?>
        <form method="post">
            <input type="password" name="pass" placeholder="请输入密码" required>
            <button type="submit">登录</button>
        </form>
    </div>
</body>
</html>
<?php exit; }

if (time() - ($_SESSION['login_time'] ?? 0) > $login_timeout) {
    session_destroy();
    header('Location:./'); exit;
}
$_SESSION['login_time'] = time();

define('TASKS_FILE', 'tasks.json');
define('HLS_DIR', 'hls_output');
define('FFMPEG_PATH', 'ffmpeg');
define('LOG_DIR', 'logs');
define('PROBE_DIR', 'probe_cache');

@mkdir(HLS_DIR, 0777, true);
@mkdir(LOG_DIR, 0777, true);
@mkdir(PROBE_DIR, 0777, true);

$tasks = json_decode(@file_get_contents(TASKS_FILE), true) ?: [];

// 检查exec是否可用
function isExecAvailable() {
    $disabled = explode(',', ini_get('disable_functions'));
    return function_exists('exec') && !in_array('exec', $disabled);
}

// 获取任务专属日志文件
function getTaskLogFile($id) {
    return LOG_DIR . "/{$id}.log";
}

// 记录日志
function logMessage($id, $msg, $type = 'INFO') {
    $timestamp = date('Y-m-d H:i:s');
    $logFile = getTaskLogFile($id);
    $logMsg = "[{$timestamp}] [{$type}] {$msg}\n";
    file_put_contents($logFile, $logMsg, FILE_APPEND);
    
    $globalLog = LOG_DIR . "/all.log";
    file_put_contents($globalLog, $logMsg, FILE_APPEND);
}

// 清除任务日志
function clearTaskLog($id) {
    $logFile = getTaskLogFile($id);
    if (file_exists($logFile)) {
        file_put_contents($logFile, '');
        return true;
    }
    return false;
}

// 读取任务日志
function getTaskLog($id, $lines = 500) {
    $logFile = getTaskLogFile($id);
    if (!file_exists($logFile)) {
        return "暂无日志";
    }
    $logs = file_get_contents($logFile);
    if ($lines > 0 && strlen($logs) > $lines * 100) {
        $logs = substr($logs, -($lines * 100));
    }
    return nl2br(htmlspecialchars($logs));
}

function clearLogs() {
    $globalLog = LOG_DIR . "/all.log";
    if (file_exists($globalLog)) {
        file_put_contents($globalLog, '');
    }
    return true;
}

// ==================== URL处理 ====================
function safeTrim($str) {
    return is_string($str) ? trim($str) : '';
}

function fullUrl($base, $path) {
    if (empty($path)) return '';
    if (strpos($path, 'http') === 0) return $path;
    if (empty($base)) return $path;
    
    $parse = parse_url($base);
    $dir = dirname($parse['path'] ?? '/');
    $baseHost = ($parse['scheme'] ?? 'http') . '://' . ($parse['host'] ?? '');
    return rtrim($baseHost . $dir, '/') . '/' . ltrim($path, '/');
}

function getFinalUrl($url) {
    if (empty($url)) return '';
    
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0");
    curl_setopt($ch, CURLOPT_NOBODY, true);
    curl_exec($ch);
    $finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    curl_close($ch);
    return $finalUrl ?: $url;
}

// ==================== 获取 PHP 代理返回的真实地址 ====================
function getRealMPDUrl($proxyUrl, &$debug = []) {
    $debug = ['status' => '获取真实地址中'];
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $proxyUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
    
    $headers = [
        'Accept: application/dash+xml,application/xml,text/html,*/*',
        'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
        'Connection: keep-alive',
    ];
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    
    $content = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);
    
    $debug['http_code'] = $httpCode;
    $debug['final_url'] = $finalUrl;
    $debug['content_type'] = $contentType;
    
    // 302重定向到MPD地址
    if ($httpCode === 302 && strpos($finalUrl, '.mpd') !== false) {
        $debug['status'] = '302重定向到MPD';
        return $finalUrl;
    }
    
    if ($httpCode !== 200) {
        $debug['status'] = "HTTP错误: {$httpCode}";
        return false;
    }
    
    // 返回的是MPD XML内容
    if (strpos($content, '<?xml') !== false || strpos($content, '<MPD') !== false) {
        $debug['status'] = '返回MPD内容';
        
        // 提取BaseURL
        if (preg_match('/<BaseURL>([^<]+)<\/BaseURL>/', $content, $matches)) {
            $baseUrl = trim($matches[1]);
            if (strpos($baseUrl, 'http') === 0) {
                $debug['extracted_baseurl'] = $baseUrl;
                $debug['status'] = '提取到BaseURL';
                return $baseUrl;
            }
        }
        
        // 提取session和token构建TVB地址
        $session = '';
        $token = '';
        if (preg_match('/session\/([^\/]+)/', $content, $matches)) {
            $session = $matches[1];
        }
        if (preg_match('/token=([^"\']+)/', $content, $matches)) {
            $token = $matches[1];
        }
        
        if ($session && $token) {
            $tvbUrl = "http://hk4-edge26-1.edgeware.tvb.com:80/session/{$session}/__cl/slocalr43/__c/ott_J_hevc/__op/cenc_m/__f/index.mpd?token={$token}";
            $debug['constructed_url'] = $tvbUrl;
            $debug['status'] = '构建TVB播放地址';
            return $tvbUrl;
        }
        
        // 保存MPD到文件
        $tempFile = PROBE_DIR . '/temp_' . md5($proxyUrl) . '.mpd';
        file_put_contents($tempFile, $content);
        $debug['cached_file'] = $tempFile;
        return false;
    }
    
    // 返回的是M3U8
    if (strpos($content, '#EXTM3U') !== false) {
        $debug['status'] = '返回的是M3U8';
        return $proxyUrl;
    }
    
    $debug['status'] = '无法识别返回内容';
    return false;
}

// ==================== 通用流探测 ====================
function probeStream($url, &$debug = []) {
    $debug = ['status' => '探测中'];
    
    $cacheFile = PROBE_DIR . '/' . md5($url) . '.json';
    
    if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < 3600) {
        $cached = json_decode(file_get_contents($cacheFile), true);
        if ($cached) {
            $debug = $cached['debug'] ?? ['status' => '从缓存读取'];
            return $cached['data'];
        }
    }
    
    if (!isExecAvailable()) {
        $debug['status'] = '无法探测：exec函数不可用';
        return false;
    }
    
    $cmd = FFMPEG_PATH . ' -i ' . escapeshellarg($url) . ' 2>&1';
    exec($cmd, $output, $returnCode);
    $ffprobeOutput = implode("\n", $output);
    
    $videoList = [];
    $audioList = [];
    $subList = [];
    
    $lines = explode("\n", $ffprobeOutput);
    $videoIndex = 0;
    $audioIndex = 0;
    $subIndex = 0;
    
    foreach ($lines as $line) {
        if (preg_match('/Stream #\d+:(\d+).*?: Video: ([\w]+)/', $line, $matches)) {
            $codec = $matches[2];
            $resolution = '';
            if (preg_match('/(\d+)x(\d+)/', $line, $resMatch)) {
                $resolution = " {$resMatch[1]}x{$resMatch[2]}";
            }
            $videoList[] = [
                'id' => $videoIndex,
                'codecs' => $codec . $resolution,
                'index' => $matches[1]
            ];
            $videoIndex++;
        }
        elseif (preg_match('/Stream #\d+:(\d+).*?: Audio: ([\w]+)/', $line, $matches)) {
            $codec = $matches[2];
            $lang = '';
            if (preg_match('/\(([a-z]{2,3})\)/', $line, $langMatch)) {
                $lang = $langMatch[1];
            }
            $audioList[] = [
                'id' => $audioIndex,
                'codecs' => $codec,
                'lang' => $lang,
                'index' => $matches[1]
            ];
            $audioIndex++;
        }
        elseif (preg_match('/Stream #\d+:(\d+).*?: Subtitle: ([\w]+)/', $line, $matches)) {
            $codec = $matches[2];
            $lang = '';
            if (preg_match('/\(([a-z]{2,3})\)/', $line, $langMatch)) {
                $lang = $langMatch[1];
            }
            $subList[] = [
                'id' => $subIndex,
                'codecs' => $codec,
                'lang' => $lang,
                'index' => $matches[1]
            ];
            $subIndex++;
        }
    }
    
    $debug['status'] = '探测成功';
    $debug['ffprobe_output'] = substr($ffprobeOutput, 0, 500);
    
    $result = [
        'video' => $videoList,
        'audio' => $audioList,
        'subtitle' => $subList,
        'originUrl' => $url
    ];
    
    file_put_contents($cacheFile, json_encode(['data' => $result, 'debug' => $debug]));
    
    return $result;
}

// ==================== MPD解析模式 ====================
function parseMPD($mpd_url, &$debug = []) {
    if (empty($mpd_url)) {
        $debug = ['status' => 'MPD地址为空'];
        return false;
    }
    
    $realUrl = getFinalUrl($mpd_url);

    $debug = ['http_code'=>0, 'content_len'=>0, 'curl_error'=>'', 'status'=>'等待下载'];
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $realUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_ENCODING, 'gzip, deflate, br');
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0');

    $mpd_content = curl_exec($ch);
    $debug['http_code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $debug['curl_error'] = curl_error($ch);
    $debug['content_len'] = strlen($mpd_content);
    curl_close($ch);

    if ($debug['http_code'] !== 200) {
        $debug['status'] = "HTTP错误: {$debug['http_code']}";
        return false;
    }
    if ($debug['content_len'] < 100) {
        $debug['status'] = 'MPD内容过小，可能被拒绝';
        return false;
    }

    libxml_use_internal_errors(true);
    $xml = simplexml_load_string($mpd_content);
    if (!$xml) {
        $errors = libxml_get_errors();
        $debug['status'] = 'XML解析失败: ' . ($errors[0]->message ?? '未知错误');
        libxml_clear_errors();
        return false;
    }

    $videoList = [];
    $audioList = [];
    $subList = [];
    $segments = [];

    if (isset($xml->Period)) {
        foreach ($xml->Period->AdaptationSet as $set) {
            $mime = strtolower(safeTrim((string)($set->attributes()->mimeType ?? '')));
            $lang = safeTrim((string)($set->attributes()->lang ?? ''));

            if (isset($set->SegmentTemplate)) {
                $t = $set->SegmentTemplate;
                $media = safeTrim((string)($t->attributes()->media ?? ''));
                $dur = (int)($t->attributes()->duration ?? 2000);
                $start = (int)($t->attributes()->startNumber ?? 1);
                $isTimeMode = strpos($media, '$Time$') !== false;
                
                for ($i = 0; $i < min(40, 80); $i++) {
                    $num = $isTimeMode ? ($start + ($i * $dur)) : ($start + $i);
                    $segUrl = str_replace(
                        ['$RepresentationID$', '$Number$', '$Time$', '$Bandwidth$'],
                        ['', $num, $num, ''],
                        $media
                    );
                    $fullSeg = fullUrl($realUrl, $segUrl);
                    if (!empty($fullSeg)) {
                        $segments[] = $fullSeg;
                    }
                }
            } elseif (isset($set->SegmentList)) {
                $segList = $set->SegmentList;
                foreach ($segList->SegmentURL as $seg) {
                    $segUrl = safeTrim((string)($seg->attributes()->media ?? ''));
                    if (!empty($segUrl)) {
                        $fullSeg = fullUrl($realUrl, $segUrl);
                        if (!empty($fullSeg)) $segments[] = $fullSeg;
                    }
                }
            }

            foreach ($set->Representation as $rep) {
                $rid = safeTrim((string)($rep->attributes()->id ?? ''));
                $codecs = safeTrim((string)($rep->attributes()->codecs ?? ''));
                if(empty($rid)) continue;

                if (strpos($mime, 'video') !== false) {
                    $videoList[] = ['id' => $rid, 'codecs' => $codecs];
                }
                elseif (strpos($mime, 'audio') !== false) {
                    $audioList[] = ['id' => $rid, 'codecs' => $codecs, 'lang' => $lang];
                }
                elseif (strpos($mime, 'subtitle') !== false || strpos($mime, 'stpp') !== false || strpos($mime, 'vtt') !== false) {
                    $subList[] = ['id' => $rid, 'codecs' => $codecs, 'lang' => $lang];
                }
            }
        }
    }
    
    if (empty($videoList) && !empty($segments)) {
        $debug['status'] = '解析成功，未找到轨道信息';
    } else {
        $debug['status'] = '解析成功';
    }
    
    return [
        'video'     => $videoList,
        'audio'     => $audioList,
        'subtitle'  => $subList,
        'segments'  => $segments,
        'originUrl' => $realUrl
    ];
}

// ==================== TVB专用流处理 ====================
function startTVBStream($id, $proxyUrl, $videoTrack = 0, $audioTrack = 0, $subTrack = -1, $clearkey = '') {
    logMessage($id, "尝试TVB专用模式: {$proxyUrl}", 'INFO');
    
    // 获取真实的MPD地址
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $proxyUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
    $content = curl_exec($ch);
    $info = curl_getinfo($ch);
    curl_close($ch);
    
    $realMpdUrl = '';
    
    // 提取真实MPD URL
    if ($info['http_code'] == 302 && strpos($info['url'], '.mpd') !== false) {
        $realMpdUrl = $info['url'];
    } elseif (preg_match('/<BaseURL>([^<]+)<\/BaseURL>/', $content, $matches)) {
        $realMpdUrl = trim($matches[1]);
    } elseif (preg_match('/https?:\/\/[^\s"\']+\.mpd[^\s"\']*/', $content, $matches)) {
        $realMpdUrl = $matches[0];
    }
    
    if (empty($realMpdUrl)) {
        logMessage($id, "无法提取真实MPD地址", 'ERROR');
        return false;
    }
    
    logMessage($id, "提取到真实MPD: {$realMpdUrl}", 'INFO');
    
    $outputDir = HLS_DIR . "/{$id}";
    if (!is_dir($outputDir)) {
        mkdir($outputDir, 0777, true);
    }
    
    $outputM3u8 = $outputDir . "/index.m3u8";
    $pidFile = HLS_DIR . "/{$id}.pid";
    $runningFile = HLS_DIR . "/{$id}.running";
    
    stopFFmpeg($id, true);
    
    $taskLogFile = getTaskLogFile($id);
    
    // TVB需要的HTTP头
    $headers = "Referer: https://www.tvb.com/\r\n";
    $headers .= "Origin: https://www.tvb.com/\r\n";
    $headers .= "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\n";
    
    $decryptionParam = '';
    if (!empty($clearkey)) {
        $clearkey = trim($clearkey);
        $decryptionParam = ' -decryption_key ' . escapeshellarg($clearkey);
        logMessage($id, "应用ClearKey: {$clearkey}", 'INFO');
    }
    
    // 构建映射参数
    $mapParams = '-map 0';
    if ($videoTrack !== 0 || $audioTrack !== 0) {
        $mapParams = '-map 0:v:' . $videoTrack . ' -map 0:a:' . $audioTrack;
        if ($subTrack >= 0) {
            $mapParams .= ' -map 0:s:' . $subTrack;
        }
    }
    
    $cmd = FFMPEG_PATH . 
           ' -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 5' .
           ' -timeout 30000000' .
           ' -headers "' . $headers . '"' .
           ' -user_agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"' .
           ' -fflags +genpts+igndts+discardcorrupt' .
           ' -i ' . escapeshellarg($realMpdUrl) . 
           $decryptionParam .
           ' ' . $mapParams .
           ' -c:v libx264 -preset ultrafast -crf 23 -tune zerolatency' .
           ' -c:a aac -b:a 128k' .
           ' -f hls -hls_time 6 -hls_list_size 0' .
           ' -hls_flags delete_segments+discont_start+omit_endlist' .
           ' -avoid_negative_ts make_zero -copytb 1' .
           ' -hls_segment_filename ' . escapeshellarg($outputDir . '/segment_%03d.ts') .
           ' ' . escapeshellarg($outputM3u8) .
           ' >> ' . escapeshellarg($taskLogFile) . ' 2>&1 & echo $!';
    
    logMessage($id, "执行TVB专用命令...", 'DEBUG');
    $pid = trim(shell_exec($cmd));
    
    if (is_numeric($pid) && $pid > 0) {
        file_put_contents($pidFile, $pid);
        file_put_contents($runningFile, '1');
        logMessage($id, "TVB专用模式启动成功，PID: {$pid}", 'SUCCESS');
        
        sleep(3);
        if (file_exists($outputM3u8) && filesize($outputM3u8) > 0) {
            logMessage($id, "M3U8文件已生成", 'SUCCESS');
            return true;
        }
        return true;
    }
    
    return false;
}

// ==================== 通用FFmpeg命令构建 ====================
function buildFFmpegCommand($input, $outputDir, $outputM3u8, $mapParams, $decryptionParam = '') {
    $reconnectParams = ' -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 5';
    $timeoutParams = ' -timeout 30000000';
    $userAgent = ' -user_agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"';
    $fflags = ' -fflags +genpts+igndts+discardcorrupt';
    
    $hlsFlags = ' -hls_flags delete_segments+discont_start+omit_endlist';
    $hlsTime = ' -hls_time 6';
    $hlsListSize = ' -hls_list_size 0';
    $hlsSegmentFilename = ' -hls_segment_filename ' . escapeshellarg($outputDir . '/segment_%03d.ts');
    
    $overwriteParams = ' -y';
    
    $cmd = FFMPEG_PATH . 
           $reconnectParams . $timeoutParams . $userAgent . $fflags . $overwriteParams .
           ' -i ' . escapeshellarg($input) . 
           $decryptionParam .
           ' ' . $mapParams .
           ' -c:v libx264 -preset ultrafast -crf 23 -tune zerolatency' .
           ' -c:a aac -b:a 128k' .
           ' -f hls' . $hlsTime . $hlsListSize . $hlsFlags . $hlsSegmentFilename .
           ' -avoid_negative_ts make_zero -copytb 1' .
           ' ' . escapeshellarg($outputM3u8);
    
    return $cmd;
}

// ==================== FFmpeg推流模式（直接模式） ====================
function startFFmpeg($id, $source, $videoTrack = 0, $audioTrack = 0, $subTrack = -1) {
    $outputDir = HLS_DIR . "/{$id}";
    if (!is_dir($outputDir)) {
        mkdir($outputDir, 0777, true);
    }
    
    $outputM3u8 = $outputDir . "/index.m3u8";
    $pidFile = HLS_DIR . "/{$id}.pid";
    $runningFile = HLS_DIR . "/{$id}.running";

    stopFFmpeg($id, true);
    
    $taskLogFile = getTaskLogFile($id);
    
    $mapParams = '';
    if ($videoTrack !== 0 || $audioTrack !== 0) {
        $mapParams = '-map 0:v:' . $videoTrack . ' -map 0:a:' . $audioTrack;
        if ($subTrack >= 0) {
            $mapParams .= ' -map 0:s:' . $subTrack;
        }
    } else {
        $mapParams = '-map 0';
    }
    
    $cmd = buildFFmpegCommand($source, $outputDir, $outputM3u8, $mapParams, '');
    $cmd .= ' >> ' . escapeshellarg($taskLogFile) . ' 2>&1 & echo $!';
    
    logMessage($id, "启动FFmpeg直播任务, 源: {$source}", 'START');
    $pid = trim(shell_exec($cmd));

    if (is_numeric($pid) && $pid > 0) {
        file_put_contents($pidFile, $pid);
        file_put_contents($runningFile, '1');
        logMessage($id, "FFmpeg进程启动成功，PID: {$pid}", 'SUCCESS');
        
        sleep(2);
        if (file_exists($outputM3u8) && filesize($outputM3u8) > 0) {
            logMessage($id, "M3U8文件已生成: {$outputM3u8}", 'SUCCESS');
        } else {
            logMessage($id, "警告：M3U8文件未生成，请检查FFmpeg日志", 'WARN');
        }
        return true;
    }
    logMessage($id, "FFmpeg进程启动失败", 'ERROR');
    return false;
}

// ==================== MPD推流模式 ====================
function startMPDToFFmpeg($id, $source, $videoTrack = 0, $audioTrack = 0, $subTrack = -1, $clearkey = '') {
    logMessage($id, "开始处理源: {$source} | ClearKey: " . ($clearkey ? '已提供' : '无'), 'INFO');
    
    $outputDir = HLS_DIR . "/{$id}";
    if (!is_dir($outputDir)) {
        mkdir($outputDir, 0777, true);
    }
    
    $outputM3u8 = $outputDir . "/index.m3u8";
    $pidFile = HLS_DIR . "/{$id}.pid";
    $runningFile = HLS_DIR . "/{$id}.running";
    
    $isPhpProxy = strpos($source, '.php') !== false;
    $realSource = $source;
    
    // 如果是 PHP 代理，先获取真实地址
    if ($isPhpProxy) {
        logMessage($id, "检测到 PHP 代理，尝试获取真实地址...", 'INFO');
        $debug = [];
        $realSource = getRealMPDUrl($source, $debug);
        logMessage($id, "获取结果: " . json_encode($debug), 'INFO');
        
        if (!$realSource) {
            logMessage($id, "无法获取真实地址，尝试直接拉流模式", 'WARN');
            return startDirectStream($id, $source, $videoTrack, $audioTrack, $subTrack, $clearkey);
        }
        
        logMessage($id, "获取到真实地址: {$realSource}", 'INFO');
    }
    
    stopFFmpeg($id, true);
    
    $taskLogFile = getTaskLogFile($id);

    $decryptionParam = '';
    if (!empty($clearkey)) {
        $clearkey = trim($clearkey);
        $decryptionParam = ' -decryption_key ' . escapeshellarg($clearkey);
        logMessage($id, "应用 ClearKey: {$clearkey}", 'INFO');
    }

    $mapParams = '';
    if ($videoTrack !== 0 || $audioTrack !== 0) {
        $mapParams = '-map 0:v:' . $videoTrack . ' -map 0:a:' . $audioTrack;
        if ($subTrack >= 0) {
            $mapParams .= ' -map 0:s:' . $subTrack;
        }
    } else {
        $mapParams = '-map 0';
    }
    
    $cmd = buildFFmpegCommand($realSource, $outputDir, $outputM3u8, $mapParams, $decryptionParam);
    $cmd .= ' >> ' . escapeshellarg($taskLogFile) . ' 2>&1 & echo $!';

    logMessage($id, "执行 FFmpeg 直播命令...", 'DEBUG');
    $pid = trim(shell_exec($cmd));

    if (is_numeric($pid) && $pid > 0) {
        file_put_contents($pidFile, $pid);
        file_put_contents($runningFile, '1');
        logMessage($id, "FFmpeg进程启动成功，PID: {$pid}", 'SUCCESS');
        
        for ($i = 0; $i < 10; $i++) {
            sleep(2);
            if (file_exists($outputM3u8) && filesize($outputM3u8) > 0) {
                logMessage($id, "M3U8文件已生成: {$outputM3u8}", 'SUCCESS');
                return true;
            }
        }
        return true;
    }

    logMessage($id, "FFmpeg进程启动失败", 'ERROR');
    return false;
}

// ==================== 直接拉流模式 ====================
function startDirectStream($id, $source, $videoTrack = 0, $audioTrack = 0, $subTrack = -1, $clearkey = '') {
    logMessage($id, "尝试直接拉流模式: {$source}", 'INFO');
    
    $outputDir = HLS_DIR . "/{$id}";
    if (!is_dir($outputDir)) {
        mkdir($outputDir, 0777, true);
    }
    
    $outputM3u8 = $outputDir . "/index.m3u8";
    $pidFile = HLS_DIR . "/{$id}.pid";
    $runningFile = HLS_DIR . "/{$id}.running";
    
    stopFFmpeg($id, true);
    
    $taskLogFile = getTaskLogFile($id);
    
    $mapParams = '';
    if ($videoTrack !== 0 || $audioTrack !== 0) {
        $mapParams = '-map 0:v:' . $videoTrack . ' -map 0:a:' . $audioTrack;
        if ($subTrack >= 0) {
            $mapParams .= ' -map 0:s:' . $subTrack;
        }
    } else {
        $mapParams = '-map 0';
    }
    
    $decryptionParam = '';
    if (!empty($clearkey)) {
        $clearkey = trim($clearkey);
        $decryptionParam = ' -decryption_key ' . escapeshellarg($clearkey);
    }
    
    $cmd = buildFFmpegCommand($source, $outputDir, $outputM3u8, $mapParams, $decryptionParam);
    $cmd .= ' >> ' . escapeshellarg($taskLogFile) . ' 2>&1 & echo $!';
    
    logMessage($id, "执行直接拉流命令...", 'DEBUG');
    $pid = trim(shell_exec($cmd));
    
    if (is_numeric($pid) && $pid > 0) {
        file_put_contents($pidFile, $pid);
        file_put_contents($runningFile, '1');
        logMessage($id, "直接拉流启动成功，PID: {$pid}", 'SUCCESS');
        sleep(3);
        return true;
    }
    
    return false;
}

function stopFFmpeg($id, $deleteFiles = false) {
    $pidFile = HLS_DIR . "/{$id}.pid";
    $runningFile = HLS_DIR . "/{$id}.running";
    
    if (file_exists($pidFile)) {
        $pid = trim(file_get_contents($pidFile));
        if (!empty($pid) && is_numeric($pid)) {
            @shell_exec("kill -9 $pid 2>/dev/null");
            logMessage($id, "停止FFmpeg进程, PID: {$pid}", 'STOP');
        }
        @unlink($pidFile);
    }
    @unlink($runningFile);
    
    if ($deleteFiles) {
        $taskDir = HLS_DIR . "/{$id}";
        if (is_dir($taskDir)) {
            foreach (glob("{$taskDir}/*.ts") as $f) @unlink($f);
            foreach (glob("{$taskDir}/*.m3u8") as $f) @unlink($f);
        }
        @unlink(HLS_DIR . "/{$id}_concat.txt");
    }
}

function isFFmpegRunning($id) {
    $runningFile = HLS_DIR . "/{$id}.running";
    if (!file_exists($runningFile)) {
        return false;
    }
    
    $outputM3u8 = HLS_DIR . "/{$id}/index.m3u8";
    if (file_exists($outputM3u8) && filesize($outputM3u8) > 0) {
        $mtime = filemtime($outputM3u8);
        if (time() - $mtime < 60) {
            return true;
        }
    }
    
    $pidFile = HLS_DIR . "/{$id}.pid";
    if (file_exists($pidFile) && isExecAvailable()) {
        $pid = trim(file_get_contents($pidFile));
        if (!empty($pid) && is_numeric($pid)) {
            exec("ps -p $pid 2>/dev/null", $output);
            if (!empty($output)) {
                return true;
            }
        }
    }
    
    return false;
}

function deleteTaskFiles($id) {
    stopFFmpeg($id, true);
    @unlink(HLS_DIR . "/{$id}.m3u8");
    @unlink(HLS_DIR . "/{$id}_concat.txt");
    @unlink(HLS_DIR . "/{$id}.running");
    @unlink(HLS_DIR . "/{$id}.pid");
    
    $taskDir = HLS_DIR . "/{$id}";
    if (is_dir($taskDir)) {
        foreach (glob("{$taskDir}/*.ts") as $f) @unlink($f);
        foreach (glob("{$taskDir}/*.m3u8") as $f) @unlink($f);
        @rmdir($taskDir);
    }
    
    $logFile = getTaskLogFile($id);
    @unlink($logFile);
}

// 自动维护任务状态
foreach ($tasks as $id => &$task) {
    if ($task['mode'] === 'mpd_pull' || $task['mode'] === 'direct') {
        $running = isFFmpegRunning($id);
        if ($task['status'] === 'running' && !$running) {
            $task['status'] = 'stopped';
            logMessage($id, "检测到进程已停止，更新状态为stopped", 'WARN');
        } elseif ($task['status'] !== 'running' && $running) {
            $task['status'] = 'running';
            logMessage($id, "检测到进程运行中，更新状态为running", 'INFO');
        }
    }
}
unset($task);
file_put_contents(TASKS_FILE, json_encode($tasks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));

$action = $_GET['action'] ?? '';

// 调试 PHP 代理
if ($action === 'debug_proxy') {
    $url = $_GET['url'] ?? '';
    if (empty($url)) {
        echo "请提供 URL";
        exit;
    }
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0');
    $content = curl_exec($ch);
    $info = curl_getinfo($ch);
    curl_close($ch);
    
    header('Content-Type: text/plain; charset=utf-8');
    echo "=== 请求信息 ===\n";
    echo "URL: {$url}\n";
    echo "HTTP Code: " . $info['http_code'] . "\n";
    echo "Content-Type: " . $info['content_type'] . "\n";
    echo "Final URL: " . $info['url'] . "\n";
    echo "\n=== 返回内容 ===\n";
    echo substr($content, 0, 3000);
    if (strlen($content) > 3000) {
        echo "\n... (内容过长，已截断)";
    }
    exit;
}

// 通用流探测
if ($action === 'probe') {
    $url = trim($_POST['url'] ?? '');
    $type = $_POST['type'] ?? 'mpd';
    $debug = [];
    
    if ($type === 'mpd') {
        $ret = parseMPD($url, $debug);
    } else {
        $ret = probeStream($url, $debug);
    }

    $videoOpt = '<option value="0">默认视频轨道</option>';
    $audioOpt = '<option value="0">默认音频轨道</option>';
    $subOpt  = '<option value="-1">不加载字幕</option>';

    if ($ret && is_array($ret)) {
        if (isset($ret['video']) && is_array($ret['video'])) {
            foreach ($ret['video'] as $k => $v) {
                $display = isset($v['id']) ? "视频轨道{$k}｜{$v['codecs']}" : "视频轨道{$k}｜" . ($v['codecs'] ?? '');
                $videoOpt .= "<option value='{$k}'>" . htmlspecialchars($display) . "</option>";
            }
        }
        if (isset($ret['audio']) && is_array($ret['audio'])) {
            foreach ($ret['audio'] as $k => $a) {
                $lang = $a['lang'] ?? '';
                $display = "音频轨道{$k}｜" . ($a['codecs'] ?? '') . ($lang ? " [{$lang}]" : '');
                $audioOpt .= "<option value='{$k}'>" . htmlspecialchars($display) . "</option>";
            }
        }
        if (isset($ret['subtitle']) && is_array($ret['subtitle'])) {
            $subOpt = '<option value="-1">不加载字幕</option>';
            foreach ($ret['subtitle'] as $k => $s) {
                $lang = $s['lang'] ?? '';
                $display = "字幕轨道{$k}｜" . ($s['codecs'] ?? '') . ($lang ? " [{$lang}]" : '');
                $subOpt .= "<option value='{$k}'>" . htmlspecialchars($display) . "</option>";
            }
        }
    }

    $segmentCount = 0;
    if ($type === 'mpd' && is_array($ret) && isset($ret['segments'])) {
        $segmentCount = count($ret['segments']);
    }

    echo json_encode([
        'video' => $videoOpt,
        'audio' => $audioOpt,
        'sub'   => $subOpt,
        'debug' => $debug ?? ['status' => '解析失败'],
        'segment_count' => $segmentCount
    ], JSON_UNESCAPED_UNICODE);
    exit;
}

// 添加任务
if ($action === 'add') {
    $id = preg_replace('/[^a-zA-Z0-9_-]/', '', trim($_POST['id'] ?? ''));
    $source = trim($_POST['source'] ?? '');
    $clearkey = trim($_POST['clearkey'] ?? '');
    $mode = $_POST['mode'] ?? 'direct';
    $vTrack = intval($_POST['video_track'] ?? 0);
    $aTrack = intval($_POST['audio_track'] ?? 0);
    $sTrack = intval($_POST['sub_track'] ?? -1);

    if (empty($id) || empty($source)) {
        header('Location:./'); exit;
    }

    $m3u8Path = '';
    $status = 'running';
    $errorMsg = '';

    if ($mode === 'mpd_pull') {
        // 检测是否是TVB流
        if (strpos($source, 'tvb.com') !== false || strpos($source, 'filegear-sg.me') !== false) {
            logMessage($id, "检测到TVB流，使用专用模式", 'INFO');
            $result = startTVBStream($id, $source, $vTrack, $aTrack, $sTrack, $clearkey);
        } else {
            $result = startMPDToFFmpeg($id, $source, $vTrack, $aTrack, $sTrack, $clearkey);
        }
        if ($result) {
            $m3u8Path = "/hls_output/{$id}/index.m3u8";
        } else {
            $errorMsg = 'MPD解析或FFmpeg启动失败，请查看日志';
            $status = 'error';
        }
    } elseif ($mode === 'direct') {
        $result = startFFmpeg($id, $source, $vTrack, $aTrack, $sTrack);
        if ($result) {
            $m3u8Path = "/hls_output/{$id}/index.m3u8";
        } else {
            $errorMsg = 'FFmpeg启动失败，请检查FFmpeg是否安装';
            $status = 'error';
        }
    } else {
        $mpdData = parseMPD($source);
        if ($mpdData && !empty($mpdData['segments'])) {
            $m3u8Content = "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:10\n#EXT-X-MEDIA-SEQUENCE:0\n";
            foreach ($mpdData['segments'] as $i => $seg) {
                $m3u8Content .= "#EXTINF:5,\n";
                $m3u8Content .= $seg . "\n";
            }
            $m3u8Content .= "#EXT-X-ENDLIST\n";
            file_put_contents(HLS_DIR . "/{$id}.m3u8", $m3u8Content);
            $m3u8Path = "/hls_output/{$id}.m3u8";
        } else {
            $errorMsg = 'MPD解析失败或无分片';
            $status = 'error';
        }
    }

    $tasks[$id] = [
        'id' => $id,
        'source' => $source,
        'clearkey' => $clearkey,
        'mode' => $mode,
        'video_track' => $vTrack,
        'audio_track' => $aTrack,
        'sub_track' => $sTrack,
        'status' => $status,
        'create_time' => time(),
        'error_msg' => $errorMsg
    ];

    file_put_contents(TASKS_FILE, json_encode($tasks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
    header('Location:./'); exit;
}

// 获取任务信息
if ($action === 'get_task') {
    $id = $_GET['id'] ?? '';
    if (isset($tasks[$id])) {
        header('Content-Type: application/json');
        echo json_encode($tasks[$id]);
    } else {
        echo json_encode(['error' => '任务不存在']);
    }
    exit;
}

// 更新任务
if ($action === 'update') {
    $id = $_POST['id'] ?? '';
    if (isset($tasks[$id]) && is_array($tasks[$id])) {
        $oldMode = $tasks[$id]['mode'];
        $newMode = $_POST['mode'] ?? $oldMode;
        
        $tasks[$id]['source'] = trim($_POST['source'] ?? '');
        $tasks[$id]['clearkey'] = trim($_POST['clearkey'] ?? '');
        $tasks[$id]['mode'] = $newMode;
        $tasks[$id]['video_track'] = intval($_POST['video_track'] ?? 0);
        $tasks[$id]['audio_track'] = intval($_POST['audio_track'] ?? 0);
        $tasks[$id]['sub_track'] = intval($_POST['sub_track'] ?? -1);
        
        // 保存配置后，如果任务状态是运行中，则用新配置重新启动
        if ($tasks[$id]['status'] === 'running') {
            stopFFmpeg($id, true);
            
            if ($newMode === 'mpd_pull') {
                if (strpos($tasks[$id]['source'], 'tvb.com') !== false || strpos($tasks[$id]['source'], 'filegear-sg.me') !== false) {
                    startTVBStream($id, $tasks[$id]['source'], 
                        $tasks[$id]['video_track'],
                        $tasks[$id]['audio_track'],
                        $tasks[$id]['sub_track'],
                        $tasks[$id]['clearkey']);
                } else {
                    startMPDToFFmpeg($id, $tasks[$id]['source'], 
                        $tasks[$id]['video_track'],
                        $tasks[$id]['audio_track'],
                        $tasks[$id]['sub_track'],
                        $tasks[$id]['clearkey']);
                }
            } elseif ($newMode === 'direct') {
                startFFmpeg($id, $tasks[$id]['source'],
                    $tasks[$id]['video_track'],
                    $tasks[$id]['audio_track'],
                    $tasks[$id]['sub_track']);
            }
        }
        
        file_put_contents(TASKS_FILE, json_encode($tasks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
    }
    header('Location:./'); exit;
}

// 启动/停止
if ($action === 'toggle') {
    $id = $_GET['id'] ?? '';
    $result = ['success' => false];
    if (isset($tasks[$id]) && is_array($tasks[$id])) {
        if ($tasks[$id]['status'] === 'running') {
            $tasks[$id]['status'] = 'stopped';
            stopFFmpeg($id, true);
            logMessage($id, "用户手动停止任务", 'STOP');
        } else {
            $tasks[$id]['status'] = 'running';
            if ($tasks[$id]['mode'] === 'direct') {
                startFFmpeg($id, $tasks[$id]['source'],
                    $tasks[$id]['video_track'] ?? 0,
                    $tasks[$id]['audio_track'] ?? 0,
                    $tasks[$id]['sub_track'] ?? -1);
            } elseif ($tasks[$id]['mode'] === 'mpd_pull') {
                if (strpos($tasks[$id]['source'], 'tvb.com') !== false || strpos($tasks[$id]['source'], 'filegear-sg.me') !== false) {
                    startTVBStream($id, $tasks[$id]['source'],
                        $tasks[$id]['video_track'] ?? 0,
                        $tasks[$id]['audio_track'] ?? 0,
                        $tasks[$id]['sub_track'] ?? -1,
                        $tasks[$id]['clearkey'] ?? '');
                } else {
                    startMPDToFFmpeg($id, $tasks[$id]['source'],
                        $tasks[$id]['video_track'] ?? 0,
                        $tasks[$id]['audio_track'] ?? 0,
                        $tasks[$id]['sub_track'] ?? -1,
                        $tasks[$id]['clearkey'] ?? '');
                }
            }
            logMessage($id, "用户手动启动任务", 'START');
        }
        file_put_contents(TASKS_FILE, json_encode($tasks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
        $result['success'] = true;
    }
    header('Content-Type: application/json');
    echo json_encode($result);
    exit;
}

// 删除
if ($action === 'del') {
    $id = $_GET['id'] ?? '';
    $result = ['success' => false];
    if (isset($tasks[$id])) {
        deleteTaskFiles($id);
        unset($tasks[$id]);
        file_put_contents(TASKS_FILE, json_encode($tasks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
        $result['success'] = true;
    }
    header('Content-Type: application/json');
    echo json_encode($result);
    exit;
}

// 批量删除
if ($action === 'batch_del') {
    $ids = json_decode(file_get_contents('php://input'), true)['ids'] ?? [];
    if (is_array($ids)) {
        foreach ($ids as $id) {
            if (isset($tasks[$id])) {
                deleteTaskFiles($id);
                unset($tasks[$id]);
            }
        }
    }
    file_put_contents(TASKS_FILE, json_encode($tasks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
    header('Content-Type: application/json');
    echo json_encode(['success' => true]);
    exit;
}

// 导出
if ($action === 'export') {
    $baseUrl = 'http://' . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['SCRIPT_NAME']), '/') . '/' . HLS_DIR . '/';
    $txt = '';
    if (is_array($tasks)) {
        foreach ($tasks as $v) {
            if (is_array($v)) {
                if ($v['mode'] === 'direct' || $v['mode'] === 'mpd_pull') {
                    $url = $baseUrl . $v['id'] . "/index.m3u8";
                } else {
                    $url = $baseUrl . $v['id'] . ".m3u8";
                }
                $txt .= $v['id'] . ' | ' . $url . "\n";
            }
        }
    }
    header('Content-Type: text/plain');
    header('Content-Disposition: attachment; filename="m3u8_list.txt"');
    echo $txt; exit;
}

// 获取任务日志
if ($action === 'get_task_log') {
    $id = $_GET['id'] ?? '';
    if (!empty($id)) {
        echo getTaskLog($id);
    } else {
        echo "任务ID不能为空";
    }
    exit;
}

// 清除任务日志
if ($action === 'clear_task_log') {
    $id = $_GET['id'] ?? '';
    $result = ['success' => false];
    if (!empty($id)) {
        $result['success'] = clearTaskLog($id);
    }
    header('Content-Type: application/json');
    echo json_encode($result);
    exit;
}

// 全局日志
if ($action === 'get_logs') {
    $globalLog = LOG_DIR . "/all.log";
    if (file_exists($globalLog)) {
        $logs = file_get_contents($globalLog);
        echo nl2br(htmlspecialchars(substr($logs, -5000)));
    } else {
        echo "暂无日志";
    }
    exit;
}

if ($action === 'clear_logs') {
    clearLogs();
    header('Content-Type: application/json');
    echo json_encode(['success' => true]);
    exit;
}

$baseUrl = 'http://' . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['SCRIPT_NAME']), '/') . '/' . HLS_DIR . '/';
$exec_available = isExecAvailable();
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>MPD / FFmpeg 转 HLS 直播系统</title>
    <style>
        *{margin:0;padding:0;box-sizing:border-box;font-family:"Microsoft YaHei",sans-serif;}
        body{background:#1e1e2f;color:#e8e8e8;padding:20px;}
        .container{max-width:1400px;margin:0 auto;}
        .title{text-align:center;font-size:28px;margin-bottom:30px;color:#fff;}
        .card{background:#2a2a41;border-radius:12px;padding:25px;margin-bottom:25px;}
        .card h3{margin-bottom:20px;color:#c1c1ff;}
        input, select{width:100%;padding:10px 14px;border-radius:6px;border:none;background:#373757;color:#fff;outline:none;margin-bottom:12px;}
        .form-row{display:flex;gap:12px;flex-wrap:wrap;align-items:center;}
        button{padding:10px 18px;border-radius:6px;border:none;cursor:pointer;font-size:14px;transition:all 0.2s;}
        button:hover{opacity:0.85;transform:translateY(-1px);}
        .btn-primary{background:#6c63ff;color:#fff;}
        .btn-danger{background:#e74c3c;color:#fff;}
        .btn-success{background:#00b894;color:#fff;}
        .btn-ffmpeg{background:#e67e22;color:#fff;}
        .btn-edit{background:#3498db;color:#fff;}
        .btn-copy{background:#6c63ff;color:#fff;padding:5px 12px;font-size:12px;}
        .btn-play{background:#e67e22;color:#fff;padding:5px 12px;font-size:12px;}
        .btn-log{background:#9b59b6;color:#fff;}
        table{width:100%;border-collapse:collapse;margin-top:10px;}
        th,td{padding:12px;text-align:center;border-bottom:1px solid #444;}
        th{background:#32324f;color:#c1c1ff;}
        .url-text{max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:#373757;padding:4px 8px;border-radius:4px;font-size:12px;}
        .toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#00b894;color:#fff;padding:12px 24px;border-radius:6px;opacity:0;transition:all 0.3s;z-index:9999;}
        .toast.show{opacity:1;top:30px;}
        .toast.error{background:#e74c3c;}
        .log-viewer{background:#1e1e2f;padding:10px;border-radius:6px;font-family:monospace;font-size:12px;max-height:300px;overflow-y:auto;margin-top:10px;}
        .badge{display:inline-block;padding:2px 6px;border-radius:4px;font-size:11px;margin-left:5px;}
        .badge-direct{background:#e67e22;}
        .badge-mpd-pull{background:#6c63ff;}
        .badge-mpd-static{background:#3498db;}
        .badge-error{background:#e74c3c;}
        .row{display:flex;gap:12px;margin-bottom:12px;}
        .row > *{flex:1;}
        .error-msg{color:#ff5555;font-size:12px;margin-top:5px;}
        .status-bar{background:#2b2b45;padding:10px 15px;border-radius:8px;margin-bottom:20px;display:flex;gap:20px;flex-wrap:wrap;}
        .status-item{display:flex;align-items:center;gap:8px;}
        .status-dot{width:10px;height:10px;border-radius:50%;display:inline-block;}
        .status-dot.green{background:#00b894;}
        .status-dot.red{background:#e74c3c;}
        .action-buttons{display:flex;gap:6px;justify-content:center;flex-wrap:wrap;}
        .mode-switch{display:flex;gap:10px;margin-bottom:15px;flex-wrap:wrap;}
        .mode-btn{padding:8px 20px;border-radius:6px;cursor:pointer;text-align:center;background:#373757;transition:all 0.2s;}
        .mode-btn.active{background:#6c63ff;}
        .mode-btn:hover{opacity:0.8;}
        .clearkey-input{display:block;}
        .clearkey-input.hide{display:none;}
        .probe-result{font-size:12px;color:#ffd26d;margin-top:5px;}
        
        .player-modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.95);display:none;justify-content:center;align-items:center;z-index:10001;}
        .player-container{background:#000;border-radius:12px;width:90%;max-width:1200px;position:relative;}
        .player-close{position:absolute;top:-40px;right:0;background:#e74c3c;border:none;color:#fff;padding:8px 16px;border-radius:6px;cursor:pointer;font-size:16px;}
        .player-video{width:100%;height:auto;max-height:80vh;border-radius:8px;}
        .player-info{padding:15px;background:#2a2a41;border-radius:0 0 12px 12px;color:#fff;}
        
        .edit-track-row{margin:15px 0;}
        .edit-track-row label{display:block;margin-bottom:5px;color:#c1c1ff;font-size:13px;}
        .edit-mode-switch{display:flex;gap:10px;margin:15px 0;}
        .edit-mode-btn{padding:8px 16px;border-radius:6px;cursor:pointer;background:#373757;text-align:center;flex:1;}
        .edit-mode-btn.active{background:#6c63ff;}
        
        .loading{display:inline-block;width:16px;height:16px;border:2px solid #fff;border-radius:50%;border-top-color:transparent;animation:spin 1s linear infinite;}
        @keyframes spin{to{transform:rotate(360deg);}}
        
        .debug-btn{margin-top:10px;background:#f39c12;}
    </style>
</head>
<body>
<div class="toast" id="toast">操作成功</div>
<div class="container">
    <h2 class="title">MPD / FFmpeg 转 HLS 直播系统</h2>
    
    <div class="status-bar">
        <div class="status-item">
            <span class="status-dot <?= $exec_available ? 'green' : 'red' ?>"></span>
            <span>系统命令: <?= $exec_available ? '可用' : '不可用' ?></span>
        </div>
        <div class="status-item">
            <span class="status-dot green"></span>
            <span>自动重连: 已启用</span>
        </div>
    </div>

    <div class="card">
        <h3>新建任务</h3>
        <div class="mode-switch">
            <div class="mode-btn active" data-mode="direct">🎬 直接推流模式</div>
            <div class="mode-btn" data-mode="mpd_pull">🔄 MPD解析后推流</div>
            <div class="mode-btn" data-mode="mpd_static">📄 MPD静态解析</div>
        </div>
        
        <form method="post" action="?action=add" id="addForm">
            <input name="id" placeholder="任务ID（英文、数字、下划线）" required>
            <input name="source" id="source" placeholder="输入URL" required>
            <input name="clearkey" id="clearkey" class="clearkey-input hide" placeholder="ClearKey秘钥（格式: key或key:iv）" value="">
            <input type="hidden" name="mode" id="mode" value="direct">
            
            <div id="trackOptions" style="display:none;">
                <div class="form-row">
                    <select id="video_track" name="video_track"><option value="0">默认视频轨道</option></select>
                    <select id="audio_track" name="audio_track"><option value="0">默认音频轨道</option></select>
                    <select id="sub_track" name="sub_track"><option value="-1">不加载字幕</option></select>
                    <button type="button" id="probeBtn" class="btn-primary">探测流信息</button>
                    <button type="button" id="debugProxyBtn" class="btn-ffmpeg debug-btn">调试PHP代理</button>
                </div>
                <div id="probeResult" class="probe-result"></div>
            </div>
            
            <div id="mpdDebug" style="background:#2b2b45;padding:12px;border-radius:6px;font-size:13px;color:#ffd26d;margin:10px 0;min-height:20px;"></div>
            <button type="submit" class="btn-primary">生成直播流</button>
        </form>
    </div>

    <div class="card">
        <h3>任务列表</h3>
        <div style="margin-bottom:15px;display:flex;gap:10px;flex-wrap:wrap;">
            <button type="button" class="btn-danger" id="batchDelBtn">批量删除</button>
            <a href="?action=export"><button type="button" class="btn-primary">导出全部链接</button></a>
            <button type="button" class="btn-ffmpeg" onclick="viewGlobalLogs()">查看全局日志</button>
            <button type="button" class="btn-ffmpeg" onclick="clearGlobalLogs()" style="background:#f39c12;">清除全局日志</button>
        </div>

        <table>
            <thead>
                <tr>
                    <th><input type="checkbox" id="checkAll"></th>
                    <th>任务ID</th>
                    <th>模式</th>
                    <th>状态</th>
                    <th>播放链接</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody id="taskList">
            <?php if (is_array($tasks)): foreach($tasks as $item):
                if (!is_array($item)) continue;
                $status = $item['status'] ?? 'running';
                $mode = $item['mode'] ?? 'direct';
                $isPullMode = ($mode === 'direct' || $mode === 'mpd_pull');
                $realRunning = $isPullMode ? isFFmpegRunning($item['id']) : ($status === 'running');
                $showStatus = $realRunning ? '运行中' : ($status === 'error' ? '错误' : '已停止');
                $statusColor = $realRunning ? '#00ff9d' : ($status === 'error' ? '#ff5555' : '#ffaa00');
                $btnText = $realRunning ? '停止' : '启动';
                $btnClass = $realRunning ? 'btn-danger' : 'btn-success';
                
                if ($mode === 'direct') {
                    $playUrl = $baseUrl . $item['id'] . "/index.m3u8";
                    $typeLabel = '🎬 直接推流';
                    $badgeClass = 'badge-direct';
                } elseif ($mode === 'mpd_pull') {
                    $playUrl = $baseUrl . $item['id'] . "/index.m3u8";
                    $typeLabel = '🔄 MPD转推';
                    $badgeClass = 'badge-mpd-pull';
                } else {
                    $playUrl = $baseUrl . $item['id'] . ".m3u8";
                    $typeLabel = '📄 静态解析';
                    $badgeClass = 'badge-mpd-static';
                }
                $hasError = !empty($item['error_msg']);
            ?>
            <tr data-id="<?=htmlspecialchars($item['id'])?>">
                <td><input type="checkbox" class="task-checkbox" value="<?=htmlspecialchars($item['id'])?>"></td>
                <td>
                    <?=htmlspecialchars($item['id'])?>
                    <span class="badge <?= $badgeClass ?>"><?= $typeLabel ?></span>
                    <?php if($hasError):?><span class="badge badge-error">错误</span><?php endif;?>
                </td>
                <td><?= $typeLabel ?></td>
                <td><span style="color:<?= $statusColor ?>"><?= $showStatus ?></span><?php if($hasError):?><div class="error-msg"><?=htmlspecialchars($item['error_msg'])?></div><?php endif;?></td>
                <td>
                    <div style="display:flex;gap:8px;align-items:center;justify-content:center;">
                        <div class="url-text"><?= htmlspecialchars($playUrl) ?></div>
                        <button class="btn-copy" onclick="copyToClipboard('<?= htmlspecialchars($playUrl) ?>')">复制</button>
                        <button class="btn-play" onclick="playStream('<?= htmlspecialchars($playUrl) ?>', '<?= htmlspecialchars($item['id']) ?>')">播放</button>
                    </div>
                </td>
                <td>
                    <div class="action-buttons">
                        <button class="<?= $btnClass ?>" style="padding:5px 10px;font-size:12px;" onclick="toggleStream('<?=htmlspecialchars($item['id'])?>')"><?= $btnText ?></button>
                        <button class="btn-edit" style="padding:5px 10px;font-size:12px;" onclick="openEditFull('<?=htmlspecialchars($item['id'])?>')">编辑</button>
                        <button class="btn-log" style="padding:5px 10px;font-size:12px;" onclick="viewTaskLog('<?=htmlspecialchars($item['id'])?>')">查看日志</button>
                        <button class="btn-danger" style="padding:5px 10px;font-size:12px;" onclick="deleteStream('<?=htmlspecialchars($item['id'])?>')">删除</button>
                    </div>
                </td>
            </tr>
            <?php endforeach; endif; ?>
            <?php if(empty($tasks) || !is_array($tasks)): ?>
            <tr><td colspan="6" style="text-align:center;color:#888;">暂无任务</td></tr>
            <?php endif; ?>
            </tbody>
        </table>
    </div>

    <div class="card" id="logCard" style="display:none;">
        <h3 id="logTitle">任务日志 <button class="btn-danger" style="padding:5px 10px;font-size:12px;margin-left:10px;" onclick="clearCurrentLog()">清空日志</button></h3>
        <div class="log-viewer" id="logViewer"></div>
    </div>
</div>

<!-- 编辑弹窗 -->
<div id="editModal" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);display:none;justify-content:center;align-items:center;z-index:10000;overflow-y:auto;">
    <div style="background:#2a2a41;padding:30px;border-radius:12px;width:600px;max-width:95%;margin:20px auto;">
        <h3 style="color:#c1c1ff;margin-bottom:20px;">编辑任务</h3>
        <form method="post" action="?action=update" id="editForm">
            <input type="hidden" id="edit_id" name="id">
            
            <label style="display:block;margin-bottom:5px;color:#c1c1ff;">推流地址</label>
            <input id="edit_source" name="source" placeholder="源地址" required>
            
            <label style="display:block;margin-bottom:5px;color:#c1c1ff;">ClearKey秘钥</label>
            <input id="edit_clearkey" name="clearkey" placeholder="ClearKey秘钥（格式: key或key:iv）">
            
            <label style="display:block;margin-bottom:5px;color:#c1c1ff;">推流模式</label>
            <div class="edit-mode-switch" id="editModeSwitch">
                <div class="edit-mode-btn" data-mode="direct">🎬 直接推流</div>
                <div class="edit-mode-btn" data-mode="mpd_pull">🔄 MPD转推</div>
                <div class="edit-mode-btn" data-mode="mpd_static">📄 静态解析</div>
            </div>
            <input type="hidden" id="edit_mode" name="mode">
            
            <div id="editTrackOptions" style="margin-top:15px;">
                <div class="edit-track-row">
                    <label>视频轨道</label>
                    <select id="edit_video_track" name="video_track" style="width:100%;">
                        <option value="0">默认视频轨道</option>
                    </select>
                </div>
                <div class="edit-track-row">
                    <label>音频轨道</label>
                    <select id="edit_audio_track" name="audio_track" style="width:100%;">
                        <option value="0">默认音频轨道</option>
                    </select>
                </div>
                <div class="edit-track-row">
                    <label>字幕轨道</label>
                    <select id="edit_sub_track" name="sub_track" style="width:100%;">
                        <option value="-1">不加载字幕</option>
                    </select>
                </div>
                <div class="edit-track-row">
                    <button type="button" id="editProbeBtn" class="btn-primary" style="width:100%;">重新探测流信息</button>
                </div>
                <div id="editProbeResult" style="font-size:12px;color:#ffd26d;margin-top:8px;"></div>
            </div>
            
            <div style="margin-top:20px;display:flex;gap:12px;">
                <button type="submit" class="btn-primary" style="flex:1;padding:12px;">保存修改</button>
                <button type="button" class="btn-danger" style="flex:1;padding:12px;" onclick="closeEdit()">取消</button>
            </div>
        </form>
    </div>
</div>

<!-- 播放器模态框 -->
<div id="playerModal" class="player-modal">
    <div class="player-container">
        <button class="player-close" onclick="closePlayer()">关闭</button>
        <video id="playerVideo" class="player-video" controls autoplay></video>
        <div class="player-info" id="playerInfo"></div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
let currentTaskId = null;
let currentProbeType = 'direct';
let editCurrentMode = 'direct';

function showToast(msg, isError) {
    var toast = $('#toast');
    toast.text(msg);
    if (isError) {
        toast.addClass('error');
    } else {
        toast.removeClass('error');
    }
    toast.addClass('show');
    setTimeout(function() {
        toast.removeClass('show');
    }, 2000);
}

function copyToClipboard(text) {
    if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(function() {
            showToast('复制成功');
        }).catch(function() {
            fallbackCopy(text);
        });
    } else {
        fallbackCopy(text);
    }
}

function fallbackCopy(text) {
    var textarea = document.createElement('textarea');
    textarea.value = text;
    textarea.style.position = 'fixed';
    textarea.style.top = '-9999px';
    document.body.appendChild(textarea);
    textarea.select();
    try {
        document.execCommand('copy');
        showToast('复制成功');
    } catch (err) {
        showToast('复制失败', true);
    }
    document.body.removeChild(textarea);
}

function playStream(url, title) {
    var video = document.getElementById('playerVideo');
    var info = document.getElementById('playerInfo');
    info.innerHTML = '正在播放: ' + title + '<br>播放地址: ' + url;
    
    if (Hls && Hls.isSupported()) {
        if (window.hlsPlayer) {
            window.hlsPlayer.destroy();
        }
        window.hlsPlayer = new Hls();
        window.hlsPlayer.loadSource(url);
        window.hlsPlayer.attachMedia(video);
        window.hlsPlayer.on(Hls.Events.MANIFEST_PARSED, function() {
            video.play();
        });
        window.hlsPlayer.on(Hls.Events.ERROR, function(event, data) {
            console.error('HLS错误:', data);
        });
    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
        video.src = url;
        video.addEventListener('loadedmetadata', function() {
            video.play();
        });
    } else {
        video.src = url;
        video.play();
    }
    
    document.getElementById('playerModal').style.display = 'flex';
}

function closePlayer() {
    var video = document.getElementById('playerVideo');
    video.pause();
    video.src = '';
    if (window.hlsPlayer) {
        window.hlsPlayer.destroy();
        window.hlsPlayer = null;
    }
    document.getElementById('playerModal').style.display = 'none';
}

function toggleStream(id) {
    $.get('?action=toggle&id=' + id, function(res) {
        if (res.success) {
            showToast('操作成功');
            setTimeout(function() { location.reload(); }, 500);
        } else {
            showToast('操作失败', true);
        }
    }, 'json').fail(function() {
        showToast('操作失败', true);
    });
}

function deleteStream(id) {
    if (!confirm('确定删除该任务吗？删除后所有分片和日志将被清除！')) return;
    $.get('?action=del&id=' + id, function(res) {
        if (res.success) {
            showToast('删除成功');
            $('tr[data-id="' + id + '"]').fadeOut(300, function() {
                $(this).remove();
            });
        } else {
            showToast('删除失败', true);
        }
    }, 'json').fail(function() {
        showToast('删除失败', true);
    });
}

function openEditFull(id) {
    $.get('?action=get_task&id=' + id, function(task) {
        if (task.error) {
            showToast(task.error, true);
            return;
        }
        
        editCurrentMode = task.mode || 'direct';
        
        $('#edit_id').val(task.id);
        $('#edit_source').val(task.source);
        $('#edit_clearkey').val(task.clearkey || '');
        $('#edit_mode').val(task.mode);
        
        $('.edit-mode-btn').removeClass('active');
        $('.edit-mode-btn[data-mode="' + task.mode + '"]').addClass('active');
        
        $('#editTrackOptions').show();
        
        $('#edit_video_track').val(task.video_track || 0);
        $('#edit_audio_track').val(task.audio_track || 0);
        $('#edit_sub_track').val(task.sub_track !== undefined ? task.sub_track : -1);
        
        performEditProbe(task.source, task.mode);
        
        $('#editModal').fadeIn();
    }, 'json');
}

function performEditProbe(url, type) {
    if (!url) {
        $('#editProbeResult').html('请先填写URL地址');
        return;
    }
    
    $('#editProbeResult').html('<span class="loading"></span> 正在探测流信息...');
    
    $.post('?action=probe', {
        url: url, 
        type: type === 'mpd_pull' || type === 'mpd_static' ? 'mpd' : 'direct'
    }, function(res){
        $('#edit_video_track').html(res.video);
        $('#edit_audio_track').html(res.audio);
        $('#edit_sub_track').html(res.sub);
        
        var videoCount = (res.video.match(/option value/g) || []).length - 1;
        var audioCount = (res.audio.match(/option value/g) || []).length - 1;
        var subCount = (res.sub.match(/option value/g) || []).length - 1;
        
        $('#editProbeResult').html('✅ 探测成功！视频轨道: ' + videoCount + '，音频轨道: ' + audioCount + '，字幕轨道: ' + subCount);
        showToast('探测成功');
    }, 'json').fail(function() {
        $('#editProbeResult').html('❌ 探测失败，请检查URL是否正确');
    });
}

$(document).on('click', '.edit-mode-btn', function() {
    $('.edit-mode-btn').removeClass('active');
    $(this).addClass('active');
    var mode = $(this).data('mode');
    $('#edit_mode').val(mode);
    editCurrentMode = mode;
    
    var url = $('#edit_source').val();
    if (url) {
        performEditProbe(url, mode);
    }
});

$(document).on('click', '#editProbeBtn', function() {
    var url = $('#edit_source').val();
    var mode = $('#edit_mode').val();
    performEditProbe(url, mode);
});

$(document).on('change', '#edit_source', function() {
    var url = $(this).val();
    if (url) {
        performEditProbe(url, $('#edit_mode').val());
    }
});

function closeEdit() {
    $('#editModal').fadeOut();
}

function viewTaskLog(id) {
    currentTaskId = id;
    $('#logTitle').html('任务日志: ' + id + ' <button class="btn-danger" style="padding:5px 10px;font-size:12px;margin-left:10px;" onclick="clearTaskLog()">清空日志</button>');
    $('#logCard').show();
    $('#logViewer').html('加载中...');
    $.get('?action=get_task_log&id=' + id, function(data){
        $('#logViewer').html(data);
    });
}

function viewGlobalLogs() {
    currentTaskId = null;
    $('#logTitle').html('全局日志 <button class="btn-danger" style="padding:5px 10px;font-size:12px;margin-left:10px;" onclick="clearGlobalLogs()">清空日志</button>');
    $('#logCard').show();
    $('#logViewer').html('加载中...');
    $.get('?action=get_logs', function(data){
        $('#logViewer').html(data);
    });
}

function clearTaskLog() {
    if (currentTaskId) {
        $.get('?action=clear_task_log&id=' + currentTaskId, function(res) {
            if (res.success) {
                showToast('任务日志已清除');
                viewTaskLog(currentTaskId);
            }
        }, 'json');
    }
}

function clearCurrentLog() {
    if (currentTaskId) {
        clearTaskLog();
    } else {
        clearGlobalLogs();
    }
}

function clearGlobalLogs() {
    $.get('?action=clear_logs', function(res) {
        if (res.success) {
            showToast('全局日志已清除');
            viewGlobalLogs();
        }
    }, 'json');
}

$('.mode-btn').click(function(){
    $('.mode-btn').removeClass('active');
    $(this).addClass('active');
    var mode = $(this).data('mode');
    $('#mode').val(mode);
    currentProbeType = mode === 'mpd_pull' || mode === 'mpd_static' ? 'mpd' : 'direct';
    
    if (mode === 'mpd_pull' || mode === 'mpd_static') {
        $('#trackOptions').show();
        $('#clearkey').removeClass('hide');
        $('#mpdDebug').html('💡 MPD模式：请输入MPD地址，点击探测获取轨道信息<br>💡 自动重连：断线后自动重新连接');
        $('#source').attr('placeholder', 'MPD地址，如: https://example.com/manifest.mpd');
    } else {
        $('#trackOptions').show();
        $('#clearkey').addClass('hide');
        $('#clearkey').val('');
        $('#mpdDebug').html('💡 直接推流模式：支持普通视频文件、RTMP、HLS等<br>💡 自动重连：断线后自动重新连接');
        $('#source').attr('placeholder', '输入URL（视频文件/RTMP/HLS地址）');
    }
    
    $('#video_track').html('<option value="0">默认视频轨道</option>');
    $('#audio_track').html('<option value="0">默认音频轨道</option>');
    $('#sub_track').html('<option value="-1">不加载字幕</option>');
    $('#probeResult').html('');
});

$('#probeBtn').click(function(){
    var url = $('#source').val().trim();
    if(!url){showToast('请先填写URL地址', true);return;}
    $('#mpdDebug').html('正在探测流信息...');
    $('#probeResult').html('');
    
    $.post('?action=probe', {
        url: url, 
        type: currentProbeType
    }, function(res){
        $('#video_track').html(res.video);
        $('#audio_track').html(res.audio);
        $('#sub_track').html(res.sub);
        var d = res.debug;
        var segInfo = res.segment_count ? '，共 ' + res.segment_count + ' 个分片' : '';
        $('#mpdDebug').html('状态：' + (d.status || '未知') + '<br>HTTP码：' + (d.http_code || 0) + ' | 内容大小：' + (d.content_len || 0) + ' 字节' + segInfo);
        
        var videoCount = (res.video.match(/option value/g) || []).length - 1;
        var audioCount = (res.audio.match(/option value/g) || []).length - 1;
        var subCount = (res.sub.match(/option value/g) || []).length - 1;
        
        $('#probeResult').html('✅ 探测成功！视频轨道: ' + videoCount + '，音频轨道: ' + audioCount + '，字幕轨道: ' + subCount);
        showToast('探测成功，共 ' + videoCount + ' 个视频轨道');
    }, 'json').fail(function() {
        $('#mpdDebug').html('探测失败，请检查URL是否正确');
        $('#probeResult').html('❌ 探测失败，请检查URL是否正确');
    });
});

$('#debugProxyBtn').click(function(){
    var url = $('#source').val().trim();
    if(!url){
        showToast('请先填写URL地址', true);
        return;
    }
    window.open('?action=debug_proxy&url=' + encodeURIComponent(url), '_blank');
});

$('#checkAll').click(function(){
    $('.task-checkbox').prop('checked', this.checked);
});

$('#batchDelBtn').click(function(){
    var ids = [];
    $('.task-checkbox:checked').each(function() {
        ids.push($(this).val());
    });
    if (ids.length === 0) {
        showToast('请先选择要删除的任务', true);
        return;
    }
    if (confirm('确定删除选中的 ' + ids.length + ' 个任务吗？\n将同时删除所有ts分片文件和日志')) {
        $.ajax({
            url: '?action=batch_del',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({ids: ids}),
            dataType: 'json',
            success: function(res) {
                if (res.success) {
                    showToast('批量删除成功');
                    location.reload();
                } else {
                    showToast('批量删除失败', true);
                }
            },
            error: function() {
                showToast('批量删除失败', true);
            }
        });
    }
});

$('#addForm').on('submit', function(e) {
    e.preventDefault();
    $.post('?action=add', $(this).serialize(), function() {
        showToast('任务创建成功');
        setTimeout(function() { location.reload(); }, 500);
    }).fail(function() {
        showToast('创建失败', true);
    });
});

$('#editForm').on('submit', function(e) {
    e.preventDefault();
    $.post('?action=update', $(this).serialize(), function() {
        showToast('保存成功');
        closeEdit();
        setTimeout(function() { location.reload(); }, 500);
    }).fail(function() {
        showToast('保存失败', true);
    });
});

$(document).ready(function() {
    $('.mode-btn[data-mode="direct"]').click();
});
</script>
</body>
</html>