release: 5.0.1 — fix VOD hover preview + merge-group progress bar
Bug 1 — VOD-Hover Storyboard zeigte am unteren Rand einen statischen Streifen vom Original-Thumbnail (Subpixel-Mismatch + Aspect-Ratio-Konflikt). Fix: Overlay haengt jetzt an .vod-thumb-wrap statt .vod-card, mit explizitem width+height aus dem Thumbnail-BoundingRect — keine CSS-aspect-ratio-Interferenz mehr. Bug 2 — Merge-Group Download zeigte einen eingefrorenen Progress-Bar bei Multi-Part-VODs (Part X/Y). Root Cause: der weighted-progress Wrapper clamped progress=-1 (HLS unknown-total 1s-Tick) auf 0, was overallProgress auf priorWeight fix-nagelte. Bar oszillierte zwischen indeterminate-animation und einem fixen ~10% Wert. Fix: lastVodProgress persistiert zwischen Path-A-Ticks und Path-B-Streamlink-%-Lines, sodass der Bar smooth waehrend einer Part hochzaehlt. 210 unit tests + e2e:release gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c2b9b5759a
commit
44daa65fe6
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "twitch-vod-manager",
|
||||
"version": "5.1.0-alpha.2",
|
||||
"version": "5.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "twitch-vod-manager",
|
||||
"version": "5.1.0-alpha.2",
|
||||
"version": "5.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twitch-vod-manager",
|
||||
"version": "5.1.0-alpha.2",
|
||||
"version": "5.0.1",
|
||||
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
||||
"main": "dist/main.js",
|
||||
"author": "xRangerDE",
|
||||
|
||||
13
src/main.ts
13
src/main.ts
@ -5373,15 +5373,24 @@ async function processDownloadMergeGroup(
|
||||
const vodWeight = vodDuration / totalDurationSec;
|
||||
const priorWeight = mg.items.slice(0, i).reduce((s, v) => s + parseDuration(v.duration_str), 0) / totalDurationSec;
|
||||
|
||||
// Persistente per-part vodProgress, damit Path-A-Ticks (progress=-1,
|
||||
// 1s-rhythmus mit unknown-total) den determinate Bar nicht auf
|
||||
// priorWeight zuruecksetzen. Wir merken uns die letzte positive
|
||||
// Streamlink-Prozentangabe (Path B) und nutzen sie, bis ein neuer
|
||||
// Wert kommt. Ohne das oszilliert die Bar zwischen indeterminate
|
||||
// und priorWeight, optisch eingefroren.
|
||||
let lastVodProgress = 0;
|
||||
const result = await downloadVODPart(
|
||||
vodItem.url,
|
||||
tmpFilename,
|
||||
null, // startTime: null = full VOD
|
||||
null, // endTime: null = full VOD
|
||||
(progress) => {
|
||||
if (progress.progress > 0 && progress.progress <= 100) {
|
||||
lastVodProgress = progress.progress;
|
||||
}
|
||||
// Weighted progress: download phase = 0-70%
|
||||
const vodProgress = progress.progress > 0 ? progress.progress : 0;
|
||||
const overallProgress = (priorWeight + vodWeight * (vodProgress / 100)) * 70;
|
||||
const overallProgress = (priorWeight + vodWeight * (lastVodProgress / 100)) * 70;
|
||||
onProgress({
|
||||
...progress,
|
||||
id: item.id,
|
||||
|
||||
@ -12,6 +12,7 @@ interface ActiveHover {
|
||||
vodId: string;
|
||||
intervalId: number;
|
||||
overlay: HTMLElement;
|
||||
card: HTMLElement; // .vod-card, fuer preview-active toggle (separat vom overlay-host)
|
||||
}
|
||||
|
||||
const vodStoryboardClientCache = new Map<string, VodStoryboard | null>();
|
||||
@ -79,8 +80,7 @@ function clearHoverPreview(): void {
|
||||
pendingHoverVodId = null;
|
||||
if (!activeHover) return;
|
||||
window.clearInterval(activeHover.intervalId);
|
||||
const card = activeHover.overlay.parentElement;
|
||||
if (card) card.classList.remove('preview-active');
|
||||
activeHover.card.classList.remove('preview-active');
|
||||
// Brief opacity fade-out, then remove from DOM.
|
||||
activeHover.overlay.style.opacity = '0';
|
||||
const overlayToRemove = activeHover.overlay;
|
||||
@ -124,20 +124,29 @@ async function activateHoverPreview(card: HTMLElement, vodId: string): Promise<v
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'vod-storyboard-preview';
|
||||
// Scale the sprite so a single cell exactly fills the card width.
|
||||
// The thumbnail aspect-ratio (16:9) matches typical cell aspect
|
||||
// (e.g. 220x124 ≈ 1.77) so width-stretch keeps proportions.
|
||||
const cardWidth = card.getBoundingClientRect().width;
|
||||
const cellAspect = storyboard.cellWidth / storyboard.cellHeight;
|
||||
const scale = cardWidth / storyboard.cellWidth;
|
||||
|
||||
// Anchor an .vod-thumb-wrap statt .vod-card: das wrap-Element hat
|
||||
// exakt Thumbnail-Bounds (kein card-border, kein info/actions-Bereich
|
||||
// darunter). Faellt zurueck auf das card selbst, falls das Markup
|
||||
// mal anders ist.
|
||||
const anchor = card.querySelector('.vod-thumb-wrap') as HTMLElement | null;
|
||||
const host = anchor ?? card;
|
||||
const thumb = card.querySelector('.vod-thumbnail') as HTMLElement | null;
|
||||
const thumbRect = (thumb ?? host).getBoundingClientRect();
|
||||
const width = thumbRect.width;
|
||||
const height = thumbRect.height;
|
||||
|
||||
const scale = width / storyboard.cellWidth;
|
||||
overlay.style.backgroundImage = `url("${storyboard.spriteDataUrl.replace(/"/g, '%22')}")`;
|
||||
overlay.style.backgroundSize = `${storyboard.cols * storyboard.cellWidth * scale}px ${storyboard.rows * storyboard.cellHeight * scale}px`;
|
||||
overlay.style.height = `${cardWidth / cellAspect}px`;
|
||||
overlay.style.backgroundRepeat = 'no-repeat';
|
||||
overlay.style.width = `${width}px`;
|
||||
overlay.style.height = `${height}px`;
|
||||
// Initial position = first chosen cell.
|
||||
const first = cellsToShow[0];
|
||||
overlay.style.backgroundPosition = `-${first.col * storyboard.cellWidth * scale}px -${first.row * storyboard.cellHeight * scale}px`;
|
||||
|
||||
card.appendChild(overlay);
|
||||
host.appendChild(overlay);
|
||||
// Trigger CSS transition to opacity:1 on the next frame.
|
||||
requestAnimationFrame(() => { card.classList.add('preview-active'); });
|
||||
|
||||
@ -148,7 +157,7 @@ async function activateHoverPreview(card: HTMLElement, vodId: string): Promise<v
|
||||
frameIdx++;
|
||||
}, FRAME_INTERVAL_MS);
|
||||
|
||||
activeHover = { vodId, intervalId, overlay };
|
||||
activeHover = { vodId, intervalId, overlay, card };
|
||||
}
|
||||
|
||||
(window as unknown as { ensureVodHoverHandlersBound: typeof ensureVodHoverHandlersBound }).ensureVodHoverHandlersBound = ensureVodHoverHandlersBound;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user