dynamicSegmentationSplit 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
HTTP/RANGEKOTLIN JVM 17+NO UI REQUIRED
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.
The progress bar is not decoration. Its three regions mirror the actual transfer pipeline: probe, parallel shards, verify.
1probe
A Range request asks one question before the file is cut: can this server serve byte ranges?
16shards
Sixteen segments move at once. Work stealing takes the remaining block away from a slow connection.
1verify
Every returned range is checked, then the merged file is checked against the expected byte count.
Range support is settled first. If the server cannot do it, the engine stops pretending and downloads straight through.
The block count is about four times the connection count, so a slow worker never owns the last remaining piece.
The bytes returned are compared with the range that was asked for; the merged file is compared with the total size.
| Mechanism | Operational consequence |
|---|---|
| Segmented parallel download | HTTP Range segments downloaded in parallel, with connection reuse (HTTP/2 multiplexing + keep-alive). |
| Dynamic segmentation + work stealing | Fine-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 adaptivity | Concurrency drops only on 429/503 or after consecutive failures. Ordinary jitter never touches the thread count — no AIMD oscillation. |
| Resume from real progress | Segments are persisted on disk; pause/resume continues from what actually landed, not from a remembered number. |
| Range tampering rejected | The 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 check | Total 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.
Five controls below use the engine defaults. The switch is live in the page; the numeric consequences come from measured runs.
dynamicSegmentationSplit 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
tailAssistGive 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
overlapMergeMerge 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
bufferedSegmentWriteWrite 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
trustAllCertsTrust any certificate.
Packet-capture debugging only. It turns certificate validation off, so production keeps it off.
OFF BY DEFAULT
| Capability | Config field | Notes |
|---|---|---|
| Global speed limit | globalSpeedLimitBytesPerSec | 0 = unlimited |
| Thread count | maxConnectionsPerTask | 1..256 |
| Concurrent tasks | maxConcurrentTasks | 1..64 |
| Max retries | maxRetries | 0..50 |
| Dynamic segmentation | dynamicSegmentation | true / false |
| Proxy | proxy | Direct / System / Manual(HTTP,SOCKS,auth) / Pac(url) |
| DNS | dns | System / StaticHosts / DoH(url) |
| Ignore SSL | trustAllCerts | packet-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.
Take the engine alone or assemble the whole protocol stack. The dependency direction never reverses.
turbodl-coreengine / zero plugin dependencyturbo-plugin-runtimelifecycle + extension pointsturbo-plugin-bootstrapone-call wiringturbo-plugin-hlsHLS VOD backendturbo-plugin-jsQuickJS loaderturbo-plugin-parsershare-link parserturbodl-cliscriptable command line| Artifact | Role | Required? |
|---|---|---|
| turbodl-core | Download 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-runtime | Plugin runtime kernel Lifecycle, cleanup (disposer), event bus, service registry, extension points, version handshake, diagnostics. Knows nothing about any specific protocol. | optional |
| turbo-plugin-bootstrap | Bootstrap wiring One-call wiring of the base plugins (Kotlin loader + HTTP backend) and the bridge from the engine's event stream. | optional |
| turbo-plugin-hls | HLS 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-js | JavaScript 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-parser | Platform 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-cli | Command-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
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.
| Plugin | Refused on purpose | Why |
|---|---|---|
HLS VOD Backendbackend.hls | Live playlists (no `EXT-X-ENDLIST`)DRM / `SAMPLE-AES`fMP4 `EXT-X-MAP`Discontinuities | Refused explicitly: an unsupported stream fails loudly instead of producing a file that looks fine and is not. |
JavaScript Plugin Loaderloader.js | Registering 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.platforms | Logging 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 host | Its 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. |
The CLI can print one JSON object per event. The panel and terminal below use the same field names as the engine.
Every field comes from the engine event stream; the SDK renders no interface of its own.
{"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}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.txtNo. It is a pure Kotlin/JVM library. Android is one consumer; a server or desktop tool is another. The only requirement is JVM 17+.
No. turbodl-core has no plugin-framework dependency. The framework earns its place only when you need protocols beyond HTTP.
Yes for video-on-demand through the official plugin: playlist resolution, concurrent segments and AES-128. Live playlists, DRM and fMP4 are refused explicitly.
Not from this project. The SDK deliberately renders no interface. What ships is the engine, the plugin framework and a command-line tool.
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.
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.
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.