mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
fix(playback): clarify external player launch feedback (#1388)
This commit is contained in:
1 parent
8442747c37
commit
d73acd6bfc
101 files changed
+8819
-850
No files matched your search
@@ -3,6 +3,7 @@ import { readFileSync } from 'fs';
|
||||
import { basename, join } from 'path';
|
||||
import type {
|
||||
ExternalPlayerName,
|
||||
ExternalPlayerSession,
|
||||
PlayerContentInfo,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import {
|
||||
@@ -189,6 +190,7 @@ type CapturedExternalPlayerLaunch = {
|
||||
};
|
||||
|
||||
type PlaybackRecommendationCaptureSnapshot = {
|
||||
closed: string[];
|
||||
completed: number;
|
||||
launches: CapturedExternalPlayerLaunch[];
|
||||
released: boolean;
|
||||
@@ -197,6 +199,7 @@ type PlaybackRecommendationCaptureSnapshot = {
|
||||
type PlaybackRecommendationCaptureState =
|
||||
PlaybackRecommendationCaptureSnapshot & {
|
||||
releaseFirstResponse: (() => void) | undefined;
|
||||
sessions: Record<string, ExternalPlayerSession>;
|
||||
waitForFirstResponse: Promise<void>;
|
||||
};
|
||||
|
||||
@@ -209,10 +212,12 @@ async function installPlaybackRecommendationLaunchCapture(
|
||||
releaseFirstResponse = resolve;
|
||||
});
|
||||
const state: PlaybackRecommendationCaptureState = {
|
||||
closed: [],
|
||||
completed: 0,
|
||||
launches: [],
|
||||
released: false,
|
||||
releaseFirstResponse,
|
||||
sessions: {},
|
||||
waitForFirstResponse,
|
||||
};
|
||||
const globalRef = globalThis as typeof globalThis & {
|
||||
@@ -223,7 +228,7 @@ async function installPlaybackRecommendationLaunchCapture(
|
||||
const captureLaunch =
|
||||
(player: ExternalPlayerName) =>
|
||||
async (
|
||||
_event: unknown,
|
||||
rawEvent: unknown,
|
||||
url: string,
|
||||
title: string,
|
||||
thumbnail?: string,
|
||||
@@ -248,30 +253,81 @@ async function installPlaybackRecommendationLaunchCapture(
|
||||
],
|
||||
player,
|
||||
});
|
||||
const now = new Date().toISOString();
|
||||
const id = `e2e-recommended-${player}-${invocation}`;
|
||||
const event = rawEvent as {
|
||||
sender: {
|
||||
send: (
|
||||
channel: string,
|
||||
session: ExternalPlayerSession
|
||||
) => void;
|
||||
};
|
||||
};
|
||||
const launching: ExternalPlayerSession = {
|
||||
canClose: true,
|
||||
id,
|
||||
player,
|
||||
startedAt: now,
|
||||
status: 'launching',
|
||||
streamUrl: url,
|
||||
thumbnail: thumbnail ?? null,
|
||||
title,
|
||||
updatedAt: now,
|
||||
};
|
||||
state.sessions[id] = launching;
|
||||
event.sender.send('EXTERNAL_PLAYER_SESSION_UPDATE', launching);
|
||||
|
||||
if (invocation === 1 && !state.released) {
|
||||
await state.waitForFirstResponse;
|
||||
}
|
||||
|
||||
state.completed += 1;
|
||||
const now = new Date().toISOString();
|
||||
return {
|
||||
canClose: false,
|
||||
id: `e2e-recommended-${player}-${invocation}`,
|
||||
const completed: ExternalPlayerSession = {
|
||||
...launching,
|
||||
canClose: player === 'mpv',
|
||||
error:
|
||||
player === 'vlc'
|
||||
? 'E2E player launch failed'
|
||||
: undefined,
|
||||
player,
|
||||
startedAt: now,
|
||||
status: 'opened',
|
||||
streamUrl: url,
|
||||
thumbnail: thumbnail ?? null,
|
||||
title,
|
||||
updatedAt: now,
|
||||
status: player === 'mpv' ? 'opened' : 'error',
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
state.sessions[id] = completed;
|
||||
event.sender.send('EXTERNAL_PLAYER_SESSION_UPDATE', completed);
|
||||
return completed;
|
||||
};
|
||||
|
||||
ipcMain.removeHandler('OPEN_MPV_PLAYER');
|
||||
ipcMain.removeHandler('OPEN_VLC_PLAYER');
|
||||
ipcMain.handle('OPEN_MPV_PLAYER', captureLaunch('mpv'));
|
||||
ipcMain.handle('OPEN_VLC_PLAYER', captureLaunch('vlc'));
|
||||
ipcMain.removeHandler('CLOSE_EXTERNAL_PLAYER_SESSION');
|
||||
ipcMain.handle(
|
||||
'CLOSE_EXTERNAL_PLAYER_SESSION',
|
||||
(rawEvent: unknown, sessionId: string) => {
|
||||
const session = state.sessions[sessionId];
|
||||
if (!session) return null;
|
||||
const closed: ExternalPlayerSession = {
|
||||
...session,
|
||||
canClose: false,
|
||||
status: 'closed',
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
state.closed.push(sessionId);
|
||||
state.sessions[sessionId] = closed;
|
||||
const event = rawEvent as {
|
||||
sender: {
|
||||
send: (
|
||||
channel: string,
|
||||
update: ExternalPlayerSession
|
||||
) => void;
|
||||
};
|
||||
};
|
||||
event.sender.send('EXTERNAL_PLAYER_SESSION_UPDATE', closed);
|
||||
return closed;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -289,6 +345,7 @@ async function getPlaybackRecommendationCapture(
|
||||
);
|
||||
}
|
||||
return {
|
||||
closed: capture.closed,
|
||||
completed: capture.completed,
|
||||
launches: capture.launches,
|
||||
released: capture.released,
|
||||
@@ -313,7 +370,7 @@ async function releasePlaybackRecommendationCapture(
|
||||
});
|
||||
}
|
||||
|
||||
test('@electron @dash ClearKey DASH filters DRM fallback and eligible MKV opens in MPV', async ({
|
||||
test('@electron @dash ClearKey DASH filters DRM fallback and reports external launch states', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
const fixtureServer = await startDashFixtureServer();
|
||||
@@ -439,6 +496,7 @@ test('@electron @dash ClearKey DASH filters DRM fallback and eligible MKV opens
|
||||
},
|
||||
] satisfies CapturedExternalPlayerLaunch[];
|
||||
expect(await getPlaybackRecommendationCapture(app)).toEqual({
|
||||
closed: [],
|
||||
completed: 0,
|
||||
launches: [],
|
||||
released: false,
|
||||
@@ -450,18 +508,33 @@ test('@electron @dash ClearKey DASH filters DRM fallback and eligible MKV opens
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toEqual({
|
||||
closed: [],
|
||||
completed: 0,
|
||||
launches: expectedLaunches,
|
||||
released: false,
|
||||
});
|
||||
expect(
|
||||
(await getPlaybackRecommendationCapture(app)).launches.filter(
|
||||
({ player }) => player === 'vlc'
|
||||
)
|
||||
).toHaveLength(0);
|
||||
await expect(mpvFallback).toHaveCount(0);
|
||||
await expect(vlcFallback).toHaveClass(
|
||||
/web-player-diagnostic__player-card--primary/
|
||||
await expect(mpvFallback).toBeVisible();
|
||||
await expect(vlcFallback).toBeVisible();
|
||||
await expect(mpvFallback).toContainText('Opening MPV');
|
||||
await expect(mpvFallback).toContainText('Opening player');
|
||||
await expect(mpvFallback).toHaveAttribute('aria-busy', 'true');
|
||||
await expect(vlcFallback).toHaveAttribute('aria-disabled', 'true');
|
||||
await vlcFallback.evaluate((button: HTMLButtonElement) =>
|
||||
button.click()
|
||||
);
|
||||
await expect
|
||||
.poll(() => getPlaybackRecommendationCapture(app))
|
||||
.toEqual({
|
||||
closed: [],
|
||||
completed: 0,
|
||||
launches: expectedLaunches,
|
||||
released: false,
|
||||
});
|
||||
const dock = app.mainWindow.locator('app-external-playback-dock');
|
||||
await expect(dock).toContainText('Opening player');
|
||||
await expect(dock.locator('.external-playback-dock')).toHaveAttribute(
|
||||
'aria-busy',
|
||||
'true'
|
||||
);
|
||||
|
||||
await releasePlaybackRecommendationCapture(app);
|
||||
@@ -470,6 +543,7 @@ test('@electron @dash ClearKey DASH filters DRM fallback and eligible MKV opens
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toEqual({
|
||||
closed: [],
|
||||
completed: 1,
|
||||
launches: expectedLaunches,
|
||||
released: true,
|
||||
@@ -483,14 +557,77 @@ test('@electron @dash ClearKey DASH filters DRM fallback and eligible MKV opens
|
||||
})
|
||||
);
|
||||
expect(await getPlaybackRecommendationCapture(app)).toEqual({
|
||||
closed: [],
|
||||
completed: 1,
|
||||
launches: expectedLaunches,
|
||||
released: true,
|
||||
});
|
||||
await expect(mpvFallback).toHaveCount(0);
|
||||
await expect(mpvFallback).toBeVisible();
|
||||
await expect(mpvFallback).toContainText('Open MPV again');
|
||||
await expect(mpvFallback).toContainText('Player started');
|
||||
await expect(vlcFallback).toHaveClass(
|
||||
/web-player-diagnostic__player-card--primary/
|
||||
);
|
||||
await expect(dock).toContainText('Player started');
|
||||
const dockThemeStyles = await dock
|
||||
.locator('.external-playback-dock')
|
||||
.evaluate((element) => {
|
||||
const body = element.ownerDocument.body;
|
||||
const wasDark = body.classList.contains('dark-theme');
|
||||
const read = () => {
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
borderColor: style.borderColor,
|
||||
color: style.color,
|
||||
onSurface: style
|
||||
.getPropertyValue('--app-heading-color')
|
||||
.trim(),
|
||||
surface: style
|
||||
.getPropertyValue('--app-widget-bg')
|
||||
.trim(),
|
||||
};
|
||||
};
|
||||
body.classList.remove('dark-theme');
|
||||
const light = read();
|
||||
body.classList.add('dark-theme');
|
||||
const dark = read();
|
||||
body.classList.toggle('dark-theme', wasDark);
|
||||
return { dark, light };
|
||||
});
|
||||
expect(dockThemeStyles.light.surface).not.toBe('');
|
||||
expect(dockThemeStyles.dark.surface).not.toBe('');
|
||||
expect(dockThemeStyles.light.onSurface).not.toBe('');
|
||||
expect(dockThemeStyles.dark.onSurface).not.toBe('');
|
||||
expect(dockThemeStyles.light).not.toEqual(dockThemeStyles.dark);
|
||||
|
||||
await vlcFallback.click();
|
||||
const expectedBothLaunches = [
|
||||
...expectedLaunches,
|
||||
{ ...expectedLaunches[0], player: 'vlc' as const },
|
||||
];
|
||||
await expect
|
||||
.poll(() => getPlaybackRecommendationCapture(app), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toEqual({
|
||||
closed: ['e2e-recommended-mpv-1'],
|
||||
completed: 2,
|
||||
launches: expectedBothLaunches,
|
||||
released: true,
|
||||
});
|
||||
await expect(mpvFallback).toBeVisible();
|
||||
await expect(vlcFallback).toBeVisible();
|
||||
await expect(vlcFallback).toContainText('Try VLC again');
|
||||
await expect(vlcFallback).toContainText('External player error');
|
||||
await expect(mpvFallback).toHaveClass(
|
||||
/web-player-diagnostic__player-card--primary/
|
||||
);
|
||||
await expect(dock).toContainText('E2E player launch failed');
|
||||
await expect(
|
||||
dock.getByRole('button', { name: 'Dismiss' })
|
||||
).toBeVisible();
|
||||
await dock.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await expect(dock).toBeHidden();
|
||||
} finally {
|
||||
await releasePlaybackRecommendationCapture(app).catch(() => undefined);
|
||||
await closeElectronApp(app);
|
||||
|
||||
Reference in new issue
Block a user