mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 14:16:16 -08:00
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:
1 parent
8e1cb05913
commit
417eaf5005
14 files changed
+545
-16
No files matched your search
@@ -0,0 +1,98 @@
|
||||
import { Injector } from '@angular/core';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { of } from 'rxjs';
|
||||
import {
|
||||
EmbeddedMpvSupport,
|
||||
Settings,
|
||||
STORE_KEY,
|
||||
VideoPlayer,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { EpgSourceSettingsService } from './epg-source-settings.service';
|
||||
import { SettingsStore } from './settings-store.service';
|
||||
|
||||
/** What the main process answers when the Linux `mpv` probe finds nothing. */
|
||||
const MPV_MISSING: EmbeddedMpvSupport = {
|
||||
supported: false,
|
||||
platform: 'linux',
|
||||
reason: 'Embedded MPV on Linux requires the mpv executable on PATH.',
|
||||
frameCopyAvailable: false,
|
||||
frameCopyUnavailableReason: 'helper-probe-failed',
|
||||
};
|
||||
|
||||
describe('SettingsStore saved Embedded MPV selection', () => {
|
||||
const testWindow = window as unknown as {
|
||||
electron?: { getEmbeddedMpvSupport: jest.Mock };
|
||||
};
|
||||
const originalElectron = testWindow.electron;
|
||||
let storage: { get: jest.Mock; set: jest.Mock };
|
||||
let injector: Injector;
|
||||
|
||||
/** Loads settings as on startup and lets the support check finish. */
|
||||
async function startWithSavedEmbeddedMpv(
|
||||
support: EmbeddedMpvSupport
|
||||
): Promise<InstanceType<typeof SettingsStore>> {
|
||||
testWindow.electron = {
|
||||
getEmbeddedMpvSupport: jest.fn().mockResolvedValue(support),
|
||||
};
|
||||
const store = injector.get(SettingsStore);
|
||||
await store.loadSettings();
|
||||
await new Promise((resolve) => setTimeout(resolve));
|
||||
expect(testWindow.electron.getEmbeddedMpvSupport).toHaveBeenCalled();
|
||||
return store;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
const saved: Partial<Settings> = { player: VideoPlayer.EmbeddedMpv };
|
||||
storage = {
|
||||
get: jest.fn(() => of(saved)),
|
||||
set: jest.fn(() => of(undefined)),
|
||||
};
|
||||
injector = Injector.create({
|
||||
providers: [
|
||||
SettingsStore,
|
||||
EpgSourceSettingsService,
|
||||
{ provide: StorageMap, useValue: storage },
|
||||
],
|
||||
});
|
||||
jest.spyOn(
|
||||
injector.get(EpgSourceSettingsService),
|
||||
'synchronize'
|
||||
).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testWindow.electron = originalElectron;
|
||||
});
|
||||
|
||||
it('keeps the saved player when the support check is inconclusive', async () => {
|
||||
// A slow login shell: mpv was looked up before its PATH arrived.
|
||||
const store = await startWithSavedEmbeddedMpv({
|
||||
...MPV_MISSING,
|
||||
inconclusive: true,
|
||||
});
|
||||
|
||||
expect(store.player()).toBe(VideoPlayer.EmbeddedMpv);
|
||||
expect(storage.set).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('falls back to the default player on a final unsupported answer', async () => {
|
||||
const store = await startWithSavedEmbeddedMpv(MPV_MISSING);
|
||||
|
||||
expect(store.player()).toBe(VideoPlayer.VideoJs);
|
||||
expect(storage.set).toHaveBeenCalledWith(
|
||||
STORE_KEY.Settings,
|
||||
expect.objectContaining({ player: VideoPlayer.VideoJs })
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the saved player when Embedded MPV is supported', async () => {
|
||||
const store = await startWithSavedEmbeddedMpv({
|
||||
supported: true,
|
||||
platform: 'linux',
|
||||
engine: 'native',
|
||||
});
|
||||
|
||||
expect(store.player()).toBe(VideoPlayer.EmbeddedMpv);
|
||||
expect(storage.set).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -338,14 +338,15 @@ export const SettingsStore = signalStore(
|
||||
try {
|
||||
const support =
|
||||
await window.electron.getEmbeddedMpvSupport();
|
||||
if (!support.supported) {
|
||||
if (support.supported) {
|
||||
scheduleEmbeddedMpvPrepare();
|
||||
} else if (!support.inconclusive) {
|
||||
// An inconclusive answer is no verdict on this
|
||||
// machine: the saved player stays as it is.
|
||||
await this.updateSettings({
|
||||
player: DEFAULT_SETTINGS.player,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleEmbeddedMpvPrepare();
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Failed to verify embedded MPV support; reverting to the default inline player.',
|
||||
|
||||
Reference in new issue
Block a user