A lightweight, cross-platform system-tray companion for Shoko Server that plays your anime in mpv, reports watch progress back to the server, (optionally) integrates with the Media Session plugin for remote playback control, 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
- Plays in mpv — catches
shoko://URLs, launches mpv, plays the stream. - Playback events — periodic position updates, start/pause/resume/stop events, auto-marks watched at ≥ 97.5%.
Play(new media) andResume(unpause current) are separate commands. - Resume support — pre-fetches resume position and seeks mpv on file load.
- Server connections — multiple connections with route fallback; auto-discovered from the
shoko://URLs or manually configured. - Managed folder mappings — resolves "Open Folder" actions from the web UI to local paths.
- Discord Rich Presence — optionally shows series/episode info in Discord.
- Cross-platform tray app — Windows, Linux, macOS. Native notifications.
When the Media Session plugin is installed on the Shoko server, the companion can connect to its SignalR hub for remote playback control. This enables:
- Other devices/clients for the user to see what's playing and send play/pause/seek/stop commands
- Live state updates pushed via SignalR alongside REST-based scrobbling
- Per-connection auto-connect with manual Connect/Disconnect in settings
| 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. |
| 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/shoko-companion/settings.json if XDG_CONFIG_HOME is not set) |
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). |
PlaybackSyncingBehavior |
enum | AfterPlayback |
How aggressively to sync playback events: AfterPlayback (stop only), OnEveryEvent (play/pause/resume/stop), LiveSync (everything + periodic progress). |
MediaSessionAutoConnectId |
Guid | null |
Server connection ID to auto-connect for the Media Session SignalR hub. Null disables auto-connect. |
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. |
PrivacyModeForRestrictedContent |
bool | false |
Auto-activate privacy mode for restricted 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. |
PrivacyMode |
bool | false |
Global privacy mode master switch. When enabled, sub-toggles below restrict what is shared. When disabled, individual feature toggles control behavior independently. |
PrivacyModeHideDiscord |
bool | false |
Hide anime title and poster from Discord presence; show generic "Watching Anime" instead. Only effective when PrivacyMode is true. |
PrivacyModeHideMediaPlaybackInfo |
bool | false |
Strip title, stream URL, file ID, and thumbnail from Media Session hub state. Basic state (Playing/Paused) and position/duration still reported. |
PrivacyModeDisableRemoteControl |
bool | false |
Disallow remote Play/Pause/Resume/Seek/Stop via Media Session API. |
PrivacyModeDisableRemoteScreenshots |
bool | false |
Disallow remote screenshot capture via Media Session API. |
PrivacyModeDisablePlaybackEvents |
bool | false |
Disable all scrobble/sync events to the Shoko server. |
PrivacyModeForRestrictedContent |
bool | false |
When true, restricted (adult) content automatically activates privacy mode using the configured sub-toggles. |
PrivacyModeMpvKeybinding |
string | Ctrl+p |
mpv keybinding to toggle privacy mode during playback. Sent via JSON IPC; no Lua scripts needed. |
MediaSessionEnabled |
bool | false |
Global enabled switch for Media Session API integration. When disabled, auto-connect won't run and sub-controls are greyed out. |
ScreenshotSubtitleBehavior |
enum | OnlyWhenPaused |
Controls subtitle visibility during screenshots: Disabled (subs always visible), OnlyWhenPaused (hide subs only when paused — avoids visual flicker), Always (hide subs on every capture). |
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