The application for watch anime and read manga for Android, iOS, MacOS, Windows, Linux and Web.
π± Hoyomi β Your Ultimate Anime & Manga Companion
Hoyomi
Hoyomi is a modern, open-source app to watch anime and read manga in one seamless experience. Designed to be lightweight, fast, and fully cross-platform, Hoyomi runs smoothly on Android, iOS, Linux, macOS, Windows, and even on the web.
[!WARNING]
App is development
Multiple service support. Please check this:
- lib/core_services/comic/services - Service for comic, manga...
- lib/core_services/eiga/services - Service for eiga, film...
typescript. Check out the example here hoyomibridgets/example or hoyomi-plugin-animehay
β¨ Features
- π₯ Stream anime with subtitle support (custom fonts, colors, styles)
- π Read manga from multiple sources in a clean, scrollable reader
- π Multi-platform: Mobile, desktop & web support out of the box
- π¨ Highly customizable UI: Dark/light themes, font settings, subtitle styles
- π¬ Offline support: Cache and view anime/manga offline
(development) - π§© Plugin-friendly: Extend functionality with community-built integrations
- π Privacy first: No tracking, no ads, no data collection
π Download
Get the app from our releases page.iOS Sideloading Sources
If your device runs iOS 14.0 beta 2 β 16.6.1, 16.7 RC (20H18), or 17.0, youβre eligible to be a first-class citizen of TrollStore π. Install it for free, then enjoy Hoyomi with permanent installation β no resigning, no revokes, forever free.
π‘οΈ Open Source & Community Driven
Hoyomi is fully open-source, actively maintained by the community. We welcome contributions and ideas. Youβre not just a user β youβre part of the project.
π¦ Available On
- β Android
- β iOS
- β Windows (.exe)
- β Linux (.deb, .AppImage)
- β macOS (.dmg)
- β Web (PWA)
π Privacy & EULA
Hoyomi does not collect, store, or share your personal data. Your activity stays on your device. See our full Privacy Policy and EULA for details.
β€οΈ Built with Flutter. Made with love in Japan-inspired spirit.
Screenshot preview
Support platform
- [x] Android
- [x] Isar for android SDK <= 23
- [x] iOS side via TrollStore
- [x] iOS side by other methods
Creating New Plugins
Hoyomi's architecture allows for easy extension through custom plugins written in TypeScript. You can create new comic or eiga services by following these general steps:
- Define your service interface: Create a TypeScript file that defines the methods and data structures for your comic or eiga service, adhering to the
hoyomibridgetsconventions. - Implement the service logic: Write the actual logic for fetching data, parsing content, and handling interactions within your TypeScript service.
- Register your plugin: Use the
hoyomibridgetsto register your implemented service, making it available to the Hoyomi application.
// exampleeigaplugin.ts
import {
ABEigaService,
createOImage,
registerPlugin,
StatusEnum,
type EigaCategory,
type EigaEpisode,
type EigaEpisodes,
type EigaHome,
type MetaEiga,
type ServerSource,
type ServiceInit,
type SourceVideo
} from "hoyomibridgets";
class MyCustomEigaService extends ABEigaService { override init: ServiceInit = { name: "My Custom Eiga", faviconUrl: createOImage("https://example.com/favicon.ico"), rootUrl: "https://example.com" };
async getURL(eigaId: string, chapterId?: string): Promise<string> { // Logic to get the URL for a specific eiga or episode return https://example.com/eiga/${eigaId}; }
async home(): Promise<EigaHome> { // Logic to fetch home page data (categories, popular eiga, etc.) return { categories: [ { name: "Popular Eiga", items: [ { name: "Example Eiga 1", eigaId: "example-1", image: createOImage("https://example.com/image1.jpg") } ] } ] }; }
async getCategory(params: { categoryId: string; page: number; filters: { [key: string]: string[] | null }; }): Promise<EigaCategory> { // Logic to fetch eiga within a specific category return { name: "Category Name", url: "", items: [], page: params.page, totalItems: 0, totalPages: 0 }; }
async getDetails(eigaId: string): Promise<MetaEiga> { // Logic to fetch detailed information about an eiga return { name: "Example Eiga Details", image: createOImage("https://example.com/details.jpg"), status: StatusEnum.Ongoing, genres: ["Action", "Adventure"], description: "A brief description of the eiga.", seasons: [] }; }
async getEpisodes(eigaId: string): Promise<EigaEpisodes> { // Logic to fetch episodes for an eiga return { episodes: [ { name: "Episode 1", episodeId: "ep-1" } ] }; }
async getSource(params: { eigaId: string; episode: EigaEpisode; server?: ServerSource; }): Promise<SourceVideo> { // Logic to get video source for an episode return { src: "https://example.com/video.mp4", url: "https://example.com/video.mp4", type: "video/mp4" }; }
async search(params: { keyword: string; page: number; filters: { [key: string]: string[] | null }; quick: boolean; }): Promise<EigaCategory> { // Logic to search for eiga return { name: Search results for "${params.keyword}", url: "", items: [], page: params.page, totalItems: 0, totalPages: 0 }; } }
registerPlugin(MyCustomEigaService);
Here's a simplified example of a Comic plugin:
// examplecomicplugin.ts
import {
ABComicService,
ComicModes,
createOImage,
registerPlugin,
StatusEnum,
type ComicCategory,
type ComicHome,
type MetaComic,
type OImage,
type ServiceInit
} from "hoyomibridgets";
class MyCustomComicService extends ABComicService { override init: ServiceInit = { name: "My Custom Comic", faviconUrl: createOImage("https://example.com/comic-favicon.ico"), rootUrl: "https://example.com/comic" };
async getURL(comicId: string, chapterId?: string): Promise<string> { // Logic to get the URL for a specific comic or chapter return https://example.com/comic/${comicId}; }
async home(): Promise<ComicHome> { // Logic to fetch home page data (categories, popular comics, etc.) return { categories: [ { name: "Popular Comics", items: [ { name: "Example Comic 1", comicId: "comic-1", image: createOImage("https://example.com/comic1.jpg") } ] } ] }; }
async getCategory(params: { categoryId: string; page: number; filters: { [key: string]: string[] | null }; }): Promise<ComicCategory> { // Logic to fetch comics within a specific category return { name: "Comic Category Name", url: "", items: [], page: params.page, totalItems: 0, totalPages: 0 }; }
async getDetails(comicId: string): Promise<MetaComic> { // Logic to fetch detailed information about a comic return { name: "Example Comic Details", image: createOImage("https://example.com/comic-details.jpg"), status: StatusEnum.Ongoing, genres: ["Fantasy", "Adventure"], description: "A brief description of the comic.", chapters: [ { name: "Chapter 1", chapterId: "ch-1", time: new Date(), order: 1, } ], lastModified: new Date() }; }
async getPages(comicId: string, chapterId: string): Promise<OImage[]> { // Logic to get image pages for a specific chapter return [ createOImage("https://example.com/comic/page1.jpg"), createOImage("https://example.com/comic/page2.jpg"), ]; }
async search(params: { keyword: string; page: number; filters: { [key: string]: string[] | null }; quick: boolean; }): Promise<ComicCategory> { // Logic to search for comics return { name: Search results for "${params.keyword}", url: "", items: [], page: params.page, totalItems: 0, totalPages: 0 }; }
getComicModes(comic: MetaComic): ComicModes { // Define how the comic should be read (e.g., leftToRight, rightToLeft, webtoon) return ComicModes.leftToRight; } }
registerPlugin(MyCustomComicService);
Todo
- [ ] Add background image for
details_comic - [ ] Add information
bookfor reader - [ ] Fix logic fake page
- [ ] Page eiga details
- [ ] Fix zoomer read manga
- [ ] Responsive for video player
- [ ] AppBar all page
- [ ] API comment for eiga
- [ ] API follow anime
- [ ] API notification
- [ ] A11y manga reader
- [ ] API playlist
- [ ] API playlist online
- [ ] Search icon for all section
- [ ] Bottom sheet show all options
- [ ] Add multiple server in eiga
Development
Prerequisites
Step 1: Set up the Firebase project
The first step is to set up the Firebase project and enable Google sign-in. if you already have a flutter project, you can skip this step.- Go to the Firebase console and create a new project.
- Click on the
Authenticationlink in the left-hand menu, then click on theSign-in Methodtab. - Enable the
Googlesign-in method.
Step 2: Configure the OAuth client
You need your application's client ID and Secret from Google Cloud Console to enable Google sign-in. If youβve it already then skip this step.- To get the client ID and secret, follow the steps from the given link.
- Choose
a web application. - In the
Authorized redirect URIsandAuthorized JavaScript origin, enter the URLhttp://localhost
Setup Serverless
To deploy the serverless application, you need to set up a serverless provider. Here are the steps to set up Deno:Step 1: Configure the Firebase Admin
Please gotoProject Settings
- Click to
Generate new private key - Paste file download to
serverless/service-account-key.json
Step 2: Configre the Postgres database
The server required database for working- Run
cd serverless - Add
DATABASE_URLfromsetting projectto.env
Setup Application
Step 1 (Required in mobile platform)
[!TIP]>
This project depends Firebase. Please first run
> >> flutterfire configure
and configuring file/android/app/google-services.json,/ios/Runner/GoogleService-Info.plist(two file auto create byflutterfire)
/android/app/google-services.jsonrequired for Android/ios/Runner/GoogleService-Info.plistrequired for iOS
[!TIP]>
NOTE (If you development for iOS)>
Please editCFBundleURLSchemesinios/Runner/Info.plist
Step 2 (Required in desktop platform)
Goto https://console.cloud.google.com/apis/credentials and getClient ID and Client secret from OAuth 2.0 Client IDs (Use Web application)
Set to .env
GOOGLECLIENTID=<Client ID> GOOGLECLIENTSECRET=<Client secret> Step 3
Set to.env BASEAPIGENERAL=<URL base API general serverless>
GitHub Actions
To release the application, the following secrets must be provided:
General
ENV_CONTENT- Content of the.envfile. (not encode base64)
For Android
KEYSTORE_CONTENT- Base64-encoded content of thekeystore.jksfile.KEYSTORE_PASSWORD- Password used to sign thekeystore.jksfile.KEYSTORE_ALIAS- Alias used to sign thekeystore.jksfile.GOOGLESERVICESJSON- Base64-encoded content of thegoogle-services.jsonfile (automatically generated byflutterfire).
For iOS
GOOGLESERVICEINFO_PLIST- Base64-encoded content of theGoogleService-Info.plistfile (automatically generated byflutterfire).
Note: Thegoogle-services.json(for Android) andGoogleService-Info.plist(for iOS) files are automatically created when you run theflutterfire configurecommand during Firebase setup.