feat(overlay): add subtitle selection modal and Jellyfin 12 fixes

- Add an optional subtitle selection modal for primary/secondary mpv tracks (subtitleSelection.enabled, g-s sequence shortcut) with key-sequence conflict handling
- Authenticate Jellyfin URLs with the ApiKey query, answer remote keep-alives, clear now-playing on stop, and restore episode titles in Anki misc info
- Honor the configured mpv executable when Jellyfin starts playback via a shared mpv-process launcher
- Bump electron-builder to 26.16.1
- Condense and reconcile changelog fragments; update config example and docs
This commit is contained in:
2026-09-22 18:51:59 -07:00
114 changed files with 2404 additions and 381 deletions
+14
View File
@@ -119,6 +119,7 @@ import { IPC_CHANNELS } from './shared/ipc/contracts';
import type { SubtitleGenerationProgress } from './shared/subtitle-generation';
const overlayLayer = resolveOverlayLayerFromArgv(process.argv);
const onSubtitleSelectionOpen = createQueuedIpcListener(IPC_CHANNELS.event.subtitleSelectionOpen);
const onSubtitleGenerationOpen = createQueuedIpcListener(IPC_CHANNELS.event.subtitleGenerationOpen);
type EmptyListener = () => void;
@@ -501,6 +502,11 @@ const electronAPI: ElectronAPI = {
) as Promise<boolean>,
getSubtitleStyle: (): Promise<SubtitleStyleConfig | null> =>
ipcRenderer.invoke(IPC_CHANNELS.request.getSubtitleStyle),
onSubtitleSelectionOpen,
getSubtitleSelection: () => ipcRenderer.invoke(IPC_CHANNELS.request.getSubtitleSelection),
applySubtitleSelection: (
request: import('./shared/subtitle-selection').SubtitleSelectionRequest,
) => ipcRenderer.invoke(IPC_CHANNELS.request.applySubtitleSelection, request),
onSubsyncManualOpen: onSubsyncManualOpenEvent,
runSubsyncManual: (request: SubsyncManualRunRequest): Promise<SubsyncResult> =>
ipcRenderer.invoke(IPC_CHANNELS.request.runSubsyncManual, request),
@@ -618,6 +624,14 @@ const electronAPI: ElectronAPI = {
reportOverlayContentBounds: (measurement: OverlayContentMeasurement) => {
ipcRenderer.send(IPC_CHANNELS.command.reportOverlayContentBounds, measurement);
},
onSessionBindingsChanged: (
callback: (bindings: import('./types').CompiledSessionBinding[]) => void,
) => {
ipcRenderer.on(
IPC_CHANNELS.event.sessionBindingsChanged,
(_event, bindings: import('./types').CompiledSessionBinding[]) => callback(bindings),
);
},
onConfigHotReload: (callback: (payload: ConfigHotReloadPayload) => void) => {
ipcRenderer.on(
IPC_CHANNELS.event.configHotReload,