A modern anime streaming experience built with Flutter.
Stream • Download • Track with AniList • Customize everything
Anime, movies and series in one black-glass Android client. A bare React Native app with a Media3-backed offline library, episode alerts, a watchlist that follows you between sessions, optional Discord presence, and a Next.js site that serves the newest APK directly from GitHub Releases.
Created and maintained by ItzFallenMe.
AniFox hosts no media. Metadata and artwork come from TMDB; playback is embedded from third-party providers. Use the app only for content you are allowed to watch.
| Area | What it does |
|---|---|
| Home | Cinematic billboard, Continue watching rail, paged Trending grid, and an in-app update banner when a newer release exists. |
| Browse | Every TMDB catalog as a paged screen: Popular, Now Playing, Upcoming, Top Rated and Trending movies; Popular, Airing Today, On The Air, Top Rated and Trending series. |
| Anime | Anime spotlight plus ongoing, most popular, top rated and anime-film rails. |
| Search | Debounced multi-search across movies, series and anime, with page-by-page results and the last eight searches kept locally. |
| Detail | Backdrop hero, score, synopsis, cast, similar titles, season/episode picker, trailer sheet, bookmark toggle, Continue Sx · Ex and next-episode alert. |
| Player | Eight embed providers (EmbedWave first), in-player server switching, reload and recovery, autoplay, an ad-free navigation guard, landscape fullscreen that rotates in and back out automatically, and Download — available in the fullscreen bar too. |
| Library | Watchlist, resume history, recent searches, episode alerts and offline downloads — all stored on-device. |
| You | Library counts, streaming-server list, data sources, disclaimer, settings and creator credit. |
| Settings | Glass tab bar intensity, default server, autoplay, landscape player, Discord Rich Presence, reset. |
The catalog is TMDB metadata. Playback is embedded from third-party providers, exactly like the original sarui-movies player this project grew out of.
Every list — home, catalogs, anime rails, search and "more like this" — passes
through src/services/tmdbPolicy.ts:
- entries without a poster or title are dropped, so cards never render broken;
- unreleased titles are hidden using TMDB statuses and future release dates — the Upcoming catalog is the deliberate exception;
- blank localized titles fall back to the original name, and synopses always render a sentence.
CATALOG_LANGUAGE and FALLBACK_LANGUAGE in src/config/env.ts control
localization. When they differ, missing synopses are re-requested in the
fallback language and copied across by id.
Providers build signed manifests at runtime, so there is no public URL to resolve ahead of time. Downloads therefore work from the stream the player is already playing:
src/utils/streamProbe.tsis injected into the player WebView before the provider's scripts. It watchesfetch/XHR and resource-timing entries and posts the first.m3u8/.mp4URL back to React Native.- When a stream is detected, the player panel enables Download film /
Download episode. Android's
CookieManagersupplies provider cookies and the player page is sent as theReferer. AnifoxDownloadsqueues a Media3DownloadRequest. A customDownloaderFactoryapplies those headers to every manifest and segment request and supports HLS and progressive MP4.- Completed titles appear under You → Downloads and play in a native Media3 activity backed by the same cache — no network required.
Downloads continue in the background through a foreground DownloadService and
are stored in app-private storage. The downloader can only capture streams the
page exposes unencrypted: DRM-protected players and cross-origin iframes that
never expose a manifest cannot be downloaded.
The app is side-loaded, so updates come from GitHub Releases:
src/services/updates.tsasks the public GitHub API for the newest release, compares it withAPP_INFO.versionand caches the answer for six hours;- a dismissible banner on Home shows the new version and opens the APK asset (preferring universal, then arm64) when tapped;
- dismissing remembers that version, so the banner does not return.
No background polling, no account, no telemetry.
Presence is optional and off by default; switch it on under Settings → Integrations → Discord → Rich Presence. It publishes only while the player is open (title, season/episode, poster and an elapsed timer) and clears when you leave.
It uses Discord's Social SDK, bundled at
android/app/libs/discord_partner_sdk.aar. On Android the SDK publishes presence
over RPC to the installed Discord app, so there is no login, no OAuth and no
token — the Discord app just has to be installed and signed in. The chain is:
src/services/discordPresence.ts— JS API and status.discord/DiscordPresenceModule.kt— React Native module.discord/sdk/DiscordSdkAdapterImpl.kt— SDK adapter (loads the native libs).src/main/cpp/discord_presence.cpp— JNI bridge overdiscordpp.h; it owns one worker thread that pumpsRunCallbacks()and callsSetApplicationId/UpdateRichPresence.
The application id lives in DISCORD_CLIENT_ID in src/config/env.ts and must
have the Social SDK enabled in the Discord Developer Portal.
A "log in with Discord" button cannot enable presence on its own, and pasting a user token (as some music apps do) turns the app into an automated user account, which Discord's Terms prohibit. Neither is used here.
- React Native 0.88 (bare CLI, new architecture, Hermes) — Android only
- TypeScript 6, React 19
- React Navigation 7 (bottom tabs + native stack)
react-native-webviewfor embed playback and trailers- Media3 1.5.1 (ExoPlayer, HLS, UI, database) for downloads and offline playback
@react-native-async-storage/async-storagefor settings and the library@sbaiahmed1/react-native-blurfor the glass tab barlucide-react-native,react-native-linear-gradient- Outfit + Inter, bundled as Android assets (and reused by the website)
- Next.js 14.2.35 + Tailwind for the marketing site
- Node 22.11+
- JDK 17 or 21 and the Android SDK (platform 37, build tools 37.0.0)
- A device or emulator on Android 7.0+ (minSdk 24)
npm install
npm start # Metro
npm run android # build + installsrc/config/env.ts reads TMDB_API_KEY at runtime; replace the bundled value
with your own free key from https://www.themoviedb.org/settings/api, then
restart Metro with npm start -- --reset-cache. No native rebuild needed.
CATALOG_REGION picks the region for regional catalogs;
CATALOG_LANGUAGE / FALLBACK_LANGUAGE localize metadata.
cd android
./gradlew assembleReleaseRelease builds are split per ABI (armeabi-v7a, arm64-v8a, x86_64) with a
universal fallback, so the output directory contains several APKs. The template
signs with the debug keystore — generate your own key before publishing.
version in package.json, versionCode / versionName in
android/app/build.gradle and APP_INFO.version in src/config/env.ts must
agree. v2.1.2 uses versionCode 5.
A separate Next.js app using the same black-glass design language, the app's Inter/Outfit fonts and the brand orange.
| Route | Purpose |
|---|---|
/ |
Landing page: hero, features, player/library showcases, providers, FAQ, download CTA. |
/download |
APK list — every asset of recent GitHub releases with architecture, size and direct link. |
/privacy, /terms |
Legal pages. |
/api/releases |
JSON release + asset list, cached for ten minutes. |
/api/release |
307 redirect to the newest APK (universal → arm64 → any). |
All download buttons point at /download; the API routes read GitHub on the
server, keeping any optional GITHUB_TOKEN private.
cd website
npm install
npm run dev
npm run buildDeploying to Vercel: import the repository, set the project root to
website/, framework preset Next.js, and add GITHUB_TOKEN
(optional, avoids API rate limits). Nothing else is required.
android.yml— on every push tomainand on manual dispatch: typecheck, lint, test, build every APK and upload them as artifacts.release.yml— onv*tags (or manual dispatch with a tag): runs the same checks, builds every APK, writesSHA256SUMS.txt, and publishes a GitHub Release with all of them attached. The website's download page updates from that release automatically.
Both pipelines publish the same set, named from package.json's version:
| Asset | ABI | Use it for |
|---|---|---|
anifox-2.1.2-arm64-v8a.apk |
arm64-v8a | Almost every phone from ~2017 on (recommended) |
anifox-2.1.2-armeabi-v7a.apk |
armeabi-v7a | Older 32-bit devices |
anifox-2.1.2-x86_64.apk |
x86_64 | Emulators and x86 tablets |
anifox-2.1.2-universal.apk |
all | Any device, larger download |
app-release.apk |
all | Plain alias of the universal build for direct links |
To publish: bump package.json, android/app/build.gradle and
src/config/env.ts together, then git tag v2.1.0 && git push origin v2.1.0.
Add TMDB_API_KEY under Settings → Secrets and variables → Actions so
released builds ship with a working catalog.
├── android/ native project (com.itzfallenme.anifox)
│ └── app/
│ ├── libs/ bundled discord_partner_sdk.aar
│ └── src/main/
│ ├── cpp/ JNI bridge for the Discord Social SDK
│ └── java/com/itzfallenme/anifox/
│ ├── discord/ presence bridge + SDK adapter
│ ├── notifications/ episode-alert scheduling + receiver
│ ├── offline/ Media3 downloads, service, offline player
│ └── orientation/ landscape / portrait lock for the player
├── assets/anifox-logo.png app mark / launcher icon source
├── src/
│ ├── components/ billboard, cards, rows, update banner, UI kit
│ ├── config/ catalogs, embed servers, env, settings
│ ├── hooks/ feeds, details, library, downloads, updates
│ ├── navigation/ stack, tabs, floating glass tab bar
│ ├── screens/ Splash, Home, Browse, Catalog, Anime, Search,
│ │ Detail, Player, You, Settings, Watchlist,
│ │ Alerts, Downloads
│ ├── services/ TMDB, policy, library stores, alerts,
│ │ downloads, presence, update checker
│ ├── theme/ colours, type, spacing, glow
│ ├── types/ TMDB models + helpers
│ └── utils/ ad guard, stream probe, formatting
├── website/ Next.js marketing + APK downloads site
├── .github/workflows/ build and release pipelines
├── __tests__/ unit tests
└── App.tsx root component
- A server is chosen from
src/config/embedServers.ts; the default is EmbedWave, with seven alternatives (VidLink, VidSrc.pm, 2Embed.skin, 2Embed, Videasy, 111Movies, Vidsrc.su). - Its URL builder takes the TMDB id plus season/episode and returns the embed URL; autoplay follows the saved setting.
- The provider streams inside a WebView with fullscreen video enabled.
- The stage opens in landscape fullscreen when Landscape player is on (default). The header's fullscreen button toggles it by hand, Android back leaves fullscreen before it leaves the player, and closing the player hands the device back to the system orientation.
- A stall shows Try again / Next server, and the chips below the stage switch providers at any time.
Embed pages draw their own controls, so the app keeps a large fullscreen button in its own header (42 pt tile, 8 pt hit slop) instead of relying on the provider's cramped one. In fullscreen the panel is replaced by a slim bottom bar with Download, the provider chips and an exit button, so neither download nor server switching is ever out of reach.
The WebView is framed at the provider's native 16:9 inside a black stage
rather than stretched to the phone's 20:9 screen — that aspect mismatch is what
made the provider's fullscreen and server-name controls sit on top of each
other. The frame is measured with onLayout and centred, with notch insets
respected on both sides. Rotation goes through NativeModules.AnifoxOrientation
(android/.../orientation/OrientationModule.kt), which sets sensor landscape or
sensor portrait on the current activity; because MainActivity handles
configChanges, rotating never recreates the React instance, so playback and
downloads keep running.
src/utils/adBlock.ts keeps embeds watchable:
- no popunders — second windows are blocked and
window.open, dialogs andbeforeunloadtraps are neutralised before page scripts run; - no ad surfaces — known ad/tracker iframes and overlays are removed on load and on DOM mutations;
- contained navigation — store/messaging schemes and ad hosts are refused, and the top frame cannot be redirected away from the provider.
The player's Ad-free toggle disables the guard for providers that need the redirect (compatibility mode).
npm run typecheck
npm run lint
npm test
npx react-native bundle --platform android --dev false \
--entry-file index.js --bundle-output /tmp/index.android.bundle --assets-dest /tmp/assets58 tests cover the catalog policy, TMDB endpoints, embed URL builders, the ad guard, preference defaults, the library stores (watchlist, resume ordering, search dedupe, alert timing, date formatting), stream detection and download identity.
The Android build itself needs the SDK; CI runs it on every push.
Everything personal lives on the device: watchlist, history, searches, alerts, downloads and settings. No account, analytics or crash reporter is present. Requests go only to TMDB, the chosen embed provider, GitHub (update check) and Discord (only if presence is enabled).
AniFox does not host, upload or distribute video content. All titles, artwork and trademarks belong to their respective owners. This is a personal project, not affiliated with any streaming service, TMDB or Discord.
MIT © 2026 ItzFallenMe. See LICENSE.
