A lightweight, cross-platform system-tray companion for Shoko Server that plays your anime in mpv, reports watch progress back to the server, and (optionally) shows what you're watching as Discord Rich Presence.
- Binary:
shoko-companion - Display name: Shoko Companion
- Framework: .NET 10 · Avalonia 11
- Platforms: Windows (x64), Linux (x64 / arm64), macOS (arm64)
- Repository: github.com/ShokoAnime/Shoko.Companion
- You click "Send to External Player" in the Shoko Web UI (wording to be revisited). The browser opens a
shoko:URL. - The companion catches that URL, launches mpv, and starts playback of the generated playlist.
- While you watch, it syncs playback events to the server (resume position + watched state), mirroring the server's "watched at ≥ 97.5%" rule.
- It optionally drives Discord Rich Presence and shows native OS notifications for important events.
- It lives in the system tray — right-click for settings, Discord toggle, folder management, and exit.
- Plays in mpv — catches
shoko://URLs from the web UI, launches mpv, plays the stream. - Playback events — periodic position updates, start/pause/resume/stop events, auto-marks watched at ≥ 97.5%.
- Resume support — pre-fetches resume position and seeks mpv on file load.
- Server connections — multiple connections with route fallback; auto-discovers from the first URL.
- Managed folder mappings — resolves "Open Folder" actions from the web UI to local paths.
- Discord Rich Presence — shows series/episode info (optional, toggle from tray).
- Cross-platform tray app — Windows, Linux, macOS. Native notifications.
| Requirement | Notes |
|---|---|
| A running Shoko Server | v3 API reachable from this machine. |
| mpv | Must be installed and discoverable (in PATH or a common install location). mpv.io |
| .NET 10 runtime | Only if you run the framework-dependent build. The single-file self-contained build bundles the runtime. |
| Discord desktop client | Only if you enable Rich Presence. |
# from the repo root
dotnet build Shoko.Companion/Shoko.Companion.csproj
# run (framework-dependent)
dotnet run --project Shoko.Companion/Shoko.Companion.csproj# Self-contained single-file (bundles .NET runtime)
dotnet publish Shoko.Companion/Shoko.Companion.csproj -c Release -r win-x64 -p:PublishSingleFile=true -p:SelfContained=true -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=true
# Linux x64 / arm64 / macOS arm64 (change -r accordingly)
dotnet publish ... -r linux-x64 -p:PublishSingleFile=true -p:SelfContained=true -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=true
dotnet publish ... -r linux-arm64 -p:PublishSingleFile=true -p:SelfContained=true -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=true
dotnet publish ... -r osx-arm64 -p:PublishSingleFile=true -p:SelfContained=true -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=trueAvalonia extracts its native libraries (Skia/HarfBuzz) to a temp directory on first run — this is expected.
Settings live in a JSON file under a per-platform config root:
| Platform | Location |
|---|---|---|
| Windows | %APPDATA%\shoko-companion\settings.json |
| macOS | ~/Library/Application Support/shoko-companion/settings.json |
| Linux | $XDG_CONFIG_HOME/shoko-companion/settings.json (falls back to ~/.config/...) |
Logs are written next to it under logs/.
Relocate the entire config/log root two ways (precedence: --home → env var → platform default):
# --home argument
shoko-companion --home /path/to/home
# --force: bypass the running-instance check (e.g. after a crash left a stale lock file)
shoko-companion --force
# SHOKO_COMPANION_HOME environment variable
SHOKO_COMPANION_HOME=/path/to/dev-home dotnet run --project Shoko.Companion/Shoko.Companion.csproj| Key | Type | Default | Description |
|---|---|---|---|
Connections |
array | [] |
List of server connections (see below). |
MpvPath |
string | null |
Path to the mpv binary. Auto-discovered and saved on first use. |
MpvFullScreen |
bool | true |
Launch mpv in full-screen mode. |
OnNewUrlAction |
string | "Append" |
When a new shoko: URL arrives while playing: "Replace" (stop + start new), "Ignore" (silently discard), or "Append" (add to mpv playlist). |
PlaybackSyncingEnabled |
bool | true |
Master toggle for all playback event syncing (start/end/pause/resume). |
LivePlaybackSyncingEnabled |
bool | false |
Periodic position updates during playback (requires PlaybackSyncingEnabled). |
SyncUserDataInitialSkipEventCount |
int | 3 |
Number of initial non-pause events to skip after starting, letting the player settle. |
SyncUserDataLiveScrobbleTickThreshold |
int | 3 |
Number of position events accumulated before sending a live progress update. |
SyncUserDataLivePositionThresholdMs |
int | 5000 |
Minimum position change (ms) required to trigger a live progress update. |
SkipRestrictedContent |
bool | true |
Skip playback event syncing for restricted/adult content. |
ScrobbleIntervalMs |
int | 10000 |
How often to send progress updates (min 5000). |
DiscordEnabled |
bool | false |
Enable Discord Rich Presence. |
DiscordClientIdOverride |
string | null |
Override for the built-in Discord app ID. |
DiscordIdlePresence |
bool | false |
Show "Browsing" → "Idle" presence when nothing is playing. |
DiscordPrivacyMode |
bool | false |
Hide anime title and poster from Discord presence; show generic "Watching Anime" instead. |
AlwaysUseConfiguredRoutes |
bool | false |
Skip direct URL reachability check; always use the connection's route table. |
LogLevel |
string | "Info" |
One of: Trace, Debug, Info, Warn, Error. |
Each connection object in Connections:
| Key | Type | Default | Description |
|---|---|---|---|
Name |
string | "" |
Display name for the connection (user-editable). |
Routes |
array | [] |
Ordered list of route objects (see below). Probed in order. |
ApiKey |
string | null |
API key for this server. |
IgnoredManagedFolderIds |
array | [] |
Managed folder IDs the user has silenced. |
ManagedFolderMappings |
array | [] |
Local path mappings for the server's managed folders. |
CachedManagedFolders |
array | null |
Cached folder metadata from the server (not persisted). |
Each route object in Routes:
| Key | Type | Default | Description |
|---|---|---|---|
BaseUrl |
string | "" |
Host, port, and optional sub-path (e.g. server:8111 or 192.168.1.5:8111/subpath). No protocol prefix. |
UseHttps |
bool | false |
Whether to connect via HTTPS. |
You can edit settings.json by hand (the app hot-reloads it) or via the tray menu.
shoko: URL ──▶ DispatchUrl ──┬─▶ PlaybackCoordinator
│ │
│ ┌─────┼────────┬──────────────┐
│ ▼ ▼ ▼ ▼
│ Api Mpv Discord Notifications
│ Client IPC Presence
│
└─▶ FolderActionHandler
│
▼
File Manager
DispatchUrl(inApp) parses the incoming URL and routesplaytoPlaybackCoordinatorandopen-foldertoFolderActionHandler.PlaybackCoordinatororchestrates everything: resolves the playlist JSON, pre-fetches resume position, forwards the stream URL to mpv, observes mpv state, and syncs playback events.MpvIpcClientspeaks mpv's JSON IPC and raises typed property/event callbacks.ShokoApiClientwraps the v3 endpoints (/api/auth,/api/v3/Playlist/Generate,/api/v3/File/{id}/UserData,/api/v3/File/{id}/Scrobble).ShokoUrlParserhandles both legacy and new URL formats.FolderActionHandlerresolves managed folder IDs to local paths and opens the OS file manager.SingleInstanceManagerensures only one instance runs and forwards URLs to it.
See USAGE.md for the step-by-step user guide.
The repo ships VS Code targets (.vscode/launch.json):
- Launch Shoko.Companion — plain run (shows the connection setup window on first run).
- Launch Shoko.Companion (with URL) — passes a sample
shoko:URL so you can step through the full flow.
Both use SHOKO_COMPANION_HOME=${workspaceFolder}/companion-dev so debugging stays isolated from your real config.
With the debug companion running, right-click the tray icon and choose
Register URL Scheme. This registers the debug executable (with its
--home <dev-home> baked in) as the shoko:// handler.
Whenever a shoko:// link is clicked in your browser, the registered
handler launches a secondary instance that immediately forwards the URL
to the running debug session via the named pipe and exits — a lightweight
trampoline. If nothing is running, one starts up.
When you're done, unregister to restore the production handler — either from the tray menu or the command line:
dotnet run --project Shoko.Companion/Shoko.Companion.csproj -- unregisterRun the tests:
dotnet test Shoko.Companion.Tests/Shoko.Companion.Tests.csprojShoko Companion would not exist without these amazing projects:
- Shoko Server — the anime metadata server that powers the entire ecosystem. This companion is just a sidekick.
- mpv — the best media player, with a beautiful JSON IPC that makes programmatic control a joy.
- Avalonia — cross-platform UI framework that lets us target Windows, Linux, and macOS from one codebase. Underpinned by Skia and HarfBuzz.
- DiscordRichPresence by Lachee — clean C# bindings for Discord's Rich Presence SDK.
- NLog — structured logging that matches the server's JSONL format.
- Newtonsoft.Json — the de facto JSON library for .NET.
- xunit and Moq — testing and mocking, keeping things solid.
- .NET — the runtime and ecosystem that makes cross-platform desktop apps viable.
MIT © Shoko