feat(settings): expose shared web controls toggle

This commit is contained in:
4gray committed 2026-07-17 07:53:06 +02:00
1 parent 0e49a6f3e1
commit c33fb957ff
23 files changed
+163 -84

No files matched your search

@@ -31,6 +31,7 @@ export function createSettingsForm(
) {
return formBuilder.group({
player: [VideoPlayer.VideoJs],
webPlayerSharedControls: false,
...(supportsEpg
? { epgUrl: new FormArray<FormControl<string | null>>([]) }
: {}),
@@ -114,6 +115,7 @@ export function createSettingsFromFormValue(
return {
player: value.player ?? VideoPlayer.VideoJs,
webPlayerSharedControls: value.webPlayerSharedControls ?? false,
streamFormat: value.streamFormat ?? StreamFormat.AutoStreamFormat,
openStreamOnDoubleClick: value.openStreamOnDoubleClick ?? false,
language: value.language ?? Language.ENGLISH,
@@ -69,6 +69,31 @@
</div>
</div>
@if (isWebPlayerSelected()) {
<div
class="setting-item"
data-test-id="web-player-shared-controls-setting"
>
<div class="setting-item__meta">
<h4>
{{ 'SETTINGS.WEB_PLAYER_SHARED_CONTROLS' | translate }}
</h4>
<p>
{{
'SETTINGS.WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="webPlayerSharedControls"
data-test-id="web-player-shared-controls-toggle"
></mat-checkbox>
</div>
</div>
}
@if (supportsManagedExternalPlayers() && isExternalPlayerSelected()) {
<div
class="setting-item"
@@ -18,6 +18,9 @@ const VLC_ARGUMENTS_PLACEHOLDER = '--video-on-top\n--width=640\n--height=360';
describe('SettingsPlaybackSectionComponent', () => {
let fixture: ComponentFixture<SettingsPlaybackSectionComponent>;
const queryByTestId = (testId: string): Element | null =>
fixture.nativeElement.querySelector(`[data-test-id="${testId}"]`);
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
@@ -65,9 +68,7 @@ describe('SettingsPlaybackSectionComponent', () => {
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector(
'[data-test-id="external-player-double-click-setting"]'
)
queryByTestId('external-player-double-click-setting')
).toBeNull();
expect(fixture.nativeElement.textContent).not.toContain(
'SETTINGS.OPEN_STREAM_ON_DOUBLE_CLICK'
@@ -80,9 +81,7 @@ describe('SettingsPlaybackSectionComponent', () => {
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector(
'[data-test-id="external-player-double-click-setting"]'
)
queryByTestId('external-player-double-click-setting')
).toBeNull();
});
@@ -98,9 +97,7 @@ describe('SettingsPlaybackSectionComponent', () => {
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector(
'[data-test-id="external-player-double-click-setting"]'
)
queryByTestId('external-player-double-click-setting')
).not.toBeNull();
expect(fixture.nativeElement.textContent).toContain(
'SETTINGS.OPEN_EXTERNAL_PLAYER_ON_DOUBLE_CLICK'
@@ -116,18 +113,14 @@ describe('SettingsPlaybackSectionComponent', () => {
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector(
'[data-test-id="external-player-double-click-setting"]'
)
queryByTestId('external-player-double-click-setting')
).toBeNull();
form.controls['player'].setValue(VideoPlayer.MPV);
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector(
'[data-test-id="external-player-double-click-setting"]'
)
queryByTestId('external-player-double-click-setting')
).not.toBeNull();
});
@@ -146,6 +139,44 @@ describe('SettingsPlaybackSectionComponent', () => {
expect(option?.textContent).toContain('auto');
});
it.each([
VideoPlayer.VideoJs,
VideoPlayer.Html5Player,
VideoPlayer.ArtPlayer,
])('shows shared web controls for %s', (player) => {
fixture.componentRef.setInput('form', createForm(player));
fixture.detectChanges();
expect(
queryByTestId('web-player-shared-controls-setting')
).not.toBeNull();
});
it.each([VideoPlayer.EmbeddedMpv, VideoPlayer.MPV, VideoPlayer.VLC])(
'hides shared web controls for %s',
(player) => {
fixture.componentRef.setInput('form', createForm(player));
fixture.detectChanges();
expect(
queryByTestId('web-player-shared-controls-setting')
).toBeNull();
}
);
it('binds the shared web controls checkbox to the settings form', () => {
const form = createForm();
form.controls['webPlayerSharedControls'].setValue(true);
fixture.componentRef.setInput('form', form);
fixture.detectChanges();
const checkbox = fixture.nativeElement.querySelector<HTMLInputElement>(
'[data-test-id="web-player-shared-controls-toggle"] input[type="checkbox"]'
);
expect(checkbox?.checked).toBe(true);
});
it('keeps the double-click option visible when path settings are unavailable', () => {
fixture.componentRef.setInput('form', createForm(VideoPlayer.MPV));
fixture.componentRef.setInput('isDesktop', true);
@@ -157,18 +188,12 @@ describe('SettingsPlaybackSectionComponent', () => {
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector(
'[data-test-id="external-player-double-click-setting"]'
)
queryByTestId('external-player-double-click-setting')
).not.toBeNull();
expect(fixture.nativeElement.textContent).not.toContain(
MPV_PATH_DESCRIPTION
);
expect(
fixture.nativeElement.querySelector(
'[data-test-id="mpv-compatible-player-tip"]'
)
).toBeNull();
expect(queryByTestId('mpv-compatible-player-tip')).toBeNull();
});
it.each<[string, boolean, boolean, boolean, boolean]>([
@@ -188,9 +213,7 @@ describe('SettingsPlaybackSectionComponent', () => {
);
fixture.detectChanges();
const setting = fixture.nativeElement.querySelector(
'[data-test-id="embedded-mpv-frame-copy-setting"]'
);
const setting = queryByTestId('embedded-mpv-frame-copy-setting');
expect(Boolean(setting)).toBe(expected);
}
);
@@ -199,20 +222,12 @@ describe('SettingsPlaybackSectionComponent', () => {
fixture.componentRef.setInput('isDesktop', true);
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector(
'[data-test-id="recording-folder-setting"]'
)
).not.toBeNull();
expect(queryByTestId('recording-folder-setting')).not.toBeNull();
fixture.componentRef.setInput('isDesktop', false);
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector(
'[data-test-id="recording-folder-setting"]'
)
).toBeNull();
expect(queryByTestId('recording-folder-setting')).toBeNull();
});
it('shows MPV bundle guidance and the IINA executable tip for desktop MPV playback', () => {
@@ -227,11 +242,7 @@ describe('SettingsPlaybackSectionComponent', () => {
expect(fixture.nativeElement.textContent).toContain(
MPV_PATH_DESCRIPTION
);
expect(
fixture.nativeElement.querySelector(
'[data-test-id="mpv-compatible-player-tip"]'
)
).not.toBeNull();
expect(queryByTestId('mpv-compatible-player-tip')).not.toBeNull();
expect(fixture.nativeElement.textContent).toContain(
MPV_COMPATIBLE_PLAYER_TIP
);
@@ -253,11 +264,7 @@ describe('SettingsPlaybackSectionComponent', () => {
expect(fixture.nativeElement.textContent).not.toContain(
MPV_COMPATIBLE_PLAYER_TIP
);
expect(
fixture.nativeElement.querySelector(
'[data-test-id="mpv-compatible-player-tip"]'
)
).toBeNull();
expect(queryByTestId('mpv-compatible-player-tip')).toBeNull();
});
it('shows VLC bundle guidance without the IINA tip for desktop VLC playback', () => {
@@ -272,11 +279,7 @@ describe('SettingsPlaybackSectionComponent', () => {
expect(fixture.nativeElement.textContent).toContain(
VLC_PATH_DESCRIPTION
);
expect(
fixture.nativeElement.querySelector(
'[data-test-id="mpv-compatible-player-tip"]'
)
).toBeNull();
expect(queryByTestId('mpv-compatible-player-tip')).toBeNull();
expect(fixture.nativeElement.textContent).not.toContain(
MPV_COMPATIBLE_PLAYER_TIP
);
@@ -295,11 +298,7 @@ describe('SettingsPlaybackSectionComponent', () => {
expect(fixture.nativeElement.textContent).not.toContain(
VLC_PATH_DESCRIPTION
);
expect(
fixture.nativeElement.querySelector(
'[data-test-id="mpv-compatible-player-tip"]'
)
).toBeNull();
expect(queryByTestId('mpv-compatible-player-tip')).toBeNull();
});
it('does not show external-player path guidance for embedded players', () => {
@@ -316,11 +315,7 @@ describe('SettingsPlaybackSectionComponent', () => {
expect(fixture.nativeElement.textContent).not.toContain(
VLC_PATH_DESCRIPTION
);
expect(
fixture.nativeElement.querySelector(
'[data-test-id="mpv-compatible-player-tip"]'
)
).toBeNull();
expect(queryByTestId('mpv-compatible-player-tip')).toBeNull();
});
it('shows MPV command-line arguments only when MPV is selected', () => {
@@ -332,16 +327,8 @@ describe('SettingsPlaybackSectionComponent', () => {
);
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(queryByTestId('mpv-player-arguments-setting')).not.toBeNull();
expect(queryByTestId('vlc-player-arguments-setting')).toBeNull();
expect(fixture.nativeElement.textContent).toContain(
'SETTINGS.MPV_PLAYER_ARGUMENTS_LABEL'
);
@@ -361,16 +348,8 @@ describe('SettingsPlaybackSectionComponent', () => {
);
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(queryByTestId('vlc-player-arguments-setting')).not.toBeNull();
expect(queryByTestId('mpv-player-arguments-setting')).toBeNull();
expect(fixture.nativeElement.textContent).toContain(
'SETTINGS.VLC_PLAYER_ARGUMENTS_LABEL'
);
@@ -385,6 +364,7 @@ describe('SettingsPlaybackSectionComponent', () => {
function createForm(player = VideoPlayer.VideoJs): FormGroup {
return new FormGroup({
player: new FormControl(player),
webPlayerSharedControls: new FormControl(false),
streamFormat: new FormControl(StreamFormat.AutoStreamFormat),
openStreamOnDoubleClick: new FormControl(false),
showExternalPlaybackBar: new FormControl(true),
@@ -53,6 +53,15 @@ export class SettingsPlaybackSectionComponent {
readonly supportsExternalPlayerPathSettings = input(false);
readonly selectRecordingFolder = output<void>();
isWebPlayerSelected(): boolean {
const player = this.form().value.player;
return (
player === VideoPlayer.VideoJs ||
player === VideoPlayer.Html5Player ||
player === VideoPlayer.ArtPlayer
);
}
isExternalPlayerSelected(): boolean {
const player = this.form().value.player;
return player === VideoPlayer.MPV || player === VideoPlayer.VLC;
@@ -84,6 +84,7 @@ export class MockRouter {
const DEFAULT_SETTINGS = {
player: VideoPlayer.VideoJs,
webPlayerSharedControls: false,
streamFormat: StreamFormat.AutoStreamFormat,
openStreamOnDoubleClick: false,
language: Language.ENGLISH,
@@ -661,6 +662,19 @@ describe('SettingsComponent', () => {
});
});
it('hydrates the shared web controls setting from the settings store', () => {
const mockStore = settingsStore as unknown as MockSettingsStore;
mockStore._setSettings({
webPlayerSharedControls: true,
});
component.setSettings();
expect(
component.settingsForm.get('webPlayerSharedControls')?.value
).toBe(true);
});
it('hides the embedded mpv option when the desktop support probe reports unsupported', async () => {
window.electron = {
...window.electron,
@@ -1301,9 +1315,7 @@ describe('SettingsComponent', () => {
it('updates the EPG view mode through the epg section output', () => {
const mockStore = settingsStore as unknown as MockSettingsStore;
const listButton = (
fixture.nativeElement as HTMLElement
).querySelector(
const listButton = (fixture.nativeElement as HTMLElement).querySelector(
'[data-test-id="epg-view-mode-list"]'
) as HTMLButtonElement;
@@ -1398,6 +1410,21 @@ describe('SettingsComponent', () => {
});
});
it('saves the shared web controls setting on submit', async () => {
const mockStore = settingsStore as unknown as MockSettingsStore;
mockStore.updateSettings.mockResolvedValue(undefined);
component.settingsForm.get('webPlayerSharedControls')?.setValue(true);
component.onSubmit();
await fixture.whenStable();
expect(mockStore.updateSettings).toHaveBeenCalledWith(
expect.objectContaining({
webPlayerSharedControls: true,
})
);
});
it('clears external player paths in Electron when saved as empty', async () => {
const mockStore = settingsStore as unknown as MockSettingsStore;
mockStore.updateSettings.mockResolvedValue(undefined);
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "جارٍ المسح…",
"EPG_URL_ERROR": "القيمة ليست رابطًا صالحًا",
"VIDEO_PLAYER_DESCRIPTION": "اختر مشغل الفيديو الافتراضي",
"WEB_PLAYER_SHARED_CONTROLS": "عناصر تحكم موحّدة لمشغلات الويب (تجريبي)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "استخدم عناصر التحكم المشتركة في IPTVnator مع HTML5 وVideo.js وArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "اختر تنسيق البث الافتراضي (xtream)",
"LANGUAGE_DESCRIPTION": "اختر لغة التطبيق",
"THEME_DESCRIPTION": "اختر السمة البصرية للتطبيق",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "جاري المسح…",
"EPG_URL_ERROR": "القيمة ماشي رابط صحيح",
"VIDEO_PLAYER_DESCRIPTION": "اختار مشغل الفيديو الافتراضي",
"WEB_PLAYER_SHARED_CONTROLS": "عناصر تحكم موحدة لمشغلات الويب (تجريبية)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "استعمل عناصر التحكم المشتركة ديال IPTVnator مع HTML5 وVideo.js وArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "اختار صيغة البث الافتراضية (xtream)",
"LANGUAGE_DESCRIPTION": "اختار لغة التطبيق",
"THEME_DESCRIPTION": "اختار المظهر المرئي للتطبيق",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Ачышчаецца...",
"EPG_URL_ERROR": "Уведзенае значэнне не з'яўляецца URL-адрасам.",
"VIDEO_PLAYER_DESCRIPTION": "Выбраць стандартны відэапрайгравальнік",
"WEB_PLAYER_SHARED_CONTROLS": "Адзіныя элементы кіравання для вэб-прайгравальнікаў (эксперыментальна)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Выкарыстоўваць агульныя элементы кіравання IPTVnator у HTML5, Video.js і ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Выберыце фармат патоку па змаўчанні (xtream)",
"LANGUAGE_DESCRIPTION": "Выбраць мову праграмы",
"THEME_DESCRIPTION": "Выбраць візуальную тэму афармлення",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Wird gelöscht…",
"EPG_URL_ERROR": "Der Wert ist keine gültige URL",
"VIDEO_PLAYER_DESCRIPTION": "Wählen Sie den Standard-Videoplayer aus",
"WEB_PLAYER_SHARED_CONTROLS": "Einheitliche Steuerung für Web-Player (experimentell)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Verwendet die gemeinsamen IPTVnator-Steuerelemente in HTML5, Video.js und ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Standard-Streamformat auswählen (Xtream)",
"LANGUAGE_DESCRIPTION": "Wählen Sie die Sprache der Anwendung aus",
"THEME_DESCRIPTION": "Wählen Sie das visuelle Thema der Anwendung aus",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Διαγραφή…",
"EPG_URL_ERROR": "Η τιμή δεν είναι έγκυρη διεύθυνση URL",
"VIDEO_PLAYER_DESCRIPTION": "Επιλογή προεπιλεγμένου προγράμματος αναπαραγωγής βίντεο",
"WEB_PLAYER_SHARED_CONTROLS": "Ενοποιημένα στοιχεία ελέγχου για web players (πειραματικό)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Χρησιμοποιήστε τα κοινά στοιχεία ελέγχου του IPTVnator σε HTML5, Video.js και ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Επιλογή προεπιλεγμένης μορφής ροής (xtream)",
"LANGUAGE_DESCRIPTION": "Επιλέξτε τη γλώσσα της εφαρμογής",
"THEME_DESCRIPTION": "Επιλέξτε οπτικό θέμα της εφαρμογής",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Clearing…",
"EPG_URL_ERROR": "The value is not a valid URL",
"VIDEO_PLAYER_DESCRIPTION": "Select default video player",
"WEB_PLAYER_SHARED_CONTROLS": "Unified controls for web players (experimental)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Use IPTVnator's shared controls in HTML5, Video.js, and ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Select default stream format (xtream)",
"LANGUAGE_DESCRIPTION": "Select language of the application",
"THEME_DESCRIPTION": "Select visual theme of the application",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Borrando…",
"EPG_URL_ERROR": "El valor no es una URL válida.",
"VIDEO_PLAYER_DESCRIPTION": "Seleccionar reproductor de vídeo predeterminado",
"WEB_PLAYER_SHARED_CONTROLS": "Controles unificados para reproductores web (experimental)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Usa los controles compartidos de IPTVnator en HTML5, Video.js y ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Selecciona el formato de transmision predeterminado (Xtream)",
"LANGUAGE_DESCRIPTION": "Seleccione el idioma de la aplicación",
"THEME_DESCRIPTION": "Seleccione el tema visual de la aplicación.",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Effacement…",
"EPG_URL_ERROR": "La valeur n'est pas une URL valide",
"VIDEO_PLAYER_DESCRIPTION": "Sélectionnez le lecteur vidéo par défaut",
"WEB_PLAYER_SHARED_CONTROLS": "Commandes unifiées pour les lecteurs web (expérimental)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Utilise les commandes partagées d’IPTVnator dans HTML5, Video.js et ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Sélectionnez le format de flux par défaut (xtream)",
"LANGUAGE_DESCRIPTION": "Sélectionnez la langue de l'application",
"THEME_DESCRIPTION": "Sélectionnez le thème visuel de l'application",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Cancellazione…",
"EPG_URL_ERROR": "Il valore non è una URL valida",
"VIDEO_PLAYER_DESCRIPTION": "Seleziona lettore video predefinito",
"WEB_PLAYER_SHARED_CONTROLS": "Controlli unificati per i player web (sperimentale)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Usa i controlli condivisi di IPTVnator in HTML5, Video.js e ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Seleziona il formato di streaming predefinito (xtream)",
"LANGUAGE_DESCRIPTION": "Seleziona la lingua dell'applicazione",
"THEME_DESCRIPTION": "Seleziona il tema visivo dell'applicazione",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "クリア中…",
"EPG_URL_ERROR": "有効なURLではありません",
"VIDEO_PLAYER_DESCRIPTION": "デフォルトのビデオプレーヤーを選択",
"WEB_PLAYER_SHARED_CONTROLS": "Webプレーヤーの統一コントロール(試験的)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "HTML5、Video.js、ArtPlayerでIPTVnator共通のコントロールを使用します。",
"STREAM_FORMAT_DESCRIPTION": "デフォルトのストリーム形式を選択(Xtream)",
"LANGUAGE_DESCRIPTION": "アプリケーションの言語を選択",
"THEME_DESCRIPTION": "アプリケーションの視覚テーマを選択",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "지우는 중…",
"EPG_URL_ERROR": "값이 유효한 URL이 아닙니다.",
"VIDEO_PLAYER_DESCRIPTION": "기본 비디오 플레이어 선택",
"WEB_PLAYER_SHARED_CONTROLS": "웹 플레이어 통합 컨트롤(실험적)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "HTML5, Video.js 및 ArtPlayer에서 IPTVnator 공통 컨트롤을 사용합니다.",
"STREAM_FORMAT_DESCRIPTION": "기본 스트림 형식 선택 (xtream)",
"LANGUAGE_DESCRIPTION": "애플리케이션 언어 선택",
"THEME_DESCRIPTION": "애플리케이션의 시각적 테마 선택",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Wissen…",
"EPG_URL_ERROR": "De opgegeven waarde is geen geldige URL.",
"VIDEO_PLAYER_DESCRIPTION": "Selecteer de standaard videospeler",
"WEB_PLAYER_SHARED_CONTROLS": "Uniforme bediening voor webspelers (experimenteel)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Gebruik de gedeelde IPTVnator-bediening in HTML5, Video.js en ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Selecteer het standaard streamformaat (xtream)",
"LANGUAGE_DESCRIPTION": "Selecteer de taal van de applicatie",
"THEME_DESCRIPTION": "Kies het visuele thema van de applicatie",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Czyszczenie…",
"EPG_URL_ERROR": "Wartość nie jest prawidłowym URL",
"VIDEO_PLAYER_DESCRIPTION": "Wybierz domyślny odtwarzacz wideo",
"WEB_PLAYER_SHARED_CONTROLS": "Ujednolicone sterowanie odtwarzaczami internetowymi (eksperymentalne)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Używaj wspólnych elementów sterowania IPTVnator w HTML5, Video.js i ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Wybierz domyślny format strumienia (xtream)",
"LANGUAGE_DESCRIPTION": "Wybierz język aplikacji",
"THEME_DESCRIPTION": "Wybierz motyw wizualny aplikacji",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Limpando…",
"EPG_URL_ERROR": "O valor não é uma URL válida",
"VIDEO_PLAYER_DESCRIPTION": "Selecionar reprodutor de vídeo padrão",
"WEB_PLAYER_SHARED_CONTROLS": "Controlos unificados para leitores web (experimental)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Usa os controlos partilhados do IPTVnator em HTML5, Video.js e ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Selecionar formato de stream padrão (xtream)",
"LANGUAGE_DESCRIPTION": "Selecionar idioma do aplicativo",
"THEME_DESCRIPTION": "Selecionar tema visual do aplicativo",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Очистка…",
"EPG_URL_ERROR": "Введенное значение не является URL-адресом.",
"VIDEO_PLAYER_DESCRIPTION": "Выбрать стандартный видео плеер",
"WEB_PLAYER_SHARED_CONTROLS": "Единые элементы управления для веб-плееров (экспериментально)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Использовать общие элементы управления IPTVnator в HTML5, Video.js и ArtPlayer.",
"STREAM_FORMAT_DESCRIPTION": "Выбрать формат потока по умолчанию (Xtream)",
"LANGUAGE_DESCRIPTION": "Выбрать язык приложения",
"THEME_DESCRIPTION": "Выбрать визуальную тему оформления",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "Temizleniyor…",
"EPG_URL_ERROR": "Girilen değer geçerli bir URL değildir",
"VIDEO_PLAYER_DESCRIPTION": "Varsayılan video oynatıcıyı seçin",
"WEB_PLAYER_SHARED_CONTROLS": "Web oynatıcıları için birleşik kontroller (deneysel)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "HTML5, Video.js ve ArtPlayer'da IPTVnator'ın ortak kontrollerini kullan.",
"STREAM_FORMAT_DESCRIPTION": "Varsayılan yayın formatını seçin (xtream)",
"LANGUAGE_DESCRIPTION": "Uygulamanın dilini seçin",
"THEME_DESCRIPTION": "Uygulamanın görünüm temasını seçin",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "清除中…",
"EPG_URL_ERROR": "该值不是有效的 URL",
"VIDEO_PLAYER_DESCRIPTION": "选择默认视频播放器",
"WEB_PLAYER_SHARED_CONTROLS": "Web 播放器统一控件(实验性)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "在 HTML5、Video.js 和 ArtPlayer 中使用 IPTVnator 的共享控件。",
"STREAM_FORMAT_DESCRIPTION": "选择默认的流格式(xtream)",
"LANGUAGE_DESCRIPTION": "选择应用程序的语言",
"THEME_DESCRIPTION": "选择应用程序的视觉主题",
+2
View File
@@ -296,6 +296,8 @@
"CLEARING_EPG_DATA": "正在清除…",
"EPG_URL_ERROR": "該值不是有效的網址",
"VIDEO_PLAYER_DESCRIPTION": "選擇預設影片播放器",
"WEB_PLAYER_SHARED_CONTROLS": "Web 播放器統一控制項(實驗性)",
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "在 HTML5、Video.js 和 ArtPlayer 中使用 IPTVnator 的共用控制項。",
"STREAM_FORMAT_DESCRIPTION": "選擇預設串流格式(xtream)",
"LANGUAGE_DESCRIPTION": "選擇應用程式語言",
"THEME_DESCRIPTION": "選擇應用程式視覺主題",