A native, cross-platform Stremio client built on a Rust core with a Flutter UI.

Xtremio is the client half of a two-part project. The other half is

zond/stream-server, a headless

torrent-streaming server written in Rust, which Xtremio embeds in its own

process. It pairs that with

stremio-core -- the official Rust

engine for addons, catalogs, library and playback state, built here from a

fork -- and

media_kit/libmpv for playback.

All of this is built and runs today. docs/STATUS.md is the screen-by-screen inventory; a feature with a design document links it.

- Catalogs and search across every addon installed, and a library. A board of continue-watching and a row per catalog that answered, discover over the engine's own filters, a search that asks every addon supporting it -- and on the board, in search and under a title's sources, a line naming the addons that could not answer, so a dead addon is never mistaken for a title nobody has.

- Torrent streaming with no external binary. stream-serverruns in-process on loopback: nothing to ship beside the app, launch, or keep alive on mobile. In the default layout a title's sources are one row per release rather than one per addon offering it, in a section per resolution, ranked by peers per megabyte unless another order is picked; a torrent starts behind a card that says what it is doing -- checking, finding peers, buffering -- instead of a spinner.

- Offline downloads. A download is a file pinned in the embedded server: it is kept, piece by piece, in the one torrent-data root the streaming cache uses, and never exists as a whole file. A finished download plays through the same in-process server off the pieces already on the device -- no peer, no tracker, no network -- and only once the server answers that it holds the file whole. On Android a foreground service keeps one going after the app is left.

- A player rather than a video widget. Buffered seek bar, keyboard and remote shortcuts, audio tracks, embedded and addon subtitles, a stats OSD reporting hwdec, the swarm, what this device holds of the stream either side of the playhead and what a torrent has committed and moved since it went live, and an up-next countdown that hands over to the next episode.

- Subtitle timing that is nudged or measured, and then remembered. Shift the lines by hand from a panel that survives the controls fading, mark a line where it belongs and let two marks give the rate, or have the drift measured against another subtitle the viewer says is in sync -- which is where a stretch comes from, since nothing here presses a multiplier and no declared frame rate decides one. What was fixed is stored against the series and the subtitle's release group (a shift against the video release too), so the next episode starts right (docs/ARCHITECTURE.md).

- Android TV and Google TV as their own layout, not a phone app on a big screen: D-pad traversal with a focus memory per tab, a focus ring built to read over unknown poster art, remote keys in the player, ten-foot density and overscan. Run on a physical Chromecast with Google TV (ANDROID.md).

- Casting to a Chromecast from an Android phone, where the receiver can

decode what the embedded server is already serving. The bytes go over the

LAN untouched, from a second listener on the server that exists only while

a cast session does and serves only torrents and archives the app has

already opened -- no control routes, no /proxy. The player screen becomes a remote, and a cast does not binge: the end of an episode on the television never starts the next one. What it refuses, why it refuses rather than guesses, and the fact that no real receiver has confirmed it yet are in docs/CASTING.md.

- Sharing you can see and stop. Share while idle (Settings, on by default) keeps uploading to other peers when nothing is playing; off, the server chokes every peer until a player reads from it again. A status light on the main screens is lit only while the server measures bytes moving to or from peers with nothing playing, never because of the setting: an arrow up for uploading, down for bytes coming in (an offline download filling in, or the title played last fetching what it keeps). Pressed while it shows an upload, it offers Not now (until the next start) or Stop sharing; while it shows bytes coming in, a Cancel for each offline download on its way.

- Addons installed from the web. An addon site's Install button hands the

OS a stremio://link; where Xtremio can register that scheme it opens that addon's details screen, and nothing is installed until the button waiting there is pressed. The contract in full, and the registration per platform, is in docs/DEEP_LINKS.md.

Every version tag builds Linux, Windows, macOS and both Android ABIs and attaches them to a GitHub Release -- that is where a build comes from. Nothing is tagged yet, so until the first one that page is empty and building it yourself is the only way. Two things about those builds are worth knowing before installing, and the release notes say both: the APKs are signed with the Flutter template's debug key, and the macOS build is unsigned.

flutter pub get

make run DEVICE=linux # flutter run -d linux, stamped with version and commit

make linux # a release build; also apk, apk-tv, apk-split, macos, ios (compile check)The Makefile adds two --dart-defines so the Diagnostics screen can say which

build it is, and its APK targets also set the ABI and a version code that

Android installs over an APK from make apk-split. Plain

flutter run -d <device> works too and reports app: unknown. A build needs

Flutter stable (CI uses 3.47.1) and a Rust toolchain no older than

rust-version in rust/Cargo.toml (1.97.1): the Rust crate is compiled by

the build itself, through cargokit. Linux desktop also needs clang, cmake,

ninja, pkg-config, GTK 3 dev libraries, and libmpv-dev (media_kit links

libmpv); Android has a document of its own, ANDROID.md.

Everything else a dev machine wants is in docs/OPERATIONS.md.

┌──────────────────────────────────────────────────────────────┐

│ Flutter UI (this repo) — screens, navigation, playback UI │

├──────────────────────────────────────────────────────────────┤

│ Dart ⇄ Rust FFI (flutter_rust_bridge), crate in rust/ │

│ • stremio-core → addons, catalogs, search, library, │

│ account, playback state (the "brain") │

│ • stream-server → embedded: settings, stats, storage and │

│ downloads as FFI calls │

├──────────────────────────────────────────────────────────────┤

│ media_kit / libmpv — fetches the media over loopback HTTP, │

│ decodes and renders it (direct play; codecs and subtitles │

│ on-device) │

└──────────────────────────────────────────────────────────────┘

The UI stays thin: discovery/library/addon logic lives in stremio-core, the

bytes come from stream-server, and the client's job is presentation plus

driving libmpv. stream-server runs in-process: the Rust crate in rust/

links it as a library and starts it on its own thread with its own runtime,

bound to 127.0.0.1 on a port the OS picks and retargeting stremio-core at

the address it reads back, so no sidecar binary ships and no fixed port is

lost to a desktop Stremio. The Dart side never speaks HTTP to it: libmpv

fetches the media routes, the app's own questions -- settings, a torrent's

stats, storage, downloads -- are FFI calls into the server's library API, and

stremio-core's requests to it carry a per-launch bearer token that only the

Rust side holds. The only HTTP it serves beyond loopback is the media listener

a cast session turns on and off. Because a capable on-device player handles

codecs and subtitles, the server never transcodes -- it just gets bytes onto an

HTTP connection. Settings can point stremio-core at a remote streaming server

by URL, and torrents then play straight off that one; the embedded server

still starts and still fronts an addon's direct links. The embedded one is

the default.

How that bridge is built, what crosses it and what every field of the state means is in docs/ARCHITECTURE.md.

Every git dependency in rust/Cargo.toml is pinned to a rev with its

reason beside it: the three forks are listed in

docs/ARCHITECTURE.md.

The hard constraint is BitTorrent: the streaming path needs raw TCP/UDP sockets, a local HTTP server, disk cache, and libmpv. That decides everything.

What is genuinely not built:

- Cloud storage sources (e.g. Google Drive) -- most naturally via a Stremio addon that resolves cloud files to playable URLs; the provider's OAuth or API-key setup is the fiddly part.

- Media3 remuxing for casting, to let a receiver play a stream it cannot decode as it stands. It would run on the sending device with its platform hardware codec (Android MediaCodec first) -- never ffmpeg, never software transcoding in the Rust core. Until then such a stream is refused rather than mangled.

AGENTS.md is what a change has to satisfy here: single-concept

commits, the verification that gates them, and the rules a real television

taught us. Read it before opening a pull request. CI runs the same checks on every push

to main and every pull request: formatting, analysis, the Flutter and Rust

suites, cargo clippy -D warnings, a cargo check of the core for 32-bit

Android, and a check that the flutter_rust_bridge bindings regenerate to

what is committed (AGENTS.md

has the commands).

The source here is MIT (LICENSE). A compiled Xtremio embeds

stream-server, which links unrar-rs (GPL-3.0-or-later) so a RAR archive in

a torrent plays: distributed binaries are GPL-3.0-or-later

(LICENSE-GPL-3.0). unrar-rs asks that a binary reproduce its

licence file, the unRAR restriction included, so that text ships in the app too

(LICENSE-unrar-rs; Settings → About → Open source

licences). stream-server with default-features = false drops both.