- Give every task a random staging identity persisted in the saved
state: two tasks downloading the same playlist into the same
directory get separate segment folders and journals instead of
deleting each other's progress
- Bind the journal to a fingerprint of the full download plan (media
URL, ordered segments, ranges, keys); foreign or unreadable journals
are discarded rather than applied to the wrong segments, and the
ledger is rewritten through a temp file + rename
- Legacy states without a staging identity migrate to a fresh folder
instead of reusing the shared URL-hash folder
- Merge into a unique temporary file next to the target, check write
and close errors, then remove-then-rename over the final name: a
failed merge preserves the staged segments for a retry and re-merging
after a crash stays idempotent
- Drop the staging folder when a URL patch invalidates the plan
- Parse the request extra in Setup so restored fetchers resend Referer,
Cookie and other configured headers on resumed segment/key requests;
the store/restore regression test round-trips the state and the task
meta through JSON exactly like the engine storage does
- Rebuild the resolved plan on Start when the resource survived but the
plan was lost to a crash between the task save and the first
checkpoint, keeping the existing output name
- Patch now merges partial updates instead of replacing the request and
options wholesale: URL-only patches keep the output path and renamed
file, header-only patches keep the resolved plan and take effect on
the next run, Labels merge per key, Proxy swaps rebuild the client,
and nil arguments are no longer a panic. Editing a running task is
rejected, consistent with the ed2k protocol
- Cover retry-after-failure counter resets, running-task patch
rejection and the missing-state self-heal with regression tests
- Give every Start its own run context, WaitGroup (including the queue
producer), supervisor done channel and snapshots of the plan, request
headers, HTTP client and merge target; Pause/Close wait for all run
goroutines, including a merge in flight, before returning
- Build the HTTP client in Resolve/Start before workers launch instead of
lazily inside do(), which raced between concurrent segment downloads
- Reject task edits while a run is active instead of mutating shared state
- Reset counters per run and seed them from the completed-segment journal;
count only stored output bytes as progress, never discarded retry bytes
- Report Res.Size only when every segment is unencrypted with a known
length; ciphertext sums overstate the final output, indeterminate plans
rely on the engine's TotalDownloaded backfill at completion
New hls protocol in internal/protocol/hls (registered ahead of http):
- master playlist variant picking (highest bandwidth), RFC3986 segment
URL resolution, fMP4 init segments, EXT-X-BYTERANGE, EXT-X-GAP skip
- AES-128 decryption with mid-playlist key rotation and sequence-derived
IVs; explicit errors for live streams, SAMPLE-AES and multi-period fMP4
- concurrent segment downloads with retry, journal-based resume across
pause/app restarts, automatic in-order merge into a single .ts/.mp4
- best-effort Content-Length probing for determinate progress
- anti-hotlink Referer/Cookie/UA via task extra headers (Go transport
does not filter them)
Core: fix SchemeFilter extension match for URLs with query strings
(also benefits .torrent links); Flutter: Protocol.hls enum, hls protocol
config model (prevents config round-trip wipe) and HLS settings block
with localized labels in all 22 locales.
Tests: parser/crypto unit tests, httptest e2e (Referer-enforced CDN,
encrypted streams, byterange, pause/resume, store/restore) and an
opt-in live-site acceptance gate (HLS_LIVE_URL).