// 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)
}