同步上游 zip:合并任务后台化 + 合并整剧可直接播放;保留 ultrafast+4线程 转码优化与 .gitignore 本地规则

This commit is contained in:
yvan
2026-09-18 10:16:15 +08:00
parent 80485e9dba
commit b96363d787
21 changed files with 1173 additions and 172 deletions
+10 -7
View File
@@ -52,7 +52,7 @@
const prefetchToggle = node('prefetchNextEpisode');
const prefetchStatus = node('prefetchStatus');
try {prefetchToggle.checked = localStorage.getItem('juku.playback.prefetchNext') !== 'false';} catch (_) {}
const mobile = window.JukuPlayerMobile({panel, video, state: () => ({title: dramaName, episode: episodes[currentIndex - 1]?.episode, total: episodes.length, duration: playbackDuration, loading, releaseStatus, vip: episodes[currentIndex - 1]?.vip}), showEpisodes: showEpisodePanel, showPreferences: section => setPlayerPreferences(true, section)});
const mobile = window.JukuPlayerMobile({panel, video, state: () => ({title: dramaName, merged: episodes[currentIndex - 1]?.merged, episode: episodes[currentIndex - 1]?.episode, total: episodes.length, duration: playbackDuration, loading, releaseStatus, vip: episodes[currentIndex - 1]?.vip}), showEpisodes: showEpisodePanel, showPreferences: section => setPlayerPreferences(true, section)});
const controls = window.JukuPlayerControls({panel, video, mobile: () => mobile.active(), rotation: () => mobile.rotation(), canSwipe: () => panel.open && episodes.length > 0 && !panel.classList.contains('episodes-open') && node('playerPreferences').hidden, swipe: direction => {
const next = currentIndex + direction;
if (next < 1) return '已经是第一集';
@@ -281,7 +281,7 @@
button.setAttribute('aria-current', String(index === currentIndex));
button.classList.toggle('watched', completedEpisodes.has(index));
}
node('playbackEpisodeCount').textContent = episodes.length ? '第 ' + (episodes[currentIndex - 1]?.episode || currentIndex || '—') + ' 集 / 共 ' + episodes.length + ' 集' : '正在获取分集';
node('playbackEpisodeCount').textContent = episodes[currentIndex - 1]?.merged ? episodes[currentIndex - 1].title : episodes.length ? '第 ' + (episodes[currentIndex - 1]?.episode || currentIndex || '—') + ' 集 / 共 ' + episodes.length + ' 集' : '正在获取分集';
node('jumpEpisode').max = episodes.length || 1;
node('jumpEpisode').disabled = !episodes.length;
node('jumpEpisodeBtn').disabled = !episodes.length;
@@ -322,10 +322,10 @@
episodeButtons = episodes.slice(episodePage * 30, episodePage * 30 + 30).map(episode => {
const button = document.createElement('button');
button.className = 'secondary';
button.textContent = episode.episode + (episode.vip ? ' VIP' : '');
button.textContent = episode.merged ? '全集' : episode.episode + (episode.vip ? ' VIP' : '');
button.classList.toggle('vip-episode', Boolean(episode.vip));
button.title = (episode.title || '第 ' + episode.episode + ' 集') + (episode.vip ? ' · VIP,仅试看' : '');
button.setAttribute('aria-label', '播放第 ' + episode.episode + ' 集' + (episode.vip ? ',VIP,仅试看' : ''));
button.setAttribute('aria-label', episode.merged ? '播放' + episode.title : '播放第 ' + episode.episode + ' 集' + (episode.vip ? ',VIP,仅试看' : ''));
button.dataset.episodeIndex = episode.index;
button.addEventListener('click', () => {playEpisode(episode.index); closeEpisodePanel();});
episodeList.appendChild(button);
@@ -515,8 +515,11 @@
}
function updatePlaybackHint(source) {
if (!collectionMode) {
node('playbackHint').textContent = '直接观看,不加入下载任务。开启预缓存后,临近播完时提前准备下一集;关闭窗口即停止取流并释放缓存。';
if (source === 'merged' || episodes[currentIndex - 1]?.merged) {
node('playbackHint').textContent = '正在播放本地合并文件,支持拖动进度和继续观看。';
} else if (!collectionMode) {
const delivery = mediaPlan?.delivery === 'redirect' ? '当前为客户端直连,视频直接从源站获取。' : mediaPlan?.delivery === 'proxy' ? '当前由服务器中转视频。' : '';
node('playbackHint').textContent = delivery + '直接观看,不加入下载任务。开启预缓存后,临近播完时提前准备下一集;关闭窗口即停止取流并释放缓存。';
} else {
const prefix = source === 'local' ? '本集播放本地已完成文件。' : source === 'online' ? '本集在线缓冲,同时使用原下载任务保存视频。' : '已完成分集优先播放本地,播到未完成分集时自动下载该集。';
node('playbackHint').textContent = prefix + '只补下载播到的分集;关闭播放器不取消下载,可在下载合集中暂停或取消。';
@@ -691,7 +694,7 @@
let reader;
try {
if (collectionMode && preparedIndex !== index) {
statusText.textContent = '正在检查本地分集并准备下载…';
statusText.textContent = episodes[index - 1].merged ? '正在读取本地合并文件…' : '正在检查本地分集并准备下载…';
const preparation = await requestJSON('/api/ui/playback/prepare', {session: currentSession, episode: index}, signal);
if (signal.aborted) throw abortError();
preparedIndex = index;