/** * Settings Page Controller */ class SettingsPage { constructor(app) { this.app = app; this.tabs = document.querySelectorAll('.tabs .tab'); this.tabContents = document.querySelectorAll('.tab-content'); this.init(); } init() { // Tab switching this.tabs.forEach(tab => { tab.addEventListener('click', () => this.switchTab(tab.dataset.tab)); }); // Player settings this.initPlayerSettings(); // Transcoding settings this.initTranscodingSettings(); // User management (admin only) this.initUserManagement(); } initPlayerSettings() { const arrowKeysToggle = document.getElementById('setting-arrow-keys'); const overlayDurationInput = document.getElementById('setting-overlay-duration'); const defaultVolumeSlider = document.getElementById('setting-default-volume'); const volumeValueDisplay = document.getElementById('volume-value'); const rememberVolumeToggle = document.getElementById('setting-remember-volume'); const autoPlayNextToggle = document.getElementById('setting-autoplay-next'); // Load current settings if (this.app.player?.settings) { arrowKeysToggle.checked = this.app.player.settings.arrowKeysChangeChannel; overlayDurationInput.value = this.app.player.settings.overlayDuration; defaultVolumeSlider.value = this.app.player.settings.defaultVolume; volumeValueDisplay.textContent = this.app.player.settings.defaultVolume + '%'; rememberVolumeToggle.checked = this.app.player.settings.rememberVolume; autoPlayNextToggle.checked = this.app.player.settings.autoPlayNextEpisode; } // Arrow keys toggle arrowKeysToggle.addEventListener('change', () => { this.app.player.settings.arrowKeysChangeChannel = arrowKeysToggle.checked; this.app.player.saveSettings(); }); // Overlay duration overlayDurationInput.addEventListener('change', () => { this.app.player.settings.overlayDuration = parseInt(overlayDurationInput.value) || 5; this.app.player.saveSettings(); }); // Default volume slider defaultVolumeSlider.addEventListener('input', () => { const value = defaultVolumeSlider.value; volumeValueDisplay.textContent = value + '%'; this.app.player.settings.defaultVolume = parseInt(value); this.app.player.saveSettings(); }); // Remember volume toggle rememberVolumeToggle.addEventListener('change', () => { this.app.player.settings.rememberVolume = rememberVolumeToggle.checked; this.app.player.saveSettings(); }); // Auto-play next episode toggle autoPlayNextToggle.addEventListener('change', () => { this.app.player.settings.autoPlayNextEpisode = autoPlayNextToggle.checked; this.app.player.saveSettings(); }); // EPG refresh interval const epgRefreshSelect = document.getElementById('epg-refresh-interval'); if (epgRefreshSelect && this.app.player?.settings) { // Load saved value from player settings epgRefreshSelect.value = this.app.player.settings.epgRefreshInterval || '24'; // Save on change - server will restart its sync timer via PUT /api/settings epgRefreshSelect.addEventListener('change', () => { this.app.player.settings.epgRefreshInterval = epgRefreshSelect.value; this.app.player.saveSettings(); }); } // Update last refreshed display this.updateEpgLastRefreshed(); } async initTranscodingSettings() { // Encoder settings const hwEncoderSelect = document.getElementById('setting-hw-encoder'); const maxResolutionSelect = document.getElementById('setting-max-resolution'); const qualitySelect = document.getElementById('setting-quality'); // Stream processing (use -tc suffix IDs from Transcoding tab) const forceProxyToggle = document.getElementById('setting-force-proxy-tc'); const autoTranscodeToggle = document.getElementById('setting-auto-transcode-tc'); const forceTranscodeToggle = document.getElementById('setting-force-transcode-tc'); const forceVideoTranscodeToggle = document.getElementById('setting-force-video-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'); // Fetch settings directly from API to avoid race condition with VideoPlayer let s; try { s = await API.settings.get(); } catch (err) { console.warn('[Settings] Failed to load settings from API, using player defaults:', err); 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 (forceVideoTranscodeToggle) forceVideoTranscodeToggle.checked = s.forceVideoTranscode === 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'; } // Event listeners for encoder settings hwEncoderSelect?.addEventListener('change', () => { this.app.player.settings.hwEncoder = hwEncoderSelect.value; this.app.player.saveSettings(); }); maxResolutionSelect?.addEventListener('change', () => { this.app.player.settings.maxResolution = maxResolutionSelect.value; this.app.player.saveSettings(); }); qualitySelect?.addEventListener('change', () => { this.app.player.settings.quality = qualitySelect.value; this.app.player.saveSettings(); }); // Audio Mix Preset const audioMixSelect = document.getElementById('setting-audio-mix'); if (audioMixSelect) { audioMixSelect.value = s.audioMixPreset || 'auto'; audioMixSelect.addEventListener('change', () => { this.app.player.settings.audioMixPreset = audioMixSelect.value; this.app.player.saveSettings(); }); } // Upscaling Settings const upscaleEnabledToggle = document.getElementById('setting-upscale-enabled'); const upscaleMethodSelect = document.getElementById('setting-upscale-method'); const upscaleTargetSelect = document.getElementById('setting-upscale-target'); const upscaleMethodContainer = document.getElementById('upscale-method-container'); const upscaleTargetContainer = document.getElementById('upscale-target-container'); // Helper to toggle upscale options visibility const toggleUpscaleOptions = (enabled) => { if (upscaleMethodContainer) upscaleMethodContainer.style.display = enabled ? 'flex' : 'none'; if (upscaleTargetContainer) upscaleTargetContainer.style.display = enabled ? 'flex' : 'none'; }; // Load upscaling settings if (upscaleEnabledToggle) { upscaleEnabledToggle.checked = s.upscaleEnabled || false; toggleUpscaleOptions(upscaleEnabledToggle.checked); } if (upscaleMethodSelect) upscaleMethodSelect.value = s.upscaleMethod || 'hardware'; if (upscaleTargetSelect) upscaleTargetSelect.value = s.upscaleTarget || '1080p'; // Upscaling event handlers upscaleEnabledToggle?.addEventListener('change', () => { this.app.player.settings.upscaleEnabled = upscaleEnabledToggle.checked; this.app.player.saveSettings(); toggleUpscaleOptions(upscaleEnabledToggle.checked); }); upscaleMethodSelect?.addEventListener('change', () => { this.app.player.settings.upscaleMethod = upscaleMethodSelect.value; this.app.player.saveSettings(); }); upscaleTargetSelect?.addEventListener('change', () => { this.app.player.settings.upscaleTarget = upscaleTargetSelect.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(); }); forceVideoTranscodeToggle?.addEventListener('change', () => { this.app.player.settings.forceVideoTranscode = forceVideoTranscodeToggle.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(`
Recommended encoder: ${hwInfo.recommended}
`; } else { html = `No GPU acceleration detected. Using software encoding.
`; } container.innerHTML = html; } catch (err) { console.error('Error loading hardware info:', err); container.innerHTML = 'Failed to load hardware info
'; } } initUserManagement() { // User tab visibility is handled in show() method // when currentUser is available // Handle add user form const addUserForm = document.getElementById('add-user-form'); if (addUserForm) { addUserForm.addEventListener('submit', async (e) => { e.preventDefault(); const username = document.getElementById('new-username').value; const password = document.getElementById('new-password').value; const role = document.getElementById('new-role').value; try { await API.users.create({ username, password, role }); alert('User created successfully!'); addUserForm.reset(); this.loadUsers(); } catch (err) { alert('Error creating user: ' + err.message); } }); } } async loadUsers() { const userList = document.getElementById('user-list'); if (!userList) return; try { const users = await API.users.getAll(); // Store users in memory for easy access during edit this.users = users; if (users.length === 0) { userList.innerHTML = '