Files
djk/internal/webui/index.html
T

308 lines
42 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#f7f7f2">
<title>短剧库</title>
<script src="/assets/theme.js"></script>
<link rel="stylesheet" href="/assets/app.css">
<link rel="stylesheet" href="/assets/library.css">
<link rel="stylesheet" href="/assets/downloads.css">
<link rel="stylesheet" href="/assets/player.css">
<link rel="stylesheet" href="/assets/library-tools.css">
<link rel="stylesheet" href="/assets/history.css">
</head>
<body>
<a class="skip-link" href="#workspaceMain">跳到主要内容</a>
<header class="app-header">
<a class="brand" href="#library" aria-label="短剧库首页"><span class="brand-mark" aria-hidden="true"><span data-icon="brand"></span></span><span class="brand-name">短剧库</span></a>
<nav class="desktop-nav" aria-label="主导航">
<button class="nav-button" data-page="library" aria-current="page"><span data-icon="library"></span>剧库</button>
<button class="nav-button" data-page="following"><span data-icon="bookmark"></span>追剧<span class="nav-count" data-follow-count hidden></span></button>
<button class="nav-button" data-page="downloads"><span data-icon="download"></span>下载<span class="nav-count" data-download-count hidden></span></button>
</nav>
<div class="header-actions">
<button id="refreshBtn" class="secondary"><span class="button-content"><span data-icon="refresh"></span><span id="refreshBtnLabel">更新剧库</span></span></button>
<button id="openAccountBtn" class="quiet" aria-label="登录或注册账号" aria-haspopup="dialog" aria-controls="accountPanel"><span data-icon="user"></span><span id="accountLabel" class="account-label">登录</span></button>
<details id="headerMenu" class="action-menu header-menu"><summary id="moreButton" aria-label="更多" aria-expanded="false" aria-controls="morePanel"><span data-icon="more"></span></summary><div id="morePanel" class="action-popover menu-list"><button id="menuUsersBtn" data-page="users" data-admin-only hidden><span data-icon="user"></span>用户管理<span data-icon="right"></span></button><button id="openSettingsBtn"><span data-icon="settings"></span>代理 / 设置<span data-icon="right"></span></button><button id="openEmbyBtn" data-admin-only hidden aria-haspopup="dialog" aria-controls="embyPanel"><span data-icon="library"></span>Emby 同步<span data-icon="right"></span></button><button id="menuHistoryBtn"><span data-icon="clock"></span>历史记录<span data-icon="right"></span></button><button id="sourceStatusBtn"><span data-icon="activity"></span>站源状态<span data-icon="right"></span></button><button id="aboutBtn"><span data-icon="help"></span>使用提示<span data-icon="right"></span></button><span id="libraryMenuUpdatedAt" class="small"></span></div></details>
</div>
</header>
<div id="viewerNotice" class="viewer-notice" role="status" hidden></div>
<main class="workspace" id="workspaceMain" tabindex="-1">
<section class="page-view" id="libraryPage" data-view="library" aria-label="剧库">
<div class="library-toolbar">
<div class="library-search" id="librarySearch">
<div class="search-field"><span data-icon="search"></span><input id="searchInput" type="search" maxlength="80" autocomplete="off" placeholder="搜索剧名、简介或标签" aria-label="搜索剧库"><button id="clearSearchBtn" class="quiet icon-button" aria-label="清空搜索" hidden><span data-icon="close"></span></button></div>
<button id="onlineSearchBtn" class="subtle">联网搜索</button>
</div>
<div id="recommendToolbar" class="recommend-toolbar" hidden><label class="catalog-select"><select id="recommendGenre" aria-label="红果推荐分类"><option value="short_play">真人剧</option><option value="comic_series">漫剧</option><option value="ai_series">AI 剧</option></select><span data-icon="chevron"></span></label><span class="small">分类推荐 · 无需登录</span><button id="recommendRefreshBtn" class="quiet button-content" title="从当前分类重新获取推荐,结果可能相同"><span data-icon="refresh"></span>重新获取</button></div>
<div class="catalog-controls" id="libraryFilters" role="group" aria-label="筛选与排序">
<label class="catalog-select"><select id="sourceSelect" aria-label="站源筛选"><option value="">全部站源</option></select><span data-icon="chevron"></span></label>
<label class="catalog-select"><select id="channelSelect" aria-label="分类筛选"><option value="">全部分类</option></select><span data-icon="chevron"></span></label>
<label class="catalog-select"><select id="sortSelect" aria-label="剧库排序"><option value="default">默认排序</option><option value="newest">最新上线</option><option value="oldest">最早上线</option><option value="heat">热度最高</option><option value="views">播放最多</option><option value="title">名称排序</option></select><span data-icon="chevron"></span></label>
<button id="vipFilterBtn" class="quiet catalog-action vip-filter" data-vip-filter aria-pressed="false" hidden>隐藏 VIP</button>
<button id="openRankingsBtn" class="quiet catalog-action" aria-label="站点榜单" title="站点榜单" aria-haspopup="dialog" aria-controls="rankingPanel"><span class="button-content"><span data-icon="chart"></span><span class="control-label">榜单</span></span></button><button id="batchSelectBtn" data-download-only class="quiet catalog-action" aria-label="批量选择" title="批量选择" aria-pressed="false"><span class="button-content"><span data-icon="select"></span><span id="batchSelectLabel" class="control-label">批量选择</span></span></button>
</div>
</div>
<div class="recent-searches" id="recentSearches" hidden aria-label="最近搜索"></div>
<div id="libraryFeedback" class="library-feedback" hidden><span id="libraryStatus" class="small" role="status" aria-live="polite"></span><button id="retryLibraryBtn" class="quiet" hidden>重试读取</button></div>
<div class="catalog-layout" id="catalogLayout">
<div class="catalog-content">
<div class="section-heading catalog-heading"><div class="library-view-switch" role="group" aria-label="浏览内容"><button id="libraryModeBtn" class="quiet" aria-pressed="true">剧库</button><button id="recommendModeBtn" class="quiet" aria-pressed="false">红果推荐</button><span id="dramaCount" class="small" role="status"></span></div><section id="continueSection" class="continue-section" aria-labelledby="continueTitle" hidden>
<div class="section-heading"><h2 id="continueTitle">继续观看</h2><button class="quiet" data-go="following" aria-label="查看全部在看的剧集"><span data-icon="right"></span></button></div>
<div id="continueList" class="continue-list"></div>
</section><div class="row"><span id="libraryUpdatedAt" class="small library-updated"></span><button id="resetFiltersBtn" class="quiet" hidden>重置</button><button id="activityToggle" data-download-only class="quiet activity-toggle" aria-pressed="false"><span class="button-content"><span data-icon="sidebar"></span><span id="activityToggleLabel">任务侧栏</span></span></button></div></div>
<div id="libraryScroll"><div id="cards" class="cards" aria-label="剧集列表" aria-busy="true"></div></div>
<div id="recommendFooter" class="recommend-footer" hidden><span id="recommendStatus" class="small" role="status" aria-live="polite"></span><button id="recommendMoreBtn" class="secondary">继续推荐</button></div>
<div id="libraryBatchBar" data-download-only class="batch-bar" hidden><span id="dramaSelection" class="small"></span><span class="spacer"></span><div class="batch-range" role="group" aria-label="选择范围"><button id="selectVisibleBtn" class="quiet" title="全选筛选结果">全选</button><button id="invertVisibleBtn" class="quiet" title="反选筛选结果">反选</button><button id="deselectDramasBtn" class="quiet">取消勾选</button></div><label class="download-quality" data-download-only>画质<select data-download-quality aria-label="批量下载清晰度"></select></label><button id="enqueueBtn">加入下载</button><button id="finishBatchBtn" class="secondary">完成</button></div>
</div>
<aside id="activityPanel" data-download-only class="activity-panel" aria-label="下载概览" hidden><div class="section-heading"><h2>下载概览</h2><button id="closeActivityBtn" class="quiet icon-button" aria-label="收起下载概览"><span data-icon="close"></span></button></div><div id="activityTasks"></div><button class="quiet" data-go="downloads">打开下载管理 ›</button></aside>
</div>
</section>
<section class="page-view" id="followingPage" data-view="following" aria-label="我的追剧" hidden>
<div class="page-heading"><div><h1>我的追剧</h1><p>留住想看的,接着上次看。</p></div><button id="openHistoryBtn" class="secondary button-content" aria-label="完整观看记录" aria-haspopup="dialog" aria-controls="historyPanel"><span data-icon="clock"></span>历史记录</button></div>
<div class="following-toolbar"><div class="segments" role="group" aria-label="追剧分组"><button data-follow-tab="watching" aria-pressed="true">在看 <span id="watchingCount">0</span></button><button data-follow-tab="saved" aria-pressed="false">想看 <span id="savedCount">0</span></button><button data-follow-tab="completed" aria-pressed="false">已看 <span id="completedCount">0</span></button></div><input id="followingSearch" type="search" maxlength="80" aria-label="搜索追剧清单" placeholder="搜索我的追剧"></div>
<div id="followingError" class="error notice" role="status"></div><button id="retryFollowingBtn" class="secondary" hidden>重新读取追剧清单</button>
<div id="followingList" class="following-list" aria-live="polite"></div>
<p class="page-footnote">新集提示来自已更新的剧库资料。完整播放记录仍保留在“历史记录”。</p>
</section>
<section class="page-view" id="downloadsPage" data-view="downloads" aria-label="下载管理" hidden>
<div class="page-heading"><div><h1>下载管理 <span id="taskCount" class="small"></span></h1><p>文件保存在运行短剧库的电脑。</p></div><button id="downloadLocationBtn" class="quiet button-content" aria-label="更改下载保存位置"><span data-icon="settings"></span>更改目录</button></div>
<div class="download-destination"><span data-icon="folder"></span><span class="small">保存至</span><span id="downloadLocation" class="download-path">正在读取保存目录…</span></div>
<div class="download-summary"><div class="summary-tile"><strong id="downloadActiveCount">0</strong><span>正在进行</span></div><div class="summary-tile"><strong id="downloadCompleteCount">0</strong><span>已完成分集</span></div><div class="summary-tile"><strong id="downloadWrittenBytes">0 B</strong><span>任务累计写入</span></div></div>
<div class="download-filters"><input id="taskSearch" type="search" placeholder="搜索下载合集" aria-label="搜索下载合集"><select id="taskStatus" aria-label="下载状态"><option value="">全部状态</option><option value="completed">已完成</option><option value="failed">失败</option><option value="unfinished">未完成</option><option value="running">进行中</option><option value="paused">已暂停</option><option value="canceled">已取消</option></select><select id="releaseStatus" aria-label="剧集完结状态"><option value="">全部完结状态</option><option value="finished">完结</option><option value="ongoing">未完结</option><option value="unknown">状态未知</option></select></div>
<div class="download-selection toolbar"><label class="download-quality" data-download-only>新任务画质<select id="downloadQuality" data-download-quality aria-label="新任务下载清晰度"></select></label><button id="downloadBatchBtn" class="secondary" aria-pressed="false">批量管理</button><div id="downloadBatchControls" class="toolbar" hidden><button id="selectGroupsBtn" class="secondary">全选</button><button id="invertGroupsBtn" class="secondary">反选</button><button id="deselectGroupsBtn" class="secondary">取消勾选</button><details id="batchMenu" class="action-menu"><summary>批量操作</summary><div class="action-popover"><button id="pauseSelectedBtn" class="secondary">暂停所选</button><button id="resumeSelectedBtn" class="secondary">继续所选</button><button id="cancelSelectedBtn" class="secondary">取消所选下载</button><button id="retrySelectedBtn" class="secondary">重试失败 / 取消</button><button id="updateSelectedBtn" class="secondary">更新所选合集</button><button id="mergeSelectedBtn">合并所选合集</button><label><input id="deleteEpisodesAfterMerge" type="checkbox"> 合并成功后删除分集文件</label><button id="clearTasksBtn" class="danger">清理任务记录</button></div></details><span id="taskSelection" class="small"></span></div></div>
<div id="taskError" class="error notice" role="status"></div><div id="ffmpegNotice" class="small notice" role="status" aria-live="polite" hidden></div>
<div id="taskScroll"><div id="groups" class="groups"></div></div>
<p class="page-footnote">清理任务记录会保留视频。暂停保留已完成分集,未完成的分集继续时重新下载。</p>
</section>
<section class="page-view users-page" id="usersPage" data-view="users" aria-label="用户管理" hidden>
<div class="page-heading"><div><h1>用户管理</h1><p>管理访问方式、账号和各账号的使用权限。</p></div><button id="adminRefreshBtn" class="secondary button-content"><span data-icon="refresh"></span>刷新</button></div>
<div id="adminPanel">
<div class="users-settings-grid">
<section class="admin-card" aria-labelledby="adminPolicyTitle"><h2 id="adminPolicyTitle">访问设置</h2>
<form id="adminPolicyForm" class="account-form">
<label class="account-check"><input id="adminRequireLogin" type="checkbox"><span>必须登录才能使用剧库<small>关闭时允许匿名观看,记录仍各自独立。</small></span></label>
<label class="account-check"><input id="adminAllowRegistration" type="checkbox" checked><span>允许用户自行注册<small>关闭时,仅管理员可以创建账号。</small></span></label>
<button id="adminSavePolicyBtn" class="secondary" type="submit" disabled>保存访问设置</button>
</form>
<p class="small admin-access-hint">匿名访客和自行注册的账号默认可用全部站源及下载功能。需要按账号控制权限时,开启必须登录并关闭自行注册。</p>
</section>
<section class="admin-card" aria-labelledby="adminCreateTitle"><h2 id="adminCreateTitle">创建用户</h2>
<form id="adminCreateUserForm" class="account-form">
<label>用户名<input id="adminNewUsername" type="text" minlength="2" maxlength="32" autocomplete="off" autocapitalize="none" required></label>
<label>初始密码<input id="adminNewUserPassword" type="password" minlength="10" maxlength="128" autocomplete="new-password" required placeholder="10–128 个字符"></label>
<fieldset class="account-source-fieldset"><legend>可用站源</legend><div id="adminNewUserSources" class="account-source-choices"></div></fieldset>
<label class="account-check"><input id="adminNewOnlineOnly" type="checkbox"><span>仅在线观看<small>隐藏下载入口,禁止下载、任务管理和 Emby 导出。</small></span></label>
<button id="adminCreateUserBtn" class="secondary" type="submit">创建用户</button>
</form>
</section>
</div>
<p id="adminStatus" class="small account-status" role="status" aria-live="polite"></p>
<section class="admin-card admin-users-card" aria-labelledby="adminAccountsTitle"><div class="admin-list-heading"><h2 id="adminAccountsTitle">已有用户 <span id="adminAccountCount" class="small"></span></h2><input id="adminAccountSearch" type="search" maxlength="32" placeholder="搜索用户名" aria-label="搜索用户"></div><ul id="adminAccountList" class="admin-account-list" aria-label="已注册账号"></ul><p id="adminAccountEmpty" class="small" hidden>没有匹配的账号。</p></section>
</div>
</section>
</main>
<nav class="mobile-nav" aria-label="手机主导航">
<button class="nav-button" data-page="library" aria-current="page"><span data-icon="library"></span><span>剧库</span></button>
<button class="nav-button" data-page="following"><span data-icon="bookmark"></span><span>追剧</span><span class="nav-count" data-follow-count hidden></span></button>
<button class="nav-button" data-page="downloads"><span data-icon="download"></span><span>下载</span><span class="nav-count" data-download-count hidden></span></button>
</nav>
<dialog id="detailPanel" class="detail-dialog" aria-labelledby="detailTitle"><div class="dialog-heading"><h2>剧集详情</h2><button class="quiet icon-button" data-close aria-label="关闭详情"><span data-icon="close"></span></button></div><div class="dialog-body" id="detailContent"></div></dialog>
<dialog id="infoPanel" aria-labelledby="infoTitle"><div class="dialog-heading"><h2 id="infoTitle">信息</h2><button class="quiet icon-button" data-close aria-label="关闭信息"><span data-icon="close"></span></button></div><div class="dialog-body" id="infoContent"></div></dialog>
<dialog id="rankingPanel" class="ranking-dialog" aria-labelledby="rankingTitle">
<div class="ranking-header">
<div class="heading"><h2 id="rankingTitle">站点榜单</h2><div class="ranking-heading-actions"><button id="refreshRankingBtn" class="secondary">刷新</button><button id="closeRankingsBtn" class="secondary" aria-label="关闭榜单">关闭</button></div></div>
<div class="ranking-controls"><select id="rankingSource" aria-label="榜单站源"></select><button id="rankingVipFilterBtn" class="quiet vip-filter" data-vip-filter aria-pressed="false" hidden>隐藏 VIP</button><label class="download-quality" data-download-only>下载画质<select data-download-quality aria-label="榜单下载清晰度"></select></label><div id="rankingTabs" class="ranking-tabs" role="tablist" aria-label="榜单类型"></div></div>
<p id="rankingSummary" class="ranking-summary" aria-live="polite">按站点名次展示,独立于剧库筛选。</p>
</div>
<div id="rankingContent" class="ranking-content" role="tabpanel" tabindex="0" aria-label="榜单列表" aria-busy="false"><ol id="rankingList" class="ranking-list"></ol></div>
<div class="ranking-footer">
<div class="ranking-footer-row"><span id="rankingFetchedAt" class="small" role="status" aria-live="polite"></span><button id="rankingMoreBtn" class="secondary" hidden>加载更多</button></div>
<p id="rankingError" class="error ranking-error" role="alert"></p><p id="rankingActionStatus" class="ranking-action-status" role="status" aria-live="polite"></p>
</div>
</dialog>
<dialog id="historyPanel" class="history-dialog" aria-labelledby="historyTitle">
<div class="history-header">
<div class="heading"><h2 id="historyTitle">观看记录 <span id="historyCount" class="small"></span></h2><div class="history-heading-actions"><button id="clearHistoryBtn" class="secondary" disabled>清空</button><button id="closeHistoryBtn" class="secondary" aria-label="关闭观看记录">关闭</button></div></div>
<input id="historySearch" type="search" maxlength="80" placeholder="搜索本浏览器的观看记录" aria-label="搜索本浏览器的观看记录">
<div id="historyError" class="error" role="status" aria-live="polite"></div>
</div>
<div id="historyContent" class="history-content" tabindex="0" aria-label="观看记录列表" aria-busy="false"><ol id="historyList" class="history-list"></ol></div>
<div class="history-footer"><span id="historySummary" class="small"></span><button id="retryHistoryBtn" class="secondary" hidden>重试</button></div>
</dialog>
<dialog id="settingsPanel" class="settings-dialog" aria-labelledby="settingsTitle">
<div class="heading"><h2 id="settingsTitle">剧库设置</h2><button id="closeSettingsBtn" class="secondary" aria-label="关闭设置">关闭</button></div>
<div class="settings-body"><div class="appearance-row"><div><h3>外观</h3><p class="small">选择适合当前环境的明暗模式</p></div><select id="themeSelect" aria-label="外观"><option value="system">跟随系统</option><option value="light">浅色</option><option value="dark">深色</option></select></div>
<p id="serverSettingsNotice" class="small">登录管理员账号后可调整代理、保存目录与服务并发。</p><div id="serverSettings" hidden><div class="directory-input"><label class="small">下载目录<input id="downloadDirectory" type="text" placeholder="./短剧下载" autocomplete="off"></label><button id="browseDirectoryBtn" class="secondary">选择文件夹</button></div>
<p class="small" id="downloadDirectoryHint">点击按钮打开本机系统文件夹选择框,也可输入相对/绝对路径。重启后新合集使用新目录,原任务和文件保留。</p>
<div class="settings-grid">
<label>下载并发<input id="downloadConcurrency" type="number" min="1" max="32" value="2"></label>
<label>剧库请求并发<input id="requestConcurrency" type="number" min="1" max="16" value="2"></label>
<label>同站间隔(毫秒)<input id="requestInterval" type="number" min="100" max="60000" step="100" value="500"></label>
<label>代理模式<select id="proxyMode"><option value="auto">自动(环境变量 / 系统代理)</option><option value="direct">直连(不使用代理)</option><option value="manual">自定义代理</option></select></label>
<label class="wide">代理地址<input id="proxyURL" type="url" placeholder="http://127.0.0.1:7897 或 socks5://127.0.0.1:1080" autocomplete="off"></label>
<label>用户名(可选)<input id="proxyUsername" type="text" autocomplete="off"></label>
<label>密码(可选)<input id="proxyPassword" type="password" autocomplete="new-password"></label>
</div>
<div class="toolbar"><button id="saveSettingsBtn">保存并应用</button><button id="checkNetworkBtn" class="secondary">检测当前连接</button><span id="settingsStatus" class="small" aria-live="polite"></span></div>
<details class="playback-settings"><summary>播放与资源限制</summary>
<label class="playback-option"><input id="originalPlaybackEnabled" type="checkbox"><span>优先原始 MP4 / HLS 播放</span></label>
<p class="small">默认关闭,使用原有兼容播放链路;开启后兼容内容免转码,需要处理时优先保留视频。</p>
<div class="settings-grid">
<label>播放会话上限<input id="playback-maxSessions" type="number" min="1" max="64" value="4"></label>
<label>上游媒体连接上限<input id="playback-maxMediaRequests" type="number" min="1" max="64" value="8"></label>
<label>解密 / 换封装并发<input id="playback-maxRemuxJobs" type="number" min="0" max="8" value="2"></label>
<label>音频转码并发<input id="playback-maxAudioTranscodes" type="number" min="0" max="8" value="1"></label>
<label>视频转码并发<input id="playback-maxVideoTranscodes" type="number" min="0" max="8" value="1"></label>
<label>下一集预备并发<input id="playback-maxPrefetchJobs" type="number" min="0" max="4" value="1"></label>
<label>封装缓存(MB)<input id="playback-cacheMB" type="number" min="64" max="8192" value="512"></label>
</div>
<p class="small">Emby 探测或取流结束即释放会话名额。处理并发设为 0 可禁用该类任务;原始播放不占转码名额。</p>
<p>允许尝试客户端直连的站源</p><div id="playbackDirectSources" class="playback-direct-sources"></div>
<p id="playbackDirectHint" class="small">先开启“优先原始 MP4 / HLS 播放”,再选择直连站源。满足源站鉴权、编码、HTTPS 和跨域条件才会直连,失败退回中转。</p>
<div class="toolbar"><button id="savePlaybackSettings" class="secondary">保存播放设置</button><span id="playbackSettingsStatus" class="small" role="status"></span></div>
</details>
<div class="small">保存后自动持久化。降低并发不终止正在下载的任务;代理变更用于后续连接,失败任务可重试。同一站源的剧库请求顺序执行,下载与剧库请求使用独立并发。</div>
</div>
</div><section class="viewer-settings" aria-labelledby="viewerSettingsTitle">
<h3 id="viewerSettingsTitle">个人记录 <span class="small" id="viewerIdentityLabel">当前浏览器</span></h3>
<p class="small" id="viewerRecordsHint">登录后可跨设备接着看,观看记录和追剧清单各自独立。</p><button class="quiet" data-account-open>打开我的账号</button>
<div id="legacyImportSection" hidden>
<div class="viewer-import-row"><label class="small">旧版记录恢复码<input id="legacyImportCode" type="password" maxlength="64" autocomplete="off" placeholder="粘贴一次性恢复码"></label><button id="legacyImportBtn" class="secondary">接回旧记录</button></div>
<p class="small">恢复码在服务所在电脑的 data/viewer-legacy-code 文件中。旧版记录接入当前登录账号;未登录时接回当前浏览器。</p>
</div>
<p id="viewerImportStatus" class="small" role="status" aria-live="polite"></p>
</section>
<div class="statusbar" id="configText"></div>
<div class="small notice" id="ffmpegSettingsStatus" role="status"></div>
</dialog>
<dialog id="embyPanel" class="emby-dialog" aria-labelledby="embyTitle">
<div class="dialog-heading"><h2 id="embyTitle">Emby 自动同步</h2><button class="quiet icon-button" data-close aria-label="关闭 Emby 同步"><span data-icon="close"></span></button></div>
<div class="dialog-body">
<p class="small">自动写入剧集和分集文件,后续补入新集数,无需先下载视频。请将输出目录加入 Emby 的电视剧媒体库。</p>
<form id="embyForm" class="emby-form">
<label class="account-check"><input id="embyEnabled" type="checkbox"><span>启用自动同步</span></label>
<div class="settings-grid">
<label class="wide">媒体库输出目录<input id="embyOutputDir" type="text" maxlength="2048" required placeholder="程序所在服务器的文件夹"></label>
<label class="wide">Emby 能访问的剧库地址<input id="embyBaseURL" type="url" maxlength="2048" placeholder="http://剧库服务器:8998"></label>
<label>检查间隔(分钟)<input id="embyInterval" type="number" min="5" max="1440" value="60" required></label>
</div>
<fieldset class="account-source-fieldset"><legend>自动加入的范围</legend><label class="account-check"><input id="embyFollowing" type="checkbox" checked><span>我的追剧<small>当前管理员的“在看”和“想看”,其他用户的记录独立保留。</small></span></label><label class="account-check"><input id="embyDownloads" type="checkbox" checked><span>下载完成的合集<small>已下载分集优先读取本地文件,其余分集可在线播放。</small></span></label></fieldset>
<p id="embyOwner" class="small"></p>
<details class="emby-refresh-settings"><summary>扫描与海报同步(可选)</summary><div class="settings-grid"><label class="wide">Emby 服务器地址<input id="embyServerURL" type="url" maxlength="2048" placeholder="http://emby:8096" autocomplete="off"></label><label class="wide">Emby API Key<input id="embyAPIKey" type="password" maxlength="2048" autocomplete="new-password"></label></div><p class="small">填写后自动通知扫描,并由 Emby 获取和缓存海报,导出目录无需保存图片。已有海报会保留;本程序同步的海报随资料更新。未填写时只生成媒体库文件,使用 Emby 的实时监控或定时扫描。更换 Emby 服务器后需重新填写 API Key。</p></details>
<div class="toolbar"><button id="embySaveBtn" type="submit">保存并应用</button><button id="embyRunBtn" class="secondary" type="button" disabled>立即同步</button></div>
</form>
<p id="embyFeedback" class="small" role="status" aria-live="polite"></p>
<div class="emby-status"><strong id="embySummary">尚未同步</strong><p id="embyPosterSummary" class="small"></p><p id="embySchedule" class="small"></p><p id="embyResult" class="small" role="status"></p></div>
<p id="embyEmpty" class="small">启用后会自动加入所选范围内的剧集。</p><ul id="embyEntries" class="emby-entries" aria-label="Emby 同步记录"></ul>
<p class="small">移出所有同步范围或关闭同步会停止后续更新,已导入的文件保留。手动导出 ZIP 的入口仍在剧集详情中。</p>
</div>
</dialog>
<dialog id="accountPanel" class="account-dialog" aria-labelledby="accountTitle">
<div class="dialog-heading"><h2 id="accountTitle">我的账号</h2><button class="quiet icon-button" data-close aria-label="关闭账号"><span data-icon="close"></span></button></div>
<div class="dialog-body">
<div id="accountGuest">
<p class="account-intro">登录后,换设备也能接着看。</p>
<div class="account-tabs segments" role="group" aria-label="账号操作"><button data-account-mode="login" aria-pressed="true">登录</button><button data-account-mode="register" aria-pressed="false">注册</button></div>
<p id="accountRegistrationDisabled" class="small" hidden>注册已关闭,请联系管理员创建账号。</p>
<form id="accountForm" class="account-form">
<label>用户名<input id="accountUsername" name="username" type="text" minlength="2" maxlength="32" autocomplete="username" autocapitalize="none" spellcheck="false" required placeholder="中文、字母、数字均可"></label>
<label>密码<input id="accountPassword" name="password" type="password" minlength="10" maxlength="128" autocomplete="current-password" required placeholder="10–128 个字符"></label>
<label id="accountConfirmRow" hidden>确认密码<input id="accountPasswordConfirm" type="password" minlength="10" maxlength="128" autocomplete="new-password"></label>
<label id="accountImportOnRegisterRow" class="account-check" hidden><input id="accountImportOnRegister" type="checkbox" checked><span>把此浏览器原有记录接入新账号</span></label>
<button id="accountSubmitBtn" type="submit">登录</button>
</form>
<p class="small account-note">这是当前剧库服务的账号,观看进度和追剧清单各自独立。</p>
</div>
<div id="accountMember" hidden>
<div class="account-member-heading"><div><span class="small">已登录</span><strong id="accountName"></strong></div><button id="accountLogoutBtn" class="secondary">退出登录</button></div>
<p class="small">在其他设备打开同一剧库,登录此账号即可继续观看。</p>
<div id="accountGuestImportSection" class="account-import" hidden><p class="small">此浏览器还有未接入账号的旧记录。接回时保留账号已有记录。</p><button id="accountImportGuestBtn" class="secondary">接回此浏览器原有记录</button></div>
<p id="accountForcedPasswordHint" class="account-initial-note" hidden>请先修改服务启动时生成的随机初始密码,再使用剧库。</p>
<details id="accountPasswordDetails" class="account-password"><summary>修改密码</summary><form id="accountPasswordForm" class="account-form">
<label>当前密码<input id="accountCurrentPassword" type="password" minlength="10" maxlength="128" autocomplete="current-password" required></label>
<label>新密码<input id="accountNewPassword" type="password" minlength="10" maxlength="128" autocomplete="new-password" required></label>
<label>确认新密码<input id="accountNewPasswordConfirm" type="password" minlength="10" maxlength="128" autocomplete="new-password" required></label>
<button type="submit">保存新密码</button><p class="small">修改后,其他设备需重新登录。</p>
</form></details>
<button id="accountManageUsersBtn" class="secondary account-manage-users button-content" data-go="users" data-admin-only hidden><span data-icon="user"></span>用户管理</button>
</div>
<a id="accountAnonymousLink" class="account-anonymous-link" href="/" hidden>继续匿名浏览</a>
<p id="accountStatus" class="account-status small" role="status" aria-live="polite"></p>
</div>
</dialog>
<dialog id="playerPanel" class="player-dialog" aria-labelledby="playerTitle">
<div id="playerViewport" class="player-viewport">
<div class="player-heading"><div><span class="small">正在观看</span><h2 id="playerTitle">在线观看</h2></div><button id="closePlayerBtn" class="quiet icon-button" aria-label="关闭播放器"><span data-icon="close"></span></button></div>
<div class="player-layout">
<section class="player-screen" aria-label="视频播放">
<div id="playbackStage" class="playback-stage">
<video id="onlineVideo" controls playsinline preload="auto" tabindex="0" aria-label="在线播放器" aria-keyshortcuts="Space ArrowUp ArrowDown ArrowLeft ArrowRight F"></video>
<div class="mobile-player-heading mobile-only">
<button id="mobileBackBtn" class="quiet icon-button" aria-label="返回剧库"><span data-icon="chevron"></span></button>
<span id="mobileHeadingTitle" class="landscape-only"></span><span id="mobileEpisodeLabel">正在加载</span>
<button id="mobileRateBtn" class="quiet" aria-label="调整播放倍速">倍速</button>
<button id="mobileRotateBtn" class="quiet icon-button" aria-label="横屏播放" aria-pressed="false" disabled><span data-icon="rotate"></span></button>
<button id="mobilePlayerSettingsBtn" class="quiet icon-button" aria-label="打开播放设置"><span data-icon="more"></span></button>
</div>
<button id="mobileLandscapeBtn" class="mobile-landscape-watch mobile-only button-content" hidden><span data-icon="rotate"></span>全屏观看</button>
<button id="mobilePlayPauseBtn" class="mobile-play-toggle mobile-only" aria-label="播放"><span data-icon="play"></span></button>
<div class="mobile-player-caption mobile-only">
<span id="mobileEpisodeVIP" class="mobile-vip-label" hidden>VIP · 仅试看</span><h2 id="mobilePlayerTitle">正在加载</h2>
<div class="mobile-progress-heading"><span class="mobile-swipe-hint">上下滑动切换分集</span><span id="mobilePlaybackTime">0:00 / 0:00</span></div>
<div class="mobile-progress-row">
<button id="mobileTransportBtn" class="quiet icon-button landscape-only" aria-label="暂停"><span class="control-play"><span data-icon="play"></span></span><span class="control-pause"><span data-icon="pause"></span></span></button>
<button id="mobileNextEpisodeBtn" class="quiet icon-button landscape-only" aria-label="下一集" disabled><span data-icon="next"></span></button>
<span id="mobileCurrentTime" class="landscape-only mobile-time">0:00</span>
<input id="mobilePlaybackProgress" type="range" min="0" max="1" step="0.1" value="0" aria-label="播放进度" disabled>
<span id="mobileDuration" class="landscape-only mobile-time">0:00</span>
</div>
<div class="mobile-bottom-actions">
<button id="mobileLandscapeRateBtn" class="quiet landscape-only" aria-label="调整播放倍速">倍速</button>
<button id="mobileLandscapeQualityBtn" class="quiet landscape-only" aria-label="调整清晰度">自动</button>
<button id="mobileEpisodesBtn" class="mobile-episode-rail" aria-label="打开选集"><span>选集 <small id="mobileEpisodeSummary">获取分集中</small></span><span class="mobile-episode-chevron"><span data-icon="chevron"></span></span></button>
</div>
</div>
<div id="playbackGestureStatus" class="playback-gesture-status" role="status" aria-live="polite" aria-atomic="true" hidden></div>
<div id="playbackDanmaku" class="playback-danmaku" aria-hidden="true"></div>
<button id="exitPlayerFullscreenBtn" class="secondary stage-fullscreen-exit" aria-label="退出播放器全屏">退出全屏</button>
</div>
<div class="player-toolbar"><button id="toggleEpisodesBtn" class="secondary" aria-expanded="false" aria-controls="playerEpisodesPanel">选集</button><button id="previousEpisodeBtn" class="secondary icon-button" aria-label="上一集" title="上一集" disabled><span data-icon="previous"></span></button><button id="nextEpisodeBtn" class="button-content" disabled>下一集<span data-icon="next"></span></button><label id="playbackQualityControl" hidden>画质<select id="playbackQuality" aria-label="播放清晰度"><option value="0">自动</option></select></label><label>倍速<select id="playbackRate" aria-label="播放倍速"><option value="0.75">0.75 倍</option><option value="1" selected>1 倍</option><option value="1.25">1.25 倍</option><option value="1.5">1.5 倍</option><option value="2">2 倍</option><option value="3">3 倍</option></select></label><button id="playerFullscreenBtn" class="secondary icon-button" aria-label="全屏" title="全屏" hidden><span data-icon="fullscreen"></span></button><button id="playerPreferencesBtn" class="secondary icon-button" aria-label="播放设置" aria-expanded="false" aria-controls="playerPreferences"><span data-icon="settings"></span></button></div>
<div class="playback-feedback"><span id="playbackStatus" class="small" role="status" aria-live="polite"></span><span id="prefetchStatus" class="small" role="status" aria-live="polite" hidden></span></div>
<div id="playbackHistoryStatus" class="small" role="status" aria-live="polite"></div><div class="playback-error-row"><div id="playbackError" class="error" role="alert"></div><button id="retryPlaybackBtn" class="secondary" disabled hidden>重试播放</button></div>
<div id="playerPreferences" class="player-preferences" hidden>
<div class="mobile-settings-heading mobile-only"><h3 id="mobilePreferencesTitle">更多</h3><button id="closePlayerPreferencesBtn" class="quiet icon-button" aria-label="关闭播放设置"><span data-icon="close"></span></button></div>
<div id="mobileRateGroup" class="mobile-setting-group mobile-only"><span>倍速</span><div id="mobileRateChoices" class="mobile-setting-choices" role="group" aria-label="播放倍速"></div></div>
<div id="mobileQualityGroup" class="mobile-setting-group mobile-only"><span>清晰度</span><div id="mobileQualityChoices" class="mobile-setting-choices" role="group" aria-label="播放画质"></div></div>
<div class="mobile-setting-group mobile-only"><span id="mobileVideoSize">读取视频尺寸…</span><label class="preference-toggle"><span>自动旋转</span><input id="mobileAutoRotate" type="checkbox" checked></label><p id="mobileOrientationHint" class="small">跟随手机方向,也可随时手动旋转。</p><button id="mobileOrientationPermissionBtn" class="secondary" hidden>启用方向感应</button></div>
<label class="preference-toggle"><span>自动下一集</span><input id="autoNextEpisode" type="checkbox" checked></label><label class="preference-toggle"><span>预缓存下一集<small>临近结束时提前准备</small></span><input id="prefetchNextEpisode" type="checkbox" checked></label><label class="preference-toggle" id="danmakuControl" hidden><span>弹幕</span><input id="danmakuEnabled" type="checkbox" checked></label><span id="danmakuStatus" class="small" role="status" aria-live="polite" hidden></span>
<div class="toolbar"><button id="pictureInPictureBtn" class="secondary" hidden>画中画</button><button id="mobileFullscreenBtn" class="secondary mobile-only" hidden>全屏播放</button></div><p id="playbackHint" class="small player-hint">观看进度自动保存。</p><p class="small player-shortcuts"><span class="player-keyboard-hint">空格播放/暂停,← → 跳转 5 秒,↑ ↓ 调节音量,长按 → 临时 3 倍速,F 全屏。<br></span>上滑下一集、下滑上一集;播放时长按画面临时 3 倍速,松开恢复。横屏时点画面显示或隐藏操作栏。</p>
</div>
</section>
<nav id="playerEpisodesPanel" class="player-episodes" aria-label="选择播放分集"><div class="episode-panel-heading"><div><h3>选集</h3><p id="playbackEpisodeCount" class="small"></p></div><button id="closeEpisodesBtn" class="quiet icon-button" aria-label="关闭选集"><span data-icon="close"></span></button></div><div class="episode-navigation"><select id="episodeRange" aria-label="分集范围"></select><button id="currentEpisodeBtn" class="quiet">当前集</button></div><div id="playbackEpisodes" class="player-episode-list"></div><form id="episodeJumpForm" class="episode-jump"><label for="jumpEpisode">跳到</label><input id="jumpEpisode" type="number" min="1" inputmode="numeric" aria-label="分集序号"><span>集</span><button id="jumpEpisodeBtn" class="secondary" type="submit">前往</button></form></nav>
</div>
<button id="playerSheetBackdrop" class="player-sheet-backdrop" aria-label="关闭播放面板" tabindex="-1"></button>
</div>
</dialog>
<div class="toast" id="messageText" role="status" aria-live="polite"></div>
<script src="/assets/navigation.js"></script>
<script src="/assets/player-danmaku.js"></script>
<script src="/assets/history.js"></script>
<script src="/assets/player-orientation.js"></script>
<script src="/assets/player-mobile.js"></script>
<script src="/assets/player-controls.js"></script>
<script src="/assets/hls.min.js"></script>
<script src="/assets/player-media.js"></script>
<script src="/assets/player-capabilities.js"></script>
<script src="/assets/player-session.js"></script>
<script src="/assets/player.js?v=20260916-mobile"></script>
<script src="/assets/library-sort.js"></script>
<script src="/assets/vip-filter.js"></script>
<script src="/assets/rankings.js"></script>
<script type="module" src="/assets/main.js"></script>
</body>
</html>