Compare commits
No commits in common. "4b171b849f50b333956ebd48b5b3afd6359c3c45" and "af0f583affe27a319a291af00fb05fa594a83bc8" have entirely different histories.
4b171b849f
...
af0f583aff
1
go.mod
1
go.mod
@ -12,7 +12,6 @@ require (
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/uuid v1.4.0 // indirect
|
github.com/google/uuid v1.4.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
|
||||||
github.com/pion/datachannel v1.5.5 // indirect
|
github.com/pion/datachannel v1.5.5 // indirect
|
||||||
github.com/pion/dtls/v2 v2.2.8 // indirect
|
github.com/pion/dtls/v2 v2.2.8 // indirect
|
||||||
github.com/pion/ice/v3 v3.0.2 // indirect
|
github.com/pion/ice/v3 v3.0.2 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -37,8 +37,6 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
|
|||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
|
||||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
|
47
main.go
47
main.go
@ -2,10 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.t-juice.club/torjus/ministream/server"
|
"git.t-juice.club/torjus/ministream/server"
|
||||||
@ -14,12 +11,6 @@ import (
|
|||||||
|
|
||||||
const Version = "v0.1.1"
|
const Version = "v0.1.1"
|
||||||
|
|
||||||
type ctxKey string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ctxKeyConfig ctxKey = "config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.App{
|
app := cli.App{
|
||||||
Name: "ministream",
|
Name: "ministream",
|
||||||
@ -28,16 +19,12 @@ func main() {
|
|||||||
{
|
{
|
||||||
Name: "serve",
|
Name: "serve",
|
||||||
Usage: "Start livestreaming server.",
|
Usage: "Start livestreaming server.",
|
||||||
Before: loadConfig,
|
Action: func(ctx *cli.Context) error {
|
||||||
Action: func(c *cli.Context) error {
|
store := server.NewUserStore()
|
||||||
cfg := configFromCtx(c.Context)
|
|
||||||
|
|
||||||
srv := server.NewServer(cfg)
|
srv := server.NewServer(store)
|
||||||
srv.Addr = cfg.HTTP.ListenAddr
|
srv.Addr = ":8080"
|
||||||
slog.Info("Starting HTTP-server", "addr", srv.Addr, "cfg", cfg)
|
srv.ListenAndServe()
|
||||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -50,27 +37,3 @@ func main() {
|
|||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig(c *cli.Context) error {
|
|
||||||
cfg, err := server.ConfigFromDefault()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Context = context.WithValue(c.Context, ctxKeyConfig, cfg)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func configFromCtx(ctx context.Context) *server.Config {
|
|
||||||
value := ctx.Value(ctxKeyConfig)
|
|
||||||
if value == nil {
|
|
||||||
panic("unable to load config")
|
|
||||||
}
|
|
||||||
|
|
||||||
config, ok := value.(*server.Config)
|
|
||||||
if !ok {
|
|
||||||
panic("config type assertion failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
# SiteName is used for displaying title of site on frontend.
|
|
||||||
# Default: "ministream"
|
|
||||||
# Env: MINISTREAM_SITENAME
|
|
||||||
SiteName = "stream.example.org"
|
|
||||||
|
|
||||||
[HTTP]
|
|
||||||
# HTTPListenAddr is which port the HTTP server will listen on.
|
|
||||||
# Default: ":8080"
|
|
||||||
# Env: MINISTREAM_HTTP_LISTENADDR
|
|
||||||
ListenAddr = ":8080"
|
|
||||||
|
|
||||||
[WebRTC]
|
|
||||||
# UDPMin is the minimum used for ephemeral ports.
|
|
||||||
# Default: 50000
|
|
||||||
# Env: MINISTREAM_WEBRTC_UDPMIN
|
|
||||||
UDPMin = 50000
|
|
||||||
|
|
||||||
# UDPMax is the maximum used for ephemeral ports.
|
|
||||||
# Default: 50050
|
|
||||||
# Env: MINISTREAM_WEBRTC_UDPMAX
|
|
||||||
UDPMAX = 50050
|
|
105
server/config.go
105
server/config.go
@ -1,105 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
SiteName string `toml:"siteName"`
|
|
||||||
HTTP ConfigHTTP `toml:"http"`
|
|
||||||
WebRTC ConfigWebRTC `toml:"WebRTC"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigHTTP struct {
|
|
||||||
ListenAddr string `json:"ListenAddr" toml:"ListenAddr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigWebRTC struct {
|
|
||||||
UDPMin int `toml:"UDPMin"`
|
|
||||||
UDPMax int `toml:"UDPMax"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
|
||||||
return &Config{
|
|
||||||
SiteName: "ministream",
|
|
||||||
HTTP: ConfigHTTP{
|
|
||||||
ListenAddr: ":8080",
|
|
||||||
},
|
|
||||||
WebRTC: ConfigWebRTC{
|
|
||||||
UDPMin: 50000,
|
|
||||||
UDPMax: 50050,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) OverrideFromEnv() {
|
|
||||||
if siteName, ok := os.LookupEnv("MINISTREAM_SITENAME"); ok {
|
|
||||||
c.SiteName = siteName
|
|
||||||
}
|
|
||||||
|
|
||||||
if httpAddr, ok := os.LookupEnv("MINISTREAM_HTTP_LISTENADDR"); ok {
|
|
||||||
c.HTTP.ListenAddr = httpAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
if value, ok := os.LookupEnv("MINISTREAM_WEBRTC_UDPMIN"); ok {
|
|
||||||
min, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
panic("MINISTREAM_WEBRTC_UDPMIN is invalid")
|
|
||||||
}
|
|
||||||
c.WebRTC.UDPMin = min
|
|
||||||
}
|
|
||||||
if value, ok := os.LookupEnv("MINISTREAM_WEBRTC_UDPMAX"); ok {
|
|
||||||
max, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
panic("MINISTREAM_WEBRTC_UDPMAX is invalid")
|
|
||||||
}
|
|
||||||
c.WebRTC.UDPMin = max
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigFromReader(r io.Reader) (*Config, error) {
|
|
||||||
var c Config
|
|
||||||
|
|
||||||
err := toml.NewDecoder(r).Decode(&c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigFromFile(path string) (*Config, error) {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return ConfigFromReader(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConfigFromDefault() (*Config, error) {
|
|
||||||
var config *Config
|
|
||||||
defaultPaths := []string{
|
|
||||||
"ministream.toml",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range defaultPaths {
|
|
||||||
c, err := ConfigFromFile(p)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
config = c
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if config == nil {
|
|
||||||
config = DefaultConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package server_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.t-juice.club/torjus/ministream/server"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfig(t *testing.T) {
|
|
||||||
t.Run("FromReader", func(t *testing.T) {
|
|
||||||
configString := `SiteName = "ministream.example.org"`
|
|
||||||
expectedSiteName := "ministream.example.org"
|
|
||||||
r := strings.NewReader(configString)
|
|
||||||
|
|
||||||
c, err := server.ConfigFromReader(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error reading config: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SiteName != expectedSiteName {
|
|
||||||
t.Errorf("SiteName incorrect. Got %s want %s", c.SiteName, expectedSiteName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.Run("OverrideFromEnv", func(t *testing.T) {
|
|
||||||
c := server.DefaultConfig()
|
|
||||||
expectedSiteName := "ms.example.org"
|
|
||||||
|
|
||||||
t.Setenv("MINISTREAM_SITENAME", expectedSiteName)
|
|
||||||
|
|
||||||
c.OverrideFromEnv()
|
|
||||||
|
|
||||||
if c.SiteName != expectedSiteName {
|
|
||||||
t.Errorf("SiteName incorrect. Got %s want %s", c.SiteName, expectedSiteName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -19,15 +19,15 @@ import (
|
|||||||
var static embed.FS
|
var static embed.FS
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
users *UserStore
|
||||||
streams *StreamStore
|
streams *StreamStore
|
||||||
config *Config
|
|
||||||
http.Server
|
http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(config *Config) *Server {
|
func NewServer(store *UserStore) *Server {
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
streams: NewStreamStore(config),
|
users: store,
|
||||||
config: config,
|
streams: NewStreamStore(),
|
||||||
}
|
}
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
@ -41,7 +41,6 @@ func NewServer(config *Config) *Server {
|
|||||||
r.Patch("/whip/{streamKey}", srv.PatchHandler)
|
r.Patch("/whip/{streamKey}", srv.PatchHandler)
|
||||||
r.Post("/whip/{streamKey}", srv.PostOfferHandler)
|
r.Post("/whip/{streamKey}", srv.PostOfferHandler)
|
||||||
r.Get("/stats", srv.streams.StatsHandler)
|
r.Get("/stats", srv.streams.StatsHandler)
|
||||||
r.Get("/api/siteinfo", srv.InfoHandler)
|
|
||||||
|
|
||||||
srv.Handler = r
|
srv.Handler = r
|
||||||
|
|
||||||
@ -56,17 +55,6 @@ func corsMiddleware(next http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(fn)
|
return http.HandlerFunc(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) InfoHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var infoResponse struct {
|
|
||||||
SiteName string `json:"siteName"`
|
|
||||||
}
|
|
||||||
infoResponse.SiteName = s.config.SiteName
|
|
||||||
|
|
||||||
if err := json.NewEncoder(w).Encode(&infoResponse); err != nil {
|
|
||||||
slog.Warn("Error writing info response")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) OptionsHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) OptionsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
slog.Info("Got OPTIONS")
|
slog.Info("Got OPTIONS")
|
||||||
}
|
}
|
||||||
|
@ -20,23 +20,19 @@ var ErrNoSuchStream error = fmt.Errorf("no such stream")
|
|||||||
|
|
||||||
type StreamStore struct {
|
type StreamStore struct {
|
||||||
Streams map[string]*Stream
|
Streams map[string]*Stream
|
||||||
|
|
||||||
config *Config
|
|
||||||
webRTCConfig webrtc.Configuration
|
webRTCConfig webrtc.Configuration
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStreamStore(config *Config) *StreamStore {
|
func NewStreamStore() *StreamStore {
|
||||||
s := &StreamStore{
|
s := &StreamStore{
|
||||||
Streams: make(map[string]*Stream),
|
Streams: make(map[string]*Stream),
|
||||||
config: config,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stream struct {
|
type Stream struct {
|
||||||
store *StreamStore
|
|
||||||
peerConnection *webrtc.PeerConnection
|
peerConnection *webrtc.PeerConnection
|
||||||
peerConnectionStats map[string]*stats.Stats
|
peerConnectionStats map[string]*stats.Stats
|
||||||
peerConnectionStatsMu sync.Mutex
|
peerConnectionStatsMu sync.Mutex
|
||||||
@ -100,10 +96,7 @@ func (s *Stream) AddListener(sd *webrtc.SessionDescription) (*webrtc.SessionDesc
|
|||||||
//i.Add(statsInterceptorFactory)
|
//i.Add(statsInterceptorFactory)
|
||||||
|
|
||||||
se := webrtc.SettingEngine{}
|
se := webrtc.SettingEngine{}
|
||||||
_ = se.SetEphemeralUDPPortRange(
|
_ = se.SetEphemeralUDPPortRange(50000, 50050)
|
||||||
uint16(s.store.config.WebRTC.UDPMin),
|
|
||||||
uint16(s.store.config.WebRTC.UDPMax),
|
|
||||||
)
|
|
||||||
|
|
||||||
webRTCConfig := webrtc.Configuration{
|
webRTCConfig := webrtc.Configuration{
|
||||||
ICEServers: []webrtc.ICEServer{
|
ICEServers: []webrtc.ICEServer{
|
||||||
@ -186,7 +179,6 @@ func (s *StreamStore) Add(streamKey string, sd *webrtc.SessionDescription) (*web
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
stream := &Stream{
|
stream := &Stream{
|
||||||
store: s,
|
|
||||||
lastUpdate: time.Now(),
|
lastUpdate: time.Now(),
|
||||||
peerConnectionStats: make(map[string]*stats.Stats),
|
peerConnectionStats: make(map[string]*stats.Stats),
|
||||||
}
|
}
|
||||||
@ -234,7 +226,7 @@ func (s *StreamStore) Add(streamKey string, sd *webrtc.SessionDescription) (*web
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
se := webrtc.SettingEngine{}
|
se := webrtc.SettingEngine{}
|
||||||
_ = se.SetEphemeralUDPPortRange(uint16(s.config.WebRTC.UDPMin), uint16(s.config.WebRTC.UDPMax))
|
_ = se.SetEphemeralUDPPortRange(50000, 50050)
|
||||||
|
|
||||||
// se.BufferFactory = func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser {
|
// se.BufferFactory = func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser {
|
||||||
// buf := packetio.NewBuffer()
|
// buf := packetio.NewBuffer()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user