mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 05:06:16 -08:00
fix(portals): a failed MPV/VLC movie launch is reported to the viewer
The launch settlement swallowed a rejected launch together with a superseded one, so the menu's launch had nothing to report. It now hands a failure that nothing superseded to the page, which logs it and shows the playback error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf191eb1a0
commit
93523a1d3c
4 files changed
+57
-1
No files matched your search
@@ -5,6 +5,8 @@ interface OwnedExternalLaunchOptions {
|
||||
owns: () => boolean;
|
||||
close: (session: ExternalPlayerSession) => Promise<void>;
|
||||
warnCloseFailure: (error: unknown) => void;
|
||||
/** The launch itself failed while nothing superseded it: the user's to hear about. */
|
||||
reportLaunchFailure?: (error: unknown) => void;
|
||||
clearPending: () => void;
|
||||
clearOwnership: () => void;
|
||||
}
|
||||
@@ -38,7 +40,10 @@ export async function settleOwnedExternalLaunch(
|
||||
options.clearPending();
|
||||
options.clearOwnership();
|
||||
return false;
|
||||
} catch {
|
||||
} catch (error) {
|
||||
if (options.owns()) {
|
||||
options.reportLaunchFailure?.(error);
|
||||
}
|
||||
// A partial reuse can reject while Electron retains a matching
|
||||
// closable error session. Keep its credential-free identity so the
|
||||
// next source start can still find and close that exact process.
|
||||
|
||||
@@ -211,6 +211,45 @@ describe('VodDetailsPlaybackService — external playback handoff', () => {
|
||||
expect(openResolvedPlayback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reports a failed launch to the page unless another start superseded it', async () => {
|
||||
const reportExternalLaunchFailure = jest.fn();
|
||||
service.bind({
|
||||
vodId: routeVodId,
|
||||
vodInfo: signal(null),
|
||||
activeSource,
|
||||
supersedePendingSwitch: jest.fn(),
|
||||
reportExternalLaunchFailure,
|
||||
});
|
||||
const failure = new Error('mpv is not installed');
|
||||
const playback = {
|
||||
streamUrl: 'https://example.com/alt.mkv',
|
||||
title: 'Example Movie',
|
||||
contentInfo: {
|
||||
playlistId: 'playlist-1',
|
||||
contentXtreamId: 1,
|
||||
contentType: 'vod' as const,
|
||||
},
|
||||
};
|
||||
|
||||
openResolvedPlayback.mockRejectedValueOnce(failure);
|
||||
await service.startResolvedPlayback(playback);
|
||||
expect(reportExternalLaunchFailure).toHaveBeenCalledWith(failure);
|
||||
|
||||
// The route moved on before the failure arrived: the error belongs
|
||||
// to a launch nothing shows any more.
|
||||
let shown = true;
|
||||
let rejectLaunch: (error: Error) => void = () => undefined;
|
||||
openResolvedPlayback.mockImplementationOnce(
|
||||
() => new Promise((_, reject) => (rejectLaunch = reject))
|
||||
);
|
||||
const stale = service.startResolvedPlayback(playback, () => shown);
|
||||
await new Promise((resolve) => setTimeout(resolve));
|
||||
shown = false;
|
||||
rejectLaunch(failure);
|
||||
await expect(stale).resolves.toBe(false);
|
||||
expect(reportExternalLaunchFailure).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('closes a closable error before starting a replacement source', async () => {
|
||||
activeSession.set({
|
||||
...sessionFor(ROUTE_PLAYLIST, ROUTE_VOD_ID),
|
||||
|
||||
@@ -57,6 +57,8 @@ export interface VodDetailsPlaybackBindings {
|
||||
activeSource?: Signal<PlayerContentInfo | null>;
|
||||
/** Retires a source resolution that the accepted fallback now supersedes. */
|
||||
supersedePendingSwitch: () => void;
|
||||
/** An MPV/VLC launch failed while nothing superseded it; the page tells the user. */
|
||||
reportExternalLaunchFailure?: (error: unknown) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -532,6 +534,8 @@ export class VodDetailsPlaybackService {
|
||||
'Closing a superseded external player failed.',
|
||||
error
|
||||
),
|
||||
reportLaunchFailure: (error) =>
|
||||
this.bindings()?.reportExternalLaunchFailure?.(error),
|
||||
clearPending: () => this.clearExternalLaunchPending(generation),
|
||||
clearOwnership: () => this.clearExternalLaunchOwnership(generation),
|
||||
});
|
||||
|
||||
@@ -420,6 +420,14 @@ export class VodDetailsRouteComponent implements OnInit, OnDestroy {
|
||||
activeSource: this.msUi.activeAlternativeSource,
|
||||
supersedePendingSwitch: () =>
|
||||
this.multiSource.supersedePendingSwitch(),
|
||||
reportExternalLaunchFailure: (error) => {
|
||||
this.logger.error('External launch failed', error);
|
||||
this.snackBar.open(
|
||||
this.translateService.instant('PORTALS.PLAYBACK_ERROR'),
|
||||
undefined,
|
||||
{ duration: 3000 }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
|
||||
Reference in new issue
Block a user