fix(overlay): poll cheap playback state in the open subtitle sidebar (#290)

This commit is contained in:
sudacode authored and GitHub committed 2026-10-11 00:35:27 -07:00
1 parent 7108e086ba
commit a6d96e9966
14 files changed
+286 -108

No files matched your search

+44 -76
View File
@@ -92,8 +92,10 @@ import type {
SessionActionDispatchRequest,
SecondarySubMode,
SubtitleData,
SubtitleCue,
SubtitleMiningContext,
SubtitlePosition,
SubtitleSidebarPlaybackState,
WindowGeometry,
} from './types';
import { OPEN_ANKI_CARD_ACTION_ID } from './types';
@@ -2047,6 +2049,33 @@ const linuxOverlayModeRuntime = createLinuxOverlayModeRuntime({
logDebug: (message) => logger.debug(message),
});
let subtitleSidebarRequestedOpen = false;
let subtitleSidebarCuesRef: SubtitleCue[] | null = null;
let subtitleSidebarCuesRevision = 0;
/**
* Cheap sidebar state polled ~12x/s while the sidebar is open. Reads only cached state, no
* mpv round-trips. `cuesRevision` bumps whenever a new parsed cue array is swapped in, so a
* re-parse of the same source still tells the renderer to fetch the full snapshot.
*/
function getSubtitleSidebarPlaybackState(): SubtitleSidebarPlaybackState {
if (appState.activeParsedSubtitleCues !== subtitleSidebarCuesRef) {
subtitleSidebarCuesRef = appState.activeParsedSubtitleCues;
subtitleSidebarCuesRevision += 1;
}
return {
sourceKey: JSON.stringify([
appState.activeParsedSubtitleMediaPath,
appState.activeParsedSubtitleSource,
]),
cuesRevision: subtitleSidebarCuesRevision,
currentTimeSec: appState.mpvClient?.currentTimePos ?? null,
currentSubtitle: {
text: appState.currentSubText,
startTime: appState.mpvClient?.currentSubStart ?? null,
endTime: appState.mpvClient?.currentSubEnd ?? null,
},
};
}
const SEEK_THRESHOLD_SECONDS = 3;
const EXPLICIT_SEEK_INTENT_TTL_MS = 2000;
let explicitSeekIntentExpiresAtMs = 0;
@@ -5850,26 +5879,19 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
getCurrentSubtitleRaw: () => appState.currentSubText,
getCurrentSubtitleAss: () => appState.currentSubAssText,
getSubtitleSidebarOpen: () => subtitleSidebarRequestedOpen,
getSubtitleSidebarPlaybackState: () => getSubtitleSidebarPlaybackState(),
// Full snapshot: resolves the active source over mpv IPC and re-parses it when it changed.
// The renderer fetches it on open, on source/cue changes, and on a slow fallback cadence.
getSubtitleSidebarSnapshot: async () => {
const currentSubtitle = {
text: appState.currentSubText,
startTime: appState.mpvClient?.currentSubStart ?? null,
endTime: appState.mpvClient?.currentSubEnd ?? null,
};
const currentTimeSec = appState.mpvClient?.currentTimePos ?? null;
const config = configService.getConfig().subtitleSidebar;
const cachedSnapshot = () => ({
...getSubtitleSidebarPlaybackState(),
cues: appState.activeParsedSubtitleCues,
config,
});
const client = appState.mpvClient;
if (!client?.connected) {
return {
sourceKey: JSON.stringify([
appState.activeParsedSubtitleMediaPath,
appState.activeParsedSubtitleSource,
]),
cues: appState.activeParsedSubtitleCues,
currentTimeSec,
currentSubtitle,
config,
};
return cachedSnapshot();
}
try {
@@ -5883,16 +5905,7 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
]);
const videoPath = typeof videoPathRaw === 'string' ? videoPathRaw : '';
if (!videoPath) {
return {
sourceKey: JSON.stringify([
appState.activeParsedSubtitleMediaPath,
appState.activeParsedSubtitleSource,
]),
cues: appState.activeParsedSubtitleCues,
currentTimeSec,
currentSubtitle,
config,
};
return cachedSnapshot();
}
if (
shouldUseCachedYoutubeParsedCues({
@@ -5901,16 +5914,7 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
cachedCueCount: appState.activeParsedSubtitleCues.length,
})
) {
return {
sourceKey: JSON.stringify([
appState.activeParsedSubtitleMediaPath,
appState.activeParsedSubtitleSource,
]),
cues: appState.activeParsedSubtitleCues,
currentTimeSec,
currentSubtitle,
config,
};
return cachedSnapshot();
}
const resolvedSource = await resolveActiveSubtitleSidebarSourceHandler({
@@ -5921,30 +5925,12 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
videoPath,
});
if (!resolvedSource) {
return {
sourceKey: JSON.stringify([
appState.activeParsedSubtitleMediaPath,
appState.activeParsedSubtitleSource,
]),
cues: appState.activeParsedSubtitleCues,
currentTimeSec,
currentSubtitle,
config,
};
return cachedSnapshot();
}
try {
if (appState.activeParsedSubtitleSource === resolvedSource.sourceKey) {
return {
sourceKey: JSON.stringify([
appState.activeParsedSubtitleMediaPath,
appState.activeParsedSubtitleSource,
]),
cues: appState.activeParsedSubtitleCues,
currentTimeSec,
currentSubtitle,
config,
};
return cachedSnapshot();
}
const content = await loadSubtitleSourceText(resolvedSource.path);
@@ -5952,30 +5938,12 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
appState.activeParsedSubtitleCues = cues;
appState.activeParsedSubtitleSource = resolvedSource.sourceKey;
appState.activeParsedSubtitleMediaPath = videoPath || null;
return {
sourceKey: JSON.stringify([
appState.activeParsedSubtitleMediaPath,
appState.activeParsedSubtitleSource,
]),
cues,
currentTimeSec,
currentSubtitle,
config,
};
return cachedSnapshot();
} finally {
await resolvedSource.cleanup?.();
}
} catch {
return {
sourceKey: JSON.stringify([
appState.activeParsedSubtitleMediaPath,
appState.activeParsedSubtitleSource,
]),
cues: appState.activeParsedSubtitleCues,
currentTimeSec,
currentSubtitle,
config,
};
return cachedSnapshot();
}
},
getPlaybackPaused: () =>