fix: DASH multi-phase progress + HLS fragment progress + indeterminate animation

This commit is contained in:
mini
2026-02-19 00:51:15 +08:00
parent deae827252
commit d419158c80
3 changed files with 39 additions and 9 deletions

View File

@@ -39,17 +39,38 @@ def cleanup_task(task_id: str):
def _make_hook(task_id: str):
"""yt-dlp progress hook with real-time tracking and cancel support."""
"""yt-dlp progress hook: handles DASH multi-phase + HLS fragments + cancel."""
state = {"phase": 0} # counts "finished" events (video phase, audio phase…)
PHASE_WEIGHTS = [0.80, 0.19] # phase-0 → 0-80%, phase-1 → 80-99%
def hook(d):
flag = _cancel_flags.get(task_id)
if flag and flag.is_set():
raise yt_dlp.utils.DownloadCancelled("Cancelled by user")
if d["status"] == "downloading":
total = d.get("total_bytes") or d.get("total_bytes_estimate") or 0
done = d.get("downloaded_bytes", 0)
_download_progress[task_id] = int(done * 100 / total) if total else 0
done = d.get("downloaded_bytes", 0)
if total > 0:
phase_pct = done / total # 0.01.0
else:
# HLS / unknown size: use fragment index
fc = d.get("fragment_count") or 0
fi = d.get("fragment_index") or 0
phase_pct = (fi / fc) if fc > 0 else 0.5 # 0.5 = "working"
ph = min(state["phase"], len(PHASE_WEIGHTS) - 1)
base = sum(PHASE_WEIGHTS[:ph]) * 100
span = PHASE_WEIGHTS[ph] * 100
pct = int(base + phase_pct * span)
_download_progress[task_id] = max(1, pct) # at least 1 to show activity
elif d["status"] == "finished":
_download_progress[task_id] = 99 # merging, not 100 yet
state["phase"] += 1
done_pct = int(sum(PHASE_WEIGHTS[:state["phase"]]) * 100)
_download_progress[task_id] = min(done_pct, 99)
return hook
VIDEO_BASE_PATH = os.getenv("VIDEO_BASE_PATH", "/home/xdl/xdl_videos")

View File

@@ -33,12 +33,12 @@
<!-- Downloading: circular progress ring + cancel X -->
<div v-if="v.status === 'downloading'" class="thumb-overlay ring-overlay">
<svg class="ring" viewBox="0 0 44 44">
<svg class="ring" :class="{ 'ring-spin': v.progress <= 1 }" viewBox="0 0 44 44">
<circle class="ring-bg" cx="22" cy="22" r="18" />
<circle class="ring-fill" cx="22" cy="22" r="18"
:stroke-dasharray="`${(v.progress / 100) * 113} 113`" />
:stroke-dasharray="v.progress > 1 ? `${(v.progress / 100) * 113} 113` : '30 113'" />
</svg>
<span class="ring-pct">{{ v.progress }}%</span>
<span class="ring-pct">{{ v.progress > 1 ? v.progress + '%' : '…' }}</span>
<button class="ring-cancel" @click.stop="cancelDownload(v)" title="Cancel">✕</button>
</div>
@@ -529,6 +529,8 @@ onUnmounted(() => { if (pollTimer) clearInterval(pollTimer) })
gap: 0.2rem; background: rgba(0,0,0,0.72); border-radius: 6px;
}
.ring { width: 36px; height: 36px; transform: rotate(-90deg); }
.ring-spin { animation: spin 1.2s linear infinite; }
@keyframes spin { to { transform: rotate(270deg); } }
.ring-bg { fill: none; stroke: #333; stroke-width: 4; }
.ring-fill { fill: none; stroke: #1da1f2; stroke-width: 4; stroke-linecap: round; transition: stroke-dasharray 0.4s; }
.ring-pct { font-size: 0.65rem; color: #7fdbff; font-weight: 600; margin-top: -2px; }

View File

@@ -37,7 +37,7 @@
<div class="progress-bar">
<div class="progress-fill" :style="{ width: progress + '%' }"></div>
</div>
<p>{{ statusText }}</p>
<p>{{ statusText.replace('0%', '…') }}</p>
<a v-if="downloadReady" :href="downloadUrl" class="download-link" download>💾 Save to device</a>
</div>
</div>
@@ -193,7 +193,14 @@ h1 { font-size: 2rem; margin-bottom: 0.5rem; }
.cancel-btn:hover { background: rgba(231,76,60,0.1); }
.task-status { margin-top: 1.5rem; }
.progress-bar { height: 8px; background: #333; border-radius: 4px; overflow: hidden; margin-bottom: 0.5rem; }
.progress-fill { height: 100%; background: #1da1f2; transition: width 0.3s; }
.progress-fill { height: 100%; background: #1da1f2; transition: width 0.5s; }
.progress-bar:has(.progress-fill[style="width: 0%"]),
.progress-bar:has(.progress-fill[style="width: 1%"]) {
background: linear-gradient(90deg, #1a1a2e 25%, #1da1f2 50%, #1a1a2e 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer { to { background-position: -200% 0; } }
.download-link {
display: inline-block; margin-top: 0.5rem; padding: 0.8rem 2rem;
background: #27ae60; color: #fff; border-radius: 8px; text-decoration: none; font-weight: 600;