CHANNEL TurboDL
RELv0.4.1.0 API1.0.0 TEST199 PASS

HTTP/RANGEKOTLIN JVM 17+NO UI REQUIRED

Many hands,one file.

A Kotlin/JVM engine splits one HTTP transfer across sixteen workers, resumes from bytes that actually landed, and rejects a server that lies about Range.

RELEASE
v0.4.1.0
ENGINE
1 REQUEST → 16 SHARDS
VERIFY
199 TESTS

One request becomes 16 moving parts

The progress bar is not decoration. Its three regions mirror the actual transfer pipeline: probe, parallel shards, verify.

01

1probe

A Range request asks one question before the file is cut: can this server serve byte ranges?

02

16shards

Sixteen segments move at once. Work stealing takes the remaining block away from a slow connection.

03

1verify

Every returned range is checked, then the merged file is checked against the expected byte count.

  1. PROBE

    Range support is settled first. If the server cannot do it, the engine stops pretending and downloads straight through.

  2. SCHEDULE

    The block count is about four times the connection count, so a slow worker never owns the last remaining piece.

  3. VERIFY

    The bytes returned are compared with the range that was asked for; the merged file is compared with the total size.

Six decisions baked into the byte plane

MechanismOperational consequence
Segmented parallel downloadHTTP Range segments downloaded in parallel, with connection reuse (HTTP/2 multiplexing + keep-alive).
Dynamic segmentation + work stealingFine-grained pre-splitting plus work stealing, so one slow connection cannot drag the whole transfer down — and the "last segment on a single thread" long tail disappears.
Restrained adaptivityConcurrency drops only on 429/503 or after consecutive failures. Ordinary jitter never touches the thread count — no AIMD oscillation.
Resume from real progressSegments are persisted on disk; pause/resume continues from what actually landed, not from a remembered number.
Range tampering rejectedThe bytes actually returned are checked against the Range that was requested — a server that ignores Range and sends the whole file is caught, not written as misplaced data.
Byte-level integrity checkTotal size is verified after the merge step; a short or oversized result is rejected.

Each line is measurable in the repository. Two exist because the obvious implementation was measured and rejected.

Every control maps to a public field

Five controls below use the engine defaults. The switch is live in the page; the numeric consequences come from measured runs.

dynamicSegmentation

Split work finer than the connection count.

About four blocks per connection, so the final stretch is never one worker on its own. Off: fixed blocks plus work stealing.

ON BY DEFAULT

tailAssist

Give the tail to whatever is idle.

At most two splits per segment in the endgame; nothing below 2 MiB is split, so a 16 MiB block tops out at four parts.

ON BY DEFAULT

overlapMerge

Merge while the last bytes arrive.

Measured on 512 MB / 64 connections: 11.08 s to 10.39 s, about 6%. It can duplicate a write with tail assist, so it stays off.

OFF BY DEFAULT

bufferedSegmentWrite

Write segments through a buffer.

16 MB / 128 connections / 64 KB segments: 2576 ms direct, 2584 ms buffered. The 0.3% difference is noise; resume stays honest without it.

OFF BY DEFAULT

trustAllCerts

Trust any certificate.

Packet-capture debugging only. It turns certificate validation off, so production keeps it off.

OFF BY DEFAULT

CapabilityConfig fieldNotes
Global speed limitglobalSpeedLimitBytesPerSec0 = unlimited
Thread countmaxConnectionsPerTask1..256
Concurrent tasksmaxConcurrentTasks1..64
Max retriesmaxRetries0..50
Dynamic segmentationdynamicSegmentationtrue / false
ProxyproxyDirect / System / Manual(HTTP,SOCKS,auth) / Pac(url)
DNSdnsSystem / StaticHosts / DoH(url)
Ignore SSLtrustAllCertspacket-capture debugging
Theming—upper-layer app's job; the SDK renders no UI

The SDK renders no interface by design: speed limits, queues and progress remain values your application chooses how to show.

One repository, seven deliberately separate artifacts

Take the engine alone or assemble the whole protocol stack. The dependency direction never reverses.

  1. turbodl-coreengine / zero plugin dependency
  2. turbo-plugin-runtimelifecycle + extension points
  3. turbo-plugin-bootstrapone-call wiring
  4. turbo-plugin-hlsHLS VOD backend
  5. turbo-plugin-jsQuickJS loader
  6. turbo-plugin-parsershare-link parser
  7. turbodl-cliscriptable command line
ArtifactRoleRequired?
turbodl-coreDownload engine SDK
Segmented parallel download, resume, retry, speed limit, DNS/proxy. Publishes fine on its own and has no plugin-framework dependency.
required
turbo-plugin-runtimePlugin runtime kernel
Lifecycle, cleanup (disposer), event bus, service registry, extension points, version handshake, diagnostics. Knows nothing about any specific protocol.
optional
turbo-plugin-bootstrapBootstrap wiring
One-call wiring of the base plugins (Kotlin loader + HTTP backend) and the bridge from the engine's event stream.
optional
turbo-plugin-hlsHLS VOD adapter
Resolves M3U8 playlists, downloads segments concurrently (with per-segment retry), decrypts AES-128, honors EXT-X-BYTERANGE. Live/DRM/fMP4 playlists are refused explicitly instead of producing corrupt output.
optional
turbo-plugin-jsJavaScript plugin loader
Loads TurboDL plugins written in JavaScript. Embeds QuickJS — one runtime and one context per script — and exposes a stable `host`/`plugin` ABI with coarse-grained capabilities.
optional
turbo-plugin-parserPlatform link parser
Turns a share link into download requests: Weibo (MP4 + image sets), Xiaohongshu (watermark-free video + original images), Pixiv (every page's original). Holds no credentials — where a platform needs a login it returns null and the host's own session handling takes over.
optional
turbodl-cliCommand-line tool
A complete CLI built on the SDK: multi-threaded, resumable, batch mode, NDJSON output for scripts and agents. Shipped as a fat JAR.
optional

FAIL-CLOSEDEXPLICIT REFUSALS

What it refuses to pretend

An unsupported stream fails loudly. The alternative — writing a file that looks fine and is not — is the failure mode this section exists to prevent.

PluginRefused on purposeWhy
HLS VOD Backendbackend.hlsLive playlists (no `EXT-X-ENDLIST`)DRM / `SAMPLE-AES`fMP4 `EXT-X-MAP`DiscontinuitiesRefused explicitly: an unsupported stream fails loudly instead of producing a file that looks fine and is not.
JavaScript Plugin Loaderloader.jsRegistering a `DownloadBackend` (the byte plane stays in Kotlin)Publishing a service (a service is a Kotlin object graph)`require` / npm / `fetch` — the host ABI is the entire surface`jar:` or remote script sources (local file / `inline:` / `data:` only, for now)A script computes URLs, headers and signatures; it never streams download data. That boundary is what keeps a script from becoming a bandwidth bottleneck.
Platform Link Parserparser.platformsLogging in — a platform that needs a session returns null and the host's own account handling takes overPixiv animations (ugoira): a zip plus a frame-timing table needs image processingPixiv novelsSigned APIs (Douyin, Kuaishou, Bilibili): per-platform signature algorithms stay in the hostIts result type carries no path: a `DownloadRequest` needs a `destination: File`, but a parser runs before the host has chosen a directory. The temp-dir destination is filled in only at the boundary, so the host still applies its own path policy.

What the engine emits while it runs

The CLI can print one JSON object per event. The panel and terminal below use the same field names as the engine.

PROGRESS EVENT62%
statedownloading
downloaded212.4 MB
total341.8 MB
percent62
speed18.6 MB/s
connections16
eta_ms7 020

Every field comes from the engine event stream; the SDK renders no interface of its own.

NDJSON / STDOUTturbodl --json · one event per line
{"type":"start","url":"https://example.com/image.iso","output":"/tmp/image.iso"}
{"type":"timing","stage":"metadata","t_ms":184,"probe_ms":96,"resume":""}
{"type":"progress","state":"downloading","downloaded":222732288,"total":358411469,"percent":62,"speed":19503514,"connections":16,"eta_ms":7020}
{"type":"completed","url":"https://example.com/image.iso","file":"/tmp/image.iso","bytes":358411469,"sha256":"0f3a…"}
{"type":"summary","total":1,"failed":0}
shell
java -jar turbodl-cli-all.jar <url> -o out.bin -c 16
java -jar turbodl-cli-all.jar <url> --json
java -jar turbodl-cli-all.jar --batch tasks.txt

Questions before the first download

Does this need Android?

No. It is a pure Kotlin/JVM library. Android is one consumer; a server or desktop tool is another. The only requirement is JVM 17+.

Do I have to use the plugin framework?

No. turbodl-core has no plugin-framework dependency. The framework earns its place only when you need protocols beyond HTTP.

Can it download HLS?

Yes for video-on-demand through the official plugin: playlist resolution, concurrent segments and AES-128. Live playlists, DRM and fMP4 are refused explicitly.

Is there a GUI?

Not from this project. The SDK deliberately renders no interface. What ships is the engine, the plugin framework and a command-line tool.

Where do I get the files?

Every file is an attachment of the v0.4.1.0 release: a self-contained CLI JAR and an offline Maven bundle. They are not on Maven Central yet.

What happens when a server lies about Range?

The returned bytes are compared with the requested range. A server that ignores Range is caught and that segment fails instead of writing misplaced bytes.

Start with one coordinate

Add the engine to your build and hand the bytes to the runtime:

Not on Maven Central yet. The release’s offline Maven bundle is the supported way to consume it; the download page spells out how.