mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 12:36:15 -08:00
fix(playback): wait for mpv's reply to every reused-instance IPC command (#1876)
Every reused-instance MPV IPC command now carries a request_id and waits, within the existing 2 s bound, for mpv's matching reply; an error or missing reply falls back to a fresh launch like a socket failure, and the racing post-loadfile seek is gone. MPV launches run one at a time through a launch queue so overlapping launches never act on the same child; Stop on a queued launch delegates to the in-flight launch's own closer, a failed exit stays retryable, and the dock keeps the newest launch. Position polling and output-error reporting moved out of mpv-session.service.ts, which leaves the max-lines baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
94570bc387
commit
4e9795ac54
21 files changed
+2920
-394
No files matched your search
@@ -62,6 +62,49 @@ describe('ExternalPlaybackService', () => {
|
||||
expect(service.visibleSession()).toEqual(next);
|
||||
});
|
||||
|
||||
it('keeps the newest launch when an older launch republishes as launching', () => {
|
||||
const older = createSession({
|
||||
id: 'session-older',
|
||||
startedAt: '2026-03-07T10:00:00.000Z',
|
||||
});
|
||||
const newer = createSession({
|
||||
id: 'session-newer',
|
||||
title: 'Newer Example',
|
||||
startedAt: '2026-03-07T10:00:01.000Z',
|
||||
});
|
||||
listener?.(older);
|
||||
listener?.(newer);
|
||||
|
||||
// The older launch is still settling and republishes itself.
|
||||
listener?.({
|
||||
...older,
|
||||
canClose: true,
|
||||
updatedAt: '2026-03-07T10:00:02.000Z',
|
||||
});
|
||||
|
||||
expect(service.activeSession()?.id).toBe('session-newer');
|
||||
});
|
||||
|
||||
it('orders launches made in the same millisecond by launch order', () => {
|
||||
const older = createSession({ id: 'session-older', launchOrder: 1 });
|
||||
const newer = createSession({
|
||||
id: 'session-newer',
|
||||
title: 'Newer Example',
|
||||
launchOrder: 2,
|
||||
});
|
||||
listener?.(older);
|
||||
listener?.(newer);
|
||||
|
||||
// Same startedAt: only the launch order tells the two apart.
|
||||
listener?.({
|
||||
...older,
|
||||
canClose: true,
|
||||
updatedAt: '2026-03-07T10:00:02.000Z',
|
||||
});
|
||||
|
||||
expect(service.activeSession()?.id).toBe('session-newer');
|
||||
});
|
||||
|
||||
it('matches only active non-error sessions to content info', () => {
|
||||
const playing = createSession({
|
||||
status: 'playing',
|
||||
|
||||
@@ -4,6 +4,17 @@ import {
|
||||
PlayerContentInfo,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
|
||||
/** Launch order when both carry it, else the start time. */
|
||||
function startedBefore(
|
||||
session: ExternalPlayerSession,
|
||||
other: ExternalPlayerSession
|
||||
): boolean {
|
||||
if (session.launchOrder !== undefined && other.launchOrder !== undefined) {
|
||||
return session.launchOrder < other.launchOrder;
|
||||
}
|
||||
return Date.parse(session.startedAt) < Date.parse(other.startedAt);
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@@ -105,17 +116,25 @@ export class ExternalPlaybackService {
|
||||
const current = this.activeSession();
|
||||
const restoresCurrentReplacement =
|
||||
current?.id === session.restoredFromSessionId;
|
||||
// A launching update means a new launch only when it did not start
|
||||
// before the current one: an older launch still settling (MPV runs
|
||||
// launches one at a time) must not take the dock back from it.
|
||||
const isLatestLaunch =
|
||||
session.status === 'launching' &&
|
||||
(!current ||
|
||||
current.id === session.id ||
|
||||
!startedBefore(session, current));
|
||||
|
||||
if (
|
||||
!current ||
|
||||
current.id === session.id ||
|
||||
session.status === 'launching' ||
|
||||
isLatestLaunch ||
|
||||
restoresCurrentReplacement
|
||||
) {
|
||||
this.activeSession.set(session);
|
||||
}
|
||||
|
||||
if (session.status === 'launching' || restoresCurrentReplacement) {
|
||||
if (isLatestLaunch || restoresCurrentReplacement) {
|
||||
this.dismissedSessionId.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user