feat(downloads): estimate a transcode's size so the progress bar moves
A transcode is produced as it is sent — chunked, with no Content-Length — and the worker reported progress 0.0 for its whole duration: an empty bar reading "0%" while the byte count climbed for an hour. That is the case every film whose audio must be re-encoded lands in. The backend already fetches the item to decide the audio policy, and that item carries what a prediction needs: the source's size (an `original` download copies the picture, so the output is the source give or take the audio track) and its runtime (a preset re-encodes at fixed rates, so the size is rate × runtime — from a preset table the URL builder now shares, so the two cannot drift). The prediction is made where the URL is resolved and persisted as the row's file_size. The worker uses it only when the response has no length; the server's figure always wins; an estimated bar is capped at 99% so a low prediction never shows a finished download still running; and the Completed event now carries the bytes actually written so the frontend stops persisting the row's file_size as the final size. The row renders three honest states: exact "42%", estimated "~42%" with "X / ~Y", or — with no total at all — an indeterminate band and the bytes so far, never "0%". The single-video button joins the series/season buttons on the enqueue path so all three resolve, and predict, in one place. DR-290, UT-252, UT-253, UT-254. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { describeProgress } from "./downloadProgress";
|
||||
|
||||
const base = {
|
||||
status: "downloading" as const,
|
||||
progress: 0,
|
||||
bytesDownloaded: 0,
|
||||
fileSize: undefined as number | undefined,
|
||||
fileSizeEstimated: false,
|
||||
};
|
||||
|
||||
// TRACES: UR-071 | DR-290 | UT-254
|
||||
describe("describeProgress", () => {
|
||||
it("shows an indeterminate bar, not 0%, when the size is unknown", () => {
|
||||
// A transcode has no Content-Length and (before the estimate) no total:
|
||||
// 200 MB in, the bar read "0%". That is not progress information.
|
||||
const view = describeProgress({ ...base, bytesDownloaded: 200 * 1024 * 1024 });
|
||||
expect(view.kind).toBe("indeterminate");
|
||||
expect(view.percent).toBeNull();
|
||||
expect(view.label).toBe("200.0 MB");
|
||||
});
|
||||
|
||||
it("marks an estimated total as approximate", () => {
|
||||
const view = describeProgress({
|
||||
...base,
|
||||
progress: 0.42,
|
||||
bytesDownloaded: 420,
|
||||
fileSize: 1000,
|
||||
fileSizeEstimated: true,
|
||||
});
|
||||
expect(view.kind).toBe("estimated");
|
||||
expect(view.percent).toBe(42);
|
||||
expect(view.percentLabel).toBe("~42%");
|
||||
expect(view.label).toBe("420 B / ~1000 B");
|
||||
});
|
||||
|
||||
it("reports an exact total plainly", () => {
|
||||
const view = describeProgress({
|
||||
...base,
|
||||
progress: 0.5,
|
||||
bytesDownloaded: 512,
|
||||
fileSize: 1024,
|
||||
});
|
||||
expect(view.kind).toBe("exact");
|
||||
expect(view.percent).toBe(50);
|
||||
expect(view.percentLabel).toBe("50%");
|
||||
expect(view.label).toBe("512 B / 1.0 KB");
|
||||
});
|
||||
|
||||
it("keeps a paused download's bar where it stopped", () => {
|
||||
const view = describeProgress({
|
||||
...base,
|
||||
status: "paused",
|
||||
progress: 0.25,
|
||||
bytesDownloaded: 256,
|
||||
fileSize: 1024,
|
||||
});
|
||||
expect(view.kind).toBe("exact");
|
||||
expect(view.percent).toBe(25);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user