feat: add dedicated transcoding settings tab
This commit is contained in:
@@ -4025,4 +4025,51 @@ kbd {
|
|||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hardware Info Grid (Transcoding Tab) */
|
||||||
|
.hw-info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-info-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--color-bg-secondary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-info-item.hw-available {
|
||||||
|
border-color: var(--color-success);
|
||||||
|
background: rgba(16, 185, 129, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-info-item.hw-unavailable {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-badge {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-info-item.hw-available .hw-badge {
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-info-item.hw-unavailable .hw-badge {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-name {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
+88
-31
@@ -368,6 +368,7 @@
|
|||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button class="tab active" data-tab="sources">Sources</button>
|
<button class="tab active" data-tab="sources">Sources</button>
|
||||||
<button class="tab" data-tab="player">Player</button>
|
<button class="tab" data-tab="player">Player</button>
|
||||||
|
<button class="tab" data-tab="transcode">Transcoding</button>
|
||||||
<button class="tab" data-tab="content">Manage Content</button>
|
<button class="tab" data-tab="content">Manage Content</button>
|
||||||
<button class="tab" data-tab="users" id="users-tab" style="display:none;">Users</button>
|
<button class="tab" data-tab="users" id="users-tab" style="display:none;">Users</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -484,21 +485,75 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>Streaming</h3>
|
<h3>Keyboard Shortcuts</h3>
|
||||||
|
<div class="shortcuts-grid">
|
||||||
|
<div class="shortcut"><kbd>↑</kbd> / <kbd>↓</kbd> Channel up/down (or volume)</div>
|
||||||
|
<div class="shortcut"><kbd>←</kbd> / <kbd>→</kbd> Volume down/up (when arrows = channels)</div>
|
||||||
|
<div class="shortcut"><kbd>Space</kbd> / <kbd>K</kbd> Play/Pause</div>
|
||||||
|
<div class="shortcut"><kbd>F</kbd> Fullscreen</div>
|
||||||
|
<div class="shortcut"><kbd>M</kbd> Mute/Unmute</div>
|
||||||
|
<div class="shortcut"><kbd>I</kbd> Toggle info overlay</div>
|
||||||
|
<div class="shortcut"><kbd>PgUp</kbd> / <kbd>PgDn</kbd> Channel up/down</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transcoding Tab -->
|
||||||
|
<div id="tab-transcode" class="tab-content">
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Detected Hardware</h3>
|
||||||
|
<div id="hw-info-container" class="hw-info-container">
|
||||||
|
<p class="hint">Loading hardware information...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Encoder Settings</h3>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<label class="setting-toggle">
|
<div class="setting-info" style="flex: 1;">
|
||||||
<input type="checkbox" id="setting-auto-transcode">
|
<span class="setting-label">Hardware Encoder</span>
|
||||||
<span class="toggle-slider"></span>
|
<span class="setting-hint">Select which GPU encoder to use for transcoding. Auto will use the best
|
||||||
</label>
|
available.</span>
|
||||||
<div class="setting-info">
|
|
||||||
<span class="setting-label">Auto Transcode (Smart)</span>
|
|
||||||
<span class="setting-hint">Automatically detect stream codecs and only transcode/remux when needed.
|
|
||||||
Adds ~1-3s probe delay on first play.</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<select id="setting-hw-encoder" class="source-select" style="width: auto; min-width: 200px;">
|
||||||
|
<option value="auto">Auto (Recommended)</option>
|
||||||
|
<option value="nvenc">NVIDIA NVENC</option>
|
||||||
|
<option value="amf">AMD AMF</option>
|
||||||
|
<option value="qsv">Intel QuickSync</option>
|
||||||
|
<option value="vaapi">VAAPI (Linux)</option>
|
||||||
|
<option value="software">Software (CPU)</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info" style="flex: 1;">
|
||||||
|
<span class="setting-label">Max Resolution</span>
|
||||||
|
<span class="setting-hint">Limit transcoded output resolution. Lower = faster, more compatible.</span>
|
||||||
|
</div>
|
||||||
|
<select id="setting-max-resolution" class="source-select" style="width: auto; min-width: 200px;">
|
||||||
|
<option value="4k">4K (2160p)</option>
|
||||||
|
<option value="1080p" selected>1080p (Full HD)</option>
|
||||||
|
<option value="720p">720p (HD)</option>
|
||||||
|
<option value="480p">480p (SD)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info" style="flex: 1;">
|
||||||
|
<span class="setting-label">Quality Preset</span>
|
||||||
|
<span class="setting-hint">Higher quality uses more bandwidth and CPU/GPU.</span>
|
||||||
|
</div>
|
||||||
|
<select id="setting-quality" class="source-select" style="width: auto; min-width: 200px;">
|
||||||
|
<option value="high">High</option>
|
||||||
|
<option value="medium" selected>Medium</option>
|
||||||
|
<option value="low">Low (Fastest)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Stream Processing</h3>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<label class="setting-toggle">
|
<label class="setting-toggle">
|
||||||
<input type="checkbox" id="setting-force-proxy">
|
<input type="checkbox" id="setting-force-proxy-tc">
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -509,7 +564,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<label class="setting-toggle">
|
<label class="setting-toggle">
|
||||||
<input type="checkbox" id="setting-force-transcode">
|
<input type="checkbox" id="setting-auto-transcode-tc">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">Auto Transcode (Smart)</span>
|
||||||
|
<span class="setting-hint">Automatically detect stream codecs and only transcode/remux when needed.
|
||||||
|
Adds ~1-3s probe delay on first play.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<label class="setting-toggle">
|
||||||
|
<input type="checkbox" id="setting-force-transcode-tc">
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -520,13 +586,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<label class="setting-toggle">
|
<label class="setting-toggle">
|
||||||
<input type="checkbox" id="setting-force-remux">
|
<input type="checkbox" id="setting-force-remux-tc">
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<span class="setting-label">Force Remux</span>
|
<span class="setting-label">Force Remux</span>
|
||||||
<span class="setting-hint">Remux streams to MP4 container. Enable for raw .ts streams
|
<span class="setting-hint">Remux streams to MP4 container. Enable for raw .ts streams from IPTV
|
||||||
from IPTV middleware like m3u-editor or dispatcharr.</span>
|
middleware like m3u-editor or dispatcharr.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
@@ -535,46 +601,37 @@
|
|||||||
<span class="setting-hint">Container format for Xtream streams. Try TS if you experience buffering
|
<span class="setting-hint">Container format for Xtream streams. Try TS if you experience buffering
|
||||||
issues.</span>
|
issues.</span>
|
||||||
</div>
|
</div>
|
||||||
<select id="setting-stream-format" class="source-select" style="width: auto; min-width: 200px;">
|
<select id="setting-stream-format-tc" class="source-select" style="width: auto; min-width: 200px;">
|
||||||
<option value="m3u8">HLS (m3u8) - Recommended</option>
|
<option value="m3u8">HLS (m3u8) - Recommended</option>
|
||||||
<option value="ts">MPEG-TS (ts) - Better buffer</option>
|
<option value="ts">MPEG-TS (ts) - Better buffer</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>HTTP Settings</h3>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-info" style="flex: 1;">
|
<div class="setting-info" style="flex: 1;">
|
||||||
<span class="setting-label">User-Agent</span>
|
<span class="setting-label">User-Agent</span>
|
||||||
<span class="setting-hint">HTTP User-Agent for stream requests. Try VLC if your provider blocks
|
<span class="setting-hint">HTTP User-Agent for stream requests. Try VLC if your provider blocks
|
||||||
Chrome.</span>
|
Chrome.</span>
|
||||||
</div>
|
</div>
|
||||||
<select id="setting-user-agent" class="source-select" style="width: auto; min-width: 200px;">
|
<select id="setting-user-agent-tc" class="source-select" style="width: auto; min-width: 200px;">
|
||||||
<option value="chrome">Chrome (Default)</option>
|
<option value="chrome">Chrome (Default)</option>
|
||||||
<option value="vlc">VLC</option>
|
<option value="vlc">VLC</option>
|
||||||
<option value="tivimate">TiviMate</option>
|
<option value="tivimate">TiviMate</option>
|
||||||
<option value="custom">Custom...</option>
|
<option value="custom">Custom...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item" id="custom-user-agent-container" style="display: none;">
|
<div class="setting-item" id="custom-user-agent-container-tc" style="display: none;">
|
||||||
<div class="setting-info" style="flex: 1;">
|
<div class="setting-info" style="flex: 1;">
|
||||||
<span class="setting-label">Custom User-Agent</span>
|
<span class="setting-label">Custom User-Agent</span>
|
||||||
<span class="setting-hint">Enter your own User-Agent string.</span>
|
<span class="setting-hint">Enter your own User-Agent string.</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="setting-user-agent-custom" class="search-input" style="width: 300px;"
|
<input type="text" id="setting-user-agent-custom-tc" class="search-input" style="width: 300px;"
|
||||||
placeholder="Custom User-Agent string...">
|
placeholder="Custom User-Agent string...">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-section">
|
|
||||||
<h3>Keyboard Shortcuts</h3>
|
|
||||||
<div class="shortcuts-grid">
|
|
||||||
<div class="shortcut"><kbd>↑</kbd> / <kbd>↓</kbd> Channel up/down (or volume)</div>
|
|
||||||
<div class="shortcut"><kbd>←</kbd> / <kbd>→</kbd> Volume down/up (when arrows = channels)</div>
|
|
||||||
<div class="shortcut"><kbd>Space</kbd> / <kbd>K</kbd> Play/Pause</div>
|
|
||||||
<div class="shortcut"><kbd>F</kbd> Fullscreen</div>
|
|
||||||
<div class="shortcut"><kbd>M</kbd> Mute/Unmute</div>
|
|
||||||
<div class="shortcut"><kbd>I</kbd> Toggle info overlay</div>
|
|
||||||
<div class="shortcut"><kbd>PgUp</kbd> / <kbd>PgDn</kbd> Channel up/down</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Manage Content Tab -->
|
<!-- Manage Content Tab -->
|
||||||
|
|||||||
+160
-85
@@ -20,6 +20,9 @@ class SettingsPage {
|
|||||||
// Player settings
|
// Player settings
|
||||||
this.initPlayerSettings();
|
this.initPlayerSettings();
|
||||||
|
|
||||||
|
// Transcoding settings
|
||||||
|
this.initTranscodingSettings();
|
||||||
|
|
||||||
// User management (admin only)
|
// User management (admin only)
|
||||||
this.initUserManagement();
|
this.initUserManagement();
|
||||||
}
|
}
|
||||||
@@ -31,10 +34,6 @@ class SettingsPage {
|
|||||||
const volumeValueDisplay = document.getElementById('volume-value');
|
const volumeValueDisplay = document.getElementById('volume-value');
|
||||||
const rememberVolumeToggle = document.getElementById('setting-remember-volume');
|
const rememberVolumeToggle = document.getElementById('setting-remember-volume');
|
||||||
const autoPlayNextToggle = document.getElementById('setting-autoplay-next');
|
const autoPlayNextToggle = document.getElementById('setting-autoplay-next');
|
||||||
const forceProxyToggle = document.getElementById('setting-force-proxy');
|
|
||||||
const forceTranscodeToggle = document.getElementById('setting-force-transcode');
|
|
||||||
const forceRemuxToggle = document.getElementById('setting-force-remux');
|
|
||||||
const autoTranscodeToggle = document.getElementById('setting-auto-transcode');
|
|
||||||
|
|
||||||
// Load current settings
|
// Load current settings
|
||||||
if (this.app.player?.settings) {
|
if (this.app.player?.settings) {
|
||||||
@@ -44,18 +43,6 @@ class SettingsPage {
|
|||||||
volumeValueDisplay.textContent = this.app.player.settings.defaultVolume + '%';
|
volumeValueDisplay.textContent = this.app.player.settings.defaultVolume + '%';
|
||||||
rememberVolumeToggle.checked = this.app.player.settings.rememberVolume;
|
rememberVolumeToggle.checked = this.app.player.settings.rememberVolume;
|
||||||
autoPlayNextToggle.checked = this.app.player.settings.autoPlayNextEpisode;
|
autoPlayNextToggle.checked = this.app.player.settings.autoPlayNextEpisode;
|
||||||
if (forceProxyToggle) {
|
|
||||||
forceProxyToggle.checked = this.app.player.settings.forceProxy || false;
|
|
||||||
}
|
|
||||||
if (forceTranscodeToggle) {
|
|
||||||
forceTranscodeToggle.checked = this.app.player.settings.forceTranscode || false;
|
|
||||||
}
|
|
||||||
if (forceRemuxToggle) {
|
|
||||||
forceRemuxToggle.checked = this.app.player.settings.forceRemux || false;
|
|
||||||
}
|
|
||||||
if (autoTranscodeToggle) {
|
|
||||||
autoTranscodeToggle.checked = this.app.player.settings.autoTranscode || false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arrow keys toggle
|
// Arrow keys toggle
|
||||||
@@ -66,56 +53,30 @@ class SettingsPage {
|
|||||||
|
|
||||||
// Overlay duration
|
// Overlay duration
|
||||||
overlayDurationInput.addEventListener('change', () => {
|
overlayDurationInput.addEventListener('change', () => {
|
||||||
const value = Math.min(30, Math.max(1, parseInt(overlayDurationInput.value) || 5));
|
this.app.player.settings.overlayDuration = parseInt(overlayDurationInput.value) || 5;
|
||||||
overlayDurationInput.value = value;
|
|
||||||
this.app.player.settings.overlayDuration = value;
|
|
||||||
this.app.player.saveSettings();
|
this.app.player.saveSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Default volume slider
|
// Default volume slider
|
||||||
defaultVolumeSlider?.addEventListener('input', () => {
|
defaultVolumeSlider.addEventListener('input', () => {
|
||||||
const value = parseInt(defaultVolumeSlider.value);
|
const value = defaultVolumeSlider.value;
|
||||||
volumeValueDisplay.textContent = value + '%';
|
volumeValueDisplay.textContent = value + '%';
|
||||||
this.app.player.settings.defaultVolume = value;
|
this.app.player.settings.defaultVolume = parseInt(value);
|
||||||
this.app.player.saveSettings();
|
this.app.player.saveSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remember volume toggle
|
// Remember volume toggle
|
||||||
rememberVolumeToggle?.addEventListener('change', () => {
|
rememberVolumeToggle.addEventListener('change', () => {
|
||||||
this.app.player.settings.rememberVolume = rememberVolumeToggle.checked;
|
this.app.player.settings.rememberVolume = rememberVolumeToggle.checked;
|
||||||
this.app.player.saveSettings();
|
this.app.player.saveSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-play next episode toggle
|
// Auto-play next episode toggle
|
||||||
autoPlayNextToggle?.addEventListener('change', () => {
|
autoPlayNextToggle.addEventListener('change', () => {
|
||||||
this.app.player.settings.autoPlayNextEpisode = autoPlayNextToggle.checked;
|
this.app.player.settings.autoPlayNextEpisode = autoPlayNextToggle.checked;
|
||||||
this.app.player.saveSettings();
|
this.app.player.saveSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Force proxy toggle
|
|
||||||
forceProxyToggle?.addEventListener('change', () => {
|
|
||||||
this.app.player.settings.forceProxy = forceProxyToggle.checked;
|
|
||||||
this.app.player.saveSettings();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Force transcode toggle
|
|
||||||
forceTranscodeToggle?.addEventListener('change', () => {
|
|
||||||
this.app.player.settings.forceTranscode = forceTranscodeToggle.checked;
|
|
||||||
this.app.player.saveSettings();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Force remux toggle
|
|
||||||
forceRemuxToggle?.addEventListener('change', () => {
|
|
||||||
this.app.player.settings.forceRemux = forceRemuxToggle.checked;
|
|
||||||
this.app.player.saveSettings();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto transcode (smart) toggle
|
|
||||||
autoTranscodeToggle?.addEventListener('change', () => {
|
|
||||||
this.app.player.settings.autoTranscode = autoTranscodeToggle.checked;
|
|
||||||
this.app.player.saveSettings();
|
|
||||||
});
|
|
||||||
|
|
||||||
// EPG refresh interval
|
// EPG refresh interval
|
||||||
const epgRefreshSelect = document.getElementById('epg-refresh-interval');
|
const epgRefreshSelect = document.getElementById('epg-refresh-interval');
|
||||||
if (epgRefreshSelect && this.app.player?.settings) {
|
if (epgRefreshSelect && this.app.player?.settings) {
|
||||||
@@ -126,57 +87,166 @@ class SettingsPage {
|
|||||||
epgRefreshSelect.addEventListener('change', () => {
|
epgRefreshSelect.addEventListener('change', () => {
|
||||||
this.app.player.settings.epgRefreshInterval = epgRefreshSelect.value;
|
this.app.player.settings.epgRefreshInterval = epgRefreshSelect.value;
|
||||||
this.app.player.saveSettings();
|
this.app.player.saveSettings();
|
||||||
// Server-side sync timer is restarted automatically via settings API
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last refreshed display
|
// Update last refreshed display
|
||||||
this.updateEpgLastRefreshed();
|
this.updateEpgLastRefreshed();
|
||||||
|
}
|
||||||
|
|
||||||
// Stream output format
|
initTranscodingSettings() {
|
||||||
const streamFormatSelect = document.getElementById('setting-stream-format');
|
// Encoder settings
|
||||||
if (streamFormatSelect && this.app.player?.settings) {
|
const hwEncoderSelect = document.getElementById('setting-hw-encoder');
|
||||||
// Load saved value from player settings
|
const maxResolutionSelect = document.getElementById('setting-max-resolution');
|
||||||
streamFormatSelect.value = this.app.player.settings.streamFormat || 'm3u8';
|
const qualitySelect = document.getElementById('setting-quality');
|
||||||
|
|
||||||
// Save on change
|
// Stream processing (use -tc suffix IDs from Transcoding tab)
|
||||||
streamFormatSelect.addEventListener('change', () => {
|
const forceProxyToggle = document.getElementById('setting-force-proxy-tc');
|
||||||
this.app.player.settings.streamFormat = streamFormatSelect.value;
|
const autoTranscodeToggle = document.getElementById('setting-auto-transcode-tc');
|
||||||
this.app.player.saveSettings();
|
const forceTranscodeToggle = document.getElementById('setting-force-transcode-tc');
|
||||||
});
|
const forceRemuxToggle = document.getElementById('setting-force-remux-tc');
|
||||||
|
const streamFormatSelect = document.getElementById('setting-stream-format-tc');
|
||||||
|
|
||||||
|
// User-Agent (Transcoding tab versions)
|
||||||
|
const userAgentSelect = document.getElementById('setting-user-agent-tc');
|
||||||
|
const userAgentCustomInput = document.getElementById('setting-user-agent-custom-tc');
|
||||||
|
const customUaContainer = document.getElementById('custom-user-agent-container-tc');
|
||||||
|
|
||||||
|
// Load current settings
|
||||||
|
if (this.app.player?.settings) {
|
||||||
|
const s = this.app.player.settings;
|
||||||
|
|
||||||
|
if (hwEncoderSelect) hwEncoderSelect.value = s.hwEncoder || 'auto';
|
||||||
|
if (maxResolutionSelect) maxResolutionSelect.value = s.maxResolution || '1080p';
|
||||||
|
if (qualitySelect) qualitySelect.value = s.quality || 'medium';
|
||||||
|
if (forceProxyToggle) forceProxyToggle.checked = s.forceProxy === true;
|
||||||
|
if (autoTranscodeToggle) autoTranscodeToggle.checked = s.autoTranscode !== false;
|
||||||
|
if (forceTranscodeToggle) forceTranscodeToggle.checked = s.forceTranscode === true;
|
||||||
|
if (forceRemuxToggle) forceRemuxToggle.checked = s.forceRemux || false;
|
||||||
|
if (streamFormatSelect) streamFormatSelect.value = s.streamFormat || 'm3u8';
|
||||||
|
if (userAgentSelect) userAgentSelect.value = s.userAgentPreset || 'chrome';
|
||||||
|
if (userAgentCustomInput) userAgentCustomInput.value = s.userAgentCustom || '';
|
||||||
|
if (customUaContainer) {
|
||||||
|
customUaContainer.style.display = userAgentSelect?.value === 'custom' ? 'flex' : 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// User-Agent preset
|
// Event listeners for encoder settings
|
||||||
const userAgentSelect = document.getElementById('setting-user-agent');
|
hwEncoderSelect?.addEventListener('change', () => {
|
||||||
const userAgentCustomInput = document.getElementById('setting-user-agent-custom');
|
this.app.player.settings.hwEncoder = hwEncoderSelect.value;
|
||||||
const customUaContainer = document.getElementById('custom-user-agent-container');
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
if (userAgentSelect && this.app.player?.settings) {
|
maxResolutionSelect?.addEventListener('change', () => {
|
||||||
// Load saved values
|
this.app.player.settings.maxResolution = maxResolutionSelect.value;
|
||||||
userAgentSelect.value = this.app.player.settings.userAgentPreset || 'chrome';
|
this.app.player.saveSettings();
|
||||||
if (userAgentCustomInput) {
|
});
|
||||||
userAgentCustomInput.value = this.app.player.settings.userAgentCustom || '';
|
|
||||||
|
qualitySelect?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.quality = qualitySelect.value;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stream processing toggles
|
||||||
|
forceProxyToggle?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.forceProxy = forceProxyToggle.checked;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
autoTranscodeToggle?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.autoTranscode = autoTranscodeToggle.checked;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
forceTranscodeToggle?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.forceTranscode = forceTranscodeToggle.checked;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
forceRemuxToggle?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.forceRemux = forceRemuxToggle.checked;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
streamFormatSelect?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.streamFormat = streamFormatSelect.value;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
// User-Agent handlers
|
||||||
|
const toggleCustomInput = () => {
|
||||||
|
if (customUaContainer) {
|
||||||
|
customUaContainer.style.display = userAgentSelect?.value === 'custom' ? 'flex' : 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
userAgentSelect?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.userAgentPreset = userAgentSelect.value;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
toggleCustomInput();
|
||||||
|
});
|
||||||
|
|
||||||
|
userAgentCustomInput?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.userAgentCustom = userAgentCustomInput.value;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load and display hardware info in Transcoding tab
|
||||||
|
*/
|
||||||
|
async loadHardwareInfo() {
|
||||||
|
const container = document.getElementById('hw-info-container');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/settings/hw-info');
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch hardware info');
|
||||||
|
const hwInfo = await response.json();
|
||||||
|
|
||||||
|
const detected = [];
|
||||||
|
|
||||||
|
// Only show detected hardware
|
||||||
|
if (hwInfo.nvidia?.available) {
|
||||||
|
detected.push(`<div class="hw-info-item hw-available">
|
||||||
|
<span class="hw-badge">✓ NVIDIA</span>
|
||||||
|
<span class="hw-name">${hwInfo.nvidia.name}</span>
|
||||||
|
</div>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show/hide custom input
|
if (hwInfo.amf?.available) {
|
||||||
const toggleCustomInput = () => {
|
detected.push(`<div class="hw-info-item hw-available">
|
||||||
if (customUaContainer) {
|
<span class="hw-badge">✓ AMD</span>
|
||||||
customUaContainer.style.display = userAgentSelect.value === 'custom' ? 'flex' : 'none';
|
<span class="hw-name">${hwInfo.amf.name || 'Available'}</span>
|
||||||
}
|
</div>`);
|
||||||
};
|
}
|
||||||
toggleCustomInput();
|
|
||||||
|
|
||||||
// Save on change
|
if (hwInfo.qsv?.available) {
|
||||||
userAgentSelect.addEventListener('change', () => {
|
detected.push(`<div class="hw-info-item hw-available">
|
||||||
this.app.player.settings.userAgentPreset = userAgentSelect.value;
|
<span class="hw-badge">✓ Intel QSV</span>
|
||||||
this.app.player.saveSettings();
|
<span class="hw-name">Available</span>
|
||||||
toggleCustomInput();
|
</div>`);
|
||||||
});
|
}
|
||||||
|
|
||||||
userAgentCustomInput?.addEventListener('change', () => {
|
if (hwInfo.vaapi?.available) {
|
||||||
this.app.player.settings.userAgentCustom = userAgentCustomInput.value;
|
detected.push(`<div class="hw-info-item hw-available">
|
||||||
this.app.player.saveSettings();
|
<span class="hw-badge">✓ VAAPI</span>
|
||||||
});
|
<span class="hw-name">${hwInfo.vaapi.device || 'Available'}</span>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let html;
|
||||||
|
if (detected.length > 0) {
|
||||||
|
html = `<div class="hw-info-grid">${detected.join('')}</div>`;
|
||||||
|
html += `<p class="hint" style="margin-top: var(--space-sm);">Recommended encoder: <strong>${hwInfo.recommended}</strong></p>`;
|
||||||
|
} else {
|
||||||
|
html = `<p class="hint">No GPU acceleration detected. Using software encoding.</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading hardware info:', err);
|
||||||
|
container.innerHTML = '<p class="hint error">Failed to load hardware info</p>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +356,11 @@ class SettingsPage {
|
|||||||
if (tabName === 'users') {
|
if (tabName === 'users') {
|
||||||
this.loadUsers();
|
this.loadUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load hardware info when switching to transcode tab
|
||||||
|
if (tabName === 'transcode') {
|
||||||
|
this.loadHardwareInfo();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async show() {
|
async show() {
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ function getDefaultSettings() {
|
|||||||
// User-Agent settings
|
// User-Agent settings
|
||||||
userAgentPreset: 'chrome', // chrome | vlc | tivimate | custom
|
userAgentPreset: 'chrome', // chrome | vlc | tivimate | custom
|
||||||
userAgentCustom: '', // Custom UA string when preset is 'custom'
|
userAgentCustom: '', // Custom UA string when preset is 'custom'
|
||||||
|
// Transcoding settings
|
||||||
|
hwEncoder: 'auto', // auto | nvenc | amf | qsv | vaapi | software
|
||||||
|
maxResolution: '1080p', // 4k | 1080p | 720p | 480p
|
||||||
|
quality: 'medium', // high | medium | low
|
||||||
// Probe cache settings
|
// Probe cache settings
|
||||||
probeCacheTTL: 300, // 5 minutes for URL probe cache
|
probeCacheTTL: 300, // 5 minutes for URL probe cache
|
||||||
seriesProbeCacheDays: 7 // 7 days for series episode probe cache
|
seriesProbeCacheDays: 7 // 7 days for series episode probe cache
|
||||||
|
|||||||
Reference in New Issue
Block a user