54 Commits

Author SHA1 Message Date
marmei
c1970a8393 v2.1.2.0120 2020-02-15 09:01:25 +01:00
marmei
493d612d52 Merge branch 'beta' 2020-02-14 20:02:28 +01:00
marmei
4fc4330a94 FFmpeg changed: user-agent to user_agent 2020-02-08 11:10:14 +01:00
marmei
a683533824 Update changelog-beta.md 2020-01-24 2020-01-24 19:58:00 +01:00
marmei
4b9f5826cf If no user agent is specified, the default FFmpeg or VLC user agent is used. 2020-01-24 19:48:29 +01:00
marmei
ca49d70910 Update changelog-beta.md 2020-01-13 2020-01-13 23:17:55 +01:00
marmei
1b425018d4 v2.1.1.0115-beta 2020-01-13 23:04:34 +01:00
marmei
87b36c283b URL format removed from the settings 2020-01-04 17:17:28 +01:00
marmei
6da26ff4fb Merge branch 'pr/76' into beta 2020-01-04 17:01:32 +01:00
marmei
cd08985e79 Set global domain for /web 2020-01-04 17:00:58 +01:00
Thomas Dall'Agnese
dc04519229 Use relative path when posting HTML forms 2019-12-29 20:03:37 +01:00
Thomas Dall'Agnese
c4ad96b715 Use relative path when posting HTML forms 2019-12-28 18:55:46 +01:00
Thomas Dall'Agnese
45b5e602bb Revert "Use relative path when posting HTML forms"
This reverts commit d5328f6b1a.
2019-12-28 18:43:48 +01:00
marmei
1cefbf022d Workaround for IPTVX content-type bug 2019-12-28 12:28:33 +01:00
Thomas Dall'Agnese
d5328f6b1a Use relative path when posting HTML forms 2019-12-28 10:23:54 +01:00
marmei
91b80bc8bb Add xteve.xml GZIP 2019-12-16 20:23:05 +01:00
marmei
aa763726a3 Fixed broken merge 2019-12-14 10:17:42 +01:00
marmei
66c01dd1fb Missing placeholder 2019-12-13 21:05:14 +01:00
marmei
03e1abbe90 v2.1.1.0110 2019-12-13 20:31:23 +01:00
marmei
469581e280 Add mapping desc. function 2019-12-13 19:01:18 +01:00
marmei
019c98996a Update changelog-beta.md 2019-12-08 2019-12-08 17:49:50 +01:00
marmei
36db927794 v2.1.0.0106 2019-12-08 17:47:54 +01:00
marmei
f0a49788cc User-Agent for FFmpeg and VLC 2019-12-08 17:33:46 +01:00
marmei
72767d7dbd Update changelog-beta.md 2019-12-06 2019-12-06 21:00:44 +01:00
marmei
8eecbf2b78 Update changelog-beta.md 2019-12-06 2019-12-06 20:57:26 +01:00
marmei
1a1e37fe15 v2.1.0.0105: Settings for URI scheme 2019-12-06 20:48:59 +01:00
marmei
08f6fb60e3 Fix log entry, wrong buffer value 2019-11-15 10:09:19 +01:00
marmei
eded490ac7 Update changelog-beta.md 2019-11-06 2019-11-06 18:16:14 +01:00
marmei
ed770b9dbc Merge remote-tracking branch 'origin/master' 2019-11-06 18:11:21 +01:00
marmei
6129b4911a Merge pull request #53 from taeram/patch-1
Small spelling / grammar fixes.
2019-11-05 20:29:12 +01:00
Jesse Patching
477c5f30c1 Small spelling / grammar fixes. 2019-11-04 20:28:45 -07:00
marmei
65ddc6f301 v2.1.2.0101-beta 2019-11-04 18:28:41 +01:00
marmei
3ef95c1950 RV Proxy 2019-11-04 18:23:20 +01:00
marmei
3a3798cd2d WiteHeader 2019-10-23 19:02:54 +02:00
marmei
72eb7fb599 v2.1.0.0100 2019-10-11 17:59:11 +02:00
marmei
4b969b8cee Merge branch 'beta' 2019-10-11 17:54:06 +02:00
marmei
3d9266dabe XEPG performance 2019-10-11 17:53:20 +02:00
marmei
48218cda50 Update changelog-beta.md 2019-10-04 2019-10-04 19:52:44 +02:00
marmei
dc42afcd05 New beta 2019-10-04 19:39:54 +02:00
marmei
20e5e1b545 Buffer RTSP performance 2019-10-04 19:39:20 +02:00
Aaron Chi
11dd830110 fix #34 2019-10-04 17:08:16 +08:00
marmei
7c87d1d5bd Wrong CVLC path 2019-09-27 22:20:19 +02:00
marmei
6cdd44357b update changelog-beta.md 2019-09-27 19:55:36 +02:00
marmei
ad992eb615 Add FFmpeg and VLC support 2019-09-27 19:24:31 +02:00
marmei
11453c6053 v2.0.3.0030 2019-09-07 12:51:21 +02:00
marmei
81e8ae33d7 Merge branch 'Bugs_in_2.0.2' 2019-09-07 12:07:55 +02:00
marmei
f3be0fca47 Wrong warning (root) 2019-09-05 19:37:55 +02:00
marmei
1c1c89cd74 Update changelog-beta.md 2019-09-04 2019-09-04 21:16:04 +02:00
marmei
3d73dba422 Code removed #1 2019-09-04 20:16:11 +02:00
marmei
c6e74fe11c Interception of repeated save 2019-09-04 20:12:26 +02:00
marmei
18dba46c02 Bug #16 1 2019-09-01 00:24:06 +02:00
marmei
efa55b39a9 Bug #16 0 2019-08-31 20:39:39 +02:00
marmei
717fa68b7e Add an error message if the filter rule is empty.
Set the streaming status to active only once.
2019-08-28 17:56:15 +02:00
marmei
878531ff79 IPTV removed as a source 2019-08-28 16:49:41 +02:00
32 changed files with 1637 additions and 583 deletions

View File

@@ -31,7 +31,7 @@ Documentation for setup and configuration is [here](https://github.com/xteve-pro
* Merge external M3U files * Merge external M3U files
* Merge external XMLTV files * Merge external XMLTV files
* Automatic M3U and XMLTV update * Automatic M3U and XMLTV update
* M3U und XMLTV export * M3U and XMLTV export
#### Channel management #### Channel management
* Filtering streams * Filtering streams
@@ -84,7 +84,7 @@ Including:
--- ---
### xTeVe Beta branch ### xTeVe Beta branch
New features and bug fixes are only available in beta brunch. Only after successful testing, they are merged into the master branch. New features and bug fixes are only available in beta branch. Only after successful testing are they are merged into the master branch.
**It is not recommended to use the beta version in a production system.** **It is not recommended to use the beta version in a production system.**

View File

@@ -1,3 +1,55 @@
#### 2.1.1.0116-beta
If no user agent is specified, the default FFmpeg or VLC user agent is used.
#### 2.1.1.0115-beta
```diff
+ GZIP compression for xteve.xml file. (http://xteve.ip:34400/xmltv/xteve.xml.gz)
- Removed protocol setting for reverse proxy. HTTPS can also be configured in the proxy, where it makes more sense.
```
#### 2.1.0.0106-beta
```diff
+ User-Agent is now also used by VLC and FFmpeg.
```
#### 2.1.0.0105-beta
```diff
+ Fixed wrong buffer value in log
+ New setting: URL protocol for M3U and XML file
+ Add xml tag premiere to xteve.xml
```
#### 2.1.0.0101-beta
```diff
+ Reverse proxy fix
```
#### 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 #### 2.0.1.0012-beta
```diff ```diff
+ Add support for "video/m2ts" video streams (Pull request #14) + Add support for "video/m2ts" video streams (Pull request #14)

View File

@@ -24,7 +24,7 @@
<div id="content"> <div id="content">
<form id="authentication" action="/web/" method="post"> <form id="authentication" action="" method="post">
<h5>{{.account.username.title}}:</h5> <h5>{{.account.username.title}}:</h5>
<input id="username" type="text" name="username" placeholder="Username" value=""> <input id="username" type="text" name="username" placeholder="Username" value="">

View File

@@ -20,7 +20,7 @@ menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.p
var settingsCategory = new Array(); var settingsCategory = new Array();
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api")); 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.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.backup}}", "backup.path,backup.keep"));
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api")); settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"));
function showPopUpElement(elm) { function showPopUpElement(elm) {

View File

@@ -127,7 +127,7 @@ var Content = /** @class */ (function () {
var cell = new Cell(); var cell = new Cell();
cell.child = true; cell.child = true;
cell.childType = "P"; cell.childType = "P";
if (SERVER["settings"]["buffer"] == true) { if (SERVER["settings"]["buffer"] != "-") {
cell.value = data[key]["tuner"]; cell.value = data[key]["tuner"];
} }
else { else {
@@ -901,7 +901,7 @@ function openPopUp(dataType, element) {
input.setAttribute("placeholder", "{{.playlist.fileM3U.placeholder}}"); input.setAttribute("placeholder", "{{.playlist.fileM3U.placeholder}}");
content.appendRow("{{.playlist.fileM3U.title}}", input); content.appendRow("{{.playlist.fileM3U.title}}", input);
// Tuner // Tuner
if (SERVER["settings"]["buffer"] == true) { if (SERVER["settings"]["buffer"] != "-") {
var text = new Array(); var text = new Array();
var values = new Array(); var values = new Array();
for (var i = 1; i <= 100; i++) { for (var i = 1; i <= 100; i++) {
@@ -971,7 +971,7 @@ function openPopUp(dataType, element) {
input.setAttribute("placeholder", "{{.playlist.fileHDHR.placeholder}}"); input.setAttribute("placeholder", "{{.playlist.fileHDHR.placeholder}}");
content.appendRow("{{.playlist.fileHDHR.title}}", input); content.appendRow("{{.playlist.fileHDHR.title}}", input);
// Tuner // Tuner
if (SERVER["settings"]["buffer"] == true) { if (SERVER["settings"]["buffer"] != "-") {
var text = new Array(); var text = new Array();
var values = new Array(); var values = new Array();
for (var i = 1; i <= 100; i++) { for (var i = 1; i <= 100; i++) {
@@ -1253,6 +1253,12 @@ function openPopUp(dataType, element) {
} }
content.appendRow("{{.mapping.channelName.title}}", input); content.appendRow("{{.mapping.channelName.title}}", input);
content.description(data["name"]); content.description(data["name"]);
// Beschreibung
var dbKey = "x-description";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "{{.mapping.description.placeholder}}");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
content.appendRow("{{.mapping.description.title}}", input);
// Aktualisierung des Kanalnamens // Aktualisierung des Kanalnamens
if (data.hasOwnProperty("_uuid.key")) { if (data.hasOwnProperty("_uuid.key")) {
if (data["_uuid.key"] != "") { if (data["_uuid.key"] != "") {

View File

@@ -85,6 +85,50 @@ var SettingsCategory = /** @class */ (function () {
setting.appendChild(tdLeft); setting.appendChild(tdLeft);
setting.appendChild(tdRight); setting.appendChild(tdRight);
break; 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 // Checkboxen
case "authentication.web": case "authentication.web":
var tdLeft = document.createElement("TD"); var tdLeft = document.createElement("TD");
@@ -185,17 +229,6 @@ var SettingsCategory = /** @class */ (function () {
setting.appendChild(tdLeft); setting.appendChild(tdLeft);
setting.appendChild(tdRight); setting.appendChild(tdRight);
break; 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": case "api":
var tdLeft = document.createElement("TD"); var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.api.title}}" + ":"; tdLeft.innerHTML = "{{.settings.api.title}}" + ":";
@@ -260,6 +293,18 @@ var SettingsCategory = /** @class */ (function () {
setting.appendChild(tdLeft); setting.appendChild(tdLeft);
setting.appendChild(tdRight); setting.appendChild(tdRight);
break; 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; return setting;
}; };
@@ -308,6 +353,18 @@ var SettingsCategory = /** @class */ (function () {
case "user.agent": case "user.agent":
text = "{{.settings.userAgent.description}}"; text = "{{.settings.userAgent.description}}";
break; 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": case "epgSource":
text = "{{.settings.epgSource.description}}"; text = "{{.settings.epgSource.description}}";
break; break;

View File

@@ -190,6 +190,11 @@
"placeholder": "", "placeholder": "",
"description": "" "description": ""
}, },
"description": {
"title": "Channel Description",
"placeholder": "Used by the Dummy as an XML description",
"description": ""
},
"updateChannelName": { "updateChannelName": {
"title": "Update Channel Name", "title": "Update Channel Name",
"placeholder": "", "placeholder": "",
@@ -307,7 +312,7 @@
"description": "Updates all playlists, tuner and XMLTV files at startup." "description": "Updates all playlists, tuner and XMLTV files at startup."
}, },
"cacheImages": { "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." "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": { "replaceEmptyImages": {
@@ -320,7 +325,32 @@
}, },
"streamBuffering": { "streamBuffering": {
"title": "Stream Buffer", "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": { "bufferSize": {
"title": "Buffer Size", "title": "Buffer Size",
@@ -332,8 +362,8 @@
"placeholder": "100" "placeholder": "100"
}, },
"userAgent": { "userAgent": {
"title": "User agent", "title": "User Agent",
"description": "User Agent for HTTP requests", "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.",
"placeholder": "xTeVe" "placeholder": "xTeVe"
}, },
"backupPath": { "backupPath": {

View File

@@ -24,7 +24,7 @@
<div id="content"> <div id="content">
<form id="authentication" action="/web/" method="post"> <form id="authentication" action="" method="post">
<h5>{{.login.username.title}}:</h5> <h5>{{.login.username.title}}:</h5>
<input id="username" type="text" name="username" placeholder="Username" value=""> <input id="username" type="text" name="username" placeholder="Username" value="">

View File

@@ -6,6 +6,8 @@ package src
*/ */
import ( import (
"bufio"
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -13,6 +15,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"os/exec"
"path" "path"
"sort" "sort"
"strconv" "strconv"
@@ -205,7 +208,17 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
playlist.Streams[streamID] = stream playlist.Streams[streamID] = stream
BufferInformation.Store(playlistID, playlist) 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)) 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 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, ok := playlist.Streams[streamID]; ok {
if stream.Status == false { if stream.Status == false {
@@ -244,7 +261,9 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
var oldSegments []string var oldSegments []string
for { // Loop 2: Temporäre Datein sind vorhanden, Daten können zum Client gesendet werden for { // Loop 2: Temporäre Datein sind vorhanden, Daten können zum Client gesendet werden
// HTTP Clientverbindung überwachen // HTTP Clientverbindung überwachen
cn, ok := w.(http.CloseNotifier) cn, ok := w.(http.CloseNotifier)
if ok { if ok {
@@ -377,6 +396,8 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
} }
} // Ende BufferInformation
} // Ende Loop 1 } // Ende Loop 1
} }
@@ -394,7 +415,7 @@ func getTmpFiles(stream *ThisStream) (tmpFiles []string) {
return return
} }
if len(files) > 1 { if len(files) > 2 {
for _, file := range files { for _, file := range files {
@@ -430,6 +451,9 @@ func getTmpFiles(stream *ThisStream) (tmpFiles []string) {
func killClientConnection(streamID int, playlistID string, force bool) { func killClientConnection(streamID int, playlistID string, force bool) {
Lock.Lock()
defer Lock.Unlock()
if p, ok := BufferInformation.Load(playlistID); ok { if p, ok := BufferInformation.Load(playlistID); ok {
var playlist = p.(Playlist) var playlist = p.(Playlist)
@@ -454,6 +478,7 @@ func killClientConnection(streamID int, playlistID string, force bool) {
if clients.Connection <= 0 { if clients.Connection <= 0 {
BufferClients.Delete(playlistID + stream.MD5) BufferClients.Delete(playlistID + stream.MD5)
delete(playlist.Streams, streamID) delete(playlist.Streams, streamID)
delete(playlist.Clients, streamID)
} }
} }
@@ -473,6 +498,8 @@ func killClientConnection(streamID int, playlistID string, force bool) {
func clientConnection(stream ThisStream) (status bool) { func clientConnection(stream ThisStream) (status bool) {
status = true status = true
Lock.Lock()
defer Lock.Unlock()
if _, ok := BufferClients.Load(stream.PlaylistID + stream.MD5); !ok { if _, ok := BufferClients.Load(stream.PlaylistID + stream.MD5); !ok {
@@ -507,7 +534,11 @@ func clientConnection(stream ThisStream) (status bool) {
return 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 timeOut = 0
var debug string var debug string
@@ -516,6 +547,9 @@ func connectToStreamingServer(streamID int, playlist Playlist) {
var m3u8Segments []string var m3u8Segments []string
var bandwidth BandwidthCalculation var bandwidth BandwidthCalculation
var networkBandwidth = Settings.M3U8AdaptiveBandwidthMBPS * 1e+6 var networkBandwidth = Settings.M3U8AdaptiveBandwidthMBPS * 1e+6
// Größe des Buffers
var bufferSize = Settings.BufferSize
var buffer = make([]byte, 1024*bufferSize*2)
var defaultSegment = func() { var defaultSegment = func() {
@@ -548,11 +582,11 @@ func connectToStreamingServer(streamID int, playlist Playlist) {
var stream = playlist.Streams[streamID] 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) var clients = c.(ClientConnection)
clients.Error = err clients.Error = err
BufferClients.Store(stream.PlaylistID+stream.MD5, clients) BufferClients.Store(playlistID+stream.MD5, clients)
} }
@@ -568,7 +602,7 @@ func connectToStreamingServer(streamID int, playlist Playlist) {
} }
// M3U8 Segmente // M3U8 Segmente
InitBuffer: InitBuffer:
defaultSegment() defaultSegment()
if len(m3u8Segments) > 30 { if len(m3u8Segments) > 30 {
@@ -650,7 +684,7 @@ InitBuffer:
addErrorToStream(err) addErrorToStream(err)
killClientConnection(streamID, stream.PlaylistID, true) killClientConnection(streamID, playlistID, true)
clientConnection(stream) clientConnection(stream)
return return
@@ -722,7 +756,7 @@ InitBuffer:
BufferInformation.Store(playlist.PlaylistID, playlist) BufferInformation.Store(playlist.PlaylistID, playlist)
addErrorToStream(err) addErrorToStream(err)
killClientConnection(streamID, stream.PlaylistID, true) killClientConnection(streamID, playlistID, true)
clientConnection(stream) clientConnection(stream)
resp.Body.Close() resp.Body.Close()
@@ -772,7 +806,7 @@ InitBuffer:
switch contentType { switch contentType {
// M3U8 Playlist // 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) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
ShowError(err, 0) ShowError(err, 0)
@@ -795,8 +829,8 @@ InitBuffer:
var fileSize int var fileSize int
// Größe des Buffers // Größe des Buffers
buffer := make([]byte, 1024*Settings.BufferSize*2) buffer = make([]byte, 1024*bufferSize*2)
var tmpFileSize = 1024 * Settings.BufferSize * 1 var tmpFileSize = 1024 * bufferSize * 1
debug = fmt.Sprintf("Buffer Size:%d KB [SERVER CONNECTION]", len(buffer)/1024) debug = fmt.Sprintf("Buffer Size:%d KB [SERVER CONNECTION]", len(buffer)/1024)
showDebug(debug, 3) showDebug(debug, 3)
@@ -872,7 +906,9 @@ InitBuffer:
} }
// Buffer auf die Festplatte speichern // Buffer auf die Festplatte speichern
if fileSize >= tmpFileSize || n == 0 { if fileSize >= tmpFileSize/2 || n == 0 {
Lock.Lock()
bandwidth.Stop = time.Now() bandwidth.Stop = time.Now()
bandwidth.Size += fileSize bandwidth.Size += fileSize
@@ -891,6 +927,9 @@ InitBuffer:
stream.Status = true stream.Status = true
playlist.Streams[streamID] = stream playlist.Streams[streamID] = stream
BufferInformation.Store(playlistID, playlist)
Lock.Unlock()
tmpSegment++ tmpSegment++
tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment) tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
@@ -916,6 +955,7 @@ InitBuffer:
} }
fileSize = 0 fileSize = 0
buffer = make([]byte, 1024*bufferSize*2)
if n == 0 { if n == 0 {
bufferFile.Close() bufferFile.Close()
@@ -986,6 +1026,8 @@ InitBuffer:
} // Ende for loop } // Ende for loop
} // Ende BufferInformation
} }
func parseM3U8(stream *ThisStream) (err error) { func parseM3U8(stream *ThisStream) (err error) {
@@ -1241,12 +1283,6 @@ func parseM3U8(stream *ThisStream) (err error) {
break break
} }
err := checkFile(stream.Folder + "remove")
if err == nil {
os.RemoveAll(stream.Folder)
break
}
} }
} }
@@ -1305,14 +1341,311 @@ func switchBandwidth(stream *ThisStream) (err error) {
return 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
}
//args = strings.Replace(args, "[USER-AGENT]", Settings.UserAgent, -1)
// User-Agent setzen
var args []string
for i, a := range strings.Split(options, " ") {
switch bufferType {
case "FFMPEG":
a = strings.Replace(a, "[URL]", url, -1)
if i == 0 {
if len(Settings.UserAgent) != 0 {
args = []string{"-user_agent", Settings.UserAgent}
}
}
args = append(args, a)
case "VLC":
if a == "[URL]" {
a = strings.Replace(a, "[URL]", url, -1)
args = append(args, a)
if len(Settings.UserAgent) != 0 {
args = append(args, fmt.Sprintf(":http-user-agent=%s", Settings.UserAgent))
}
} else {
args = append(args, a)
}
}
}
var cmd = exec.Command(path, 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 {
Lock.Lock()
stream.Status = true
playlist.Streams[streamID] = stream
BufferInformation.Store(playlistID, playlist)
Lock.Unlock()
}
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) { func getTuner(id, playlistType string) (tuner int) {
switch Settings.Buffer { switch Settings.Buffer {
case false: case "-":
tuner = Settings.Tuner tuner = Settings.Tuner
case true: case "xteve", "ffmpeg", "vlc":
i, err := strconv.Atoi(getProviderParameter(id, playlistType, "tuner")) i, err := strconv.Atoi(getProviderParameter(id, playlistType, "tuner"))
if err == nil { if err == nil {

View File

@@ -146,3 +146,20 @@ func extractGZIP(gzipBody []byte, fileSource string) (body []byte, err error) {
body = resB.Bytes() body = resB.Bytes()
return return
} }
func compressGZIP(data *[]byte, file string) (err error) {
if len(file) != 0 {
f, err := os.Create(file)
if err != nil {
return err
}
w := gzip.NewWriter(f)
w.Write(*data)
w.Close()
}
return
}

View File

@@ -29,6 +29,9 @@ var BufferInformation sync.Map
// BufferClients : Anzahl der Clients die einen Stream über den Buffer abspielen // BufferClients : Anzahl der Clients die einen Stream über den Buffer abspielen
var BufferClients sync.Map var BufferClients sync.Map
// Lock : Lock Map
var Lock = sync.RWMutex{}
// Init : Systeminitialisierung // Init : Systeminitialisierung
func Init() (err error) { func Init() (err error) {
@@ -46,6 +49,10 @@ func Init() (err error) {
System.DVRLimit = 480 System.DVRLimit = 480
System.Compatibility = "1.4.4" 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) // 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 Settings.LogEntriesRAM = 500
@@ -91,6 +98,8 @@ func Init() (err error) {
System.File.XML = getPlatformFile(fmt.Sprintf("%s%s.xml", System.Folder.Data, System.AppName)) System.File.XML = getPlatformFile(fmt.Sprintf("%s%s.xml", System.Folder.Data, System.AppName))
System.File.M3U = getPlatformFile(fmt.Sprintf("%s%s.m3u", System.Folder.Data, System.AppName)) System.File.M3U = getPlatformFile(fmt.Sprintf("%s%s.m3u", System.Folder.Data, System.AppName))
System.Compressed.GZxml = getPlatformFile(fmt.Sprintf("%s%s.xml.gz", System.Folder.Data, System.AppName))
err = activatedSystemAuthentication() err = activatedSystemAuthentication()
if err != nil { if err != nil {
return return
@@ -109,7 +118,7 @@ func Init() (err error) {
// Überprüfen ob xTeVe als root läuft // Überprüfen ob xTeVe als root läuft
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
showWarning(2010) showWarning(2110)
} }
if System.Flag.Debug > 0 { if System.Flag.Debug > 0 {
@@ -133,7 +142,6 @@ func Init() (err error) {
// Bedingte Update Änderungen durchführen // Bedingte Update Änderungen durchführen
err = conditionalUpdateChanges() err = conditionalUpdateChanges()
if err != nil { if err != nil {
ShowError(err, 0)
return return
} }

View File

@@ -24,6 +24,8 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
var createXEPGFiles = false var createXEPGFiles = false
var debug string var debug string
// -vvv [URL] --sout '#transcode{vcodec=mp4v, acodec=mpga} :standard{access=http, mux=ogg}'
for key, value := range newSettings { for key, value := range newSettings {
if _, ok := oldSettings[key]; ok { if _, ok := oldSettings[key]; ok {
@@ -54,6 +56,10 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
} }
if len(newUpdateTimes) == 0 {
newUpdateTimes = append(newUpdateTimes, "0000")
}
value = newUpdateTimes value = newUpdateTimes
case "cache.images": case "cache.images":
@@ -94,6 +100,20 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
return return
} }
case "ffmpeg.path", "vlc.path":
var path = value.(string)
if len(path) > 0 {
err = checkFile(path)
if err != nil {
return
}
}
case "scheme.m3u", "scheme.xml":
createXEPGFiles = true
} }
oldSettings[key] = value oldSettings[key] = value
@@ -138,6 +158,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) err = saveSettings(Settings)
if err == nil { if err == nil {
@@ -366,6 +413,7 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
var filterMap = make(map[int64]interface{}) var filterMap = make(map[int64]interface{})
var newData = make(map[int64]interface{}) var newData = make(map[int64]interface{})
var defaultFilter FilterStruct var defaultFilter FilterStruct
var newFilter = false
defaultFilter.Active = true defaultFilter.Active = true
defaultFilter.CaseSensitive = false defaultFilter.CaseSensitive = false
@@ -389,6 +437,7 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
if dataID == -1 { if dataID == -1 {
// Neuer Filter // Neuer Filter
newFilter = true
dataID = createNewID() dataID = createNewID()
filterMap[dataID] = jsonToMap(mapToJSON(defaultFilter)) filterMap[dataID] = jsonToMap(mapToJSON(defaultFilter))
@@ -397,15 +446,28 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
// Filter aktualisieren / löschen // Filter aktualisieren / löschen
for key, value := range data.(map[string]interface{}) { for key, value := range data.(map[string]interface{}) {
var oldData = filterMap[dataID].(map[string]interface{})
oldData[key] = value
// Filter löschen // Filter löschen
if _, ok := data.(map[string]interface{})["delete"]; ok { if _, ok := data.(map[string]interface{})["delete"]; ok {
delete(filterMap, dataID) delete(filterMap, dataID)
break 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 +508,59 @@ func saveXEpgMapping(request RequestStruct) (err error) {
Data.XEPG.Channels = request.EpgMapping Data.XEPG.Channels = request.EpgMapping
if System.ScanInProgress == 0 {
System.ScanInProgress = 1
cleanupXEPG() 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 return
} }
@@ -612,6 +725,7 @@ func saveWizard(request RequestStruct) (nextStep int, err error) {
} }
buildXEPG(false) buildXEPG(false)
System.ScanInProgress = 0
} }

View File

@@ -95,7 +95,7 @@ func getLineupStatus() (jsonContent []byte, err error) {
lineupStatus.ScanInProgress = System.ScanInProgress lineupStatus.ScanInProgress = System.ScanInProgress
lineupStatus.ScanPossible = 0 lineupStatus.ScanPossible = 0
lineupStatus.Source = "Cable" lineupStatus.Source = "Cable"
lineupStatus.SourceList = []string{"IPTV", "Cable"} lineupStatus.SourceList = []string{"Cable"}
jsonContent, err = json.MarshalIndent(lineupStatus, "", " ") jsonContent, err = json.MarshalIndent(lineupStatus, "", " ")

View File

@@ -45,7 +45,7 @@ func getCacheImageURL(imageURL string) (cacheImageURL string) {
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesCache) != -1 { if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesCache) != -1 {
cacheImageURL = fmt.Sprintf("%s://%s/images/%s%s", System.ServerProtocol.WEB, System.Domain, urlMD5, fileExtension) cacheImageURL = fmt.Sprintf("%s://%s/images/%s%s", System.ServerProtocol.XML, System.Domain, urlMD5, fileExtension)
} else { } else {
@@ -163,7 +163,7 @@ func uploadLogo(input, filename string) (logoURL string, err error) {
return return
} }
logoURL = fmt.Sprintf("%s://%s/data_images/%s", System.ServerProtocol.WEB, System.Domain, filename) logoURL = fmt.Sprintf("%s://%s/data_images/%s", System.ServerProtocol.XML, System.Domain, filename)
return return

View File

@@ -85,7 +85,7 @@ func ShowSystemInfo() {
println("---") println("---")
fmt.Println("Settings [Streaming]") 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("Buffer Size: %d KB", Settings.BufferSize))
fmt.Println(fmt.Sprintf("Timeout: %d ms", int(Settings.BufferTimeout))) fmt.Println(fmt.Sprintf("Timeout: %d ms", int(Settings.BufferTimeout)))
fmt.Println(fmt.Sprintf("User Agent: %s", Settings.UserAgent)) fmt.Println(fmt.Sprintf("User Agent: %s", Settings.UserAgent))

View File

@@ -2,6 +2,8 @@ package m3u
import ( import (
"errors" "errors"
"fmt"
"log"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
@@ -12,6 +14,7 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
var content = string(byteStream) var content = string(byteStream)
var channelName string var channelName string
var uuids []string
var parseMetaData = func(channel string) (stream map[string]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) line = strings.Replace(line, p, "", 1)
p = strings.Replace(p, `"`, "", -1) p = strings.Replace(p, `"`, "", -1)
var parameter = strings.Split(p, "=") var parameter = strings.SplitN(p, "=", 2)
if len(parameter) == 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 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.key"] = key
stream["_uuid.value"] = value stream["_uuid.value"] = value
//os.Exit(0)
break break
} }
@@ -160,3 +169,14 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
return return
} }
func indexOfString(element string, data []string) int {
for k, v := range data {
if element == v {
return k
}
}
return -1
}

View File

@@ -43,6 +43,10 @@ func filterThisStream(s interface{}) (status bool) {
for _, filter := range Data.Filter { for _, filter := range Data.Filter {
if filter.Rule == "" {
continue
}
var group, name, search string var group, name, search string
var exclude, include string var exclude, include string
var match = false var match = false

View File

@@ -245,6 +245,8 @@ func getErrMsg(errCode int) (errMsg string) {
errMsg = fmt.Sprintf("Invalid formatting of the time") errMsg = fmt.Sprintf("Invalid formatting of the time")
case 1013: case 1013:
errMsg = fmt.Sprintf("Invalid settings file (settings.json), file must be at least version %s", System.Compatibility) 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: case 1020:
errMsg = fmt.Sprintf("Data could not be saved, invalid keyword") errMsg = fmt.Sprintf("Data could not be saved, invalid keyword")
@@ -252,12 +254,13 @@ func getErrMsg(errCode int) (errMsg string) {
// Datenbank Update // Datenbank Update
case 1030: case 1030:
errMsg = fmt.Sprintf("Invalid settings file (%s)", System.File.Settings) 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 // M3U Parser
case 1050: case 1050:
errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.") errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.")
// M3U Parser
case 1060: case 1060:
errMsg = fmt.Sprintf("Invalid characters found in the tvg parameters, streams with invalid parameters were skipped.") 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.") errMsg = fmt.Sprintf("Folder could not be created.")
case 1071: case 1071:
errMsg = fmt.Sprintf("File could not be created") errMsg = fmt.Sprintf("File could not be created")
case 1072:
errMsg = fmt.Sprintf("File not found")
// Backup // Backup
case 1090: case 1090:
@@ -290,6 +295,8 @@ func getErrMsg(errCode int) (errMsg string) {
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist") errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
case 1203: case 1203:
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist") 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 // Warnings
case 2000: 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.") errMsg = fmt.Sprintf("There are no channels mapped, use the mapping menu to assign EPG data to the channels.")
case 2010: case 2010:
errMsg = fmt.Sprintf("No valid streaming URL") 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: case 2099:
errMsg = fmt.Sprintf("Updates have been disabled by the developer") 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") errMsg = fmt.Sprintf("This error message comes from the provider")
case 4005: case 4005:
errMsg = fmt.Sprintf("Temporary buffer files could not be deleted") errMsg = fmt.Sprintf("Temporary buffer files could not be deleted")
case 4006:
errMsg = fmt.Sprintf("Server connection timeout")
// Buffer (M3U8) // Buffer (M3U8)
case 4050: case 4050:
@@ -368,7 +381,7 @@ func getErrMsg(errCode int) (errMsg string) {
case 6002: case 6002:
errMsg = fmt.Sprintf("Update failed") errMsg = fmt.Sprintf("Update failed")
case 6003: case 6003:
errMsg = fmt.Sprintf("Server not available") errMsg = fmt.Sprintf("Update server not available")
case 6004: case 6004:
errMsg = fmt.Sprintf("xTeVe update available") errMsg = fmt.Sprintf("xTeVe update available")

View File

@@ -108,3 +108,147 @@ type BandwidthCalculation struct {
Stop time.Time Stop time.Time
TimeDiff float64 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)
}
}
*/

View File

@@ -11,6 +11,7 @@ type SystemStruct struct {
APIVersion string APIVersion string
AppName string AppName string
ARCH string ARCH string
BackgroundProcess bool
Branch string Branch string
Build string Build string
Compatibility string Compatibility string
@@ -21,6 +22,16 @@ type SystemStruct struct {
Domain string Domain string
DVRLimit int DVRLimit int
FFmpeg struct {
DefaultOptions string
Path string
}
VLC struct {
DefaultOptions string
Path string
}
File struct { File struct {
Authentication string Authentication string
M3U string M3U string
@@ -31,6 +42,10 @@ type SystemStruct struct {
XML string XML string
} }
Compressed struct {
GZxml string
}
Flag struct { Flag struct {
Branch string Branch string
Debug int Debug int
@@ -176,6 +191,7 @@ type XEPGChannelStruct struct {
XName string `json:"x-name,required"` XName string `json:"x-name,required"`
XUpdateChannelIcon bool `json:"x-update-channel-icon,required"` XUpdateChannelIcon bool `json:"x-update-channel-icon,required"`
XUpdateChannelName bool `json:"x-update-channel-name,required"` XUpdateChannelName bool `json:"x-update-channel-name,required"`
XDescription string `json:"x-description,required"`
} }
// M3UChannelStructXEPG : M3U Struktur für XEPG // M3UChannelStructXEPG : M3U Struktur für XEPG
@@ -241,11 +257,15 @@ type SettingsStrcut struct {
BackupKeep int `json:"backup.keep"` BackupKeep int `json:"backup.keep"`
BackupPath string `json:"backup.path"` BackupPath string `json:"backup.path"`
Branch string `json:"git.branch,omitempty"` Branch string `json:"git.branch,omitempty"`
Buffer bool `json:"buffer"` Buffer string `json:"buffer"`
BufferSize int `json:"buffer.size.kb"` BufferSize int `json:"buffer.size.kb"`
BufferTimeout float64 `json:"buffer.timeout"` BufferTimeout float64 `json:"buffer.timeout"`
CacheImages bool `json:"cache.images"` CacheImages bool `json:"cache.images"`
EpgSource string `json:"epgSource"` 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 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) FileXMLTV []string `json:"xmltv,omitempty"` // Altes Speichersystem der Provider XML Datei Slice (Wird für die Umwandlung auf das neue benötigt)

View File

@@ -25,11 +25,15 @@ type RequestStruct struct {
AuthenticationXML *bool `json:"authentication.xml,omitempty"` AuthenticationXML *bool `json:"authentication.xml,omitempty"`
BackupKeep *int `json:"backup.keep,omitempty"` BackupKeep *int `json:"backup.keep,omitempty"`
BackupPath *string `json:"backup.path,omitempty"` BackupPath *string `json:"backup.path,omitempty"`
Buffer *bool `json:"buffer,omitempty"` Buffer *string `json:"buffer,omitempty"`
BufferSize *int `json:"buffer.size.kb, omitempty"` BufferSize *int `json:"buffer.size.kb, omitempty"`
BufferTimeout *float64 `json:"buffer.timeout,omitempty"` BufferTimeout *float64 `json:"buffer.timeout,omitempty"`
CacheImages *bool `json:"cache.images,omitempty"` CacheImages *bool `json:"cache.images,omitempty"`
EpgSource *string `json:"epgSource,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"` FilesUpdate *bool `json:"files.update,omitempty"`
TempPath *string `json:"temp.path,omitempty"` TempPath *string `json:"temp.path,omitempty"`
Tuner *int `json:"tuner,omitempty"` Tuner *int `json:"tuner,omitempty"`
@@ -37,6 +41,8 @@ type RequestStruct struct {
UserAgent *string `json:"user.agent,omitempty"` UserAgent *string `json:"user.agent,omitempty"`
XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"` XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"`
XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"` XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"`
SchemeM3U *string `json:"scheme.m3u,omitempty"`
SchemeXML *string `json:"scheme.xml,omitempty"`
} `json:"settings,omitempty"` } `json:"settings,omitempty"`
// Upload Logo // Upload Logo

View File

@@ -48,6 +48,7 @@ type Program struct {
PreviouslyShown *PreviouslyShown `xml:"previously-shown"` PreviouslyShown *PreviouslyShown `xml:"previously-shown"`
New *New `xml:"new"` New *New `xml:"new"`
Live *Live `xml:"live"` Live *Live `xml:"live"`
Premiere *Live `xml:"premiere"`
} }
// Title : Programmtitel // Title : Programmtitel

View File

@@ -113,11 +113,13 @@ func loadSettings() (settings SettingsStrcut, err error) {
defaults["authentication.xml"] = false defaults["authentication.xml"] = false
defaults["backup.keep"] = 10 defaults["backup.keep"] = 10
defaults["backup.path"] = System.Folder.Backup defaults["backup.path"] = System.Folder.Backup
defaults["buffer"] = false defaults["buffer"] = "-"
defaults["buffer.size.kb"] = 1024 defaults["buffer.size.kb"] = 1024
defaults["buffer.timeout"] = 500 defaults["buffer.timeout"] = 500
defaults["cache.images"] = false defaults["cache.images"] = false
defaults["epgSource"] = "PMS" defaults["epgSource"] = "PMS"
defaults["ffmpeg.options"] = System.FFmpeg.DefaultOptions
defaults["vlc.options"] = System.VLC.DefaultOptions
defaults["files"] = dataMap defaults["files"] = dataMap
defaults["files.update"] = true defaults["files.update"] = true
defaults["filter"] = make(map[string]interface{}) 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)) 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 settings.Version = System.DBVersion
err = saveSettings(settings) 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 return
} }

View File

@@ -10,8 +10,11 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
"os/exec"
"os/user" "os/user"
"path/filepath" "path/filepath"
"runtime"
"strings"
"text/template" "text/template"
) )
@@ -42,13 +45,25 @@ func checkFolder(path string) (err error) {
return nil return nil
} }
// Prüft ob die datei im Dateisystem existiert // Prüft ob die Datei im Dateisystem existiert
func checkFile(filename string) (err error) { func checkFile(filename string) (err error) {
var file = getPlatformFile(filename) var file = getPlatformFile(filename)
if _, err = os.Stat(file); os.IsNotExist(err) { 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 return
@@ -77,6 +92,7 @@ func GetUserHomeDirectory() (userHomeDirectory string) {
return return
} }
// Prüft Dateiberechtigung
func checkFilePermission(dir string) (err error) { func checkFilePermission(dir string) (err error) {
var filename = dir + "permission.test" var filename = dir + "permission.test"
@@ -115,6 +131,34 @@ func removeOldSystemData() {
os.RemoveAll(System.Folder.Temp) 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 { 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() System.Hostname, err = os.Hostname()
if err != nil { if err != nil {
return return

View File

@@ -41,8 +41,8 @@ func BinaryUpdate() (err error) {
resp, err := http.Get(gitInfo) resp, err := http.Get(gitInfo)
if err != nil { if err != nil {
ShowError(err, 0) ShowError(err, 6003)
return err return nil
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
@@ -169,6 +169,13 @@ checkVersion:
if settingsVersion, ok := settingsMap["version"].(string); ok { 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) // Letzte Kompatible Version (1.4.4)
if settingsVersion < System.Compatibility { if settingsVersion < System.Compatibility {
err = errors.New(getErrMsg(1013)) err = errors.New(getErrMsg(1013))
@@ -204,10 +211,37 @@ checkVersion:
} }
case "2.0.0": 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 // Falls es in einem späteren Update Änderungen an der Datenbank gibt, geht es hier weiter
break break
} }
} else { } else {

File diff suppressed because one or more lines are too long

View File

@@ -129,6 +129,12 @@ func Stream(w http.ResponseWriter, r *http.Request) {
return return
} }
switch Settings.Buffer {
case "-":
showInfo(fmt.Sprintf("Buffer:false [%s]", Settings.Buffer))
case "xteve":
if strings.Index(streamInfo.URL, "rtsp://") != -1 || strings.Index(streamInfo.URL, "rtp://") != -1 { if strings.Index(streamInfo.URL, "rtsp://") != -1 || strings.Index(streamInfo.URL, "rtp://") != -1 {
err = errors.New("RTSP and RTP streams are not supported") err = errors.New("RTSP and RTP streams are not supported")
ShowError(err, 2004) ShowError(err, 2004)
@@ -140,9 +146,14 @@ func Stream(w http.ResponseWriter, r *http.Request) {
return 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)) 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 // Prüfen ob der Buffer verwendet werden soll
switch Settings.Buffer { switch Settings.Buffer {
case true: case "-":
bufferingStream(streamInfo.PlaylistID, streamInfo.URL, streamInfo.Name, w, r)
case false:
showInfo("Streaming URL:" + streamInfo.URL) showInfo("Streaming URL:" + streamInfo.URL)
http.Redirect(w, r, streamInfo.URL, 302) http.Redirect(w, r, streamInfo.URL, 302)
showInfo("Streaming Info:URL was passed to the client.") 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.") 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 return
@@ -195,7 +206,7 @@ func Auto(w http.ResponseWriter, r *http.Request) {
// xTeVe : Web Server /xmltv/ und /m3u/ // xTeVe : Web Server /xmltv/ und /m3u/
func xTeVe(w http.ResponseWriter, r *http.Request) { func xTeVe(w http.ResponseWriter, r *http.Request) {
var requestType, groupTitle, file, content string var requestType, groupTitle, file, content, contentType string
var err error var err error
var path = strings.TrimPrefix(r.URL.Path, "/") var path = strings.TrimPrefix(r.URL.Path, "/")
var groups = []string{} var groups = []string{}
@@ -205,7 +216,6 @@ func xTeVe(w http.ResponseWriter, r *http.Request) {
// XMLTV Datei // XMLTV Datei
if strings.Contains(path, "xmltv/") { if strings.Contains(path, "xmltv/") {
w.Header().Set("Content-Type", "application/xml")
requestType = "xml" requestType = "xml"
file = System.Folder.Data + getFilenameFromPath(path) file = System.Folder.Data + getFilenameFromPath(path)
@@ -249,6 +259,13 @@ func xTeVe(w http.ResponseWriter, r *http.Request) {
return return
} }
contentType = http.DetectContentType([]byte(content))
if strings.Contains(strings.ToLower(contentType), "xml") {
contentType = "application/xml; charset=utf-8"
}
w.Header().Set("Content-Type", contentType)
if err == nil { if err == nil {
w.Write([]byte(content)) w.Write([]byte(content))
} }
@@ -305,10 +322,12 @@ func WS(w http.ResponseWriter, r *http.Request) {
var newToken string var newToken string
/*
if r.Header.Get("Origin") != "http://"+r.Host { if r.Header.Get("Origin") != "http://"+r.Host {
httpStatusError(w, r, 403) httpStatusError(w, r, 403)
return return
} }
*/
conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024) conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
if err != nil { if err != nil {
@@ -572,6 +591,8 @@ func Web(w http.ResponseWriter, r *http.Request) {
var language LanguageUI var language LanguageUI
setGlobalDomain(r.Host)
if System.Dev == true { if System.Dev == true {
lang, err = loadJSONFileToMap(fmt.Sprintf("html/lang/%s.json", Settings.Language)) lang, err = loadJSONFileToMap(fmt.Sprintf("html/lang/%s.json", Settings.Language))

View File

@@ -306,7 +306,6 @@ func createXEPGDatabase() (err error) {
} }
if len(xepgChannel.XChannelID) == 0 { if len(xepgChannel.XChannelID) == 0 {
fmt.Println(mapToJSON(xepgChannel))
delete(Data.XEPG.Channels, id) 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 { for _, dsa := range Data.Streams.Active {
var channelExists = false // Entscheidet ob ein Kanal neu zu Datenbank hinzugefügt werden soll. 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) Data.Cache.Streams.Active = append(Data.Cache.Streams.Active, m3uChannel.Name)
// XEPG Datenbank durchlaufen um nach dem Kanal zu suchen. // XEPG Datenbank durchlaufen um nach dem Kanal zu suchen.
for xepg, dxc := range Data.XEPG.Channels { for xepg, dxc := range xepgChannels {
var xepgChannel XEPGChannelStruct var xepgChannel XEPGChannelStruct
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel) err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
@@ -367,6 +372,7 @@ func createXEPGDatabase() (err error) {
//os.Exit(0) //os.Exit(0)
switch channelExists { switch channelExists {
case true: case true:
// Bereits vorhandener Kanal // Bereits vorhandener Kanal
var xepgChannel XEPGChannelStruct var xepgChannel XEPGChannelStruct
@@ -635,9 +641,10 @@ func createXMLTVFile() (err error) {
var xmlOutput = []byte(xml.Header + string(content)) var xmlOutput = []byte(xml.Header + string(content))
writeByteToFile(System.File.XML, xmlOutput) writeByteToFile(System.File.XML, xmlOutput)
xepgXML = XMLTV{} showInfo("XEPG:" + fmt.Sprintf("Compress XMLTV file (%s)", System.Compressed.GZxml))
err = compressGZIP(&xmlOutput, System.Compressed.GZxml)
//saveMapToJSONFile(System.File.Images, Data.Cache.ImageCache) xepgXML = XMLTV{}
return return
} }
@@ -711,6 +718,9 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
// Live // Live
program.Live = xmltvProgram.Live program.Live = xmltvProgram.Live
// Premiere
program.Premiere = xmltvProgram.Premiere
xepgXML.Program = append(xepgXML.Program, program) xepgXML.Program = append(xepgXML.Program, program)
} }
@@ -753,7 +763,12 @@ func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
epg.Start = epgStartTime.Format("20060102150405") + offset epg.Start = epgStartTime.Format("20060102150405") + offset
epg.Stop = epgStopTime.Format("20060102150405") + offset epg.Stop = epgStopTime.Format("20060102150405") + offset
epg.Title = append(epg.Title, &Title{Value: xepgChannel.XName + " (" + epgStartTime.Weekday().String()[0:2] + ". " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04") + ")", Lang: "en"}) epg.Title = append(epg.Title, &Title{Value: xepgChannel.XName + " (" + epgStartTime.Weekday().String()[0:2] + ". " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04") + ")", Lang: "en"})
if len(xepgChannel.XDescription) == 0 {
epg.Desc = append(epg.Desc, &Desc{Value: "xTeVe: (" + strconv.Itoa(dummyLength) + " Minutes) " + epgStartTime.Weekday().String() + " " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04"), Lang: "en"}) epg.Desc = append(epg.Desc, &Desc{Value: "xTeVe: (" + strconv.Itoa(dummyLength) + " Minutes) " + epgStartTime.Weekday().String() + " " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04"), Lang: "en"})
} else {
epg.Desc = append(epg.Desc, &Desc{Value: xepgChannel.XDescription, Lang: "en"})
}
if Settings.XepgReplaceMissingImages == true { if Settings.XepgReplaceMissingImages == true {
poster.Src = getCacheImageURL(xepgChannel.TvgLogo) poster.Src = getCacheImageURL(xepgChannel.TvgLogo)

View File

@@ -21,9 +21,8 @@ menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.p
// Kategorien für die Einstellungen // Kategorien für die Einstellungen
var settingsCategory = new Array() var settingsCategory = new Array()
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api")) 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.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,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"))
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent"))
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep")) 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")) settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"))

View File

@@ -147,7 +147,7 @@ class Content {
var cell:Cell = new Cell() var cell:Cell = new Cell()
cell.child = true cell.child = true
cell.childType = "P" cell.childType = "P"
if (SERVER["settings"]["buffer"] == true) { if (SERVER["settings"]["buffer"] != "-") {
cell.value = data[key]["tuner"] cell.value = data[key]["tuner"]
} else { } else {
cell.value = "-" cell.value = "-"
@@ -1113,7 +1113,7 @@ function openPopUp(dataType, element) {
content.appendRow("{{.playlist.fileM3U.title}}", input) content.appendRow("{{.playlist.fileM3U.title}}", input)
// Tuner // Tuner
if (SERVER["settings"]["buffer"] == true) { if (SERVER["settings"]["buffer"] != "-") {
var text:string[] = new Array() var text:string[] = new Array()
var values:string[] = new Array() var values:string[] = new Array()
@@ -1192,7 +1192,7 @@ function openPopUp(dataType, element) {
content.appendRow("{{.playlist.fileHDHR.title}}", input) content.appendRow("{{.playlist.fileHDHR.title}}", input)
// Tuner // Tuner
if (SERVER["settings"]["buffer"] == true) { if (SERVER["settings"]["buffer"] != "-") {
var text:string[] = new Array() var text:string[] = new Array()
var values:string[] = new Array() var values:string[] = new Array()
@@ -1532,6 +1532,13 @@ function openPopUp(dataType, element) {
content.description(data["name"]) content.description(data["name"])
// Beschreibung
var dbKey:string = "x-description"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.mapping.description.placeholder}}")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
content.appendRow("{{.mapping.description.title}}", input)
// Aktualisierung des Kanalnamens // Aktualisierung des Kanalnamens
if (data.hasOwnProperty("_uuid.key")) { if (data.hasOwnProperty("_uuid.key")) {
if (data["_uuid.key"] != "") { if (data["_uuid.key"] != "") {

View File

@@ -89,6 +89,62 @@ class SettingsCategory {
setting.appendChild(tdRight) setting.appendChild(tdRight)
break 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 // Checkboxen
case "authentication.web": case "authentication.web":
var tdLeft = document.createElement("TD") var tdLeft = document.createElement("TD")
@@ -216,20 +272,6 @@ class SettingsCategory {
setting.appendChild(tdRight) setting.appendChild(tdRight)
break 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": case "api":
var tdLeft = document.createElement("TD") var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.api.title}}" + ":" tdLeft.innerHTML = "{{.settings.api.title}}" + ":"
@@ -314,6 +356,22 @@ class SettingsCategory {
setting.appendChild(tdRight) setting.appendChild(tdRight)
break 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 return setting
@@ -381,6 +439,22 @@ class SettingsCategory {
text = "{{.settings.userAgent.description}}" text = "{{.settings.userAgent.description}}"
break 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": case "epgSource":
text = "{{.settings.epgSource.description}}" text = "{{.settings.epgSource.description}}"
break break

View File

@@ -35,14 +35,14 @@ var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-
Update: Automatic updates from the GitHub repository [true|false] Update: Automatic updates from the GitHub repository [true|false]
*/ */
// Name : Programname // Name : Programmname
const Name = "xTeVe" const Name = "xTeVe"
// Version : Version, die Build Nummer wird in der main func geparst. // Version : Version, die Build Nummer wird in der main func geparst.
const Version = "2.0.2.0020" const Version = "2.1.2.0120"
// DBVersion : Datanbank Version // DBVersion : Datanbank Version
const DBVersion = "2.0.0" const DBVersion = "2.1.0"
// APIVersion : API Version // APIVersion : API Version
const APIVersion = "1.1.0" const APIVersion = "1.1.0"