4.8 KiB
Subtitle Overlay Priming
Status: active Last verified: 2026-06-01 Owner: Kyle Yasuda Read when: debugging subtitle state or blank Linux/X11 overlay windows when the visible overlay is shown or recreated
Visible-overlay subtitle priming fills the overlay from mpv's current subtitle properties before waiting for the next live mpv subtitle event. This avoids a stale or blank overlay when the user manually shows the visible overlay while playback is already sitting on a subtitle.
On Linux/X11, visible-overlay show and later mpv bounds refreshes restore the Electron window shape
to the full current overlay bounds. Electron's BrowserWindow.setShape() applies a bounding shape,
not an input-only region; stale shapes can leave a mapped 1920x1080 overlay with smaller X11 shape
extents such as 800x600+0+0, so renderer and websocket subtitle state are correct while bottom
subtitles do not draw.
Entry Points
src/main.tscallsprimeCurrentSubtitleForVisibleOverlay()when manual visible-overlay show paths run.src/main.tscallsrestoreVisibleOverlayWindowShapeForShow()before visible-overlay show actions on Linux, andresetVisibleOverlayInputState()restores a full shape instead of applying an empty shape.src/main.tsalso restores the Linux/X11 shape after applying mpv overlay bounds, so a newly created 800x600 hidden Electron window cannot keep clipping after it is resized to mpv geometry.primeCurrentSubtitleForVisibleOverlay()delegates toprimeVisibleOverlaySubtitleFromMpv()insrc/main/runtime/current-subtitle-snapshot.ts.restoreVisibleOverlayWindowShapeForShow()delegates torestoreLinuxOverlayWindowShape()insrc/main/runtime/linux-overlay-window-shape.ts.- Inputs are callback deps, not globals:
getMpvClient,setCurrentSubText,getCurrentSubtitleData,consumeCachedSubtitle,onSubtitleChange,refreshCurrentSubtitle,emitSubtitle, optional secondary-subtitle callbacks, andlogDebug.
Primary Subtitle Flow
- Read the connected mpv client through
getMpvClient(). Exit if no connected client. - Request mpv
sub-text. On failure, log a[visible-overlay-subtitle-prime] failed to read sub-textdebug line and exit. - Normalize non-string
sub-textto'', then callsetCurrentSubText(text)so app state matches mpv before any overlay emission. - Empty text: call
onSubtitleChange(text), emit{ text, tokens: null }, then prime secondary subtitles. - Current cached payload: if
getCurrentSubtitleData()?.text === text, callemitSubtitle(payload)andrefreshCurrentSubtitle(text), then prime secondary subtitles. - Tokenization cache hit: call
consumeCachedSubtitle(text),onSubtitleChange(text), andemitSubtitle(cachedPayload), then prime secondary subtitles. - Cache miss: call
refreshCurrentSubtitle(text)and let normal tokenization emit the final payload.
In src/main.ts, both onSubtitleChange and refreshCurrentSubtitle pause
subtitlePrefetchService, notify it with onSeek(lastObservedTimePos), and then call the matching
subtitleProcessingController method. This gives the visible overlay priority over background
prefetch work and re-centers prefetch around the live playback time.
Emitted State
emitSubtitle(payload)maps toemitSubtitlePayload(payload), which sends the normal annotated subtitle payload to overlay windows and subtitle websocket listeners.- Secondary priming reads mpv
secondary-sub-text, stores it inmpvClient.currentSecondarySubText, and broadcastssecondary-subtitle:setto overlay windows. - If secondary
requestPropertyfails, the primary flow stays complete and only a debug line is written.
Linux/X11 Window Shape
restoreLinuxOverlayWindowShape()readsBrowserWindow.getBounds()and callssetShape()with one full-window rectangle:{ x: 0, y: 0, width, height }.- Restore the shape after
setBounds()/mpv geometry updates, not only before showing the overlay. Manual startup can create the hidden overlay at Electron's default 800x600 size before the window tracker applies the real mpv bounds. - Do not use
setShape([])as a passive reset for the visible overlay. On the tested X11/XWayland path, empty or stale bounding shapes produced invisible or clipped subtitles even though the overlay window remained mapped above mpv. - Pointer pass-through should continue to use
setIgnoreMouseEvents(true, { forward: true })and the Linux cursor-poll fallback, not bounding-shape clipping.
Config And Migration
No config or schema migration. This workflow reuses existing mpv properties, overlay IPC events, subtitle tokenization cache, and prefetch controls.