mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
fix(player): clear reused mpv request headers
This commit is contained in:
1 parent
edf47b19b7
commit
9aa93079bd
2 files changed
+196
-24
No files matched your search
@@ -45,6 +45,7 @@ export interface OpenExternalPlayerRequest {
|
||||
let mpvProcess: ChildProcess | null = null;
|
||||
let mpvSocketPath: string | null = null;
|
||||
let positionPollingInterval: NodeJS.Timeout | null = null;
|
||||
let mpvReuseOperationQueue: Promise<void> = Promise.resolve();
|
||||
|
||||
function getMpvPath(options: PlayerPathOptions = {}): string {
|
||||
return (
|
||||
@@ -182,6 +183,17 @@ function sendMpvCommand(
|
||||
});
|
||||
}
|
||||
|
||||
function enqueueMpvReuseOperation<T>(
|
||||
operation: () => Promise<T>
|
||||
): Promise<T> {
|
||||
const queuedOperation = mpvReuseOperationQueue.then(operation);
|
||||
mpvReuseOperationQueue = queuedOperation.then(
|
||||
() => undefined,
|
||||
() => undefined
|
||||
);
|
||||
return queuedOperation;
|
||||
}
|
||||
|
||||
function killStoredMpvProcess(reason: string): void {
|
||||
if (!mpvProcess || mpvProcess.killed) {
|
||||
return;
|
||||
@@ -283,36 +295,42 @@ export async function openMpvPlayer({
|
||||
) {
|
||||
traceExternalPlayer('reuse existing mpv instance');
|
||||
try {
|
||||
if (effectiveUserAgent) {
|
||||
await enqueueMpvReuseOperation(async () => {
|
||||
await sendMpvCommand('set_property', [
|
||||
'user-agent',
|
||||
effectiveUserAgent,
|
||||
effectiveUserAgent ?? '',
|
||||
]);
|
||||
}
|
||||
if (effectiveReferer) {
|
||||
await sendMpvCommand('set_property', [
|
||||
'referrer',
|
||||
effectiveReferer,
|
||||
effectiveReferer ?? '',
|
||||
]);
|
||||
}
|
||||
if (headerFields.length > 0) {
|
||||
await sendMpvCommand('set_property', [
|
||||
'http-header-fields',
|
||||
headerFields.join(','),
|
||||
]);
|
||||
}
|
||||
|
||||
const loadFileArgs: Array<string | number> = [url, 'replace'];
|
||||
const loadFileOptions: string[] = [];
|
||||
const loadFileArgs: Array<string | number> = [
|
||||
url,
|
||||
'replace',
|
||||
];
|
||||
const loadFileOptions: string[] = [];
|
||||
|
||||
if (title) {
|
||||
loadFileOptions.push(`force-media-title=${title}`);
|
||||
}
|
||||
if (loadFileOptions.length > 0) {
|
||||
loadFileArgs.push(-1, loadFileOptions.join(','));
|
||||
}
|
||||
if (title) {
|
||||
loadFileOptions.push(`force-media-title=${title}`);
|
||||
}
|
||||
if (loadFileOptions.length > 0) {
|
||||
loadFileArgs.push(-1, loadFileOptions.join(','));
|
||||
}
|
||||
|
||||
await sendMpvCommand('loadfile', loadFileArgs);
|
||||
await sendMpvCommand('loadfile', loadFileArgs);
|
||||
|
||||
if (startTime) {
|
||||
await sendMpvCommand('seek', [
|
||||
String(startTime),
|
||||
'absolute',
|
||||
]);
|
||||
}
|
||||
});
|
||||
traceExternalPlayer('loaded new url in existing mpv instance');
|
||||
|
||||
externalPlayerSessions.attachCloser(session.id, async () => {
|
||||
@@ -325,13 +343,6 @@ export async function openMpvPlayer({
|
||||
}
|
||||
});
|
||||
|
||||
if (startTime) {
|
||||
await sendMpvCommand('seek', [
|
||||
String(startTime),
|
||||
'absolute',
|
||||
]);
|
||||
}
|
||||
|
||||
if (contentInfo) {
|
||||
startPositionPolling(
|
||||
mpvSocketPath,
|
||||
|
||||
@@ -8,6 +8,13 @@ jest.mock('child_process', () => ({
|
||||
spawn: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockCreateConnection = jest.fn();
|
||||
|
||||
jest.mock('net', () => ({
|
||||
...jest.requireActual('net'),
|
||||
createConnection: mockCreateConnection,
|
||||
}));
|
||||
|
||||
jest.mock('../app', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
@@ -40,6 +47,7 @@ import { ipcMain } from 'electron';
|
||||
import { spawn, type ChildProcess } from 'child_process';
|
||||
import { EventEmitter } from 'events';
|
||||
import {
|
||||
MPV_REUSE_INSTANCE,
|
||||
MPV_PLAYER_PATH,
|
||||
store,
|
||||
VLC_PLAYER_PATH,
|
||||
@@ -59,6 +67,10 @@ import {
|
||||
shouldUseMpvSocketBridge,
|
||||
} from './player.events';
|
||||
import { resolveEffectiveExternalPlaybackRequest } from './external-player-playback-request';
|
||||
import {
|
||||
openMpvPlayer,
|
||||
shutdownMpvSession,
|
||||
} from './mpv-session.service';
|
||||
import { openVlcPlayer } from './vlc-session.service';
|
||||
|
||||
function createPathExists(existingPaths: string[]) {
|
||||
@@ -532,6 +544,155 @@ describe('player.events Stalker playback contexts', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('openMpvPlayer reusable request isolation', () => {
|
||||
const ipcCommands: unknown[][] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
ipcCommands.length = 0;
|
||||
mockGetStalkerPlaybackContextHeaders.mockReset();
|
||||
mockGetStalkerPlaybackContextHeaders.mockReturnValue(undefined);
|
||||
mockCreateConnection.mockReset();
|
||||
mockCreateConnection.mockImplementation(() => {
|
||||
const socket = Object.assign(new EventEmitter(), {
|
||||
destroy: jest.fn(),
|
||||
destroyed: false,
|
||||
end: jest.fn(),
|
||||
write: jest.fn((request: string) => {
|
||||
const payload = JSON.parse(request) as {
|
||||
command: unknown[];
|
||||
};
|
||||
ipcCommands.push(payload.command);
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
setImmediate(() => socket.emit('connect'));
|
||||
return socket;
|
||||
});
|
||||
(spawn as unknown as jest.Mock).mockReset();
|
||||
(spawn as unknown as jest.Mock).mockReturnValue(
|
||||
createMockChildProcess()
|
||||
);
|
||||
(store.get as unknown as jest.Mock).mockImplementation(
|
||||
(key: string, fallback?: unknown) =>
|
||||
key === MPV_REUSE_INSTANCE ? true : fallback
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shutdownMpvSession();
|
||||
});
|
||||
|
||||
it('clears managed UA, referrer, Authorization, and Cookie before loading a plain source', async () => {
|
||||
await openMpvPlayer({
|
||||
title: '',
|
||||
url: 'https://seed.example/stream.m3u8',
|
||||
});
|
||||
|
||||
await openMpvPlayer({
|
||||
mainOwnedHeaders: {
|
||||
Authorization: 'Bearer managed-token',
|
||||
Cookie: 'session=managed-cookie',
|
||||
Origin: 'https://portal.example',
|
||||
Referer: 'https://portal.example/player/',
|
||||
'User-Agent': 'ManagedAgent/1.0',
|
||||
},
|
||||
title: '',
|
||||
url: 'https://portal.example/managed.m3u8',
|
||||
});
|
||||
|
||||
expect(ipcCommands).toEqual([
|
||||
['set_property', 'user-agent', 'ManagedAgent/1.0'],
|
||||
[
|
||||
'set_property',
|
||||
'referrer',
|
||||
'https://portal.example/player/',
|
||||
],
|
||||
[
|
||||
'set_property',
|
||||
'http-header-fields',
|
||||
'Authorization: Bearer managed-token,Cookie: session=managed-cookie,Origin: https://portal.example,Referer: https://portal.example/player/,User-Agent: ManagedAgent/1.0',
|
||||
],
|
||||
[
|
||||
'loadfile',
|
||||
'https://portal.example/managed.m3u8',
|
||||
'replace',
|
||||
],
|
||||
]);
|
||||
|
||||
ipcCommands.length = 0;
|
||||
|
||||
await openMpvPlayer({
|
||||
title: '',
|
||||
url: 'https://plain.example/public.m3u8',
|
||||
});
|
||||
|
||||
expect(ipcCommands).toEqual([
|
||||
['set_property', 'user-agent', ''],
|
||||
['set_property', 'referrer', ''],
|
||||
['set_property', 'http-header-fields', ''],
|
||||
[
|
||||
'loadfile',
|
||||
'https://plain.example/public.m3u8',
|
||||
'replace',
|
||||
],
|
||||
]);
|
||||
expect(JSON.stringify(ipcCommands)).not.toContain('managed-token');
|
||||
expect(JSON.stringify(ipcCommands)).not.toContain('managed-cookie');
|
||||
});
|
||||
|
||||
it('serializes concurrent managed and plain loads so their request properties cannot interleave', async () => {
|
||||
await openMpvPlayer({
|
||||
title: '',
|
||||
url: 'https://seed.example/stream.m3u8',
|
||||
});
|
||||
|
||||
const managedOpen = openMpvPlayer({
|
||||
mainOwnedHeaders: {
|
||||
Authorization: 'Bearer concurrent-token',
|
||||
Cookie: 'session=concurrent-cookie',
|
||||
Referer: 'https://portal.example/player/',
|
||||
'User-Agent': 'ConcurrentAgent/1.0',
|
||||
},
|
||||
title: '',
|
||||
url: 'https://portal.example/concurrent.m3u8',
|
||||
});
|
||||
const plainOpen = openMpvPlayer({
|
||||
title: '',
|
||||
url: 'https://plain.example/concurrent.m3u8',
|
||||
});
|
||||
|
||||
await Promise.all([managedOpen, plainOpen]);
|
||||
|
||||
expect(ipcCommands).toEqual([
|
||||
['set_property', 'user-agent', 'ConcurrentAgent/1.0'],
|
||||
[
|
||||
'set_property',
|
||||
'referrer',
|
||||
'https://portal.example/player/',
|
||||
],
|
||||
[
|
||||
'set_property',
|
||||
'http-header-fields',
|
||||
'Authorization: Bearer concurrent-token,Cookie: session=concurrent-cookie,Referer: https://portal.example/player/,User-Agent: ConcurrentAgent/1.0',
|
||||
],
|
||||
[
|
||||
'loadfile',
|
||||
'https://portal.example/concurrent.m3u8',
|
||||
'replace',
|
||||
],
|
||||
['set_property', 'user-agent', ''],
|
||||
['set_property', 'referrer', ''],
|
||||
['set_property', 'http-header-fields', ''],
|
||||
[
|
||||
'loadfile',
|
||||
'https://plain.example/concurrent.m3u8',
|
||||
'replace',
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openVlcPlayer', () => {
|
||||
beforeEach(() => {
|
||||
(spawn as unknown as jest.Mock).mockReset();
|
||||
|
||||
Reference in new issue
Block a user