Add FFmpeg and VLC support

This commit is contained in:
marmei
2019-09-27 19:24:31 +02:00
parent 11453c6053
commit ad992eb615
21 changed files with 834 additions and 103 deletions

View File

@@ -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) {

View File

@@ -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++) {

View File

@@ -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;

View File

@@ -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": {

View File

@@ -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, playlistID)
switch Settings.Buffer {
case "xteve":
go connectToStreamingServer(streamID, playlistID)
case "ffmpeg", "vlc":
go bufferWithFfmpeg(streamID, playlistID)
default:
break
}
showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner))
@@ -400,7 +413,7 @@ func getTmpFiles(stream *ThisStream) (tmpFiles []string) {
return
}
if len(files) > 1 {
if len(files) > 2 {
for _, file := range files {
@@ -526,6 +539,9 @@ func connectToStreamingServer(streamID int, playlistID string) {
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() {
@@ -805,8 +821,8 @@ func connectToStreamingServer(streamID int, playlistID string) {
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)
@@ -882,7 +898,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
}
// Buffer auf die Festplatte speichern
if fileSize >= tmpFileSize || n == 0 {
if fileSize >= tmpFileSize/2 || n == 0 {
bandwidth.Stop = time.Now()
bandwidth.Size += fileSize
@@ -928,6 +944,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
}
fileSize = 0
buffer = make([]byte, 1024*bufferSize*2)
if n == 0 {
bufferFile.Close()
@@ -1319,14 +1336,255 @@ func switchBandwidth(stream *ThisStream) (err error) {
return
}
// Buffer mit FFMPEG
func bufferWithFfmpeg(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 fileSize = 0
var stream = playlist.Streams[streamID]
var buf bytes.Buffer
var tmpFolder = playlist.Streams[streamID].Folder
var url = playlist.Streams[streamID].URL
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
}
showInfo("Streaming URL:" + stream.URL)
// Größe des Buffers
buffer := make([]byte, 1024*Settings.BufferSize*2)
var tmpFileSize = 1024 * Settings.BufferSize * 1
debug = fmt.Sprintf("Buffer Size:%d KB [SERVER CONNECTION]", len(buffer)/1024)
showDebug(debug, 3)
debug = fmt.Sprintf("Buffer Size:%d KB [CLIENT CONNECTION]", tmpFileSize/1024)
showDebug(debug, 3)
var tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
bufferFile, err := os.Create(tmpFile)
if err != nil {
addErrorToStream(err)
bufferFile.Close()
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()
go func() {
// Log Daten vom Prozess im Dubug Mode 2 anzeigen.
scanner := bufio.NewScanner(logOut)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
debug = fmt.Sprintf("%s:%s", bufferType, strings.TrimSpace(scanner.Text()))
showDebug(debug, 1)
}
}()
scanner := bufio.NewScanner(stdOut)
scanner.Split(bufio.ScanBytes)
for scanner.Scan() {
if len(buf.Bytes()) == 0 && stream.Status == false {
showInfo("Streaming Status:Receive data from " + bufferType)
}
b := scanner.Bytes()
for _, i := range b {
buf.WriteByte(i)
}
fileSize = fileSize + len(b)
if clientConnection(stream) == false {
cmd.Process.Kill()
bufferFile.Close()
err = os.RemoveAll(stream.Folder)
if err != nil {
ShowError(err, 4005)
}
cmd.Wait()
return
}
if fileSize >= tmpFileSize/2 {
if stream.Status == false {
showInfo("Streaming Status:Buffering data from " + bufferType)
}
if _, err := bufferFile.Write(buf.Bytes()); err != nil {
cmd.Process.Kill()
ShowError(err, 0)
addErrorToStream(err)
cmd.Wait()
return
}
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 {
cmd.Process.Kill()
bufferFile.Close()
err = os.RemoveAll(stream.Folder)
if err != nil {
ShowError(err, 4005)
}
cmd.Wait()
return
}
bufferFile, err = os.Create(tmpFile)
if err != nil {
addErrorToStream(err)
cmd.Process.Kill()
cmd.Wait()
return
}
buf.Reset()
fileSize = 0
/*
if n == 0 {
bufferFile.Close()
run.Process.Kill()
break
}
*/
}
}
//cmd.Process.Kill()
cmd.Wait()
err = errors.New(bufferType + " error")
addErrorToStream(err)
ShowError(err, 1204)
time.Sleep(time.Duration(1000) * time.Millisecond)
clientConnection(stream)
//os.Exit(0)
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 {

View File

@@ -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 panic -i [URL] -c copy -f mpegts pipe:1"
System.VLC.DefaultOptions = "[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
@@ -133,7 +137,6 @@ func Init() (err error) {
// Bedingte Update Änderungen durchführen
err = conditionalUpdateChanges()
if err != nil {
ShowError(err, 0)
return
}

View File

@@ -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 {

View File

@@ -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))

View File

@@ -254,6 +254,8 @@ 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:
@@ -292,6 +294,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:
@@ -308,6 +312,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")
@@ -370,7 +378,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")

View File

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

View File

@@ -22,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
@@ -242,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)

View File

@@ -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"`

View File

@@ -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.FFmpegPath) == 0 {
settings.FFmpegPath = 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
}

View File

@@ -10,8 +10,11 @@ import (
"io/ioutil"
"net"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strings"
"text/template"
)
@@ -77,6 +80,7 @@ func GetUserHomeDirectory() (userHomeDirectory string) {
return
}
// Prüft Dateiberechtigung
func checkFilePermission(dir string) (err error) {
var filename = dir + "permission.test"
@@ -115,6 +119,28 @@ 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 {
path = strings.Trim(string(out), "\r\n")
}
default:
return
}
return
}
//
func removeChildItems(dir string) error {
@@ -276,6 +302,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

View File

@@ -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 {

File diff suppressed because one or more lines are too long

View File

@@ -129,20 +129,29 @@ func Stream(w http.ResponseWriter, r *http.Request) {
return
}
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)
/*
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)
showInfo("Streaming URL:" + streamInfo.URL)
http.Redirect(w, r, streamInfo.URL, 302)
showInfo("Streaming URL:" + streamInfo.URL)
http.Redirect(w, r, streamInfo.URL, 302)
showInfo("Streaming Info:URL was passed to the client")
return
}
*/
switch Settings.Buffer {
case "-":
showInfo(fmt.Sprintf("Buffer:false", Settings.Buffer))
default:
showInfo(fmt.Sprintf("Buffer:true [%s]", Settings.Buffer))
showInfo("Streaming Info:URL was passed to the client")
return
}
showInfo(fmt.Sprintf("Buffer:%t", Settings.Buffer))
if Settings.Buffer == true {
if Settings.Buffer != "-" {
showInfo(fmt.Sprintf("Buffer Size:%d KB", Settings.BufferSize))
}
@@ -152,16 +161,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

View File

@@ -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"))

View File

@@ -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()

View File

@@ -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

View File

@@ -39,10 +39,10 @@ var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-
const Name = "xTeVe"
// Version : Version, die Build Nummer wird in der main func geparst.
const Version = "2.0.3.0030"
const Version = "2.0.3.0035"
// DBVersion : Datanbank Version
const DBVersion = "2.0.0"
const DBVersion = "2.1.0"
// APIVersion : API Version
const APIVersion = "1.1.0"