- Add changelog entries for the split-cour AniList season resolution fix and the subsync stream-track fixes - Document anime browser language filter, stream subtitle download-to-disk behavior, and series/season/episode metadata threading - Update domain ownership and knowledge-base catalog for the anime browser modules - Trim the credits table and clarify the M-Extension-Server licensing note in README
12 KiB
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), 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 <userData>/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 <userData>/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 <userData>/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
<userData>/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
alangis set toja,jpn,jp,japanesebefore 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
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 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 and TsukiHime modals open with Title, Season, and Episode already filled in, so a subtitle search is one keypress rather than a retype.
- AniList progress updates use those fields directly.