mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(settings): add external player arguments (#932)
* feat(settings): add external player arguments * fix(settings): address external player argument review * fix(settings): require external player argument settings * feat(settings): add external player argument placeholders Closes https://github.com/4gray/iptvnator/issues/835
This commit is contained in:
1 parent
2f9aef1578
commit
a40d5447e7
35 files changed
+584
-70
No files matched your search
@@ -35,8 +35,10 @@ import {
|
||||
} from '../services/store.service';
|
||||
import {
|
||||
buildExternalPlayerSpawnSpec,
|
||||
buildPlayerArgsWithCustomArguments,
|
||||
buildVlcEnqueueCommands,
|
||||
isRunningInFlatpak,
|
||||
parseExternalPlayerArguments,
|
||||
parseVlcRcPlaybackState,
|
||||
parseVlcRcNumericResponse,
|
||||
resolveExternalPlayerLaunchContext,
|
||||
@@ -67,21 +69,25 @@ function getIpcMainHandler(channel: string): (...args: unknown[]) => unknown {
|
||||
|
||||
describe('player.events Flatpak launch helpers', () => {
|
||||
it('detects Flatpak only on Linux when /.flatpak-info exists', () => {
|
||||
expect(isRunningInFlatpak(createPathExists(['/.flatpak-info']), 'linux')).toBe(
|
||||
true
|
||||
);
|
||||
expect(isRunningInFlatpak(createPathExists(['/.flatpak-info']), 'darwin')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
isRunningInFlatpak(createPathExists(['/.flatpak-info']), 'linux')
|
||||
).toBe(true);
|
||||
expect(
|
||||
isRunningInFlatpak(createPathExists(['/.flatpak-info']), 'darwin')
|
||||
).toBe(false);
|
||||
expect(isRunningInFlatpak(createPathExists([]), 'linux')).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps direct Linux player launching outside Flatpak', () => {
|
||||
const launchContext = resolveExternalPlayerLaunchContext('mpv', undefined, {
|
||||
platform: 'linux',
|
||||
isFlatpak: false,
|
||||
pathExists: createPathExists(['/usr/local/bin/mpv']),
|
||||
});
|
||||
const launchContext = resolveExternalPlayerLaunchContext(
|
||||
'mpv',
|
||||
undefined,
|
||||
{
|
||||
platform: 'linux',
|
||||
isFlatpak: false,
|
||||
pathExists: createPathExists(['/usr/local/bin/mpv']),
|
||||
}
|
||||
);
|
||||
const spawnSpec = buildExternalPlayerSpawnSpec(launchContext, [
|
||||
'--ytdl=no',
|
||||
'https://example.com/stream.m3u8',
|
||||
@@ -102,11 +108,15 @@ describe('player.events Flatpak launch helpers', () => {
|
||||
});
|
||||
|
||||
it('builds Flatpak host launches with bare player names by default', () => {
|
||||
const launchContext = resolveExternalPlayerLaunchContext('vlc', undefined, {
|
||||
platform: 'linux',
|
||||
isFlatpak: true,
|
||||
pathExists: createPathExists(['/usr/bin/vlc']),
|
||||
});
|
||||
const launchContext = resolveExternalPlayerLaunchContext(
|
||||
'vlc',
|
||||
undefined,
|
||||
{
|
||||
platform: 'linux',
|
||||
isFlatpak: true,
|
||||
pathExists: createPathExists(['/usr/bin/vlc']),
|
||||
}
|
||||
);
|
||||
const spawnSpec = buildExternalPlayerSpawnSpec(launchContext, [
|
||||
'--extraintf=rc',
|
||||
'https://example.com/stream.m3u8',
|
||||
@@ -199,6 +209,38 @@ describe('player.events Flatpak launch helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('parses custom player arguments as one argument per non-empty line', () => {
|
||||
expect(
|
||||
parseExternalPlayerArguments(
|
||||
' --screen=1\n\n--geometry=1280x720\r\n --hwdec=auto-safe '
|
||||
)
|
||||
).toEqual(['--screen=1', '--geometry=1280x720', '--hwdec=auto-safe']);
|
||||
});
|
||||
|
||||
it('treats missing custom player arguments as no arguments', () => {
|
||||
expect(parseExternalPlayerArguments(undefined)).toEqual([]);
|
||||
expect(parseExternalPlayerArguments(' \n ')).toEqual([]);
|
||||
});
|
||||
|
||||
it('adds custom player arguments before IPTVnator runtime arguments', () => {
|
||||
expect(
|
||||
buildPlayerArgsWithCustomArguments(
|
||||
'--screen=1\n--geometry=1280x720',
|
||||
[
|
||||
'--ytdl=no',
|
||||
'--force-media-title=News',
|
||||
'https://example.com/stream.m3u8',
|
||||
]
|
||||
)
|
||||
).toEqual([
|
||||
'--screen=1',
|
||||
'--geometry=1280x720',
|
||||
'--ytdl=no',
|
||||
'--force-media-title=News',
|
||||
'https://example.com/stream.m3u8',
|
||||
]);
|
||||
});
|
||||
|
||||
it('disables MPV reuse and socket bridging only in Flatpak', () => {
|
||||
expect(shouldReuseMpvInstance(true, true)).toBe(false);
|
||||
expect(shouldUseMpvSocketBridge(true)).toBe(false);
|
||||
@@ -315,9 +357,7 @@ describe('buildVlcEnqueueCommands', () => {
|
||||
origin: 'https://origin.example',
|
||||
});
|
||||
|
||||
expect(commands[1]).toContain(
|
||||
':http-referrer=https://origin.example'
|
||||
);
|
||||
expect(commands[1]).toContain(':http-referrer=https://origin.example');
|
||||
});
|
||||
|
||||
it('appends a seek command when startTime is provided', () => {
|
||||
|
||||
@@ -4,12 +4,16 @@ import {
|
||||
EXTERNAL_PLAYER_SESSION_UPDATE,
|
||||
ExternalPlayerName,
|
||||
ExternalPlayerSession,
|
||||
parseExternalPlayerArguments,
|
||||
type ExternalPlayerArgumentsInput,
|
||||
} from 'shared-interfaces';
|
||||
import App from '../app';
|
||||
import {
|
||||
MPV_PLAYER_ARGUMENTS,
|
||||
MPV_PLAYER_PATH,
|
||||
MPV_REUSE_INSTANCE,
|
||||
store,
|
||||
VLC_PLAYER_ARGUMENTS,
|
||||
VLC_PLAYER_PATH,
|
||||
VLC_REUSE_INSTANCE,
|
||||
} from '../services/store.service';
|
||||
@@ -43,7 +47,10 @@ interface ExternalPlaybackSnapshot {
|
||||
|
||||
function sendExternalPlayerSessionUpdate(session: ExternalPlayerSession) {
|
||||
if (App.mainWindow && !App.mainWindow.isDestroyed()) {
|
||||
App.mainWindow.webContents.send(EXTERNAL_PLAYER_SESSION_UPDATE, session);
|
||||
App.mainWindow.webContents.send(
|
||||
EXTERNAL_PLAYER_SESSION_UPDATE,
|
||||
session
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +209,15 @@ export function buildExternalPlayerSpawnSpec(
|
||||
};
|
||||
}
|
||||
|
||||
export { parseExternalPlayerArguments };
|
||||
|
||||
export function buildPlayerArgsWithCustomArguments(
|
||||
customArguments: ExternalPlayerArgumentsInput,
|
||||
playerArgs: string[]
|
||||
): string[] {
|
||||
return [...parseExternalPlayerArguments(customArguments), ...playerArgs];
|
||||
}
|
||||
|
||||
export function shouldReuseMpvInstance(
|
||||
requestedReuseInstance: boolean,
|
||||
isFlatpak: boolean = isRunningInFlatpak()
|
||||
@@ -504,8 +520,10 @@ function isStalkerDirectStreamProfile(
|
||||
const icyMetaData = headers['Icy-MetaData'] ?? headers['icy-metadata'];
|
||||
const userAgent = headers['User-Agent'] ?? headers['user-agent'];
|
||||
|
||||
return String(icyMetaData).trim() === '1' &&
|
||||
String(userAgent).trim().toLowerCase() === 'ksplayer';
|
||||
return (
|
||||
String(icyMetaData).trim() === '1' &&
|
||||
String(userAgent).trim().toLowerCase() === 'ksplayer'
|
||||
);
|
||||
}
|
||||
|
||||
function startPositionPolling(
|
||||
@@ -592,9 +610,7 @@ async function getVlcProperty(port: number, command: string): Promise<string> {
|
||||
}
|
||||
|
||||
async function getVlcPlaybackState(port: number): Promise<string | null> {
|
||||
return parseVlcRcPlaybackState(
|
||||
await getVlcCommandResponse(port, 'status')
|
||||
);
|
||||
return parseVlcRcPlaybackState(await getVlcCommandResponse(port, 'status'));
|
||||
}
|
||||
|
||||
async function getVlcPlaybackSnapshot(
|
||||
@@ -661,7 +677,10 @@ function startVlcPositionPolling(
|
||||
}
|
||||
|
||||
// Helper function to send command to MPV via IPC
|
||||
function sendMpvCommand(command: string, args: Array<string | number>): Promise<void> {
|
||||
function sendMpvCommand(
|
||||
command: string,
|
||||
args: Array<string | number>
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!mpvSocketPath) {
|
||||
reject(new Error('No MPV socket path available'));
|
||||
@@ -715,6 +734,7 @@ ipcMain.handle(
|
||||
getMpvPath({ isFlatpak }),
|
||||
{ isFlatpak }
|
||||
);
|
||||
const customMpvArguments = store.get(MPV_PLAYER_ARGUMENTS, '');
|
||||
const requestedReuseInstance = store.get(MPV_REUSE_INSTANCE, false);
|
||||
const reuseInstance = shouldReuseMpvInstance(
|
||||
requestedReuseInstance,
|
||||
@@ -760,6 +780,8 @@ ipcMain.handle(
|
||||
headerCount: headerFields.length,
|
||||
hasContentInfo: Boolean(contentInfo),
|
||||
startTime: startTime ?? null,
|
||||
customArgumentCount:
|
||||
parseExternalPlayerArguments(customMpvArguments).length,
|
||||
});
|
||||
|
||||
// If reuse is enabled and there's an existing process, try to use it
|
||||
@@ -810,15 +832,18 @@ ipcMain.handle(
|
||||
'Successfully loaded new URL in existing MPV instance'
|
||||
);
|
||||
|
||||
externalPlayerSessions.attachCloser(session.id, async () => {
|
||||
try {
|
||||
await sendMpvCommand('quit', []);
|
||||
} catch {
|
||||
if (mpvProcess && !mpvProcess.killed) {
|
||||
mpvProcess.kill();
|
||||
externalPlayerSessions.attachCloser(
|
||||
session.id,
|
||||
async () => {
|
||||
try {
|
||||
await sendMpvCommand('quit', []);
|
||||
} catch {
|
||||
if (mpvProcess && !mpvProcess.killed) {
|
||||
mpvProcess.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Seek if startTime provided
|
||||
if (startTime) {
|
||||
@@ -904,7 +929,7 @@ ipcMain.handle(
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const spawnSpec = buildExternalPlayerSpawnSpec(
|
||||
mpvLaunchContext,
|
||||
args
|
||||
buildPlayerArgsWithCustomArguments(customMpvArguments, args)
|
||||
);
|
||||
const proc = spawn(spawnSpec.command, spawnSpec.args, {
|
||||
shell: false,
|
||||
@@ -1114,10 +1139,8 @@ ipcMain.handle(
|
||||
getVlcPath({ isFlatpak }),
|
||||
{ isFlatpak }
|
||||
);
|
||||
const requestedReuseInstance = store.get(
|
||||
VLC_REUSE_INSTANCE,
|
||||
false
|
||||
);
|
||||
const customVlcArguments = store.get(VLC_PLAYER_ARGUMENTS, '');
|
||||
const requestedReuseInstance = store.get(VLC_REUSE_INSTANCE, false);
|
||||
const reuseInstance = shouldReuseVlcInstance(
|
||||
requestedReuseInstance,
|
||||
isFlatpak
|
||||
@@ -1155,6 +1178,8 @@ ipcMain.handle(
|
||||
hasOrigin: Boolean(effectiveOrigin),
|
||||
hasContentInfo: Boolean(contentInfo),
|
||||
startTime: startTime ?? null,
|
||||
customArgumentCount:
|
||||
parseExternalPlayerArguments(customVlcArguments).length,
|
||||
});
|
||||
|
||||
// Try to drive an already-running VLC via its RC port instead of
|
||||
@@ -1316,7 +1341,10 @@ ipcMain.handle(
|
||||
const spawnVlc = (playerArgs: string[], isRetry = false) => {
|
||||
const spawnSpec = buildExternalPlayerSpawnSpec(
|
||||
vlcLaunchContext,
|
||||
playerArgs
|
||||
buildPlayerArgsWithCustomArguments(
|
||||
customVlcArguments,
|
||||
playerArgs
|
||||
)
|
||||
);
|
||||
const trackProcess = reuseInstance && !isRetry;
|
||||
const proc = spawn(spawnSpec.command, spawnSpec.args, {
|
||||
@@ -1338,8 +1366,8 @@ ipcMain.handle(
|
||||
|
||||
const markVlcSessionClosed = () => {
|
||||
if (
|
||||
externalPlayerSessions.getSession(session.id)?.status ===
|
||||
'closed'
|
||||
externalPlayerSessions.getSession(session.id)
|
||||
?.status === 'closed'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -1375,27 +1403,30 @@ ipcMain.handle(
|
||||
);
|
||||
};
|
||||
|
||||
externalPlayerSessions.attachCloser(session.id, async () => {
|
||||
await flushVlcPlaybackPosition();
|
||||
if (!proc.killed) {
|
||||
proc.kill();
|
||||
externalPlayerSessions.attachCloser(
|
||||
session.id,
|
||||
async () => {
|
||||
await flushVlcPlaybackPosition();
|
||||
if (!proc.killed) {
|
||||
proc.kill();
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Start polling if we have port and content info and NOT retrying (RC disabled on retry)
|
||||
if (!isRetry && rcPort > 0 && contentInfo) {
|
||||
startVlcPositionPolling(
|
||||
rcPort,
|
||||
contentInfo,
|
||||
session.id,
|
||||
(snapshot) => {
|
||||
lastVlcSnapshot = snapshot;
|
||||
},
|
||||
() => {
|
||||
markVlcSessionClosed();
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!isRetry && rcPort > 0 && contentInfo) {
|
||||
startVlcPositionPolling(
|
||||
rcPort,
|
||||
contentInfo,
|
||||
session.id,
|
||||
(snapshot) => {
|
||||
lastVlcSnapshot = snapshot;
|
||||
},
|
||||
() => {
|
||||
markVlcSessionClosed();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Capture stdout
|
||||
if (proc.stdout) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { normalizeExternalPlayerArguments } from 'shared-interfaces';
|
||||
import {
|
||||
MPV_PLAYER_ARGUMENTS,
|
||||
MPV_REUSE_INSTANCE,
|
||||
store,
|
||||
VLC_PLAYER_ARGUMENTS,
|
||||
VLC_REUSE_INSTANCE,
|
||||
} from '../services/store.service';
|
||||
import { httpServer } from '../server/http-server';
|
||||
@@ -15,6 +18,20 @@ export default class SettingsEvents {
|
||||
ipcMain.handle('SETTINGS_UPDATE', (_event, arg) => {
|
||||
console.log('Received SETTINGS_UPDATE with data:', arg);
|
||||
|
||||
if (arg.mpvPlayerArguments !== undefined) {
|
||||
store.set(
|
||||
MPV_PLAYER_ARGUMENTS,
|
||||
normalizeExternalPlayerArguments(arg.mpvPlayerArguments)
|
||||
);
|
||||
}
|
||||
|
||||
if (arg.vlcPlayerArguments !== undefined) {
|
||||
store.set(
|
||||
VLC_PLAYER_ARGUMENTS,
|
||||
normalizeExternalPlayerArguments(arg.vlcPlayerArguments)
|
||||
);
|
||||
}
|
||||
|
||||
// Only set values that are defined
|
||||
if (arg.mpvReuseInstance !== undefined) {
|
||||
store.set(MPV_REUSE_INSTANCE, arg.mpvReuseInstance);
|
||||
@@ -25,9 +42,13 @@ ipcMain.handle('SETTINGS_UPDATE', (_event, arg) => {
|
||||
}
|
||||
|
||||
// Handle remote control settings
|
||||
if (arg.remoteControl !== undefined || arg.remoteControlPort !== undefined) {
|
||||
if (
|
||||
arg.remoteControl !== undefined ||
|
||||
arg.remoteControlPort !== undefined
|
||||
) {
|
||||
const enabled = arg.remoteControl ?? store.get('remoteControl', false);
|
||||
const port = arg.remoteControlPort ?? store.get('remoteControlPort', 8765);
|
||||
const port =
|
||||
arg.remoteControlPort ?? store.get('remoteControlPort', 8765);
|
||||
|
||||
// Save to store
|
||||
if (arg.remoteControl !== undefined) {
|
||||
|
||||
@@ -3,14 +3,18 @@ import { getElectronConfigDirectory } from 'database';
|
||||
|
||||
export const WINDOW_BOUNDS = 'WINDOW_BOUNDS';
|
||||
export const MPV_PLAYER_PATH = 'MPV_PLAYER_PATH';
|
||||
export const MPV_PLAYER_ARGUMENTS = 'MPV_PLAYER_ARGUMENTS';
|
||||
export const VLC_PLAYER_PATH = 'VLC_PLAYER_PATH';
|
||||
export const VLC_PLAYER_ARGUMENTS = 'VLC_PLAYER_ARGUMENTS';
|
||||
export const MPV_REUSE_INSTANCE = 'MPV_REUSE_INSTANCE';
|
||||
export const VLC_REUSE_INSTANCE = 'VLC_REUSE_INSTANCE';
|
||||
|
||||
export type StoreType = {
|
||||
[WINDOW_BOUNDS]: Electron.Rectangle;
|
||||
[MPV_PLAYER_PATH]: string;
|
||||
[MPV_PLAYER_ARGUMENTS]: string;
|
||||
[VLC_PLAYER_PATH]: string;
|
||||
[VLC_PLAYER_ARGUMENTS]: string;
|
||||
[MPV_REUSE_INSTANCE]: boolean;
|
||||
[VLC_REUSE_INSTANCE]: boolean;
|
||||
};
|
||||
|
||||
@@ -44,8 +44,10 @@ const DEFAULT_SETTINGS: Settings = {
|
||||
showExternalPlaybackBar: true,
|
||||
theme: Theme.SystemTheme,
|
||||
mpvPlayerPath: '',
|
||||
mpvPlayerArguments: '',
|
||||
mpvReuseInstance: false,
|
||||
vlcPlayerPath: '',
|
||||
vlcPlayerArguments: '',
|
||||
vlcReuseInstance: false,
|
||||
remoteControl: false,
|
||||
remoteControlPort: 8765,
|
||||
|
||||
@@ -174,6 +174,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="setting-item setting-item--external-player-arguments"
|
||||
data-test-id="mpv-player-arguments-setting"
|
||||
>
|
||||
<div class="setting-item__meta">
|
||||
<h4>{{ 'SETTINGS.MPV_PLAYER_ARGUMENTS_LABEL' | translate }}</h4>
|
||||
<p>
|
||||
{{
|
||||
'SETTINGS.MPV_PLAYER_ARGUMENTS_DESCRIPTION' | translate
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="setting-item__control">
|
||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||
<mat-label for="mpvPlayerArguments">{{
|
||||
'SETTINGS.MPV_PLAYER_ARGUMENTS' | translate
|
||||
}}</mat-label>
|
||||
<textarea
|
||||
matInput
|
||||
id="mpvPlayerArguments"
|
||||
formControlName="mpvPlayerArguments"
|
||||
[placeholder]="mpvPlayerArgumentsPlaceholder"
|
||||
rows="3"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
></textarea>
|
||||
<mat-hint>{{
|
||||
'SETTINGS.EXTERNAL_PLAYER_ARGUMENTS_HINT' | translate
|
||||
}}</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__meta">
|
||||
<h4>{{ 'SETTINGS.MPV_REUSE_INSTANCE_LABEL' | translate }}</h4>
|
||||
@@ -210,6 +243,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="setting-item setting-item--external-player-arguments"
|
||||
data-test-id="vlc-player-arguments-setting"
|
||||
>
|
||||
<div class="setting-item__meta">
|
||||
<h4>{{ 'SETTINGS.VLC_PLAYER_ARGUMENTS_LABEL' | translate }}</h4>
|
||||
<p>
|
||||
{{
|
||||
'SETTINGS.VLC_PLAYER_ARGUMENTS_DESCRIPTION' | translate
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="setting-item__control">
|
||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||
<mat-label for="vlcPlayerArguments">{{
|
||||
'SETTINGS.VLC_PLAYER_ARGUMENTS' | translate
|
||||
}}</mat-label>
|
||||
<textarea
|
||||
matInput
|
||||
id="vlcPlayerArguments"
|
||||
formControlName="vlcPlayerArguments"
|
||||
[placeholder]="vlcPlayerArgumentsPlaceholder"
|
||||
rows="3"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
></textarea>
|
||||
<mat-hint>{{
|
||||
'SETTINGS.EXTERNAL_PLAYER_ARGUMENTS_HINT' | translate
|
||||
}}</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__meta">
|
||||
<h4>{{ 'SETTINGS.VLC_REUSE_INSTANCE_LABEL' | translate }}</h4>
|
||||
|
||||
@@ -11,6 +11,9 @@ const MPV_COMPATIBLE_PLAYER_TIP =
|
||||
'IINA can be launched as an MPV-compatible player on macOS, but use its executable path, such as /Applications/IINA.app/Contents/MacOS/iina-cli or /Applications/IINA.app/Contents/MacOS/IINA. IPTVnator controls, position polling, and reuse-instance behavior are guaranteed only for MPV IPC.';
|
||||
const VLC_PATH_DESCRIPTION =
|
||||
'Set the path to VLC. On macOS you can use the VLC app bundle, such as /Applications/VLC.app, or the executable path.';
|
||||
const MPV_ARGUMENTS_PLACEHOLDER =
|
||||
'--ontop\n--autofit=640x360\n--geometry=+80+80';
|
||||
const VLC_ARGUMENTS_PLACEHOLDER = '--video-on-top\n--width=640\n--height=360';
|
||||
|
||||
describe('SettingsPlaybackSectionComponent', () => {
|
||||
let fixture: ComponentFixture<SettingsPlaybackSectionComponent>;
|
||||
@@ -226,6 +229,56 @@ describe('SettingsPlaybackSectionComponent', () => {
|
||||
)
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('shows MPV command-line arguments only when MPV is selected', () => {
|
||||
fixture.componentRef.setInput('form', createForm(VideoPlayer.MPV));
|
||||
fixture.componentRef.setInput('isDesktop', true);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'[data-test-id="mpv-player-arguments-setting"]'
|
||||
)
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'[data-test-id="vlc-player-arguments-setting"]'
|
||||
)
|
||||
).toBeNull();
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'SETTINGS.MPV_PLAYER_ARGUMENTS_LABEL'
|
||||
);
|
||||
expect(
|
||||
fixture.nativeElement.querySelector<HTMLTextAreaElement>(
|
||||
'#mpvPlayerArguments'
|
||||
)?.placeholder
|
||||
).toBe(MPV_ARGUMENTS_PLACEHOLDER);
|
||||
});
|
||||
|
||||
it('shows VLC command-line arguments only when VLC is selected', () => {
|
||||
fixture.componentRef.setInput('form', createForm(VideoPlayer.VLC));
|
||||
fixture.componentRef.setInput('isDesktop', true);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'[data-test-id="vlc-player-arguments-setting"]'
|
||||
)
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'[data-test-id="mpv-player-arguments-setting"]'
|
||||
)
|
||||
).toBeNull();
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'SETTINGS.VLC_PLAYER_ARGUMENTS_LABEL'
|
||||
);
|
||||
expect(
|
||||
fixture.nativeElement.querySelector<HTMLTextAreaElement>(
|
||||
'#vlcPlayerArguments'
|
||||
)?.placeholder
|
||||
).toBe(VLC_ARGUMENTS_PLACEHOLDER);
|
||||
});
|
||||
});
|
||||
|
||||
function createForm(player = VideoPlayer.VideoJs): FormGroup {
|
||||
@@ -235,8 +288,10 @@ function createForm(player = VideoPlayer.VideoJs): FormGroup {
|
||||
openStreamOnDoubleClick: new FormControl(false),
|
||||
showExternalPlaybackBar: new FormControl(true),
|
||||
mpvPlayerPath: new FormControl(''),
|
||||
mpvPlayerArguments: new FormControl(''),
|
||||
mpvReuseInstance: new FormControl(false),
|
||||
vlcPlayerPath: new FormControl(''),
|
||||
vlcPlayerArguments: new FormControl(''),
|
||||
vlcReuseInstance: new FormControl(false),
|
||||
recordingFolder: new FormControl(''),
|
||||
});
|
||||
|
||||
@@ -29,6 +29,17 @@ import { SettingsPlayerOption } from './settings.models';
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class SettingsPlaybackSectionComponent {
|
||||
readonly mpvPlayerArgumentsPlaceholder = [
|
||||
'--ontop',
|
||||
'--autofit=640x360',
|
||||
'--geometry=+80+80',
|
||||
].join('\n');
|
||||
readonly vlcPlayerArgumentsPlaceholder = [
|
||||
'--video-on-top',
|
||||
'--width=640',
|
||||
'--height=360',
|
||||
].join('\n');
|
||||
|
||||
readonly form = input.required<FormGroup>();
|
||||
readonly activeSection = input.required<string>();
|
||||
readonly players = input.required<SettingsPlayerOption[]>();
|
||||
|
||||
@@ -211,6 +211,30 @@
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.setting-item--external-player-arguments {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
|
||||
.setting-item__control {
|
||||
justify-content: stretch;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 86px;
|
||||
resize: vertical;
|
||||
font-family:
|
||||
'JetBrains Mono', 'SFMono-Regular', Menlo, Monaco, Consolas,
|
||||
monospace;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
width: min(100%, 540px);
|
||||
display: grid;
|
||||
|
||||
@@ -76,8 +76,10 @@ const DEFAULT_SETTINGS = {
|
||||
showExternalPlaybackBar: true,
|
||||
theme: Theme.SystemTheme,
|
||||
mpvPlayerPath: '',
|
||||
mpvPlayerArguments: '',
|
||||
mpvReuseInstance: false,
|
||||
vlcPlayerPath: '',
|
||||
vlcPlayerArguments: '',
|
||||
vlcReuseInstance: false,
|
||||
remoteControl: false,
|
||||
remoteControlPort: 8765,
|
||||
@@ -953,4 +955,31 @@ describe('SettingsComponent', () => {
|
||||
expect(setMpvPlayerPath).toHaveBeenCalledWith('');
|
||||
expect(setVlcPlayerPath).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
it('saves external player command-line arguments with the settings payload', async () => {
|
||||
const mockStore = settingsStore as unknown as MockSettingsStore;
|
||||
mockStore.updateSettings.mockResolvedValue(undefined);
|
||||
const updateSettings = jest.spyOn(window.electron, 'updateSettings');
|
||||
|
||||
component.settingsForm.patchValue({
|
||||
mpvPlayerArguments: '--screen=1\n--geometry=1280x720',
|
||||
vlcPlayerArguments: '--qt-fullscreen-screennumber=1',
|
||||
});
|
||||
|
||||
component.onSubmit();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(mockStore.updateSettings).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mpvPlayerArguments: '--screen=1\n--geometry=1280x720',
|
||||
vlcPlayerArguments: '--qt-fullscreen-screennumber=1',
|
||||
})
|
||||
);
|
||||
expect(updateSettings).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mpvPlayerArguments: '--screen=1\n--geometry=1280x720',
|
||||
vlcPlayerArguments: '--qt-fullscreen-screennumber=1',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
EmbeddedMpvSupport,
|
||||
CoverSize,
|
||||
Language,
|
||||
normalizeExternalPlayerArguments,
|
||||
StartupBehavior,
|
||||
StreamFormat,
|
||||
Theme,
|
||||
@@ -187,8 +188,10 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
showExternalPlaybackBar: true,
|
||||
theme: Theme.SystemTheme,
|
||||
mpvPlayerPath: '',
|
||||
mpvPlayerArguments: '',
|
||||
mpvReuseInstance: false,
|
||||
vlcPlayerPath: '',
|
||||
vlcPlayerArguments: '',
|
||||
vlcReuseInstance: false,
|
||||
remoteControl: false,
|
||||
remoteControlPort: [
|
||||
@@ -465,13 +468,21 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
* the indexed db store
|
||||
*/
|
||||
onSubmit(): void {
|
||||
const settings = this.settingsForm.value;
|
||||
const mpvPlayerPath = this.normalizeExternalPlayerPath(
|
||||
settings.mpvPlayerPath
|
||||
);
|
||||
const vlcPlayerPath = this.normalizeExternalPlayerPath(
|
||||
settings.vlcPlayerPath
|
||||
);
|
||||
const settings = {
|
||||
...this.settingsForm.value,
|
||||
mpvPlayerPath: this.normalizeExternalPlayerPath(
|
||||
this.settingsForm.value.mpvPlayerPath
|
||||
),
|
||||
vlcPlayerPath: this.normalizeExternalPlayerPath(
|
||||
this.settingsForm.value.vlcPlayerPath
|
||||
),
|
||||
mpvPlayerArguments: normalizeExternalPlayerArguments(
|
||||
this.settingsForm.value.mpvPlayerArguments
|
||||
),
|
||||
vlcPlayerArguments: normalizeExternalPlayerArguments(
|
||||
this.settingsForm.value.vlcPlayerArguments
|
||||
),
|
||||
};
|
||||
|
||||
this.settingsStore.updateSettings(settings).then(() => {
|
||||
this.applyChangedSettings();
|
||||
@@ -479,8 +490,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
if (window.electron) {
|
||||
window.electron.updateSettings(settings);
|
||||
|
||||
window.electron.setMpvPlayerPath(mpvPlayerPath);
|
||||
window.electron.setVlcPlayerPath(vlcPlayerPath);
|
||||
window.electron.setMpvPlayerPath(settings.mpvPlayerPath);
|
||||
window.electron.setVlcPlayerPath(settings.vlcPlayerPath);
|
||||
}
|
||||
});
|
||||
if (this.isDialog) {
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "حدد مسار MPV. على macOS يمكنك استخدام حزمة تطبيق MPV مثل /Applications/mpv.app أو مسار الملف التنفيذي.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "يمكن تشغيل IINA كمشغل متوافق مع MPV على macOS، لكن استخدم مسار الملف التنفيذي مثل /Applications/IINA.app/Contents/MacOS/iina-cli أو /Applications/IINA.app/Contents/MacOS/IINA. عناصر تحكم IPTVnator وتتبع الموضع وإعادة استخدام النافذة مضمونة فقط مع MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "مسار مشغل MPV",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "إعادة استخدام نسخة MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "عند التفعيل، سيتم إعادة استخدام نفس نافذة MPV لتشغيل البثوث المختلفة. عند التعطيل، يفتح كل بث في نافذة MPV جديدة.",
|
||||
"VLC_PLAYER_PATH_LABEL": "مسار VLC",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "حدد مسار VLC. على macOS يمكنك استخدام حزمة تطبيق VLC مثل /Applications/VLC.app أو مسار الملف التنفيذي.",
|
||||
"VLC_PLAYER_PATH": "مسار مشغل VLC",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "إعادة استخدام نسخة VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "عند التفعيل، سيتم إعادة استخدام نفس نافذة VLC لتشغيل البثوث المختلفة. عند التعطيل، يفتح كل بث في نافذة VLC جديدة.",
|
||||
"REMOTE_CONTROL": "التحكم عن بعد",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "حدد مسار MPV. فـ macOS تقدر تستعمل باقة تطبيق MPV بحال /Applications/mpv.app أو مسار الملف التنفيذي.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA يقدر يتفتح كمشغل متوافق مع MPV فـ macOS، ولكن استعمل مسار الملف التنفيذي بحال /Applications/IINA.app/Contents/MacOS/iina-cli أو /Applications/IINA.app/Contents/MacOS/IINA. تحكم IPTVnator وتتبع الموضع وإعادة استعمال النافذة مضمونين غير مع MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "مسار مشغل mpv",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "إعادة استخدام نسخة MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "إلى كانت مفعلة، نفس نافذة MPV غادي تستعمل لتشغيل بثوث مختلفة. إلى كانت معطلة، كل بث كيتحل في نافذة MPV جديدة.",
|
||||
"VLC_PLAYER_PATH_LABEL": "مسار VLC",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "حدد مسار VLC. فـ macOS تقدر تستعمل باقة تطبيق VLC بحال /Applications/VLC.app أو مسار الملف التنفيذي.",
|
||||
"VLC_PLAYER_PATH": "مسار مشغل VLC",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "إعادة استخدام نسخة VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "إلى كانت مفعلة، نفس نافذة VLC غادي تستعمل لتشغيل بثوث مختلفة. إلى كانت معطلة، كل بث كيتحل في نافذة VLC جديدة.",
|
||||
"REMOTE_CONTROL": "التحكم عن بعد",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Укажыце шлях да MPV. На macOS можна выкарыстоўваць пакет праграмы MPV, напрыклад /Applications/mpv.app, або шлях да выканальнага файла.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA можна запускаць як MPV-сумяшчальны прайгравальнік на macOS, але выкарыстоўвайце шлях да выканальнага файла, напрыклад /Applications/IINA.app/Contents/MacOS/iina-cli або /Applications/IINA.app/Contents/MacOS/IINA. Кіраванне IPTVnator, апытанне пазіцыі і паўторнае выкарыстанне акна гарантаваныя толькі для MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "Шлях да mpv прайгравальніка",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "Паўторна выкарыстоўваць асобнік MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Калі ўключана, тое ж акно MPV будзе паўторна выкарыстоўвацца для прайгравання розных патокаў. Калі адключана, кожны паток адкрываецца ў новым акне MPV.",
|
||||
"VLC_PLAYER_PATH_LABEL": "Шлях да VLC прайгравальніка",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Укажыце шлях да VLC. На macOS можна выкарыстоўваць пакет праграмы VLC, напрыклад /Applications/VLC.app, або шлях да выканальнага файла.",
|
||||
"VLC_PLAYER_PATH": "Шлях да VLC прайгравальніка",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "Паўторна выкарыстоўваць асобнік VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Калі ўключана, тое ж акно VLC будзе паўторна выкарыстоўвацца для прайгравання розных патокаў. Калі адключана, кожны паток адкрываецца ў новым акне VLC.",
|
||||
"REMOTE_CONTROL": "Аддаленае кіраванне",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Lege den Pfad zu MPV fest. Unter macOS kannst du das MPV-App-Bundle wie /Applications/mpv.app oder den ausführbaren Pfad verwenden.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA kann unter macOS als MPV-kompatibler Player gestartet werden, verwende dafür aber den ausführbaren Pfad wie /Applications/IINA.app/Contents/MacOS/iina-cli oder /Applications/IINA.app/Contents/MacOS/IINA. IPTVnator-Steuerung, Positionsabfrage und Wiederverwendung der Instanz sind nur für MPV IPC garantiert.",
|
||||
"MPV_PLAYER_PATH": "MPV-Player-Pfad",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "MPV-Instanz wiederverwenden",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Wenn aktiviert, wird dasselbe MPV-Fenster zum Abspielen verschiedener Streams wiederverwendet. Wenn deaktiviert, wird jeder Stream in einem neuen MPV-Fenster geöffnet.",
|
||||
"VLC_PLAYER_PATH_LABEL": "VLC-Pfad",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Lege den Pfad zu VLC fest. Unter macOS kannst du das VLC-App-Bundle wie /Applications/VLC.app oder den ausführbaren Pfad verwenden.",
|
||||
"VLC_PLAYER_PATH": "VLC-Playerpfad",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "VLC-Instanz wiederverwenden",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Wenn aktiviert, wird dasselbe VLC-Fenster zum Abspielen verschiedener Streams wiederverwendet. Wenn deaktiviert, wird jeder Stream in einem neuen VLC-Fenster geöffnet.",
|
||||
"REMOTE_CONTROL": "Fernsteuerung",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Ορίστε τη διαδρομή προς το MPV. Στο macOS μπορείτε να χρησιμοποιήσετε το πακέτο εφαρμογής MPV, όπως /Applications/mpv.app, ή τη διαδρομή του εκτελέσιμου αρχείου.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "Το IINA μπορεί να εκκινηθεί ως συμβατό με MPV πρόγραμμα αναπαραγωγής στο macOS, αλλά χρησιμοποιήστε τη διαδρομή του εκτελέσιμου, όπως /Applications/IINA.app/Contents/MacOS/iina-cli ή /Applications/IINA.app/Contents/MacOS/IINA. Τα στοιχεία ελέγχου του IPTVnator, η παρακολούθηση θέσης και η επαναχρησιμοποίηση instance είναι εγγυημένα μόνο για MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "διαδρομή mpv προγράμματος αναπαραγωγής",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "Επαναχρησιμοποίηση της παρουσίας MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Όταν είναι ενεργοποιημένη, το ίδιο παράθυρο MPV θα επαναχρησιμοποιηθεί για την αναπαραγωγή διαφορετικών ροών. Όταν είναι απενεργοποιημένη, κάθε ροή ανοίγει σε ένα νέο παράθυρο MPV.",
|
||||
"VLC_PLAYER_PATH_LABEL": "διαδρομή VLC",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Ορίστε τη διαδρομή προς το VLC. Στο macOS μπορείτε να χρησιμοποιήσετε το πακέτο εφαρμογής VLC, όπως /Applications/VLC.app, ή τη διαδρομή του εκτελέσιμου αρχείου.",
|
||||
"VLC_PLAYER_PATH": "διαδρομή VLC player",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "Επαναχρησιμοποίηση της παρουσίας VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Όταν είναι ενεργοποιημένη, το ίδιο παράθυρο VLC θα επαναχρησιμοποιηθεί για την αναπαραγωγή διαφορετικών ροών. Όταν είναι απενεργοποιημένη, κάθε ροή ανοίγει σε ένα νέο παράθυρο VLC.",
|
||||
"REMOTE_CONTROL": "Τηλεχειριστήριο",
|
||||
|
||||
@@ -323,6 +323,9 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Set the path to MPV. On macOS you can use the MPV app bundle, such as /Applications/mpv.app, or the executable path.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA can be launched as an MPV-compatible player on macOS, but use its executable path, such as /Applications/IINA.app/Contents/MacOS/iina-cli or /Applications/IINA.app/Contents/MacOS/IINA. IPTVnator controls, position polling, and reuse-instance behavior are guaranteed only for MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "mpv player path",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "Reuse MPV instance",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "When enabled, the same MPV window will be reused for playing different streams. When disabled, each stream opens in a new MPV window.",
|
||||
"RECORDING_FOLDER_LABEL": "Recording folder",
|
||||
@@ -332,6 +335,10 @@
|
||||
"VLC_PLAYER_PATH_LABEL": "VLC path",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Set the path to VLC. On macOS you can use the VLC app bundle, such as /Applications/VLC.app, or the executable path.",
|
||||
"VLC_PLAYER_PATH": "VLC player path",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "Reuse VLC instance",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "When enabled, the same VLC window will be reused for playing different streams. When disabled, each stream opens in a new VLC window.",
|
||||
"REMOTE_CONTROL": "Remote control",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Establece la ruta a MPV. En macOS puedes usar el paquete de la app MPV, como /Applications/mpv.app, o la ruta del ejecutable.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA puede iniciarse como reproductor compatible con MPV en macOS, pero usa su ruta ejecutable, como /Applications/IINA.app/Contents/MacOS/iina-cli o /Applications/IINA.app/Contents/MacOS/IINA. Los controles de IPTVnator, el seguimiento de posición y la reutilización de instancia solo están garantizados con MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "ruta del reproductor mpv",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "Reutilizar instancia de MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Cuando esta opción está activada, se reutiliza la misma ventana de MPV para reproducir distintos streams. Cuando está desactivada, cada stream se abre en una nueva ventana de MPV.",
|
||||
"VLC_PLAYER_PATH_LABEL": "ruta VLC",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Establece la ruta a VLC. En macOS puedes usar el paquete de la app VLC, como /Applications/VLC.app, o la ruta del ejecutable.",
|
||||
"VLC_PLAYER_PATH": "Ruta del reproductor VLC",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "Reutilizar instancia de VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Cuando esta opción está activada, se reutiliza la misma ventana de VLC para reproducir distintos streams. Cuando está desactivada, cada stream se abre en una nueva ventana de VLC.",
|
||||
"REMOTE_CONTROL": "Control remoto",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Définissez le chemin vers MPV. Sur macOS, vous pouvez utiliser le bundle de l’app MPV, comme /Applications/mpv.app, ou le chemin de l’exécutable.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA peut être lancé comme lecteur compatible MPV sur macOS, mais utilisez son chemin exécutable, comme /Applications/IINA.app/Contents/MacOS/iina-cli ou /Applications/IINA.app/Contents/MacOS/IINA. Les contrôles IPTVnator, le suivi de position et la réutilisation d’instance ne sont garantis qu’avec MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "Chemin du lecteur mpv",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "Réutiliser l'instance MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Lorsqu'elle est activée, la même fenêtre MPV sera réutilisée pour lire différents flux. Lorsqu'elle est désactivée, chaque flux s'ouvre dans une nouvelle fenêtre MPV.",
|
||||
"VLC_PLAYER_PATH_LABEL": "Chemin VLC",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Définissez le chemin vers VLC. Sur macOS, vous pouvez utiliser le bundle de l’app VLC, comme /Applications/VLC.app, ou le chemin de l’exécutable.",
|
||||
"VLC_PLAYER_PATH": "Chemin du lecteur VLC",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "Réutiliser l'instance VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Lorsqu'elle est activée, la même fenêtre VLC sera réutilisée pour lire différents flux. Lorsqu'elle est désactivée, chaque flux s'ouvre dans une nouvelle fenêtre VLC.",
|
||||
"REMOTE_CONTROL": "Télécommande",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Imposta il percorso di MPV. Su macOS puoi usare il bundle dell’app MPV, come /Applications/mpv.app, oppure il percorso dell’eseguibile.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA può essere avviato come player compatibile con MPV su macOS, ma usa il suo percorso eseguibile, come /Applications/IINA.app/Contents/MacOS/iina-cli o /Applications/IINA.app/Contents/MacOS/IINA. I controlli di IPTVnator, il polling della posizione e il riuso dell’istanza sono garantiti solo con MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "percorso riproduttore mpv",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "Riutilizza istanza MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Quando attivo, la stessa finestra MPV viene riutilizzata per riprodurre stream diversi. Quando disattivo, ogni stream viene aperto in una nuova finestra MPV.",
|
||||
"VLC_PLAYER_PATH_LABEL": "Percorso VLC",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Imposta il percorso di VLC. Su macOS puoi usare il bundle dell’app VLC, come /Applications/VLC.app, oppure il percorso dell’eseguibile.",
|
||||
"VLC_PLAYER_PATH": "Percorso lettore VLC",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "Riutilizza istanza VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Quando attivo, la stessa finestra VLC viene riutilizzata per riprodurre stream diversi. Quando disattivo, ogni stream viene aperto in una nuova finestra VLC.",
|
||||
"REMOTE_CONTROL": "Telecomando",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "MPV へのパスを設定します。macOS では /Applications/mpv.app のような MPV アプリバンドル、または実行ファイルのパスを使用できます。",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA は macOS で MPV 互換プレーヤーとして起動できますが、/Applications/IINA.app/Contents/MacOS/iina-cli または /Applications/IINA.app/Contents/MacOS/IINA のような実行ファイルのパスを指定してください。IPTVnator の操作、位置ポーリング、インスタンス再利用は MPV IPC でのみ保証されます。",
|
||||
"MPV_PLAYER_PATH": "mpvプレーヤーのパス",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "MPVインスタンスを再利用",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "有効にすると、異なるストリームの再生に同じMPVウィンドウが再利用されます。無効にすると、各ストリームが新しいMPVウィンドウで開きます。",
|
||||
"VLC_PLAYER_PATH_LABEL": "VLCのパス",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "VLC へのパスを設定します。macOS では /Applications/VLC.app のような VLC アプリバンドル、または実行ファイルのパスを使用できます。",
|
||||
"VLC_PLAYER_PATH": "VLCプレーヤーのパス",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "VLCインスタンスを再利用",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "有効にすると、異なるストリームの再生に同じVLCウィンドウが再利用されます。無効にすると、各ストリームが新しいVLCウィンドウで開きます。",
|
||||
"REMOTE_CONTROL": "リモートコントロール",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "MPV 경로를 설정합니다. macOS에서는 /Applications/mpv.app 같은 MPV 앱 번들이나 실행 파일 경로를 사용할 수 있습니다.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA는 macOS에서 MPV 호환 플레이어로 실행할 수 있지만 /Applications/IINA.app/Contents/MacOS/iina-cli 또는 /Applications/IINA.app/Contents/MacOS/IINA 같은 실행 파일 경로를 사용하세요. IPTVnator 컨트롤, 위치 폴링, 인스턴스 재사용 동작은 MPV IPC에서만 보장됩니다.",
|
||||
"MPV_PLAYER_PATH": "mpv 플레이어 경로",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "MPV 인스턴스 재사용",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "활성화하면 동일한 MPV 창에서 다른 스트림을 재생합니다. 비활성화하면 각 스트림이 새 MPV 창에서 열립니다.",
|
||||
"VLC_PLAYER_PATH_LABEL": "VLC 경로",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "VLC 경로를 설정합니다. macOS에서는 /Applications/VLC.app 같은 VLC 앱 번들이나 실행 파일 경로를 사용할 수 있습니다.",
|
||||
"VLC_PLAYER_PATH": "VLC 플레이어 경로",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "VLC 인스턴스 재사용",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "활성화하면 동일한 VLC 창에서 다른 스트림을 재생합니다. 비활성화하면 각 스트림이 새 VLC 창에서 열립니다.",
|
||||
"REMOTE_CONTROL": "원격 제어",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Stel het pad naar MPV in. Op macOS kun je de MPV-appbundel gebruiken, zoals /Applications/mpv.app, of het pad naar het uitvoerbare bestand.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA kan op macOS als MPV-compatibele speler worden gestart, maar gebruik het uitvoerbare pad, zoals /Applications/IINA.app/Contents/MacOS/iina-cli of /Applications/IINA.app/Contents/MacOS/IINA. IPTVnator-bediening, positie-polling en hergebruik van de instantie zijn alleen gegarandeerd met MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "mpv-speler bestandspad",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "MPV-instantie hergebruiken",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Wanneer ingeschakeld, wordt hetzelfde MPV-venster hergebruikt voor het afspelen van verschillende streams. Wanneer uitgeschakeld, wordt elke stream geopend in een nieuw MPV-venster.",
|
||||
"VLC_PLAYER_PATH_LABEL": "VLC-bestandspad",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Stel het pad naar VLC in. Op macOS kun je de VLC-appbundel gebruiken, zoals /Applications/VLC.app, of het pad naar het uitvoerbare bestand.",
|
||||
"VLC_PLAYER_PATH": "VLC-speler bestandspad",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "VLC-instantie hergebruiken",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Wanneer ingeschakeld, wordt hetzelfde VLC-venster hergebruikt voor het afspelen van verschillende streams. Wanneer uitgeschakeld, wordt elke stream geopend in een nieuw VLC-venster.",
|
||||
"REMOTE_CONTROL": "Afstandsbediening",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Ustaw ścieżkę do MPV. W macOS możesz użyć pakietu aplikacji MPV, np. /Applications/mpv.app, albo ścieżki do pliku wykonywalnego.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA można uruchomić jako odtwarzacz zgodny z MPV w macOS, ale użyj ścieżki do pliku wykonywalnego, np. /Applications/IINA.app/Contents/MacOS/iina-cli lub /Applications/IINA.app/Contents/MacOS/IINA. Sterowanie IPTVnator, odpytywanie pozycji i ponowne użycie instancji są gwarantowane tylko dla MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "Ścieżka odtwarzacza mpv",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "Użyj ponownie instancji MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Po włączeniu to samo okno MPV będzie używane do odtwarzania różnych strumieni. Po wyłączeniu każdy strumień otwiera się w nowym oknie MPV.",
|
||||
"VLC_PLAYER_PATH_LABEL": "Ścieżka VLC",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Ustaw ścieżkę do VLC. W macOS możesz użyć pakietu aplikacji VLC, np. /Applications/VLC.app, albo ścieżki do pliku wykonywalnego.",
|
||||
"VLC_PLAYER_PATH": "Ścieżka odtwarzacza VLC",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "Użyj ponownie instancji VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Po włączeniu to samo okno VLC będzie używane do odtwarzania różnych strumieni. Po wyłączeniu każdy strumień otwiera się w nowym oknie VLC.",
|
||||
"REMOTE_CONTROL": "Zdalne sterowanie",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Defina o caminho para o MPV. No macOS você pode usar o pacote do app MPV, como /Applications/mpv.app, ou o caminho do executável.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "O IINA pode ser iniciado como player compatível com MPV no macOS, mas use o caminho do executável, como /Applications/IINA.app/Contents/MacOS/iina-cli ou /Applications/IINA.app/Contents/MacOS/IINA. Controles do IPTVnator, consulta de posição e reutilização de instância são garantidos apenas com MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "Caminho do reprodutor mpv",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "Reutilizar instância do MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Quando ativado, a mesma janela do MPV será reutilizada para reproduzir streams diferentes. Quando desativado, cada stream abre em uma nova janela do MPV.",
|
||||
"VLC_PLAYER_PATH_LABEL": "Caminho do VLC",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Defina o caminho para o VLC. No macOS você pode usar o pacote do app VLC, como /Applications/VLC.app, ou o caminho do executável.",
|
||||
"VLC_PLAYER_PATH": "Caminho do reprodutor VLC",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "Reutilizar instância do VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Quando ativado, a mesma janela do VLC será reutilizada para reproduzir streams diferentes. Quando desativado, cada stream abre em uma nova janela do VLC.",
|
||||
"REMOTE_CONTROL": "Controle remoto",
|
||||
|
||||
@@ -321,6 +321,9 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "Укажите путь к MPV. В macOS можно использовать пакет приложения MPV, например /Applications/mpv.app, или путь к исполняемому файлу.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA можно запускать как MPV-совместимый плеер в macOS, но указывайте путь к исполняемому файлу, например /Applications/IINA.app/Contents/MacOS/iina-cli или /Applications/IINA.app/Contents/MacOS/IINA. Управление из IPTVnator, опрос позиции и переиспользование экземпляра гарантированы только для MPV IPC.",
|
||||
"MPV_PLAYER_PATH": "Путь к mpv плееру",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "Аргументы командной строки mpv",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Необязательные аргументы, которые передаются в mpv при запуске нового внешнего процесса плеера.",
|
||||
"MPV_PLAYER_ARGUMENTS": "Аргументы mpv",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "Переиспользовать экземпляр MPV",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Когда включено, одно и то же окно MPV будет использоваться для воспроизведения разных потоков. Когда отключено, каждый поток открывается в новом окне MPV.",
|
||||
"RECORDING_FOLDER_LABEL": "Папка для записей",
|
||||
@@ -330,6 +333,10 @@
|
||||
"VLC_PLAYER_PATH_LABEL": "Путь к VLC плееру",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "Укажите путь к VLC. В macOS можно использовать пакет приложения VLC, например /Applications/VLC.app, или путь к исполняемому файлу.",
|
||||
"VLC_PLAYER_PATH": "Путь к VLC плееру",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "Аргументы командной строки VLC",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Необязательные аргументы, которые передаются в VLC при запуске нового внешнего процесса плеера.",
|
||||
"VLC_PLAYER_ARGUMENTS": "Аргументы VLC",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Один аргумент на строку. Эти значения применяются перед параметрами IPTVnator для конкретного потока.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "Переиспользовать экземпляр VLC",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Когда включено, одно и то же окно VLC будет использоваться для воспроизведения разных потоков. Когда отключено, каждый поток открывается в новом окне VLC.",
|
||||
"REMOTE_CONTROL": "Удалённое управление",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "MPV yolunu ayarlayın. macOS’ta /Applications/mpv.app gibi MPV uygulama paketini veya yürütülebilir dosya yolunu kullanabilirsiniz.",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA, macOS’ta MPV uyumlu bir oynatıcı olarak başlatılabilir; ancak /Applications/IINA.app/Contents/MacOS/iina-cli veya /Applications/IINA.app/Contents/MacOS/IINA gibi yürütülebilir dosya yolunu kullanın. IPTVnator kontrolleri, konum yoklama ve örnek yeniden kullanımı yalnızca MPV IPC için garanti edilir.",
|
||||
"MPV_PLAYER_PATH": "mpv oynatıcı yolu",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "MPV örneğini yeniden kullan",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Etkinleştirildiğinde, farklı yayınları oynatmak için aynı MPV penceresi yeniden kullanılır. Devre dışı bırakıldığında, her yayın yeni bir MPV penceresinde açılır.",
|
||||
"VLC_PLAYER_PATH_LABEL": "VLC yolu",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "VLC yolunu ayarlayın. macOS’ta /Applications/VLC.app gibi VLC uygulama paketini veya yürütülebilir dosya yolunu kullanabilirsiniz.",
|
||||
"VLC_PLAYER_PATH": "VLC oynatıcı yolu",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "VLC örneğini yeniden kullan",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Etkinleştirildiğinde, farklı yayınları oynatmak için aynı VLC penceresi yeniden kullanılır. Devre dışı bırakıldığında, her yayın yeni bir VLC penceresinde açılır.",
|
||||
"REMOTE_CONTROL": "Uzaktan kontrol",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "设置 MPV 路径。在 macOS 上可以使用 MPV 应用包,例如 /Applications/mpv.app,也可以使用可执行文件路径。",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA 可以在 macOS 上作为兼容 MPV 的播放器启动,但请使用它的可执行文件路径,例如 /Applications/IINA.app/Contents/MacOS/iina-cli 或 /Applications/IINA.app/Contents/MacOS/IINA。IPTVnator 控制、位置轮询和实例复用行为仅对 MPV IPC 保证可用。",
|
||||
"MPV_PLAYER_PATH": "mpv 播放器路径",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "复用 MPV 实例",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "启用后,将复用同一个 MPV 窗口播放不同的视频流。禁用后,每个视频流都会在新的 MPV 窗口中打开。",
|
||||
"VLC_PLAYER_PATH_LABEL": "VLC路径",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "设置 VLC 路径。在 macOS 上可以使用 VLC 应用包,例如 /Applications/VLC.app,也可以使用可执行文件路径。",
|
||||
"VLC_PLAYER_PATH": "VLC播放器路径",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "复用 VLC 实例",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "启用后,将复用同一个 VLC 窗口播放不同的视频流。禁用后,每个视频流都会在新的 VLC 窗口中打开。",
|
||||
"REMOTE_CONTROL": "远程控制",
|
||||
|
||||
@@ -321,11 +321,18 @@
|
||||
"MPV_PLAYER_PATH_DESCRIPTION": "設定 MPV 路徑。在 macOS 上可以使用 MPV App bundle,例如 /Applications/mpv.app,也可以使用可執行檔路徑。",
|
||||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA 可以在 macOS 上作為相容 MPV 的播放器啟動,但請使用它的可執行檔路徑,例如 /Applications/IINA.app/Contents/MacOS/iina-cli 或 /Applications/IINA.app/Contents/MacOS/IINA。IPTVnator 控制、位置輪詢與 instance 重複使用僅保證適用於 MPV IPC。",
|
||||
"MPV_PLAYER_PATH": "mpv 播放器路徑",
|
||||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||||
"MPV_REUSE_INSTANCE_LABEL": "重複使用 MPV 實體",
|
||||
"MPV_REUSE_INSTANCE_DESCRIPTION": "啟用後,相同的 MPV 視窗將重複用於播放不同串流。停用時,每個串流會在新的 MPV 視窗中開啟。",
|
||||
"VLC_PLAYER_PATH_LABEL": "VLC 路徑",
|
||||
"VLC_PLAYER_PATH_DESCRIPTION": "設定 VLC 路徑。在 macOS 上可以使用 VLC App bundle,例如 /Applications/VLC.app,也可以使用可執行檔路徑。",
|
||||
"VLC_PLAYER_PATH": "VLC 播放器路徑",
|
||||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||||
"VLC_REUSE_INSTANCE_LABEL": "重複使用 VLC 實體",
|
||||
"VLC_REUSE_INSTANCE_DESCRIPTION": "啟用後,相同的 VLC 視窗將重複用於播放不同串流。停用時,每個串流會在新的 VLC 視窗中開啟。",
|
||||
"REMOTE_CONTROL": "遠端控制",
|
||||
|
||||
@@ -160,6 +160,24 @@ When a diagnostic is actionable in Electron, the inline banner may offer `Open i
|
||||
|
||||
`PortalPlayer.openExternalPlayback(playback, player)` is the forced external launch API. It sends the playback payload to MPV or VLC regardless of the current saved player setting, so fallback buttons do not mutate preferences.
|
||||
|
||||
## External Player Arguments
|
||||
|
||||
Electron settings expose optional MPV and VLC command-line argument fields only
|
||||
when the corresponding external player is selected. The executable path remains a
|
||||
path-only setting; extra flags are stored separately as `mpvPlayerArguments` and
|
||||
`vlcPlayerArguments`.
|
||||
|
||||
Argument fields are line-oriented: one non-empty trimmed line becomes one argv
|
||||
entry. IPTVnator prepends those custom entries before its stream-specific runtime
|
||||
arguments, then keeps the stream URL last. This avoids shell parsing, keeps paths
|
||||
with spaces safe, and preserves existing settings for users who never configured
|
||||
extra arguments.
|
||||
|
||||
The arguments apply only when IPTVnator spawns a new external player process. If
|
||||
MPV or VLC instance reuse is active and an existing process is reused, subsequent
|
||||
streams are loaded through MPV IPC or VLC RC commands and new process arguments
|
||||
are not re-applied until a fresh process starts.
|
||||
|
||||
## Flatpak External Players
|
||||
|
||||
Flatpak cannot execute host-installed `mpv` or `vlc` binaries directly from the sandbox.
|
||||
|
||||
@@ -29,8 +29,10 @@ const DEFAULT_SETTINGS: Settings = {
|
||||
showExternalPlaybackBar: true,
|
||||
theme: Theme.SystemTheme,
|
||||
mpvPlayerPath: '',
|
||||
mpvPlayerArguments: '',
|
||||
mpvReuseInstance: false,
|
||||
vlcPlayerPath: '',
|
||||
vlcPlayerArguments: '',
|
||||
vlcReuseInstance: false,
|
||||
remoteControl: false,
|
||||
remoteControlPort: 8765,
|
||||
@@ -136,8 +138,10 @@ export const SettingsStore = signalStore(
|
||||
showExternalPlaybackBar: store.showExternalPlaybackBar!(),
|
||||
theme: store.theme(),
|
||||
mpvPlayerPath: store.mpvPlayerPath(),
|
||||
mpvPlayerArguments: store.mpvPlayerArguments(),
|
||||
mpvReuseInstance: store.mpvReuseInstance(),
|
||||
vlcPlayerPath: store.vlcPlayerPath(),
|
||||
vlcPlayerArguments: store.vlcPlayerArguments(),
|
||||
vlcReuseInstance: store.vlcReuseInstance(),
|
||||
remoteControl: store.remoteControl(),
|
||||
remoteControlPort: store.remoteControlPort(),
|
||||
|
||||
@@ -6,6 +6,7 @@ export * from './lib/epg-channel-with-programs.interface';
|
||||
export * from './lib/epg-channel.model';
|
||||
export * from './lib/epg-item.interface';
|
||||
export * from './lib/epg-program.model';
|
||||
export * from './lib/external-player-arguments.utils';
|
||||
export * from './lib/external-player-session.interface';
|
||||
export * from './lib/indexed-db.config';
|
||||
export * from './lib/ipc-command.class';
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
normalizeExternalPlayerArguments,
|
||||
parseExternalPlayerArguments,
|
||||
} from './external-player-arguments.utils';
|
||||
|
||||
describe('external player arguments utils', () => {
|
||||
it('parses one argument per non-empty trimmed line', () => {
|
||||
expect(
|
||||
parseExternalPlayerArguments(
|
||||
' --screen=1\n\n--geometry=1280x720\r\n --hwdec=auto-safe '
|
||||
)
|
||||
).toEqual(['--screen=1', '--geometry=1280x720', '--hwdec=auto-safe']);
|
||||
});
|
||||
|
||||
it('normalizes arguments to newline-separated trimmed lines', () => {
|
||||
expect(
|
||||
normalizeExternalPlayerArguments([
|
||||
' --screen=1 ',
|
||||
'',
|
||||
' --geometry=1280x720 ',
|
||||
])
|
||||
).toBe('--screen=1\n--geometry=1280x720');
|
||||
});
|
||||
|
||||
it('treats missing or unsupported values as empty arguments', () => {
|
||||
expect(parseExternalPlayerArguments(undefined)).toEqual([]);
|
||||
expect(parseExternalPlayerArguments(null)).toEqual([]);
|
||||
expect(parseExternalPlayerArguments(123 as unknown as string)).toEqual(
|
||||
[]
|
||||
);
|
||||
expect(normalizeExternalPlayerArguments(' \n ')).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
export type ExternalPlayerArgumentsInput =
|
||||
| string
|
||||
| readonly unknown[]
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export function parseExternalPlayerArguments(
|
||||
value: ExternalPlayerArgumentsInput
|
||||
): string[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((argument) => String(argument).trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return value
|
||||
.split(/\r?\n/)
|
||||
.map((argument) => argument.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function normalizeExternalPlayerArguments(
|
||||
value: ExternalPlayerArgumentsInput
|
||||
): string {
|
||||
return parseExternalPlayerArguments(value).join('\n');
|
||||
}
|
||||
@@ -37,8 +37,18 @@ export interface Settings {
|
||||
showExternalPlaybackBar?: boolean;
|
||||
theme: Theme;
|
||||
mpvPlayerPath: string;
|
||||
/**
|
||||
* Extra MPV CLI arguments entered one argument per line. Applied only when
|
||||
* starting a new external MPV process.
|
||||
*/
|
||||
mpvPlayerArguments: string;
|
||||
mpvReuseInstance: boolean;
|
||||
vlcPlayerPath: string;
|
||||
/**
|
||||
* Extra VLC CLI arguments entered one argument per line. Applied only when
|
||||
* starting a new external VLC process.
|
||||
*/
|
||||
vlcPlayerArguments: string;
|
||||
vlcReuseInstance: boolean;
|
||||
remoteControl: boolean;
|
||||
remoteControlPort: number;
|
||||
|
||||
Reference in new issue
Block a user