Compare commits
29 Commits
2.0.1.0010
...
2.1.0.0100
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72eb7fb599 | ||
|
|
4b969b8cee | ||
|
|
3d9266dabe | ||
|
|
48218cda50 | ||
|
|
dc42afcd05 | ||
|
|
20e5e1b545 | ||
|
|
11dd830110 | ||
|
|
7c87d1d5bd | ||
|
|
6cdd44357b | ||
|
|
ad992eb615 | ||
|
|
11453c6053 | ||
|
|
81e8ae33d7 | ||
|
|
f3be0fca47 | ||
|
|
1c1c89cd74 | ||
|
|
3d73dba422 | ||
|
|
c6e74fe11c | ||
|
|
18dba46c02 | ||
|
|
efa55b39a9 | ||
|
|
717fa68b7e | ||
|
|
878531ff79 | ||
|
|
792fd9a373 | ||
|
|
a1ec0287ef | ||
|
|
a79e824ef8 | ||
|
|
c843f424fe | ||
|
|
5c6637c048 | ||
|
|
1062e072d6 | ||
|
|
d831a099f0 | ||
|
|
a06baef4d3 | ||
|
|
f9d1a45bbd |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.DS_Store
|
||||
demo
|
||||
compiler
|
||||
files
|
||||
update_xteve*.sh
|
||||
|
||||
@@ -81,11 +81,6 @@ Including:
|
||||
- Crond: Daemon to execute scheduled commands
|
||||
- Perl: Programming language
|
||||
|
||||
## Beta version
|
||||
|
||||
New features are first available in the beta version and will be added to the master branch after successful testing
|
||||
If you prefer to use the beta version, you can always switch between master and beta branch.
|
||||
|
||||
---
|
||||
|
||||
### xTeVe Beta branch
|
||||
|
||||
@@ -1,3 +1,40 @@
|
||||
#### 2.0.3.0042-beta
|
||||
**Version 2.0.3.0042 changes the settings.json.**
|
||||
Settings from the current beta can not be used for the current master version 2.0.3
|
||||
- New default options for VLC and FFmpeg
|
||||
- VLC and FFmpeg log entries in the xTeVe log
|
||||
- Less CPU load with VLC and FFmpeg
|
||||
|
||||
#### 2.0.3.0035-beta
|
||||
```diff
|
||||
+ FFmpeg support
|
||||
+ VLC support
|
||||
```
|
||||
**Version 2.0.3.0035 changes the settings.json.**
|
||||
Settings from the current beta can not be used for the current master version 2.0.3
|
||||
|
||||
#### 2.0.2.0024-beta
|
||||
```diff
|
||||
+ Improved monitoring of the buffer process
|
||||
+ Update the XEPG database a bit faster
|
||||
```
|
||||
|
||||
##### Fixes
|
||||
- Error message if filter rule is missing
|
||||
- Channels are lost when saving again (Mapping)
|
||||
- Plex log, invalid source: IPTV
|
||||
|
||||
#### 2.0.1.0012-beta
|
||||
```diff
|
||||
+ Add support for "video/m2ts" video streams (Pull request #14)
|
||||
```
|
||||
#### 2.0.1.0011-beta
|
||||
```diff
|
||||
+ Original group title is shown in the Mapping Editor
|
||||
```
|
||||
##### Fixes
|
||||
- incorrect original-air-date
|
||||
|
||||
#### 2.0.1.0010-beta
|
||||
```diff
|
||||
+ Set timestamp to <episode-num system="original-air-date">
|
||||
|
||||
@@ -20,7 +20,7 @@ menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.p
|
||||
var settingsCategory = new Array();
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"));
|
||||
function showPopUpElement(elm) {
|
||||
|
||||
@@ -127,7 +127,7 @@ var Content = /** @class */ (function () {
|
||||
var cell = new Cell();
|
||||
cell.child = true;
|
||||
cell.childType = "P";
|
||||
if (SERVER["settings"]["buffer"] == true) {
|
||||
if (SERVER["settings"]["buffer"] != "-") {
|
||||
cell.value = data[key]["tuner"];
|
||||
}
|
||||
else {
|
||||
@@ -901,7 +901,7 @@ function openPopUp(dataType, element) {
|
||||
input.setAttribute("placeholder", "{{.playlist.fileM3U.placeholder}}");
|
||||
content.appendRow("{{.playlist.fileM3U.title}}", input);
|
||||
// Tuner
|
||||
if (SERVER["settings"]["buffer"] == true) {
|
||||
if (SERVER["settings"]["buffer"] != "-") {
|
||||
var text = new Array();
|
||||
var values = new Array();
|
||||
for (var i = 1; i <= 100; i++) {
|
||||
@@ -971,7 +971,7 @@ function openPopUp(dataType, element) {
|
||||
input.setAttribute("placeholder", "{{.playlist.fileHDHR.placeholder}}");
|
||||
content.appendRow("{{.playlist.fileHDHR.title}}", input);
|
||||
// Tuner
|
||||
if (SERVER["settings"]["buffer"] == true) {
|
||||
if (SERVER["settings"]["buffer"] != "-") {
|
||||
var text = new Array();
|
||||
var values = new Array();
|
||||
for (var i = 1; i <= 100; i++) {
|
||||
@@ -1288,6 +1288,9 @@ function openPopUp(dataType, element) {
|
||||
var input = content.createInput("text", dbKey, data[dbKey]);
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
content.appendRow("{{.mapping.m3uGroupTitle.title}}", input);
|
||||
if (data["group-title"] != undefined) {
|
||||
content.description(data["group-title"]);
|
||||
}
|
||||
// XMLTV Datei
|
||||
var dbKey = "x-xmltv-file";
|
||||
var xmlFile = data[dbKey];
|
||||
|
||||
@@ -85,6 +85,50 @@ var SettingsCategory = /** @class */ (function () {
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
case "ffmpeg.path":
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.ffmpegPath.title}}" + ":";
|
||||
var tdRight = document.createElement("TD");
|
||||
var input = content.createInput("text", "ffmpeg.path", data);
|
||||
input.setAttribute("placeholder", "{{.settings.ffmpegPath.placeholder}}");
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
tdRight.appendChild(input);
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
case "ffmpeg.options":
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.ffmpegOptions.title}}" + ":";
|
||||
var tdRight = document.createElement("TD");
|
||||
var input = content.createInput("text", "ffmpeg.options", data);
|
||||
input.setAttribute("placeholder", "{{.settings.ffmpegOptions.placeholder}}");
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
tdRight.appendChild(input);
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
case "vlc.path":
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.vlcPath.title}}" + ":";
|
||||
var tdRight = document.createElement("TD");
|
||||
var input = content.createInput("text", "vlc.path", data);
|
||||
input.setAttribute("placeholder", "{{.settings.vlcPath.placeholder}}");
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
tdRight.appendChild(input);
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
case "vlc.options":
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.vlcOptions.title}}" + ":";
|
||||
var tdRight = document.createElement("TD");
|
||||
var input = content.createInput("text", "vlc.options", data);
|
||||
input.setAttribute("placeholder", "{{.settings.vlcOptions.placeholder}}");
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
tdRight.appendChild(input);
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
// Checkboxen
|
||||
case "authentication.web":
|
||||
var tdLeft = document.createElement("TD");
|
||||
@@ -185,17 +229,6 @@ var SettingsCategory = /** @class */ (function () {
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
case "buffer":
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":";
|
||||
var tdRight = document.createElement("TD");
|
||||
var input = content.createCheckbox(settingsKey);
|
||||
input.checked = data;
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
tdRight.appendChild(input);
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
case "api":
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.api.title}}" + ":";
|
||||
@@ -260,6 +293,18 @@ var SettingsCategory = /** @class */ (function () {
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
case "buffer":
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":";
|
||||
var tdRight = document.createElement("TD");
|
||||
var text = ["{{.settings.streamBuffering.info_false}}", "xTeVe: ({{.settings.streamBuffering.info_xteve}})", "FFmpeg: ({{.settings.streamBuffering.info_ffmpeg}})", "VLC: ({{.settings.streamBuffering.info_vlc}})"];
|
||||
var values = ["-", "xteve", "ffmpeg", "vlc"];
|
||||
var select = content.createSelect(text, values, data, settingsKey);
|
||||
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
tdRight.appendChild(select);
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
}
|
||||
return setting;
|
||||
};
|
||||
@@ -308,6 +353,18 @@ var SettingsCategory = /** @class */ (function () {
|
||||
case "user.agent":
|
||||
text = "{{.settings.userAgent.description}}";
|
||||
break;
|
||||
case "ffmpeg.path":
|
||||
text = "{{.settings.ffmpegPath.description}}";
|
||||
break;
|
||||
case "ffmpeg.options":
|
||||
text = "{{.settings.ffmpegOptions.description}}";
|
||||
break;
|
||||
case "vlc.path":
|
||||
text = "{{.settings.vlcPath.description}}";
|
||||
break;
|
||||
case "vlc.options":
|
||||
text = "{{.settings.vlcOptions.description}}";
|
||||
break;
|
||||
case "epgSource":
|
||||
text = "{{.settings.epgSource.description}}";
|
||||
break;
|
||||
|
||||
@@ -307,7 +307,7 @@
|
||||
"description": "Updates all playlists, tuner and XMLTV files at startup."
|
||||
},
|
||||
"cacheImages": {
|
||||
"title": "Image caching",
|
||||
"title": "Image Caching",
|
||||
"description": "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.<br>Downloading the images may take a while and will be done in the background."
|
||||
},
|
||||
"replaceEmptyImages": {
|
||||
@@ -320,7 +320,32 @@
|
||||
},
|
||||
"streamBuffering": {
|
||||
"title": "Stream Buffer",
|
||||
"description": "- The stream is passed from xTeVe to Plex / Emby / M3U Player<br>- Small jerking of the streams can be compensated<br>- HLS / M3U8 support<br>- Re-streaming<br>- Separate tuner limit for each playlist"
|
||||
"description": "Functions of the buffer:<br>- The stream is passed from xTeVe, FFmpeg or VLC to Plex, Emby or M3U Player<br>- Small jerking of the streams can be compensated<br>- HLS / M3U8 support<br>- RTP / RTPS support (only FFmpeg or VLC)<br>- Re-streaming<br>- Separate tuner limit for each playlist",
|
||||
"info_false": "No Buffer (Client connects to the streaming server)",
|
||||
"info_xteve": "xTeVe connects to the streaming server",
|
||||
"info_ffmpeg": "FFmpeg connects to the streaming server",
|
||||
"info_vlc": "VLC connects to the streaming server"
|
||||
|
||||
},
|
||||
"ffmpegPath": {
|
||||
"title": "FFmpeg Binary Path",
|
||||
"description": "Path to FFmpeg binary.",
|
||||
"placeholder": "/path/to/ffmpeg"
|
||||
},
|
||||
"ffmpegOptions": {
|
||||
"title": "FFmpeg Options",
|
||||
"description": "FFmpeg options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings.",
|
||||
"placeholder": "Leave blank to set default settings"
|
||||
},
|
||||
"vlcPath": {
|
||||
"title": "VLC / CVLC Binary Path",
|
||||
"description": "Path to VLC / CVLC binary.",
|
||||
"placeholder": "/path/to/cvlc"
|
||||
},
|
||||
"vlcOptions": {
|
||||
"title": "VLC / CVLC Options",
|
||||
"description": "VLC / CVLC options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings.",
|
||||
"placeholder": "Leave blank to set default settings"
|
||||
},
|
||||
"bufferSize": {
|
||||
"title": "Buffer Size",
|
||||
@@ -332,8 +357,8 @@
|
||||
"placeholder": "100"
|
||||
},
|
||||
"userAgent": {
|
||||
"title": "User agent",
|
||||
"description": "User Agent for HTTP requests",
|
||||
"title": "User Agent",
|
||||
"description": "User Agent for HTTP requests. Only used if xTeVe is selected as the buffer.",
|
||||
"placeholder": "xTeVe"
|
||||
},
|
||||
"backupPath": {
|
||||
|
||||
327
src/buffer.go
327
src/buffer.go
@@ -6,6 +6,8 @@ package src
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -205,7 +208,17 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
|
||||
playlist.Streams[streamID] = stream
|
||||
BufferInformation.Store(playlistID, playlist)
|
||||
|
||||
go connectToStreamingServer(streamID, playlist)
|
||||
switch Settings.Buffer {
|
||||
|
||||
case "xteve":
|
||||
go connectToStreamingServer(streamID, playlistID)
|
||||
case "ffmpeg", "vlc":
|
||||
go thirdPartyBuffer(streamID, playlistID)
|
||||
|
||||
default:
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner))
|
||||
|
||||
@@ -219,6 +232,10 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
|
||||
|
||||
for { // Loop 1: Warten bis das erste Segment durch den Buffer heruntergeladen wurde
|
||||
|
||||
if p, ok := BufferInformation.Load(playlistID); ok {
|
||||
|
||||
var playlist = p.(Playlist)
|
||||
|
||||
if stream, ok := playlist.Streams[streamID]; ok {
|
||||
|
||||
if stream.Status == false {
|
||||
@@ -377,6 +394,8 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
|
||||
|
||||
}
|
||||
|
||||
} // Ende BufferInformation
|
||||
|
||||
} // Ende Loop 1
|
||||
|
||||
}
|
||||
@@ -394,7 +413,7 @@ func getTmpFiles(stream *ThisStream) (tmpFiles []string) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(files) > 1 {
|
||||
if len(files) > 2 {
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
@@ -454,6 +473,7 @@ func killClientConnection(streamID int, playlistID string, force bool) {
|
||||
if clients.Connection <= 0 {
|
||||
BufferClients.Delete(playlistID + stream.MD5)
|
||||
delete(playlist.Streams, streamID)
|
||||
delete(playlist.Clients, streamID)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -507,7 +527,11 @@ func clientConnection(stream ThisStream) (status bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func connectToStreamingServer(streamID int, playlist Playlist) {
|
||||
func connectToStreamingServer(streamID int, playlistID string) {
|
||||
|
||||
if p, ok := BufferInformation.Load(playlistID); ok {
|
||||
|
||||
var playlist = p.(Playlist)
|
||||
|
||||
var timeOut = 0
|
||||
var debug string
|
||||
@@ -516,6 +540,9 @@ func connectToStreamingServer(streamID int, playlist Playlist) {
|
||||
var m3u8Segments []string
|
||||
var bandwidth BandwidthCalculation
|
||||
var networkBandwidth = Settings.M3U8AdaptiveBandwidthMBPS * 1e+6
|
||||
// Größe des Buffers
|
||||
var bufferSize = Settings.BufferSize
|
||||
var buffer = make([]byte, 1024*bufferSize*2)
|
||||
|
||||
var defaultSegment = func() {
|
||||
|
||||
@@ -548,11 +575,11 @@ func connectToStreamingServer(streamID int, playlist Playlist) {
|
||||
|
||||
var stream = playlist.Streams[streamID]
|
||||
|
||||
if c, ok := BufferClients.Load(stream.PlaylistID + stream.MD5); ok {
|
||||
if c, ok := BufferClients.Load(playlistID + stream.MD5); ok {
|
||||
|
||||
var clients = c.(ClientConnection)
|
||||
clients.Error = err
|
||||
BufferClients.Store(stream.PlaylistID+stream.MD5, clients)
|
||||
BufferClients.Store(playlistID+stream.MD5, clients)
|
||||
|
||||
}
|
||||
|
||||
@@ -650,7 +677,7 @@ InitBuffer:
|
||||
|
||||
addErrorToStream(err)
|
||||
|
||||
killClientConnection(streamID, stream.PlaylistID, true)
|
||||
killClientConnection(streamID, playlistID, true)
|
||||
clientConnection(stream)
|
||||
|
||||
return
|
||||
@@ -722,7 +749,7 @@ InitBuffer:
|
||||
BufferInformation.Store(playlist.PlaylistID, playlist)
|
||||
addErrorToStream(err)
|
||||
|
||||
killClientConnection(streamID, stream.PlaylistID, true)
|
||||
killClientConnection(streamID, playlistID, true)
|
||||
clientConnection(stream)
|
||||
resp.Body.Close()
|
||||
|
||||
@@ -772,7 +799,7 @@ InitBuffer:
|
||||
switch contentType {
|
||||
|
||||
// M3U8 Playlist
|
||||
case "application/x-mpegurl", "application/vnd.apple.mpegurl", "audio/mpegurl":
|
||||
case "application/x-mpegurl", "application/vnd.apple.mpegurl", "audio/mpegurl", "audio/x-mpegurl":
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
@@ -790,13 +817,13 @@ InitBuffer:
|
||||
}
|
||||
|
||||
// Video Stream (TS)
|
||||
case "video/mpeg", "video/mp4", "video/mp2t", "application/octet-stream", "binary/octet-stream", "application/mp2t":
|
||||
case "video/mpeg", "video/mp4", "video/mp2t", "video/m2ts", "application/octet-stream", "binary/octet-stream", "application/mp2t":
|
||||
|
||||
var fileSize int
|
||||
|
||||
// Größe des Buffers
|
||||
buffer := make([]byte, 1024*Settings.BufferSize*2)
|
||||
var tmpFileSize = 1024 * Settings.BufferSize * 1
|
||||
buffer = make([]byte, 1024*bufferSize*2)
|
||||
var tmpFileSize = 1024 * bufferSize * 1
|
||||
|
||||
debug = fmt.Sprintf("Buffer Size:%d KB [SERVER CONNECTION]", len(buffer)/1024)
|
||||
showDebug(debug, 3)
|
||||
@@ -872,7 +899,7 @@ InitBuffer:
|
||||
}
|
||||
|
||||
// Buffer auf die Festplatte speichern
|
||||
if fileSize >= tmpFileSize || n == 0 {
|
||||
if fileSize >= tmpFileSize/2 || n == 0 {
|
||||
|
||||
bandwidth.Stop = time.Now()
|
||||
bandwidth.Size += fileSize
|
||||
@@ -891,6 +918,8 @@ InitBuffer:
|
||||
|
||||
stream.Status = true
|
||||
playlist.Streams[streamID] = stream
|
||||
BufferInformation.Store(playlistID, playlist)
|
||||
|
||||
tmpSegment++
|
||||
|
||||
tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
|
||||
@@ -916,6 +945,7 @@ InitBuffer:
|
||||
}
|
||||
|
||||
fileSize = 0
|
||||
buffer = make([]byte, 1024*bufferSize*2)
|
||||
|
||||
if n == 0 {
|
||||
bufferFile.Close()
|
||||
@@ -986,6 +1016,8 @@ InitBuffer:
|
||||
|
||||
} // Ende for loop
|
||||
|
||||
} // Ende BufferInformation
|
||||
|
||||
}
|
||||
|
||||
func parseM3U8(stream *ThisStream) (err error) {
|
||||
@@ -1241,12 +1273,6 @@ func parseM3U8(stream *ThisStream) (err error) {
|
||||
break
|
||||
}
|
||||
|
||||
err := checkFile(stream.Folder + "remove")
|
||||
if err == nil {
|
||||
os.RemoveAll(stream.Folder)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1305,14 +1331,275 @@ func switchBandwidth(stream *ThisStream) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Buffer mit FFMPEG
|
||||
func thirdPartyBuffer(streamID int, playlistID string) {
|
||||
|
||||
if p, ok := BufferInformation.Load(playlistID); ok {
|
||||
|
||||
var playlist = p.(Playlist)
|
||||
var debug, path, options, bufferType string
|
||||
var tmpSegment = 1
|
||||
var bufferSize = Settings.BufferSize * 1024
|
||||
var stream = playlist.Streams[streamID]
|
||||
var buf bytes.Buffer
|
||||
var fileSize = 0
|
||||
var streamStatus = make(chan bool)
|
||||
|
||||
var tmpFolder = playlist.Streams[streamID].Folder
|
||||
var url = playlist.Streams[streamID].URL
|
||||
|
||||
stream.Status = false
|
||||
|
||||
bufferType = strings.ToUpper(Settings.Buffer)
|
||||
|
||||
switch Settings.Buffer {
|
||||
|
||||
case "ffmpeg":
|
||||
path = Settings.FFmpegPath
|
||||
options = Settings.FFmpegOptions
|
||||
|
||||
case "vlc":
|
||||
path = Settings.VLCPath
|
||||
options = Settings.VLCOptions
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
var addErrorToStream = func(err error) {
|
||||
|
||||
var stream = playlist.Streams[streamID]
|
||||
|
||||
if c, ok := BufferClients.Load(playlistID + stream.MD5); ok {
|
||||
|
||||
var clients = c.(ClientConnection)
|
||||
clients.Error = err
|
||||
BufferClients.Store(playlistID+stream.MD5, clients)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
os.RemoveAll(getPlatformPath(tmpFolder))
|
||||
|
||||
err := checkFolder(tmpFolder)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
addErrorToStream(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = checkFile(path)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
addErrorToStream(err)
|
||||
return
|
||||
}
|
||||
|
||||
showInfo(fmt.Sprintf("%s path:%s", bufferType, path))
|
||||
showInfo("Streaming URL:" + stream.URL)
|
||||
|
||||
var tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
|
||||
|
||||
f, err := os.Create(tmpFile)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
addErrorToStream(err)
|
||||
return
|
||||
}
|
||||
|
||||
var args = strings.Replace(options, "[URL]", url, -1)
|
||||
var cmd = exec.Command(path, strings.Split(args, " ")...)
|
||||
|
||||
debug = fmt.Sprintf("%s:%s %s", bufferType, path, args)
|
||||
showDebug(debug, 1)
|
||||
|
||||
// Byte-Daten vom Prozess
|
||||
stdOut, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
addErrorToStream(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Log-Daten vom Prozess
|
||||
logOut, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
addErrorToStream(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(buf.Bytes()) == 0 && stream.Status == false {
|
||||
showInfo(bufferType + ":Processing data")
|
||||
}
|
||||
|
||||
cmd.Start()
|
||||
defer cmd.Wait()
|
||||
|
||||
go func() {
|
||||
|
||||
// Log Daten vom Prozess im Dubug Mode 1 anzeigen.
|
||||
scanner := bufio.NewScanner(logOut)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
for scanner.Scan() {
|
||||
|
||||
debug = fmt.Sprintf("%s log:%s", bufferType, strings.TrimSpace(scanner.Text()))
|
||||
|
||||
select {
|
||||
case <-streamStatus:
|
||||
showDebug(debug, 1)
|
||||
default:
|
||||
showInfo(debug)
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(10) * time.Millisecond)
|
||||
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
f, err = os.OpenFile(tmpFile, os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
buffer := make([]byte, 1024*4)
|
||||
|
||||
reader := bufio.NewReader(stdOut)
|
||||
|
||||
t := make(chan int)
|
||||
|
||||
go func() {
|
||||
|
||||
var timeout = 0
|
||||
for {
|
||||
time.Sleep(time.Duration(1000) * time.Millisecond)
|
||||
timeout++
|
||||
|
||||
select {
|
||||
case <-t:
|
||||
return
|
||||
default:
|
||||
t <- timeout
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
for {
|
||||
|
||||
select {
|
||||
case timeout := <-t:
|
||||
if timeout >= 20 && tmpSegment == 1 {
|
||||
cmd.Process.Kill()
|
||||
err = errors.New("Timout")
|
||||
ShowError(err, 4006)
|
||||
addErrorToStream(err)
|
||||
cmd.Wait()
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
if fileSize == 0 && stream.Status == false {
|
||||
showInfo("Streaming Status:Receive data from " + bufferType)
|
||||
}
|
||||
|
||||
if clientConnection(stream) == false {
|
||||
cmd.Process.Kill()
|
||||
f.Close()
|
||||
cmd.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
n, err := reader.Read(buffer)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
fileSize = fileSize + len(buffer[:n])
|
||||
|
||||
if _, err := f.Write(buffer[:n]); err != nil {
|
||||
cmd.Process.Kill()
|
||||
ShowError(err, 0)
|
||||
addErrorToStream(err)
|
||||
cmd.Wait()
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if fileSize >= bufferSize/2 {
|
||||
|
||||
if tmpSegment == 1 && stream.Status == false {
|
||||
close(t)
|
||||
close(streamStatus)
|
||||
showInfo(fmt.Sprintf("Streaming Status:Buffering data from %s", bufferType))
|
||||
}
|
||||
|
||||
f.Close()
|
||||
tmpSegment++
|
||||
|
||||
if stream.Status == false {
|
||||
stream.Status = true
|
||||
playlist.Streams[streamID] = stream
|
||||
BufferInformation.Store(playlistID, playlist)
|
||||
}
|
||||
|
||||
tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
|
||||
|
||||
fileSize = 0
|
||||
|
||||
var errCreate, errOpen error
|
||||
f, errCreate = os.Create(tmpFile)
|
||||
f, errOpen = os.OpenFile(tmpFile, os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if errCreate != nil || errOpen != nil {
|
||||
cmd.Process.Kill()
|
||||
ShowError(err, 0)
|
||||
addErrorToStream(err)
|
||||
cmd.Wait()
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
|
||||
err = errors.New(bufferType + " error")
|
||||
addErrorToStream(err)
|
||||
ShowError(err, 1204)
|
||||
|
||||
time.Sleep(time.Duration(500) * time.Millisecond)
|
||||
clientConnection(stream)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getTuner(id, playlistType string) (tuner int) {
|
||||
|
||||
switch Settings.Buffer {
|
||||
|
||||
case false:
|
||||
case "-":
|
||||
tuner = Settings.Tuner
|
||||
|
||||
case true:
|
||||
case "xteve", "ffmpeg", "vlc":
|
||||
|
||||
i, err := strconv.Atoi(getProviderParameter(id, playlistType, "tuner"))
|
||||
if err == nil {
|
||||
|
||||
@@ -46,6 +46,10 @@ func Init() (err error) {
|
||||
System.DVRLimit = 480
|
||||
System.Compatibility = "1.4.4"
|
||||
|
||||
// FFmpeg Default Einstellungen
|
||||
System.FFmpeg.DefaultOptions = "-hide_banner -loglevel error -i [URL] -c copy -f mpegts pipe:1"
|
||||
System.VLC.DefaultOptions = "-I dummy [URL] --sout #std{mux=ts,access=file,dst=-}"
|
||||
|
||||
// Default Logeinträge, wird später von denen aus der settings.json überschrieben. Muss gemacht werden, damit die ersten Einträge auch im Log (webUI aangezeigt werden)
|
||||
Settings.LogEntriesRAM = 500
|
||||
|
||||
@@ -109,7 +113,7 @@ func Init() (err error) {
|
||||
|
||||
// Überprüfen ob xTeVe als root läuft
|
||||
if os.Geteuid() == 0 {
|
||||
showWarning(2010)
|
||||
showWarning(2110)
|
||||
}
|
||||
|
||||
if System.Flag.Debug > 0 {
|
||||
@@ -133,7 +137,6 @@ func Init() (err error) {
|
||||
// Bedingte Update Änderungen durchführen
|
||||
err = conditionalUpdateChanges()
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
118
src/data.go
118
src/data.go
@@ -24,6 +24,8 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
||||
var createXEPGFiles = false
|
||||
var debug string
|
||||
|
||||
// -vvv [URL] --sout '#transcode{vcodec=mp4v, acodec=mpga} :standard{access=http, mux=ogg}'
|
||||
|
||||
for key, value := range newSettings {
|
||||
|
||||
if _, ok := oldSettings[key]; ok {
|
||||
@@ -94,6 +96,17 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
||||
return
|
||||
}
|
||||
|
||||
case "ffmpeg.path", "vlc.path":
|
||||
var path = value.(string)
|
||||
if len(path) > 0 {
|
||||
|
||||
err = checkFile(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
oldSettings[key] = value
|
||||
@@ -138,6 +151,33 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
||||
|
||||
}
|
||||
|
||||
// Buffer Einstellungen überprüfen
|
||||
if len(Settings.FFmpegOptions) == 0 {
|
||||
Settings.FFmpegOptions = System.FFmpeg.DefaultOptions
|
||||
}
|
||||
|
||||
if len(Settings.VLCOptions) == 0 {
|
||||
Settings.VLCOptions = System.VLC.DefaultOptions
|
||||
}
|
||||
|
||||
switch Settings.Buffer {
|
||||
|
||||
case "ffmpeg":
|
||||
|
||||
if len(Settings.FFmpegPath) == 0 {
|
||||
err = errors.New(getErrMsg(2020))
|
||||
return
|
||||
}
|
||||
|
||||
case "vlc":
|
||||
|
||||
if len(Settings.VLCPath) == 0 {
|
||||
err = errors.New(getErrMsg(2021))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = saveSettings(Settings)
|
||||
if err == nil {
|
||||
|
||||
@@ -366,6 +406,7 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
|
||||
var filterMap = make(map[int64]interface{})
|
||||
var newData = make(map[int64]interface{})
|
||||
var defaultFilter FilterStruct
|
||||
var newFilter = false
|
||||
|
||||
defaultFilter.Active = true
|
||||
defaultFilter.CaseSensitive = false
|
||||
@@ -389,6 +430,7 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
|
||||
if dataID == -1 {
|
||||
|
||||
// Neuer Filter
|
||||
newFilter = true
|
||||
dataID = createNewID()
|
||||
filterMap[dataID] = jsonToMap(mapToJSON(defaultFilter))
|
||||
|
||||
@@ -397,15 +439,28 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
|
||||
// Filter aktualisieren / löschen
|
||||
for key, value := range data.(map[string]interface{}) {
|
||||
|
||||
var oldData = filterMap[dataID].(map[string]interface{})
|
||||
oldData[key] = value
|
||||
|
||||
// Filter löschen
|
||||
if _, ok := data.(map[string]interface{})["delete"]; ok {
|
||||
|
||||
delete(filterMap, dataID)
|
||||
break
|
||||
}
|
||||
|
||||
if filter, ok := data.(map[string]interface{})["filter"].(string); ok {
|
||||
|
||||
if len(filter) == 0 {
|
||||
|
||||
err = errors.New(getErrMsg(1014))
|
||||
if newFilter == true {
|
||||
delete(filterMap, dataID)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if oldData, ok := filterMap[dataID].(map[string]interface{}); ok {
|
||||
oldData[key] = value
|
||||
}
|
||||
|
||||
}
|
||||
@@ -446,8 +501,60 @@ func saveXEpgMapping(request RequestStruct) (err error) {
|
||||
|
||||
Data.XEPG.Channels = request.EpgMapping
|
||||
|
||||
if System.ScanInProgress == 0 {
|
||||
|
||||
System.ScanInProgress = 1
|
||||
cleanupXEPG()
|
||||
buildXEPG(true)
|
||||
//buildXEPG(true)
|
||||
|
||||
go func() {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
go cachingImages()
|
||||
|
||||
System.ScanInProgress = 0
|
||||
|
||||
}()
|
||||
|
||||
} else {
|
||||
|
||||
// Wenn während des erstellen der Datanbank das Mapping erneut gespeichert wird, wird die Datenbank erst später erneut aktualisiert.
|
||||
go func() {
|
||||
|
||||
if System.BackgroundProcess == true {
|
||||
return
|
||||
}
|
||||
|
||||
System.BackgroundProcess = true
|
||||
|
||||
for {
|
||||
time.Sleep(time.Duration(1) * time.Second)
|
||||
if System.ScanInProgress == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
System.ScanInProgress = 1
|
||||
|
||||
cleanupXEPG()
|
||||
//buildXEPG(false)
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
go cachingImages()
|
||||
|
||||
System.ScanInProgress = 0
|
||||
|
||||
System.BackgroundProcess = false
|
||||
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -612,6 +719,7 @@ func saveWizard(request RequestStruct) (nextStep int, err error) {
|
||||
}
|
||||
|
||||
buildXEPG(false)
|
||||
System.ScanInProgress = 0
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func getLineupStatus() (jsonContent []byte, err error) {
|
||||
lineupStatus.ScanInProgress = System.ScanInProgress
|
||||
lineupStatus.ScanPossible = 0
|
||||
lineupStatus.Source = "Cable"
|
||||
lineupStatus.SourceList = []string{"IPTV", "Cable"}
|
||||
lineupStatus.SourceList = []string{"Cable"}
|
||||
|
||||
jsonContent, err = json.MarshalIndent(lineupStatus, "", " ")
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ func ShowSystemInfo() {
|
||||
println("---")
|
||||
|
||||
fmt.Println("Settings [Streaming]")
|
||||
fmt.Println(fmt.Sprintf("Buffer: %t", Settings.Buffer))
|
||||
fmt.Println(fmt.Sprintf("Buffer: %s", Settings.Buffer))
|
||||
fmt.Println(fmt.Sprintf("Buffer Size: %d KB", Settings.BufferSize))
|
||||
fmt.Println(fmt.Sprintf("Timeout: %d ms", int(Settings.BufferTimeout)))
|
||||
fmt.Println(fmt.Sprintf("User Agent: %s", Settings.UserAgent))
|
||||
|
||||
@@ -2,6 +2,8 @@ package m3u
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -12,6 +14,7 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
||||
|
||||
var content = string(byteStream)
|
||||
var channelName string
|
||||
var uuids []string
|
||||
|
||||
var parseMetaData = func(channel string) (stream map[string]string) {
|
||||
|
||||
@@ -53,7 +56,7 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
||||
line = strings.Replace(line, p, "", 1)
|
||||
|
||||
p = strings.Replace(p, `"`, "", -1)
|
||||
var parameter = strings.Split(p, "=")
|
||||
var parameter = strings.SplitN(p, "=", 2)
|
||||
|
||||
if len(parameter) == 2 {
|
||||
|
||||
@@ -120,9 +123,15 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
||||
|
||||
if strings.Contains(strings.ToLower(key), "id") {
|
||||
|
||||
if indexOfString(value, uuids) != -1 {
|
||||
log.Println(fmt.Sprintf("Channel: %s - %s = %s ", stream["name"], key, value))
|
||||
break
|
||||
}
|
||||
|
||||
uuids = append(uuids, value)
|
||||
|
||||
stream["_uuid.key"] = key
|
||||
stream["_uuid.value"] = value
|
||||
//os.Exit(0)
|
||||
break
|
||||
|
||||
}
|
||||
@@ -160,3 +169,14 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func indexOfString(element string, data []string) int {
|
||||
|
||||
for k, v := range data {
|
||||
if element == v {
|
||||
return k
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ func filterThisStream(s interface{}) (status bool) {
|
||||
|
||||
for _, filter := range Data.Filter {
|
||||
|
||||
if filter.Rule == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var group, name, search string
|
||||
var exclude, include string
|
||||
var match = false
|
||||
|
||||
@@ -245,6 +245,8 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
errMsg = fmt.Sprintf("Invalid formatting of the time")
|
||||
case 1013:
|
||||
errMsg = fmt.Sprintf("Invalid settings file (settings.json), file must be at least version %s", System.Compatibility)
|
||||
case 1014:
|
||||
errMsg = fmt.Sprintf("Invalid filter rule")
|
||||
|
||||
case 1020:
|
||||
errMsg = fmt.Sprintf("Data could not be saved, invalid keyword")
|
||||
@@ -252,12 +254,13 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
// Datenbank Update
|
||||
case 1030:
|
||||
errMsg = fmt.Sprintf("Invalid settings file (%s)", System.File.Settings)
|
||||
case 1031:
|
||||
errMsg = fmt.Sprintf("Database error. The database version of your settings is not compatible with this version.")
|
||||
|
||||
// M3U Parser
|
||||
case 1050:
|
||||
errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.")
|
||||
|
||||
// M3U Parser
|
||||
case 1060:
|
||||
errMsg = fmt.Sprintf("Invalid characters found in the tvg parameters, streams with invalid parameters were skipped.")
|
||||
|
||||
@@ -266,6 +269,8 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
errMsg = fmt.Sprintf("Folder could not be created.")
|
||||
case 1071:
|
||||
errMsg = fmt.Sprintf("File could not be created")
|
||||
case 1072:
|
||||
errMsg = fmt.Sprintf("File not found")
|
||||
|
||||
// Backup
|
||||
case 1090:
|
||||
@@ -290,6 +295,8 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
|
||||
case 1203:
|
||||
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
|
||||
case 1204:
|
||||
errMsg = fmt.Sprintf("Streaming was stopped by third party transcoder (FFmpeg / VLC)")
|
||||
|
||||
// Warnings
|
||||
case 2000:
|
||||
@@ -306,6 +313,10 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
errMsg = fmt.Sprintf("There are no channels mapped, use the mapping menu to assign EPG data to the channels.")
|
||||
case 2010:
|
||||
errMsg = fmt.Sprintf("No valid streaming URL")
|
||||
case 2020:
|
||||
errMsg = fmt.Sprintf("FFmpeg binary was not found. Check the FFmpeg binary path in the xTeVe settings.")
|
||||
case 2021:
|
||||
errMsg = fmt.Sprintf("VLC binary was not found. Check the VLC path binary in the xTeVe settings.")
|
||||
|
||||
case 2099:
|
||||
errMsg = fmt.Sprintf("Updates have been disabled by the developer")
|
||||
@@ -345,6 +356,8 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
errMsg = fmt.Sprintf("This error message comes from the provider")
|
||||
case 4005:
|
||||
errMsg = fmt.Sprintf("Temporary buffer files could not be deleted")
|
||||
case 4006:
|
||||
errMsg = fmt.Sprintf("Server connection timeout")
|
||||
|
||||
// Buffer (M3U8)
|
||||
case 4050:
|
||||
@@ -368,7 +381,7 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
case 6002:
|
||||
errMsg = fmt.Sprintf("Update failed")
|
||||
case 6003:
|
||||
errMsg = fmt.Sprintf("Server not available")
|
||||
errMsg = fmt.Sprintf("Update server not available")
|
||||
case 6004:
|
||||
errMsg = fmt.Sprintf("xTeVe update available")
|
||||
|
||||
|
||||
@@ -108,3 +108,147 @@ type BandwidthCalculation struct {
|
||||
Stop time.Time
|
||||
TimeDiff float64
|
||||
}
|
||||
|
||||
/*
|
||||
var args = "-hide_banner -loglevel panic -re -i " + url + " -codec copy -f mpegts pipe:1"
|
||||
//var args = "-re -i " + url + " -codec copy -f mpegts pipe:1"
|
||||
cmd := exec.Command("/usr/local/bin/ffmpeg", strings.Split(args, " ")...)
|
||||
|
||||
//run := exec.Command("/usr/local/bin/ffmpeg", "-hide_banner", "-loglevel", "panic", "-re", "-i", url, "-codec", "copy", "-f", "mpegts", "pipe:1")
|
||||
//run := exec.Command("/usr/local/bin/ffmpeg", "-re", "-i", url, "-codec", "copy", "-f", "mpegts", "pipe:1")
|
||||
|
||||
stderr, _ := cmd.StderrPipe()
|
||||
cmd.Start()
|
||||
|
||||
scanner := bufio.NewScanner(stderr)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
m := scanner.Text()
|
||||
fmt.Println(m)
|
||||
}
|
||||
cmd.Wait()
|
||||
|
||||
os.Exit(0)
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
ffmpegOut, _ := run.StderrPipe()
|
||||
//run.Start()
|
||||
|
||||
scanner = bufio.NewScanner(ffmpegOut)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
m := scanner.Text()
|
||||
fmt.Println(m)
|
||||
}
|
||||
|
||||
ffmpegOut, err = run.StdoutPipe()
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
stderr, stderrErr := run.StderrPipe()
|
||||
if stderrErr != nil {
|
||||
fmt.Println(stderrErr)
|
||||
}
|
||||
|
||||
_ = stderr
|
||||
|
||||
if startErr := run.Start(); startErr != nil {
|
||||
fmt.Println(startErr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
n, err := ffmpegOut.Read(buffer)
|
||||
_ = n
|
||||
_ = stream
|
||||
_ = fileSize
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
|
||||
ShowError(err, 0)
|
||||
addErrorToStream(err)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
defer bufferFile.Close()
|
||||
|
||||
scanner = bufio.NewScanner(ffmpegOut)
|
||||
|
||||
for scanner.Scan() {
|
||||
//fmt.Printf("%s\n", scanner.Text())
|
||||
//fmt.Println(scanner)
|
||||
thisLine := scanner.Text()
|
||||
line := make([]byte, len(thisLine))
|
||||
|
||||
buffer = append(buffer, line...)
|
||||
|
||||
fmt.Println(len(buffer))
|
||||
|
||||
if len(buffer) > tmpFileSize {
|
||||
|
||||
if _, err := bufferFile.Write(buffer[:]); err != nil {
|
||||
|
||||
ShowError(err, 0)
|
||||
addErrorToStream(err)
|
||||
run.Process.Kill()
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
buffer = make([]byte, 1024*Settings.BufferSize*2)
|
||||
|
||||
debug = fmt.Sprintf("Buffer Status:Done (%s)", tmpFile)
|
||||
showDebug(debug, 2)
|
||||
|
||||
bufferFile.Close()
|
||||
|
||||
stream.Status = true
|
||||
playlist.Streams[streamID] = stream
|
||||
BufferInformation.Store(playlistID, playlist)
|
||||
|
||||
tmpSegment++
|
||||
|
||||
tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
|
||||
|
||||
if clientConnection(stream) == false {
|
||||
|
||||
bufferFile.Close()
|
||||
run.Process.Kill()
|
||||
|
||||
err = os.RemoveAll(stream.Folder)
|
||||
if err != nil {
|
||||
ShowError(err, 4005)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
bufferFile, err = os.Create(tmpFile)
|
||||
if err != nil {
|
||||
addErrorToStream(err)
|
||||
run.Process.Kill()
|
||||
return
|
||||
}
|
||||
|
||||
fileSize = 0
|
||||
|
||||
if n == 0 {
|
||||
bufferFile.Close()
|
||||
run.Process.Kill()
|
||||
break
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,7 @@ type SystemStruct struct {
|
||||
APIVersion string
|
||||
AppName string
|
||||
ARCH string
|
||||
BackgroundProcess bool
|
||||
Branch string
|
||||
Build string
|
||||
Compatibility string
|
||||
@@ -21,6 +22,16 @@ type SystemStruct struct {
|
||||
Domain string
|
||||
DVRLimit int
|
||||
|
||||
FFmpeg struct {
|
||||
DefaultOptions string
|
||||
Path string
|
||||
}
|
||||
|
||||
VLC struct {
|
||||
DefaultOptions string
|
||||
Path string
|
||||
}
|
||||
|
||||
File struct {
|
||||
Authentication string
|
||||
M3U string
|
||||
@@ -241,11 +252,15 @@ type SettingsStrcut struct {
|
||||
BackupKeep int `json:"backup.keep"`
|
||||
BackupPath string `json:"backup.path"`
|
||||
Branch string `json:"git.branch,omitempty"`
|
||||
Buffer bool `json:"buffer"`
|
||||
Buffer string `json:"buffer"`
|
||||
BufferSize int `json:"buffer.size.kb"`
|
||||
BufferTimeout float64 `json:"buffer.timeout"`
|
||||
CacheImages bool `json:"cache.images"`
|
||||
EpgSource string `json:"epgSource"`
|
||||
FFmpegOptions string `json:"ffmpeg.options"`
|
||||
FFmpegPath string `json:"ffmpeg.path"`
|
||||
VLCOptions string `json:"vlc.options"`
|
||||
VLCPath string `json:"vlc.path"`
|
||||
FileM3U []string `json:"file,omitempty"` // Beim Wizard wird die M3U in ein Slice gespeichert
|
||||
FileXMLTV []string `json:"xmltv,omitempty"` // Altes Speichersystem der Provider XML Datei Slice (Wird für die Umwandlung auf das neue benötigt)
|
||||
|
||||
|
||||
@@ -25,11 +25,15 @@ type RequestStruct struct {
|
||||
AuthenticationXML *bool `json:"authentication.xml,omitempty"`
|
||||
BackupKeep *int `json:"backup.keep,omitempty"`
|
||||
BackupPath *string `json:"backup.path,omitempty"`
|
||||
Buffer *bool `json:"buffer,omitempty"`
|
||||
Buffer *string `json:"buffer,omitempty"`
|
||||
BufferSize *int `json:"buffer.size.kb, omitempty"`
|
||||
BufferTimeout *float64 `json:"buffer.timeout,omitempty"`
|
||||
CacheImages *bool `json:"cache.images,omitempty"`
|
||||
EpgSource *string `json:"epgSource,omitempty"`
|
||||
FFmpegOptions *string `json:"ffmpeg.options,omitempty"`
|
||||
FFmpegPath *string `json:"ffmpeg.path,omitempty"`
|
||||
VLCOptions *string `json:"vlc.options,omitempty"`
|
||||
VLCPath *string `json:"vlc.path,omitempty"`
|
||||
FilesUpdate *bool `json:"files.update,omitempty"`
|
||||
TempPath *string `json:"temp.path,omitempty"`
|
||||
Tuner *int `json:"tuner,omitempty"`
|
||||
|
||||
@@ -113,11 +113,13 @@ func loadSettings() (settings SettingsStrcut, err error) {
|
||||
defaults["authentication.xml"] = false
|
||||
defaults["backup.keep"] = 10
|
||||
defaults["backup.path"] = System.Folder.Backup
|
||||
defaults["buffer"] = false
|
||||
defaults["buffer"] = "-"
|
||||
defaults["buffer.size.kb"] = 1024
|
||||
defaults["buffer.timeout"] = 500
|
||||
defaults["cache.images"] = false
|
||||
defaults["epgSource"] = "PMS"
|
||||
defaults["ffmpeg.options"] = System.FFmpeg.DefaultOptions
|
||||
defaults["vlc.options"] = System.VLC.DefaultOptions
|
||||
defaults["files"] = dataMap
|
||||
defaults["files.update"] = true
|
||||
defaults["filter"] = make(map[string]interface{})
|
||||
@@ -159,10 +161,27 @@ func loadSettings() (settings SettingsStrcut, err error) {
|
||||
showInfo(fmt.Sprintf("Git Branch:Switching Git Branch to -> %s", settings.Branch))
|
||||
}
|
||||
|
||||
if len(settings.FFmpegPath) == 0 {
|
||||
settings.FFmpegPath = searchFileInOS("ffmpeg")
|
||||
}
|
||||
|
||||
if len(settings.VLCPath) == 0 {
|
||||
settings.VLCPath = searchFileInOS("cvlc")
|
||||
}
|
||||
|
||||
settings.Version = System.DBVersion
|
||||
|
||||
err = saveSettings(settings)
|
||||
|
||||
// Warung wenn FFmpeg nicht gefunden wurde
|
||||
if len(Settings.FFmpegPath) == 0 && Settings.Buffer == "ffmpeg" {
|
||||
showWarning(2020)
|
||||
}
|
||||
|
||||
if len(Settings.VLCPath) == 0 && Settings.Buffer == "vlc" {
|
||||
showWarning(2021)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,11 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
@@ -42,13 +45,25 @@ func checkFolder(path string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prüft ob die datei im Dateisystem existiert
|
||||
// Prüft ob die Datei im Dateisystem existiert
|
||||
func checkFile(filename string) (err error) {
|
||||
|
||||
var file = getPlatformFile(filename)
|
||||
|
||||
if _, err = os.Stat(file); os.IsNotExist(err) {
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch mode := fi.Mode(); {
|
||||
case mode.IsDir():
|
||||
err = fmt.Errorf("%s: %s", file, getErrMsg(1072))
|
||||
case mode.IsRegular():
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
@@ -77,6 +92,7 @@ func GetUserHomeDirectory() (userHomeDirectory string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Prüft Dateiberechtigung
|
||||
func checkFilePermission(dir string) (err error) {
|
||||
|
||||
var filename = dir + "permission.test"
|
||||
@@ -115,6 +131,34 @@ func removeOldSystemData() {
|
||||
os.RemoveAll(System.Folder.Temp)
|
||||
}
|
||||
|
||||
// Sucht eine Datei im OS
|
||||
func searchFileInOS(file string) (path string) {
|
||||
|
||||
switch runtime.GOOS {
|
||||
|
||||
case "linux", "darwin", "freebsd":
|
||||
var args = file
|
||||
var cmd = exec.Command("which", strings.Split(args, " ")...)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
|
||||
var slice = strings.Split(strings.Replace(string(out), "\r\n", "\n", -1), "\n")
|
||||
|
||||
if len(slice) > 0 {
|
||||
path = strings.Trim(slice[0], "\r\n")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
default:
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
func removeChildItems(dir string) error {
|
||||
|
||||
@@ -276,6 +320,22 @@ func resolveHostIP() (err error) {
|
||||
|
||||
}
|
||||
|
||||
if len(System.IPAddress) == 0 {
|
||||
|
||||
switch len(System.IPAddressesV4) {
|
||||
|
||||
case 0:
|
||||
if len(System.IPAddressesV6) > 0 {
|
||||
System.IPAddress = System.IPAddressesV6[0]
|
||||
}
|
||||
|
||||
default:
|
||||
System.IPAddress = System.IPAddressesV4[0]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
System.Hostname, err = os.Hostname()
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -41,8 +41,8 @@ func BinaryUpdate() (err error) {
|
||||
|
||||
resp, err := http.Get(gitInfo)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return err
|
||||
ShowError(err, 6003)
|
||||
return nil
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
@@ -169,6 +169,13 @@ checkVersion:
|
||||
|
||||
if settingsVersion, ok := settingsMap["version"].(string); ok {
|
||||
|
||||
if settingsVersion > System.DBVersion {
|
||||
showInfo("Settings DB Version:" + settingsVersion)
|
||||
showInfo("System DB Version:" + System.DBVersion)
|
||||
err = errors.New(getErrMsg(1031))
|
||||
return
|
||||
}
|
||||
|
||||
// Letzte Kompatible Version (1.4.4)
|
||||
if settingsVersion < System.Compatibility {
|
||||
err = errors.New(getErrMsg(1013))
|
||||
@@ -204,10 +211,37 @@ checkVersion:
|
||||
}
|
||||
|
||||
case "2.0.0":
|
||||
|
||||
if oldBuffer, ok := settingsMap["buffer"].(bool); ok {
|
||||
|
||||
var newBuffer string
|
||||
switch oldBuffer {
|
||||
case true:
|
||||
newBuffer = "xteve"
|
||||
case false:
|
||||
newBuffer = "-"
|
||||
}
|
||||
|
||||
settingsMap["buffer"] = newBuffer
|
||||
|
||||
settingsMap["version"] = "2.1.0"
|
||||
|
||||
err = saveMapToJSONFile(System.File.Settings, settingsMap)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
goto checkVersion
|
||||
|
||||
} else {
|
||||
err = errors.New(getErrMsg(1030))
|
||||
return
|
||||
}
|
||||
|
||||
case "2.1.0":
|
||||
// Falls es in einem späteren Update Änderungen an der Datenbank gibt, geht es hier weiter
|
||||
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
67
src/webUI.go
67
src/webUI.go
File diff suppressed because one or more lines are too long
@@ -129,6 +129,12 @@ func Stream(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
switch Settings.Buffer {
|
||||
|
||||
case "-":
|
||||
showInfo(fmt.Sprintf("Buffer:false", Settings.Buffer))
|
||||
|
||||
case "xteve":
|
||||
if strings.Index(streamInfo.URL, "rtsp://") != -1 || strings.Index(streamInfo.URL, "rtp://") != -1 {
|
||||
err = errors.New("RTSP and RTP streams are not supported")
|
||||
ShowError(err, 2004)
|
||||
@@ -140,9 +146,14 @@ func Stream(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
showInfo(fmt.Sprintf("Buffer:%t", Settings.Buffer))
|
||||
showInfo(fmt.Sprintf("Buffer:true [%s]", Settings.Buffer))
|
||||
|
||||
if Settings.Buffer == true {
|
||||
default:
|
||||
showInfo(fmt.Sprintf("Buffer:true [%s]", Settings.Buffer))
|
||||
|
||||
}
|
||||
|
||||
if Settings.Buffer != "-" {
|
||||
showInfo(fmt.Sprintf("Buffer Size:%d KB", Settings.BufferSize))
|
||||
}
|
||||
|
||||
@@ -152,16 +163,16 @@ func Stream(w http.ResponseWriter, r *http.Request) {
|
||||
// Prüfen ob der Buffer verwendet werden soll
|
||||
switch Settings.Buffer {
|
||||
|
||||
case true:
|
||||
bufferingStream(streamInfo.PlaylistID, streamInfo.URL, streamInfo.Name, w, r)
|
||||
|
||||
case false:
|
||||
case "-":
|
||||
showInfo("Streaming URL:" + streamInfo.URL)
|
||||
http.Redirect(w, r, streamInfo.URL, 302)
|
||||
|
||||
showInfo("Streaming Info:URL was passed to the client.")
|
||||
showInfo("Streaming Info:xTeVe is no longer involved, the client connects directly to the streaming server.")
|
||||
|
||||
default:
|
||||
bufferingStream(streamInfo.PlaylistID, streamInfo.URL, streamInfo.Name, w, r)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
@@ -205,6 +216,7 @@ func xTeVe(w http.ResponseWriter, r *http.Request) {
|
||||
// XMLTV Datei
|
||||
if strings.Contains(path, "xmltv/") {
|
||||
|
||||
w.Header().Set("Content-Type", "application/xml")
|
||||
requestType = "xml"
|
||||
|
||||
file = System.Folder.Data + getFilenameFromPath(path)
|
||||
|
||||
21
src/xepg.go
21
src/xepg.go
@@ -306,7 +306,6 @@ func createXEPGDatabase() (err error) {
|
||||
}
|
||||
|
||||
if len(xepgChannel.XChannelID) == 0 {
|
||||
fmt.Println(mapToJSON(xepgChannel))
|
||||
delete(Data.XEPG.Channels, id)
|
||||
}
|
||||
|
||||
@@ -316,6 +315,12 @@ func createXEPGDatabase() (err error) {
|
||||
|
||||
}
|
||||
|
||||
var xepgChannels = make(map[string]interface{})
|
||||
|
||||
for k, v := range Data.XEPG.Channels {
|
||||
xepgChannels[k] = v
|
||||
}
|
||||
|
||||
for _, dsa := range Data.Streams.Active {
|
||||
|
||||
var channelExists = false // Entscheidet ob ein Kanal neu zu Datenbank hinzugefügt werden soll.
|
||||
@@ -331,7 +336,7 @@ func createXEPGDatabase() (err error) {
|
||||
Data.Cache.Streams.Active = append(Data.Cache.Streams.Active, m3uChannel.Name)
|
||||
|
||||
// XEPG Datenbank durchlaufen um nach dem Kanal zu suchen.
|
||||
for xepg, dxc := range Data.XEPG.Channels {
|
||||
for xepg, dxc := range xepgChannels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
@@ -367,6 +372,7 @@ func createXEPGDatabase() (err error) {
|
||||
//os.Exit(0)
|
||||
|
||||
switch channelExists {
|
||||
|
||||
case true:
|
||||
// Bereits vorhandener Kanal
|
||||
var xepgChannel XEPGChannelStruct
|
||||
@@ -828,7 +834,16 @@ func getEpisodeNum(program *Program, xmltvProgram *Program, xepgChannel XEPGChan
|
||||
if len(xepgChannel.XCategory) > 0 && xepgChannel.XCategory != "Movie" {
|
||||
|
||||
if len(xmltvProgram.EpisodeNum) == 0 {
|
||||
program.EpisodeNum = append(program.EpisodeNum, &EpisodeNum{Value: time.Now().Format("2006-01-02 15:04:05"), System: "original-air-date"})
|
||||
|
||||
var timeLayout = "20060102150405"
|
||||
|
||||
t, err := time.Parse(timeLayout, strings.Split(xmltvProgram.Start, " ")[0])
|
||||
if err == nil {
|
||||
program.EpisodeNum = append(program.EpisodeNum, &EpisodeNum{Value: t.Format("2006-01-02 15:04:05"), System: "original-air-date"})
|
||||
} else {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.p
|
||||
var settingsCategory = new Array()
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"))
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ class Content {
|
||||
var cell:Cell = new Cell()
|
||||
cell.child = true
|
||||
cell.childType = "P"
|
||||
if (SERVER["settings"]["buffer"] == true) {
|
||||
if (SERVER["settings"]["buffer"] != "-") {
|
||||
cell.value = data[key]["tuner"]
|
||||
} else {
|
||||
cell.value = "-"
|
||||
@@ -1113,7 +1113,7 @@ function openPopUp(dataType, element) {
|
||||
content.appendRow("{{.playlist.fileM3U.title}}", input)
|
||||
|
||||
// Tuner
|
||||
if (SERVER["settings"]["buffer"] == true) {
|
||||
if (SERVER["settings"]["buffer"] != "-") {
|
||||
var text:string[] = new Array()
|
||||
var values:string[] = new Array()
|
||||
|
||||
@@ -1192,7 +1192,7 @@ function openPopUp(dataType, element) {
|
||||
content.appendRow("{{.playlist.fileHDHR.title}}", input)
|
||||
|
||||
// Tuner
|
||||
if (SERVER["settings"]["buffer"] == true) {
|
||||
if (SERVER["settings"]["buffer"] != "-") {
|
||||
var text:string[] = new Array()
|
||||
var values:string[] = new Array()
|
||||
|
||||
@@ -1572,6 +1572,10 @@ function openPopUp(dataType, element) {
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
content.appendRow("{{.mapping.m3uGroupTitle.title}}", input)
|
||||
|
||||
if (data["group-title"] != undefined) {
|
||||
content.description(data["group-title"])
|
||||
}
|
||||
|
||||
// XMLTV Datei
|
||||
var dbKey:string = "x-xmltv-file"
|
||||
var xmlFile = data[dbKey]
|
||||
|
||||
@@ -89,6 +89,62 @@ class SettingsCategory {
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
case "ffmpeg.path":
|
||||
var tdLeft = document.createElement("TD")
|
||||
tdLeft.innerHTML = "{{.settings.ffmpegPath.title}}" + ":"
|
||||
|
||||
var tdRight = document.createElement("TD")
|
||||
var input = content.createInput("text", "ffmpeg.path", data)
|
||||
input.setAttribute("placeholder", "{{.settings.ffmpegPath.placeholder}}")
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
tdRight.appendChild(input)
|
||||
|
||||
setting.appendChild(tdLeft)
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
case "ffmpeg.options":
|
||||
var tdLeft = document.createElement("TD")
|
||||
tdLeft.innerHTML = "{{.settings.ffmpegOptions.title}}" + ":"
|
||||
|
||||
var tdRight = document.createElement("TD")
|
||||
var input = content.createInput("text", "ffmpeg.options", data)
|
||||
input.setAttribute("placeholder", "{{.settings.ffmpegOptions.placeholder}}")
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
tdRight.appendChild(input)
|
||||
|
||||
setting.appendChild(tdLeft)
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
case "vlc.path":
|
||||
var tdLeft = document.createElement("TD")
|
||||
tdLeft.innerHTML = "{{.settings.vlcPath.title}}" + ":"
|
||||
|
||||
var tdRight = document.createElement("TD")
|
||||
var input = content.createInput("text", "vlc.path", data)
|
||||
input.setAttribute("placeholder", "{{.settings.vlcPath.placeholder}}")
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
tdRight.appendChild(input)
|
||||
|
||||
setting.appendChild(tdLeft)
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
case "vlc.options":
|
||||
var tdLeft = document.createElement("TD")
|
||||
tdLeft.innerHTML = "{{.settings.vlcOptions.title}}" + ":"
|
||||
|
||||
var tdRight = document.createElement("TD")
|
||||
var input = content.createInput("text", "vlc.options", data)
|
||||
input.setAttribute("placeholder", "{{.settings.vlcOptions.placeholder}}")
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
tdRight.appendChild(input)
|
||||
|
||||
setting.appendChild(tdLeft)
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
// Checkboxen
|
||||
case "authentication.web":
|
||||
var tdLeft = document.createElement("TD")
|
||||
@@ -216,20 +272,6 @@ class SettingsCategory {
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
case "buffer":
|
||||
var tdLeft = document.createElement("TD")
|
||||
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":"
|
||||
|
||||
var tdRight = document.createElement("TD")
|
||||
var input = content.createCheckbox(settingsKey)
|
||||
input.checked = data
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
tdRight.appendChild(input)
|
||||
|
||||
setting.appendChild(tdLeft)
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
case "api":
|
||||
var tdLeft = document.createElement("TD")
|
||||
tdLeft.innerHTML = "{{.settings.api.title}}" + ":"
|
||||
@@ -314,6 +356,22 @@ class SettingsCategory {
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
case "buffer":
|
||||
var tdLeft = document.createElement("TD")
|
||||
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":"
|
||||
|
||||
var tdRight = document.createElement("TD")
|
||||
var text:any[] = ["{{.settings.streamBuffering.info_false}}", "xTeVe: ({{.settings.streamBuffering.info_xteve}})", "FFmpeg: ({{.settings.streamBuffering.info_ffmpeg}})", "VLC: ({{.settings.streamBuffering.info_vlc}})"]
|
||||
var values:any[] = ["-", "xteve", "ffmpeg", "vlc"]
|
||||
|
||||
var select = content.createSelect(text, values, data, settingsKey)
|
||||
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
tdRight.appendChild(select)
|
||||
|
||||
setting.appendChild(tdLeft)
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
return setting
|
||||
@@ -381,6 +439,22 @@ class SettingsCategory {
|
||||
text = "{{.settings.userAgent.description}}"
|
||||
break
|
||||
|
||||
case "ffmpeg.path":
|
||||
text = "{{.settings.ffmpegPath.description}}"
|
||||
break
|
||||
|
||||
case "ffmpeg.options":
|
||||
text = "{{.settings.ffmpegOptions.description}}"
|
||||
break
|
||||
|
||||
case "vlc.path":
|
||||
text = "{{.settings.vlcPath.description}}"
|
||||
break
|
||||
|
||||
case "vlc.options":
|
||||
text = "{{.settings.vlcOptions.description}}"
|
||||
break
|
||||
|
||||
case "epgSource":
|
||||
text = "{{.settings.epgSource.description}}"
|
||||
break
|
||||
|
||||
6
xteve.go
6
xteve.go
@@ -35,14 +35,14 @@ var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-
|
||||
Update: Automatic updates from the GitHub repository [true|false]
|
||||
*/
|
||||
|
||||
// Name : Programname
|
||||
// Name : Programmname
|
||||
const Name = "xTeVe"
|
||||
|
||||
// Version : Version, die Build Nummer wird in der main func geparst.
|
||||
const Version = "2.0.1.0010"
|
||||
const Version = "2.1.0.0100"
|
||||
|
||||
// DBVersion : Datanbank Version
|
||||
const DBVersion = "2.0.0"
|
||||
const DBVersion = "2.1.0"
|
||||
|
||||
// APIVersion : API Version
|
||||
const APIVersion = "1.1.0"
|
||||
|
||||
Reference in New Issue
Block a user