fix(playback): keep the saved Embedded MPV player when the mpv check is inconclusive

On Linux native-view the support check runs `mpv --version` by bare name
and waits for the login shell PATH first. Since #1784 that lookup is
asynchronous with a 10 s budget; when it ran out, the check ran on the
inherited PATH and answered a plain `supported: false`. The settings store
took that as a verdict and persisted the default player over a saved
Embedded MPV selection. The main process probed again once the shell
answered, but nothing restored the setting.

`EmbeddedMpvSupport` now carries `inconclusive`. The native service sets it
on a missing mpv while its probe has only seen the inherited PATH; the IPC
handler declares that state before probing and registers the re-probe
before the check, so a throwing check cannot leave it stuck. Every other
answer stays final.

Consumers no longer settle on an inconclusive answer: the settings store
keeps the saved player, and the command palette and the settings search
probe again on their next use instead of caching it for the session.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5.1 committed 2026-10-03 19:39:41 +02:00
1 parent 8e1cb05913
commit 417eaf5005
14 files changed
+545 -16

No files matched your search

@@ -53,7 +53,7 @@ describe('WorkspacePlayerCommandsContributor', () => {
let electronStub:
| {
getEmbeddedMpvSupport: jest.Mock<
Promise<{ supported: boolean }>,
Promise<{ supported: boolean; inconclusive?: boolean }>,
[]
>;
}
@@ -63,7 +63,10 @@ describe('WorkspacePlayerCommandsContributor', () => {
function bootstrap(options: {
supportsManagedExternalPlayers: boolean;
supportsEmbeddedMpv?: boolean;
embeddedMpvSupportResult?: { supported: boolean } | null;
embeddedMpvSupportResult?: {
supported: boolean;
inconclusive?: boolean;
} | null;
}) {
viewCommands = {
registerCommand: jest.fn().mockReturnValue(() => undefined),
@@ -200,6 +203,50 @@ describe('WorkspacePlayerCommandsContributor', () => {
expect(resolveBoolean(embedded?.visible)).toBe(false);
});
it('keeps a final unsupported answer, but asks again after an inconclusive one', async () => {
const contributor = bootstrap({
supportsManagedExternalPlayers: true,
supportsEmbeddedMpv: true,
// A slow login shell: mpv was looked up before its PATH arrived.
embeddedMpvSupportResult: { supported: false, inconclusive: true },
});
const embedded = getRegistered(viewCommands).find(
(c) => c.id === 'switch-player-embedded-mpv'
);
await contributor.ensureEmbeddedMpvSupportLoaded();
expect(resolveBoolean(embedded?.visible)).toBe(false);
// The shell answered without mpv: that answer is final.
electronStub?.getEmbeddedMpvSupport.mockResolvedValue({
supported: false,
});
await contributor.ensureEmbeddedMpvSupportLoaded();
expect(resolveBoolean(embedded?.visible)).toBe(false);
expect(contributor.ensureEmbeddedMpvSupportLoaded()).toBeUndefined();
expect(electronStub?.getEmbeddedMpvSupport).toHaveBeenCalledTimes(2);
});
it('shows embedded MPV once an inconclusive answer turns into supported', async () => {
const contributor = bootstrap({
supportsManagedExternalPlayers: true,
supportsEmbeddedMpv: true,
embeddedMpvSupportResult: { supported: false, inconclusive: true },
});
const embedded = getRegistered(viewCommands).find(
(c) => c.id === 'switch-player-embedded-mpv'
);
await contributor.ensureEmbeddedMpvSupportLoaded();
electronStub?.getEmbeddedMpvSupport.mockResolvedValue({
supported: true,
});
await contributor.ensureEmbeddedMpvSupportLoaded();
expect(resolveBoolean(embedded?.visible)).toBe(true);
expect(contributor.ensureEmbeddedMpvSupportLoaded()).toBeUndefined();
});
it('switches to embedded MPV on run', () => {
bootstrap({
supportsManagedExternalPlayers: true,
@@ -120,16 +120,20 @@ export class WorkspacePlayerCommandsContributor {
}
private async loadEmbeddedMpvSupport(): Promise<void> {
// An inconclusive answer hides the command for now, but is not kept:
// the next palette open asks again.
let final = true;
try {
const support = await window.electron?.getEmbeddedMpvSupport?.();
this.embeddedMpvSupported.set(!!support?.supported);
final = !support?.inconclusive;
} catch (error) {
console.warn(
'Failed to verify embedded MPV support for the command palette.',
error
);
} finally {
this.embeddedMpvSupportChecked = true;
this.embeddedMpvSupportChecked = final;
this.embeddedMpvSupportLoad = null;
}
}