# Anime Browser Search anime sources, pick an episode, and play it in mpv with SubMiner's overlay and mining tools attached — the same way a local file or a Jellyfin stream works. Open it with `subminer anime`, with `SubMiner.AppImage --anime`, or from **Browse Anime** in the tray menu. The window stays open while you watch, so you can queue the next episode without reopening it. While the window is open, SubMiner shows a tray icon and — on macOS — appears in the Cmd+Tab switcher and the Dock (macOS ties the two together), so you can flip between the browser and mpv. SubMiner normally hides itself from the Dock because the subtitle overlay needs that to float above fullscreen video; it hides again when the window closes during playback. Launching an episode starts a full SubMiner playback session, the same as playing a local file: the overlay and mining tools attach, and the tray icon stays available. In standalone `subminer anime` mode, closing the window while a video is playing leaves playback running — reopen the browser from the tray (**Browse Anime**). The app only exits with the window when nothing is playing. ## How it works SubMiner does not implement any anime source itself. It runs **Aniyomi extension APKs** through a bundled JVM sidecar ([M-Extension-Server][mes]), asks the selected extension to resolve an episode, and hands the resulting URL to mpv. ``` extension APK → bridge (JVM) → { url, headers } → mpv → SubMiner overlay ``` Because the extension resolves the stream, whichever sources you install decide what is available. SubMiner only hosts them. ## Installing extensions **SubMiner ships no extension repositories and bundles no sources.** There is no default repository, no suggested list, and no discovery. Until you add one, the browser has nothing to search — that is deliberate, and it is what keeps SubMiner a neutral host rather than a distributor. There are two ways to add extensions. ### From a repository The window has three tabs — **Browse**, **Extensions**, and **Source settings** — and each one fills the window, so a long extension list is not squeezed in above the search results. Open the **Extensions** tab, paste a repository index URL, and choose **Add repository**. The URL must be `https` and point at a `.json` index file — `index.min.json` is the common Aniyomi name, but repositories are free to publish under another one (for example `video.min.json`). Anything else is rejected immediately rather than failing later. Everything before the file name is treated as the repository root, so `.apk` and icon URLs are resolved relative to it. Extensions your repositories offer but you do not have appear under **Available**, each with **Install**. Repositories are stored in config under `anime.repos`, so you can also manage them there and keep them in a dotfile. A repository index lists every language it knows about, which is far more than any one person reads, so the **Available** list has a language chip row above it. Pick one or more languages to narrow it, or **All** to clear the filter; picking a language replaces **All** rather than sitting beside it. Rows name the language in full ("Japanese" rather than `ja`), extensions whose sources span languages are grouped under **Multi-language**, and the Available heading counts how many of the offered extensions the filter leaves. ### Managing what is installed The Extensions tab opens with an **Installed** section listing everything in the extensions directory, with the sources each one provides and a **Remove** button. It is built from the directory rather than from a repository, so an extension you dropped in by hand — or one whose repository you have since removed — is still listed and still removable. **Update** appears next to an extension a configured repository still carries; it downloads the current version over the existing APK. ### From a file Drop Aniyomi `.apk` files into the extensions directory, shown at the top of the Extensions tab. It defaults to `/anime-extensions` — on macOS, `~/Library/Application Support/SubMiner/anime-extensions` — and can be moved with `anime.extensionsDir`. A single APK may provide several sources; each appears separately in the **Source** picker. Extensions that fail to load are listed in the Installed section with the reason, so a broken APK is visible rather than silently missing. An extension that fails to load is skipped rather than blocking the others, so one bad APK will not hide the rest. ## Searching every source at once With more than one source installed, the **Source** picker gains an **All sources** entry. Searching with it selected runs the query against every installed source at once, and each source's results appear the moment that source answers — a fast source is on screen while a slow one is still resolving. The status bar counts sources as they finish (`Searching… 3/5 sources · 42 results`). Each cover is labelled with the source it came from, and opening one always queries that source, whatever the picker says afterwards. A source that errors is named in the status bar and the rest still show their results; one extension that needs a login cannot blank the grid. If every source fails, the first error is shown in full. Typing a new search while one is still running simply starts over: results from the superseded search are discarded, even if its sources answer late. Source settings belong to a single extension, so the **Source settings** tab asks you to pick one while **All sources** is selected. ## Settings | Key | Purpose | | ------------------------ | -------------------------------------------------------------------- | | `anime.repos` | Repository index URLs. Empty by default. | | `anime.extensionsDir` | Where APKs are read from. Empty uses `/anime-extensions`. | | `anime.preferredQuality` | Preferred stream label, matched as a substring (for example `1080`). | ## Source settings Most extensions need configuration before they return anything — a server address and credentials, a preferred quality, a language filter. Open the **Source settings** tab to edit them. Changes save as you make them and persist across restarts in `/anime-source-preferences.json`. Each save is handed back to the extension, so it can react: the Jellyfin source logs in when the address and password land, then fills in its media-library picker. Password-like fields are masked. Because that file can hold credentials, it is written with owner-only permissions. ## The bridge The first launch downloads a platform bundle (~130 MB) containing the server and a matching Java runtime, so no system JDK is required. SubMiner pins one upstream release tag and the SHA-256 of each asset it has verified; the download is checked against that hash before anything runs, then unpacked into `/anime-bridge` and reused after that. Progress appears in the banner at the top of the window. The bridge stays running while the window is open. Resolved video URLs point at its own loopback proxy so the extension's cookies and headers apply, which means those URLs stop working once it exits — the window keeps it alive for the whole session. If the bridge dies anyway (killed by hand, crashed, or stopped mid-operation), the exit is detected and named in the status bar, and the next request starts a new one. Playback already in flight still ends when its stream URL dies, but the browser recovers without an app restart. Two known limits: - There is no Android WebView, so extensions that need one (typically for Cloudflare challenges) will fail with an error from the source. - Bundles are published for macOS (arm64, x64), Linux (x64), and Windows (x64), but only the ones we have hashed ourselves will run: currently macOS arm64 and Linux x64. The rest stop with "No pinned checksum for …" until a maintainer verifies them. Other platforms are unsupported outright. ## Playback Selecting an episode resolves the best available stream, applies the source's required headers as mpv `http-header-fields`, and loads it. The headers are readable back off mpv, so Anki card audio and screenshots fetch correctly too. HLS streams are routed through a small local proxy before mpv sees them. Some hosts disguise their video segments by prepending a fake image header (a real 1x1 PNG) so scrapers back off; Aniyomi's own player strips this, but ffmpeg probes the segment as a picture and playback dies with "no audio or video data played". The proxy scans each segment for the first genuine MPEG-TS packet run and drops whatever junk sits in front of it. Segments that are not TS (fMP4, subtitles, encryption keys) pass through untouched, and direct-file streams skip the proxy entirely. "Playing" in the status bar means playing: after handing mpv the stream, SubMiner waits until mpv actually configures a video output before reporting success. If mpv gives up instead — a dead host, an undecodable stream — the browser shows mpv's error rather than pretending playback started (a failed load leaves no mpv window, because the player idles windowless). ### Japanese audio, and switching tracks Sources often return a dub and the original audio as two separate entries — or as two audio tracks of one stream — and the dub is frequently listed first. SubMiner always aims at the Japanese audio: - Entries labelled as a dub are skipped as long as another entry exists. This outranks `anime.preferredQuality`: a 1080p dub is the wrong file, not a better one. If every entry is a dub, it still plays. - mpv's `alang` is set to `ja,jpn,jp,japanese` before the file loads, so a stream carrying several audio tracks starts on the Japanese one. With no Japanese track, mpv falls back to the first one as usual. - Any audio or subtitle tracks the extension supplies separately are added to mpv with `audio-add` / `sub-add`, tagged with their language, and the Japanese one is selected. The primary subtitle slot is reserved for Japanese — it is what the overlay mines. A source that only carries, say, English subtitles does not get them promoted to primary; instead the track is added with a normalized language tag (`English` → `en`), and the regular [dual-subtitle settings](configuration.md) apply: with `secondarySub.autoLoadSecondarySub` enabled and the language listed in `secondarySub.secondarySubLanguages`, it is picked up as the secondary subtitle, exactly as it would be for a local file. Every track is added, including the ones that are not selected, so all of them appear in mpv's track menu and can be switched by hand while watching (`#` cycles audio, `j` cycles subtitles by default). The extension hands over subtitle tracks as URLs, but SubMiner downloads each one to a temporary directory and gives mpv the local file. mpv is happy either way; [Subsync](/troubleshooting#subtitle-sync-subsync) is not, because alass needs a file on disk to use as the timing reference. A track that fails to download falls back to its URL so the episode still plays, the format is detected from the file's own content rather than its URL, and the directory is removed when the next episode starts or the app exits. ### Series, season, and episode The episode's identity travels with it instead of being guessed back out of the stream URL, which carries nothing but a proxy path and a file extension. The title and episode label from the source's own listing are split into series, season, and episode number once, at launch, and everything downstream reads those fields: - mpv's title reads `Series S03E04 - Episode Name`. - Stats group by series, and rewatching an episode reuses its entry instead of creating a new one. - The [Jimaku](/jimaku-integration) and [TsukiHime](/tsukihime-integration) modals open with Title, Season, and Episode already filled in, so a subtitle search is one keypress rather than a retype. - [AniList](/anilist-integration) progress updates use those fields directly. [mes]: https://github.com/1Selxo/M-Extension-Server