libtorrent_flutter 1.6.2
libtorrent_flutter: ^1.6.2 copied to clipboard
Native libtorrent 2.0 bindings for Flutter with a built-in HTTP streaming server for instant torrent video playback. Supports Windows, Linux, macOS, iOS, and Android.
libtorrent_flutter #
The only Flutter package wrapping libtorrent 2.0 — the same C++ engine powering qBittorrent, Deluge, and Transmission. Add a magnet link, pick a file, get a stream URL. Works on Windows, Linux, macOS, iOS, and Android with zero setup — all native binaries are bundled.
dependencies:
libtorrent_flutter: ^1.6.2
Why libtorrent? #
Every other Flutter torrent package uses either a Java wrapper (Android-only) or a pure-Dart implementation that can't compete on speed. libtorrent 2.0 gives you:
set_piece_deadline— tells the engine "I need this piece in 150ms", and it picks the fastest peer automatically. Far smarter than simple sequential download.- uTP support — connects to peers behind NAT that other clients can't reach.
- On-demand streaming — focuses 100% of startup bandwidth on the first pieces. Container metadata (MP4 moov, MKV cues) is fetched reactively when the player requests it via HTTP range requests, cutting startup time to seconds.
- DHT + PEX + LSD — finds peers without trackers.
Usage #
Initialize #
import 'package:libtorrent_flutter/libtorrent_flutter.dart';
// Call once, before anything else.
// Saves to system temp dir by default — no permission setup needed on any platform.
await LibtorrentFlutter.init();
// Advanced options:
await LibtorrentFlutter.init(
downloadLimit: 5 * 1024 * 1024, // 5 MB/s cap
uploadLimit: 1 * 1024 * 1024, // 1 MB/s cap
defaultSavePath: '/my/custom/path',
fetchTrackers: true, // auto-inject best public trackers (default: true)
pollInterval: Duration(milliseconds: 500),
);
Add a torrent #
final engine = LibtorrentFlutter.instance;
// From a magnet link — save path defaults to system temp
final id = engine.addMagnet('magnet:?xt=urn:btih:...');
// From a magnet link with a custom save path
final id = engine.addMagnet('magnet:?xt=urn:btih:...', '/downloads');
// From a .torrent file
final id = engine.addTorrentFile('/path/to/file.torrent');
// Remove a torrent
engine.removeTorrent(id, deleteFiles: true);
// Pause / resume
engine.pauseTorrent(id);
engine.resumeTorrent(id);
Listen for status updates #
engine.torrentUpdates.listen((Map<int, TorrentInfo> torrents) {
final t = torrents[id]!;
print('${t.name}'); // torrent name
print('${t.state.label}'); // "Downloading", "Seeding", etc.
print('${t.progress}'); // 0.0 – 1.0
print('${t.downloadRate}'); // bytes/sec
print('${t.uploadRate}'); // bytes/sec
print('${t.numPeers}');
print('${t.numSeeds}');
print('${t.hasMetadata}'); // true once file list is available
print('${t.totalDone}'); // bytes downloaded
print('${t.totalWanted}'); // bytes total
});
// Or get a snapshot right now
final Map<int, TorrentInfo> current = engine.torrents;
List files and start streaming #
// Wait for hasMetadata == true, then:
final files = engine.getFiles(id); // List<FileInfo>
for (final f in files) {
print('[${f.index}] ${f.name}');
print(' size: ${f.size} bytes');
print(' streamable: ${f.isStreamable}');
}
// Stream the largest streamable file (auto-selected)
final StreamInfo stream = engine.startStream(id);
// Stream a specific file by index
final StreamInfo stream = engine.startStream(id, fileIndex: 2);
// With a RAM cache limit — useful on mobile
// A 500MB sliding window: the engine evicts old pieces as you watch,
// keeping a 10% safety buffer behind playback so it never stutters.
final StreamInfo stream = engine.startStream(
id,
fileIndex: 2,
maxCacheBytes: 500 * 1024 * 1024, // 500 MB
);
print(stream.url); // http://127.0.0.1:PORT/stream/...
// Hand this URL to any player: media_kit, video_player, VLC, mpv, etc.
Monitor stream status #
engine.streamUpdates.listen((Map<int, StreamInfo> streams) {
for (final s in streams.values) {
print('url: ${s.url}');
print('ready: ${s.isReady}'); // true = player can connect
print('buffer: ${s.bufferPct}%'); // 0–100 buffered ahead
print('fileSize: ${s.fileSize}');
print('readHead: ${s.readHead}'); // current byte position
}
});
// Check if a specific torrent is being streamed
final bool active = engine.isStreaming(id);
// Get info for a specific stream
final StreamInfo? info = engine.getStreamInfo(streamId);
// Stop a specific stream
engine.stopStream(streamId);
// Stop all streams for a torrent
engine.stopAllStreamsForTorrent(torrentId);
Cleanup #
// Clean up one torrent: stops its streams, removes it, deletes the files
engine.disposeTorrent(id); // returns false if already gone
// Clean up everything — perfect for your exit button
engine.disposeAll();
// Full shutdown — calls disposeAll(), destroys native session
await engine.dispose();
Speed limits #
engine.setDownloadLimit(2 * 1024 * 1024); // 2 MB/s
engine.setUploadLimit(512 * 1024); // 512 KB/s
engine.setDownloadLimit(0); // back to unlimited
Platform setup #
No changes needed on Windows, Linux, and Android — everything is bundled.
iOS — add the network entitlement. In your ios/Runner/Info.plist, ensure your app allows outgoing connections (this is allowed by default on iOS, but if you use a custom NetworkExtension or have restrictive NSAppTransportSecurity settings, make sure HTTP to localhost is permitted).
macOS — add the network entitlement to your macos/Runner/*.entitlements:
<key>com.apple.security.network.client</key>
<true/>
Android — add to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
Android background streaming — Android kills your app's process when it goes to the background unless you use a Foreground Service. When streaming, start one to keep the engine alive:
// Use flutter_foreground_task or a similar package
FlutterForegroundTask.startService(
notificationTitle: 'Streaming',
notificationText: 'Torrent engine running',
);
How it works #
A single C++ file (torrent_bridge.cpp) wraps libtorrent 2.0 and compiles to a native static/shared library on every platform. Dart talks to it via FFI — no platform channels, no Kotlin, no Swift.
When you call startStream():
- The engine sets deadline=0ms on the first 8 pieces — 100% of bandwidth goes to getting the start of the file as fast as possible
- A tiny HTTP server starts on
127.0.0.1on a random free port - The server responds to byte-range requests, blocking until each piece arrives from the swarm
- When the player needs container metadata (moov atom, cues) from the end of the file, it sends a range request — the engine fetches those pieces on-demand at maximum priority
- A background loop continuously updates piece priorities around your playback position
The stream URL works with any player that handles HTTP range requests.
License #
GPL v3