Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config + webserver #10

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
# Dependency directories (remove the comment below to include it)
# vendor/

*.exe

prism
dist/
.envrc
outputs.txt
config.json
/config.bak.json
9 changes: 9 additions & 0 deletions config.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"enabled": true,
"url": "rtmp://localhost/live/test",
"width": 1920,
"height": 1080,
"bitrate": 6000
}
]
66 changes: 66 additions & 0 deletions html/edit_config.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<title>Prism RTSP Splitter</title>
<!-- Include Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>Edit Config</h1>
<form action="/save" method="post">
{{range $index, $config := .}}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">URL {{$index}}</h5>
<div class="form-group">
<input type="checkbox" class="form-control" name="enabled[{{$index}}]" {{if $config.Enabled}}checked{{end}}>
<label>URL:</label>
<input type="text" class="form-control" name="url[{{$index}}]" value="{{$config.URL}}">
</div>
<div class="form-group">
<label>Bitrate:</label>
<input type="number" class="form-control" name="bitrate[{{$index}}]" value="{{$config.Bitrate}}">
<label>Width:</label>
<input type="number" class="form-control" name="width[{{$index}}]" value="{{$config.Width}}">
<label>Height:</label>
<input type="number" class="form-control" name="height[{{$index}}]" value="{{$config.Height}}">
</div>
<button type="button" class="btn btn-danger" onclick="removeConfig({{$index}})">Remove Output</button> </div>
</div>
{{end}}
<button type="submit" class="btn btn-primary">Save</button>&nbsp;
<button type="button" class="btn btn-success" onclick="addConfig()">Add Output</button>
</form>
</div>

<!-- Include Bootstrap JS -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
function removeConfig(index) {
fetch('/remove?index=' + index, {method: 'POST'})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
window.location.reload();
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
}
function addConfig() {
fetch('/add', {method: 'POST'})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
window.location.reload();
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
}
</script>
</body>
</html>
174 changes: 165 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"html/template"
"log"
"net/http"
"os"
"strconv"
"time"

// TODO: switch to joy5?
Expand All @@ -12,9 +17,20 @@ import (
)

var (
bind = flag.String("bind", ":1935", "bind address")
bind = flag.String("bind", ":1935", "bind address")
bind_web = flag.String("bind_web", ":8080", "bind address for web server")
config_file = flag.String("config", "config.json", "config file")
config []URLConfig
)

type URLConfig struct {
Enabled bool `json:"enabled"`
URL string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
Bitrate int `json:"bitrate"`
}

type RTMPConnection struct {
url string
conn *rtmp.Conn
Expand Down Expand Up @@ -111,24 +127,155 @@ func (r *RTMPConnection) Loop() error {
return nil
}

// region webserver

func logRequest(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s %s %s", r.Method, r.URL.Path, r.URL.RawQuery, r.RemoteAddr)
next.ServeHTTP(w, r)
})
}

func handleAddConfig(w http.ResponseWriter, r *http.Request) {
config = append(config, URLConfig{})
saveConfig()
http.Redirect(w, r, "/", http.StatusSeeOther)
fmt.Println("Added new config entry")

}
func handleRemoveConfig(w http.ResponseWriter, r *http.Request) {
indexStr := r.URL.Query().Get("index")
index, err := strconv.Atoi(indexStr)
if err != nil || index < 0 || index >= len(config) {
http.Error(w, "Invalid index", http.StatusBadRequest)
return
}
config = append(config[:index], config[index+1:]...)
saveConfig()
fmt.Println("Removed config at index", index)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func handleEditConfig(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("html/edit_config.html"))
tmpl.Execute(w, config)
}
func handleSaveConfig(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}

// Parse the form data
err := r.ParseForm()
if err != nil {
http.Error(w, "Error parsing form", http.StatusInternalServerError)
return
}

// Update the config with the form data
// ...

// Save the config to the file
file, err := os.Create(*config_file)
if err != nil {
http.Error(w, "Error creating config file", http.StatusInternalServerError)
return
}
defer file.Close()

encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
err = encoder.Encode(config)
if err != nil {
http.Error(w, "Error writing config file", http.StatusInternalServerError)
return
}

http.Redirect(w, r, "/", http.StatusSeeOther)
}

func saveConfig() {
file, err := os.Create(*config_file)
if err != nil {
fmt.Println("Error creating config file:", err)
return
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
err = encoder.Encode(config)
if err != nil {
fmt.Println("Error writing config file:", err)
}
}

// endregion webserver

func readConfigFromFile(filename string) ([]URLConfig, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()

var configs []URLConfig
decoder := json.NewDecoder(file)
err = decoder.Decode(&configs)
if err != nil {
return nil, err
}

return configs, nil
}

func main() {
flag.Parse()
if len(flag.Args()) == 0 {
fmt.Println("usage: prism URL [URL...]")
var err error
if *config_file != "" {
if _, err := os.Stat(*config_file); err == nil {
config, _ = readConfigFromFile(*config_file)
fmt.Println("Read", len(config), "outputs from", *config_file)
}
}
for _, arg := range flag.Args() {
config = append(config, URLConfig{Enabled: true, URL: arg})
}

if len(config) < 1 {
config = append(config, URLConfig{Enabled: true, URL: "rtmp://localhost/live/test", Width: 1920, Height: 1080, Bitrate: 6000})
file, err := os.Create(*config_file)
if err != nil {
fmt.Println("Error creating config file:", err)
os.Exit(1)
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
err = encoder.Encode(config)

if err != nil {
fmt.Println("Error writing config file:", err)
os.Exit(1)
}
fmt.Println("Created default config file", *config_file, "with one example output")
os.Exit(1)
}

fmt.Println("Found", len(config), "outputs")

fmt.Println("Starting RTMP server...")
config := &rtmp.Config{
rtmp_config := &rtmp.Config{
ChunkSize: 128,
BufferSize: 0,
}
server := rtmp.NewServer(config)
server := rtmp.NewServer(rtmp_config)
server.Addr = *bind

conns := make([]*RTMPConnection, len(flag.Args()))
for i, u := range flag.Args() {
conns[i] = NewRTMPConnection(u)
conns := make([]*RTMPConnection, len(config))
for i, u := range config {
// print u object
fmt.Println(u)
conns[i] = NewRTMPConnection(u.URL)
}

server.HandlePublish = func(conn *rtmp.Conn) {
Expand Down Expand Up @@ -174,8 +321,17 @@ func main() {
}
}

go func() {
fmt.Println("Starting web server on http://localhost:8080")
http.Handle("/", logRequest(http.HandlerFunc(handleEditConfig)))
http.Handle("/add", logRequest(http.HandlerFunc(handleAddConfig)))
http.Handle("/remove", logRequest(http.HandlerFunc(handleRemoveConfig)))
http.Handle("/save", logRequest(http.HandlerFunc(handleSaveConfig)))
http.ListenAndServe(*bind_web, nil)
}()

fmt.Println("Waiting for incoming connection...")
err := server.ListenAndServe()
err = server.ListenAndServe()
if err != nil {
fmt.Println(err)
os.Exit(1)
Expand Down