// One entry per settings key. The categories in base.ts decide which keys // are shown and in which order; this table decides how each one renders. interface SettingsFieldOption { text:string value:string } interface SettingsField { key:string title:string type:"text" | "checkbox" | "number" | "select" | "password" placeholder?:string autocomplete?:string options?:SettingsFieldOption[] // Static text written in the source; may contain HTML. description:string } function settingsOptions(text:string[], values:string[]):SettingsFieldOption[] { var options:SettingsFieldOption[] = [] for (var i = 0; i < text.length; i++) { options.push({text: text[i], value: values[i]}) } return options } function settingsNumberOptions(from:number, to:number):SettingsFieldOption[] { var options:SettingsFieldOption[] = [] for (var i = from; i <= to; i++) { options.push({text: i.toString(), value: i.toString()}) } return options } const SETTINGS_FIELDS:SettingsField[] = [ // Text inputs {key: "update", title: "Schedule for updating (Playlist, XMLTV, Backup)", type: "text", placeholder: "0000,1000,2000", description: "Time in 24 hour format (0800 = 8:00 am). More times can be entered comma separated. Leave this field empty if no updates are to be carried out."}, {key: "backup.path", title: "Location for automatic backups", type: "text", placeholder: "/mnt/data/backup/xteve/", description: "Before any update of the provider data by the schedule, xTeVe creates a backup. The path for the automatic backups can be changed. xTeVe requires write permission for this folder."}, {key: "temp.path", title: "Location for the temporary files", type: "text", placeholder: "/tmp/xteve/", description: "Location for the buffer files."}, {key: "user.agent", title: "User Agent", type: "text", placeholder: "xTeVe", description: "User Agent for HTTP requests. For every HTTP connection, this value is used for the user agent. Should only be changed if xTeVe is blocked."}, {key: "plex.url", title: "Plex Server URL", type: "text", placeholder: "http://plex-host:32400", description: "Base URL of your Plex server. Example: http://192.168.1.10:32400"}, // The server sends the mask "********" and treats it coming back as "unchanged". {key: "plex.token", title: "Plex API Token", type: "password", placeholder: "Plex X-Plex-Token", autocomplete: "off", description: "Plex token used for authenticated API calls to refresh your DVR guide."}, {key: "buffer.timeout", title: "Timeout for new client connections", type: "text", placeholder: "100", description: "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds."}, {key: "ffmpeg.path", title: "FFmpeg Binary Path", type: "text", placeholder: "/path/to/ffmpeg", description: "Path to FFmpeg binary."}, {key: "ffmpeg.options", title: "FFmpeg Options", type: "text", placeholder: "Leave blank to set default settings", description: "FFmpeg options.
Only change if you know what you are doing.
Leave blank to set default settings."}, {key: "vlc.path", title: "VLC / CVLC Binary Path", type: "text", placeholder: "/path/to/cvlc", description: "Path to VLC / CVLC binary."}, {key: "vlc.options", title: "VLC / CVLC Options", type: "text", placeholder: "Leave blank to set default settings", description: "VLC / CVLC options.
Only change if you know what you are doing.
Leave blank to set default settings."}, {key: "udpxy", title: "UDPxy address", type: "text", placeholder: "host:port", description: "The address of your UDPxy server. If set, and the channel URLs in the m3u is multicast, xTeVe will rewrite it so that it is accessed via the UDPxy service."}, // Checkboxes {key: "authentication.web", title: "WEB Authentication", type: "checkbox", description: "Access to the web interface only possible with credentials."}, {key: "authentication.pms", title: "PMS Authentication", type: "checkbox", description: "Plex requests are only possible with authentication.
Warning!!! After activating this function xTeVe must be delete in the PMS DVR settings and set up again."}, {key: "authentication.m3u", title: "M3U Authentication", type: "checkbox", description: "Downloading the xteve.m3u file via an HTTP request is only possible with authentication."}, {key: "authentication.xml", title: "XML Authentication", type: "checkbox", description: "Downloading the xteve.xml file via an HTTP request is only possible with authentication"}, // Only rendered while web authentication is on (see SettingsCategoryItem.createCategory). {key: "authentication.api", title: "API Authentication", type: "checkbox", description: "Access to the API interface is only possible with authentication."}, {key: "files.update", title: "Updates all files at startup", type: "checkbox", description: "Updates all playlists, tuner and XMLTV files at startup."}, {key: "cache.images", title: "Image Caching", type: "checkbox", description: "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.
Downloading the images may take a while and will be done in the background."}, {key: "xepg.replace.missing.images", title: "Replace missing program images", type: "checkbox", description: "If the poster in the XMLTV program is missing, the channel logo will be used."}, {key: "api", title: "API Interface", type: "checkbox", description: "Via API interface it is possible to send commands to xTeVe. API documentation is here"}, {key: "use_plexAPI", title: "Use Plex API Refresh", type: "checkbox", description: "When enabled, xTeVe calls Plex directly to refresh DVR guide data after lineup or XEPG updates."}, // Selects {key: "xepg.missing.epg.mode", title: "Missing EPG Handling", type: "select", options: settingsOptions(["Strict (deactivate channels)", "Relaxed (keep active / dummy guide)"], ["strict", "relaxed"]), description: "Strict: channels are deactivated when XMLTV mappings disappear.
Relaxed: channels stay active with last-known mappings and fall back to a dummy guide whenever guide data cannot be resolved."}, {key: "tuner", title: "Number of Tuners", type: "select", options: settingsNumberOptions(1, 100), description: "Number of parallel connections that can be established to the provider.
Available for: Plex, Emby (HDHR), M3U (with active buffer).
After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."}, {key: "epgSource", title: "EPG Source", type: "select", options: settingsOptions(["PMS", "XEPG"], ["PMS", "XEPG"]), description: "PMS:
- Use EPG data from Plex or Emby

XEPG:
- Use of one or more XMLTV files
- Channel management
- M3U / XMLTV export (HTTP link for IPTV apps)"}, {key: "backup.keep", title: "Number of backups to keep", type: "select", options: settingsOptions(["5", "10", "20", "30", "40", "50"], ["5", "10", "20", "30", "40", "50"]), description: "Number of backups to keep. Older backups are automatically deleted."}, {key: "buffer.size.kb", title: "Buffer Size", type: "select", options: settingsOptions(["0.5 MB", "1 MB", "2 MB", "3 MB", "4 MB", "5 MB", "6 MB", "7 MB", "8 MB"], ["512", "1024", "2048", "3072", "4096", "5120", "6144", "7168", "8192"]), description: "Buffer size in MB.
M3U8: If the TS segment smaller then the buffer size, the file size of the segment is used."}, {key: "buffer", title: "Stream Buffer", type: "select", options: settingsOptions(["No Buffer (Client connects to the streaming server)", "xTeVe: (xTeVe connects to the streaming server)", "FFmpeg: (FFmpeg connects to the streaming server)", "VLC: (VLC connects to the streaming server)"], ["-", "xteve", "ffmpeg", "vlc"]), description: "Functions of the buffer:
- The stream is passed from xTeVe, FFmpeg or VLC to Plex, Emby or M3U Player
- Small jerking of the streams can be compensated
- HLS / M3U8 support
- RTP / RTPS support (only FFmpeg or VLC)
- Re-streaming
- Separate tuner limit for each playlist"}, ] function getSettingsField(settingsKey:string):SettingsField { for (var i = 0; i < SETTINGS_FIELDS.length; i++) { if (SETTINGS_FIELDS[i].key == settingsKey) { return SETTINGS_FIELDS[i] } } return undefined } class SettingsCategory { DocumentID:string = "content_settings" createCategoryHeadline(value:string):any { var element = document.createElement("H4") element.innerHTML = value return element } createHR():any { var element = document.createElement("HR") return element } // createControl builds the input for one field, filled with the current value. createControl(field:SettingsField, data:any):HTMLElement { var content:PopupContent = new PopupContent() var control:HTMLElement switch (field.type) { case "checkbox": control = content.createCheckbox(field.key); (control as HTMLInputElement).checked = data break case "select": var text:string[] = field.options.map(function(option) { return option.text }) var values:string[] = field.options.map(function(option) { return option.value }) control = content.createSelect(text, values, data, field.key) break default: // "text", "password", "number": lists (the update schedule) render comma separated. var value:any = data if (Array.isArray(data)) { value = data.join(",") } control = content.createInput(field.type, field.key, value) if (field.placeholder != undefined) { control.setAttribute("placeholder", field.placeholder) } if (field.autocomplete != undefined) { control.setAttribute("autocomplete", field.autocomplete) } break } markChangedOnChange(control) return control } createSettings(settingsKey:string):any { var setting = document.createElement("TR") var field:SettingsField = getSettingsField(settingsKey) if (field == undefined) { return setting } var data = SERVER["settings"][settingsKey] var tdLeft = document.createElement("TD") tdLeft.textContent = field.title + ":" var tdRight = document.createElement("TD") tdRight.appendChild(this.createControl(field, data)) setting.appendChild(tdLeft) setting.appendChild(tdRight) return setting } createDescription(settingsKey:string):any { var description = document.createElement("TR") var field:SettingsField = getSettingsField(settingsKey) var text:string = "" if (field != undefined) { text = field.description } var tdLeft = document.createElement("TD") tdLeft.innerHTML = "" var tdRight = document.createElement("TD") var pre = document.createElement("PRE") pre.innerHTML = text tdRight.appendChild(pre) description.appendChild(tdLeft) description.appendChild(tdRight) return description } } class SettingsCategoryItem extends SettingsCategory { headline:string settingsKeys:string constructor(headline:string, settingsKeys:string) { super() this.headline = headline this.settingsKeys = settingsKeys } createCategory():void { var headline = this.createCategoryHeadline(this.headline) var settingsKeys = this.settingsKeys var doc = document.getElementById(this.DocumentID) doc.appendChild(headline) // Tabelle für die Kategorie erstellen var table = document.createElement("TABLE") var keys = settingsKeys.split(",") keys.forEach(settingsKey => { switch (settingsKey) { case "authentication.pms": case "authentication.m3u": case "authentication.xml": case "authentication.api": if (SERVER["settings"]["authentication.web"] == false) { break } default: var item = this.createSettings(settingsKey) var description = this.createDescription(settingsKey) table.appendChild(item) table.appendChild(description) break } }); doc.appendChild(table) doc.appendChild(this.createHR()) } } function showSettings() { console.log("SETTINGS"); for (let i = 0; i < settingsCategory.length; i++) { settingsCategory[i].createCategory() } } function saveSettings() { console.log("Save Settings"); var cmd = "saveSettings" var div = document.getElementById("content_settings") var settings = div.getElementsByClassName("changed") var newSettings = new Object(); for (let i = 0; i < settings.length; i++) { var name:string var value:any switch (settings[i].tagName) { case "INPUT": switch ((settings[i] as HTMLInputElement).type) { case "checkbox": name = (settings[i] as HTMLInputElement).name value = (settings[i] as HTMLInputElement).checked newSettings[name] = value break case "text": case "password": name = (settings[i] as HTMLInputElement).name value = (settings[i] as HTMLInputElement).value switch (name) { case "update": value = value.split(",") value = value.filter(function(e:any) { return e}) break case "buffer.timeout": value = parseFloat(value) } newSettings[name] = value break } break case "SELECT": name = (settings[i] as HTMLSelectElement).name value = (settings[i] as HTMLSelectElement).value // Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert if(isNaN(value)){ newSettings[name] = value } else { newSettings[name] = parseInt(value) } break } } var data = new Object() data["settings"] = newSettings var server:Server = new Server(cmd) server.request(data) }