fix(playback): clarify external player launch feedback (#1388)

This commit is contained in:
4gray authored and GitHub committed 2026-08-09 13:33:07 +02:00
1 parent 8442747c37
commit d73acd6bfc
101 files changed
+8819 -850

No files matched your search

@@ -3,7 +3,10 @@ import type {
ExternalPlayerSession,
PlayerContentInfo,
} from '@iptvnator/shared/interfaces';
import { createExternalPlaybackButtonState } from './external-playback-button-state';
import {
createExternalPlaybackButtonState,
isLiveExternalPlayerSession,
} from './external-playback-button-state';
function session(
overrides: Partial<ExternalPlayerSession> = {}
@@ -105,6 +108,21 @@ describe('createExternalPlaybackButtonState', () => {
}
);
it('offers Stop for an errored session whose process may still be alive', () => {
const { api } = setup(
session({
status: 'error',
error: 'Process exit was not confirmed',
canClose: true,
})
);
expect(api.matchedSession()).not.toBeNull();
expect(api.buttonState()).toBe('stop');
expect(api.primaryLabel()).toBe('Stop MPV');
expect(api.isStopAction()).toBe(true);
});
it('reports a launching session', () => {
const { api } = setup(session({ status: 'launching' }));
@@ -208,3 +226,30 @@ describe('createExternalPlaybackButtonState', () => {
expect(api.matchedSession()).toBeNull();
});
});
describe('isLiveExternalPlayerSession', () => {
it('keeps ownership while a teardown error can still be stopped', () => {
expect(
isLiveExternalPlayerSession(
session({
status: 'error',
error: 'Process exit was not confirmed',
canClose: true,
})
)
).toBe(true);
});
it('treats an unclosable error and a confirmed close as terminal', () => {
expect(
isLiveExternalPlayerSession(
session({ status: 'error', canClose: false })
)
).toBe(false);
expect(
isLiveExternalPlayerSession(
session({ status: 'closed', canClose: false })
)
).toBe(false);
});
});
@@ -48,6 +48,21 @@ export interface ExternalPlaybackButtonStateApi {
buttonState: Signal<ExternalPlaybackButtonState>;
}
/**
* An error can still own a real process when bounded teardown could not
* confirm its exit. Keep that session live until Stop succeeds; only a
* confirmed close or an unclosable failure is terminal.
*/
export function isLiveExternalPlayerSession(
session: ExternalPlayerSession | null | undefined
): session is ExternalPlayerSession {
return (
!!session &&
session.status !== 'closed' &&
(session.status !== 'error' || session.canClose)
);
}
export function createExternalPlaybackButtonState(
config: ExternalPlaybackButtonStateConfig
): ExternalPlaybackButtonStateApi {
@@ -56,11 +71,7 @@ export function createExternalPlaybackButtonState(
const matchedSession = computed(() => {
const session = config.session();
// A closed or errored session says nothing about what is playing now.
if (
!session?.contentInfo ||
session.status === 'closed' ||
session.status === 'error'
) {
if (!session?.contentInfo || !isLiveExternalPlayerSession(session)) {
return null;
}
@@ -92,6 +103,7 @@ export function createExternalPlaybackButtonState(
return `Opening in ${player}...`;
case 'opened':
case 'playing':
case 'error':
return `Stop ${player}`;
default:
return null;
@@ -104,6 +116,7 @@ export function createExternalPlaybackButtonState(
return 'hourglass_top';
case 'opened':
case 'playing':
case 'error':
return 'stop_circle';
default:
return 'play_arrow';
@@ -116,7 +129,9 @@ export function createExternalPlaybackButtonState(
const isStopAction = computed(() => {
const status = matchedSession()?.status;
return status === 'opened' || status === 'playing';
return (
status === 'opened' || status === 'playing' || status === 'error'
);
});
const buttonState = computed<ExternalPlaybackButtonState>(() => {