# WebSocket / Texthooker API & Integration
**Who this page is for:** developers and tinkerers who want to consume SubMiner's live subtitle stream from their own tools — a browser tab, an automation script, or another mpv plugin. If you just want subtitles in a browser tab for Yomitan, skip to [Texthooker Integration Guide](#texthooker-integration-guide); the rest is reference for building custom clients.
A *texthooker* is a page/tool that receives the text currently on screen so a dictionary extension (like Yomitan) can look words up. SubMiner ships its own texthooker UI and also broadcasts subtitle text over local WebSockets that any client can connect to.
SubMiner exposes a small set of local integration surfaces for browser tools, automation helpers, and mpv-driven workflows:
- **Subtitle WebSocket** at `ws://127.0.0.1:6677` by default for plain subtitle pushes.
- **Annotation WebSocket** at `ws://127.0.0.1:6678` by default for token-aware clients.
- **Texthooker HTTP UI** at `http://127.0.0.1:5174` by default for browser-based subtitle consumption.
- **mpv plugin script messages** for in-player automation and extension.
This page documents those integration points and shows how to build custom consumers around them.
## Quick Reference
| Surface | Default | Purpose |
| --- | --- | --- |
| `websocket` | `ws://127.0.0.1:6677` | Basic subtitle broadcast stream |
| `annotationWebsocket` | `ws://127.0.0.1:6678` | Structured stream with token metadata |
| `texthooker` | `http://127.0.0.1:5174` | Local texthooker UI with injected websocket config |
| mpv plugin | `script-message subminer-*` | Start/stop/toggle/status automation inside mpv |
## Enable and Configure the Services
SubMiner's integration ports are configured in `config.jsonc`.
```jsonc
{
"websocket": {
"enabled": "auto",
"port": 6677
},
"annotationWebsocket": {
"enabled": true,
"port": 6678
},
"texthooker": {
"launchAtStartup": true,
"openBrowser": false
}
}
```
### How startup behaves
- `websocket.enabled: "auto"` starts the basic subtitle websocket unless SubMiner detects the external `mpv_websocket` plugin.
- `annotationWebsocket` is independent from `websocket` and stays enabled unless you explicitly disable it.
- `texthooker.launchAtStartup` starts the local HTTP UI automatically.
- `texthooker.openBrowser` controls whether SubMiner opens the texthooker page in your browser when it starts.
If you use the [mpv plugin](/mpv-plugin), it can also start a texthooker-only helper process. The launcher derives the plugin's texthooker setting from your SubMiner config (`texthooker.launchAtStartup`) and injects it at runtime — there is no plugin config file to edit.
## Developer API Documentation
### 1. Subtitle WebSocket
Use the basic subtitle websocket when you only need the current subtitle line as plain text.
- **Default URL:** `ws://127.0.0.1:6677`
- **Transport:** local WebSocket server bound to `127.0.0.1`
- **Direction:** server push only
- **Client auth:** none
- **Reconnects:** client-managed
When a client connects, SubMiner immediately sends the latest subtitle payload if one is available. After that, it pushes a new message each time the current subtitle changes.
#### Message shape
```json
{
"version": 1,
"text": "無事",
"sentence": "無事",
"tokens": []
}
```
#### Field reference
| Field | Type | Notes |
| --- | --- | --- |
| `version` | number | Current websocket payload version. Today this is `1`. |
| `text` | string | Raw subtitle text. |
| `sentence` | string | Plain subtitle text with line breaks represented as `
`. No annotation spans or attributes. |
| `tokens` | array | Always empty on the basic subtitle websocket. |
### 2. Annotation WebSocket
Use the annotation websocket for custom clients that want the same structured token payload the bundled texthooker UI consumes.
- **Default URL:** `ws://127.0.0.1:6678`
- **Payload shape:** JSON payload with `text`, rendered `sentence` HTML, and token metadata
- **Primary difference:** this stream is intended to stay on even when the basic websocket auto-disables because `mpv_websocket` is installed
In practice, if you are building a new client, prefer `annotationWebsocket` unless you specifically need compatibility with an existing `websocket` consumer.
#### Message shape
```json
{
"version": 1,
"text": "無事",
"sentence": "無事",
"tokens": [
{
"surface": "無事",
"reading": "ぶじ",
"headword": "無事",
"startPos": 0,
"endPos": 2,
"partOfSpeech": "other",
"isMerged": false,
"isKnown": true,
"isNPlusOneTarget": false,
"isNameMatch": false,
"jlptLevel": "N2",
"frequencyRank": 745,
"className": "word word-known word-jlpt-n2",
"frequencyRankLabel": "745",
"jlptLevelLabel": "N2"
}
]
}
```
Each annotation token may include:
| Token field | Type | Notes |
| --- | --- | --- |
| `surface` | string | Display text for the token |
| `reading` | string | Kana reading when available |
| `headword` | string | Dictionary headword when available |
| `startPos` / `endPos` | number | Character offsets in the subtitle text |
| `partOfSpeech` | string | SubMiner token POS label |
| `isMerged` | boolean | Whether this token represents merged content |
| `isKnown` | boolean | Marked known by SubMiner's known-word logic |
| `isNPlusOneTarget` | boolean | True when the token is the sentence's N+1 target |
| `isNameMatch` | boolean | True for prioritized character-name matches |
| `frequencyRank` | number | Frequency rank when available |
| `jlptLevel` | string | JLPT level when available |
| `className` | string | CSS-ready class list derived from token state |
| `frequencyRankLabel` | string or `null` | Preformatted rank label for UIs |
| `jlptLevelLabel` | string or `null` | Preformatted JLPT label for UIs |
### 3. HTML markup conventions
The `sentence` field is pre-rendered HTML generated by SubMiner. Depending on token state, it can include classes such as:
- `word`
- `word-known`
- `word-n-plus-one`
- `word-name-match`
- `word-jlpt-n1` through `word-jlpt-n5`
- `word-frequency-single`
- `word-frequency-band-1` through `word-frequency-band-5`
SubMiner also adds tooltip-friendly data attributes when available:
- `data-reading`
- `data-headword`
- `data-frequency-rank`
- `data-jlpt-level`
If you need a fully custom UI, ignore `sentence` and render from `tokens` instead.
## Texthooker Integration Guide
### When to use the bundled texthooker page
Use texthooker when you want a browser tab that:
- updates live from current subtitles
- works well with browser-based Yomitan setups
- inherits SubMiner's coloring preferences and websocket URL automatically
Start it with either:
```bash
subminer texthooker
# or open the page immediately
subminer texthooker -o
```
or by leaving `texthooker.launchAtStartup` enabled.
### What SubMiner injects into the page
When SubMiner serves the local texthooker UI, it injects bootstrap values into `window.localStorage`, including:
- `bannou-texthooker-websocketUrl`
- coloring toggles for known/N+1/name/frequency/JLPT styling
- CSS custom properties for SubMiner's token colors
That means the bundled page already knows which websocket to connect to and which color palette to use.
### Build a custom websocket client
Here is a minimal browser client for the annotation stream:
```html