fix: always hide mpv primary subtitles for visible overlay

This commit is contained in:
2026-02-27 18:32:29 -08:00
parent b212986682
commit 19c7448f26
22 changed files with 153 additions and 108 deletions

View File

@@ -25,10 +25,10 @@ export { cycleSecondarySubMode } from './subtitle-position';
export {
isAutoUpdateEnabledRuntime,
shouldAutoInitializeOverlayRuntimeFromConfig,
shouldBindVisibleOverlayToMpvSubVisibility,
} from './startup';
export { openYomitanSettingsWindow } from './yomitan-settings';
export { createTokenizerDepsRuntime, tokenizeSubtitle } from './tokenizer';
export { syncYomitanDefaultAnkiServer } from './tokenizer/yomitan-parser-runtime';
export { createSubtitleProcessingController } from './subtitle-processing-controller';
export { createFrequencyDictionaryLookup } from './frequency-dictionary';
export { createJlptVocabularyLookup } from './jlpt-vocab';

View File

@@ -121,7 +121,6 @@ test('dispatchMpvProtocolMessage emits subtitle text on property change', async
test('dispatchMpvProtocolMessage enforces sub-visibility hidden when overlay suppression is enabled', async () => {
const { deps, state } = createDeps({
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
isVisibleOverlayVisible: () => true,
});
@@ -130,15 +129,22 @@ test('dispatchMpvProtocolMessage enforces sub-visibility hidden when overlay sup
deps,
);
assert.deepEqual(state.commands.pop(), {
command: ['set_property', 'sub-visibility', 'no'],
});
assert.deepEqual(state.commands, [
{
command: ['set_property', 'sub-visibility', false],
},
{
command: ['set_property', 'sub-visibility', 'no'],
},
{
command: ['set', 'sub-visibility', 'no'],
},
]);
});
test('dispatchMpvProtocolMessage skips sub-visibility suppression when overlay binding is disabled', async () => {
test('dispatchMpvProtocolMessage skips sub-visibility suppression when overlay is hidden', async () => {
const { deps, state } = createDeps({
shouldBindVisibleOverlayToMpvSubVisibility: () => false,
isVisibleOverlayVisible: () => true,
isVisibleOverlayVisible: () => false,
});
await dispatchMpvProtocolMessage(

View File

@@ -48,7 +48,6 @@ export interface MpvProtocolHandleMessageDeps {
};
getSubtitleMetrics: () => MpvSubtitleRenderMetrics;
isVisibleOverlayVisible: () => boolean;
shouldBindVisibleOverlayToMpvSubVisibility?: () => boolean;
emitSubtitleChange: (payload: { text: string; isOverlayVisible: boolean }) => void;
emitSubtitleAssChange: (payload: { text: string }) => void;
emitSubtitleTiming: (payload: { text: string; start: number; end: number }) => void;
@@ -218,12 +217,10 @@ export async function dispatchMpvProtocolMessage(
subScaleByWindow: asBoolean(msg.data, deps.getSubtitleMetrics().subScaleByWindow),
});
} else if (msg.name === 'sub-visibility') {
if (
deps.isVisibleOverlayVisible() &&
asBoolean(msg.data, false) &&
(deps.shouldBindVisibleOverlayToMpvSubVisibility?.() ?? true)
) {
if (deps.isVisibleOverlayVisible() && asBoolean(msg.data, false)) {
deps.sendCommand({ command: ['set_property', 'sub-visibility', false] });
deps.sendCommand({ command: ['set_property', 'sub-visibility', 'no'] });
deps.sendCommand({ command: ['set', 'sub-visibility', 'no'] });
}
} else if (msg.name === 'sub-use-margins') {
deps.emitSubtitleMetricsChange({

View File

@@ -13,7 +13,6 @@ function makeDeps(overrides: Partial<MpvIpcClientProtocolDeps> = {}): MpvIpcClie
getResolvedConfig: () => ({}) as any,
autoStartOverlay: false,
setOverlayVisible: () => {},
shouldBindVisibleOverlayToMpvSubVisibility: () => false,
isVisibleOverlayVisible: () => false,
getReconnectTimer: () => null,
setReconnectTimer: () => {},
@@ -311,7 +310,6 @@ test('MpvIpcClient connect does not force primary subtitle visibility from bindi
const client = new MpvIpcClient(
'/tmp/mpv.sock',
makeDeps({
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
isVisibleOverlayVisible: () => true,
}),
);
@@ -332,6 +330,29 @@ test('MpvIpcClient connect does not force primary subtitle visibility from bindi
assert.equal(hasPrimaryVisibilityMutation, false);
});
test('MpvIpcClient setSubVisibility writes compatibility commands for visibility toggle', () => {
const commands: unknown[] = [];
const client = new MpvIpcClient('/tmp/mpv.sock', makeDeps());
(client as any).send = (payload: unknown) => {
commands.push(payload);
return true;
};
client.setSubVisibility(false);
assert.deepEqual(commands, [
{
command: ['set_property', 'sub-visibility', false],
},
{
command: ['set_property', 'sub-visibility', 'no'],
},
{
command: ['set', 'sub-visibility', 'no'],
},
]);
});
test('MpvIpcClient captures and disables secondary subtitle visibility on request', async () => {
const commands: unknown[] = [];
const client = new MpvIpcClient('/tmp/mpv.sock', makeDeps());

View File

@@ -99,7 +99,6 @@ export interface MpvIpcClientProtocolDeps {
getResolvedConfig: () => Config;
autoStartOverlay: boolean;
setOverlayVisible: (visible: boolean) => void;
shouldBindVisibleOverlayToMpvSubVisibility: () => boolean;
isVisibleOverlayVisible: () => boolean;
getReconnectTimer: () => ReturnType<typeof setTimeout> | null;
setReconnectTimer: (timer: ReturnType<typeof setTimeout> | null) => void;
@@ -297,8 +296,6 @@ export class MpvIpcClient implements MpvClient {
getResolvedConfig: () => this.deps.getResolvedConfig(),
getSubtitleMetrics: () => this.mpvSubtitleRenderMetrics,
isVisibleOverlayVisible: () => this.deps.isVisibleOverlayVisible(),
shouldBindVisibleOverlayToMpvSubVisibility: () =>
this.deps.shouldBindVisibleOverlayToMpvSubVisibility(),
emitSubtitleChange: (payload) => {
this.emit('subtitle-change', payload);
},
@@ -474,6 +471,9 @@ export class MpvIpcClient implements MpvClient {
setSubVisibility(visible: boolean): void {
const value = visible ? 'yes' : 'no';
this.send({
command: ['set_property', 'sub-visibility', visible],
});
this.send({
command: ['set_property', 'sub-visibility', value],
});

View File

@@ -3,12 +3,10 @@ import assert from 'node:assert/strict';
import {
isAutoUpdateEnabledRuntime,
shouldAutoInitializeOverlayRuntimeFromConfig,
shouldBindVisibleOverlayToMpvSubVisibility,
} from './startup';
const BASE_CONFIG = {
auto_start_overlay: false,
bind_visible_overlay_to_mpv_sub_visibility: true,
ankiConnect: {
behavior: {
autoUpdateNewCards: true,
@@ -27,17 +25,6 @@ test('shouldAutoInitializeOverlayRuntimeFromConfig respects auto start', () => {
);
});
test('shouldBindVisibleOverlayToMpvSubVisibility returns config value', () => {
assert.equal(shouldBindVisibleOverlayToMpvSubVisibility(BASE_CONFIG), true);
assert.equal(
shouldBindVisibleOverlayToMpvSubVisibility({
...BASE_CONFIG,
bind_visible_overlay_to_mpv_sub_visibility: false,
}),
false,
);
});
test('isAutoUpdateEnabledRuntime prefers runtime option and falls back to config', () => {
assert.equal(
isAutoUpdateEnabledRuntime(BASE_CONFIG, {

View File

@@ -18,7 +18,6 @@ interface RuntimeAutoUpdateOptionManagerLike {
export interface RuntimeConfigLike {
auto_start_overlay?: boolean;
bind_visible_overlay_to_mpv_sub_visibility: boolean;
ankiConnect?: {
behavior?: {
autoUpdateNewCards?: boolean;
@@ -156,10 +155,6 @@ export function shouldAutoInitializeOverlayRuntimeFromConfig(config: RuntimeConf
return config.auto_start_overlay === true;
}
export function shouldBindVisibleOverlayToMpvSubVisibility(config: RuntimeConfigLike): boolean {
return config.bind_visible_overlay_to_mpv_sub_visibility;
}
export function isAutoUpdateEnabledRuntime(
config: ResolvedConfig | RuntimeConfigLike,
runtimeOptionsManager: RuntimeAutoUpdateOptionManagerLike | null,