livestream/main.go

156 lines
3.5 KiB
Go
Raw Normal View History

2023-06-13 00:07:14 +03:00
package main
import (
"embed"
"errors"
"fmt"
"log"
"net"
2023-06-13 00:07:14 +03:00
"net/http"
_ "embed"
"flag"
"math/rand"
"github.com/gin-gonic/gin"
"github.com/olahol/melody"
)
type PlayState = int
const (
NotPlaying PlayState = iota
PlayingFallback
PlayingVideo
)
type State struct {
VideoProcess ffmpegProcess
PlayState PlayState
CurrentFile string
Config
Websocket *melody.Melody
Users map[net.Addr]WebsocketUserInfo
}
type WebsocketUserInfo struct {
Username string
Color string
2023-06-13 00:07:14 +03:00
}
type Config struct {
OutDir string
FallbackFile string
MediaFolder string
IgnoreHiddenFiles bool
PublicPort int
2024-02-23 19:42:04 +02:00
AdminApiPort int
2023-06-13 00:07:14 +03:00
}
func (s *State) getRandomMediaFile() (foundFile string, err error) {
var files []string
files, err = listFilesRecursively(s.MediaFolder, s.IgnoreHiddenFiles)
if err != nil {
return
}
foundFile = files[rand.Intn(len(files))]
return
}
func newState(c Config) (state State, err error) {
state = State{
Config: c,
PlayState: NotPlaying,
Users: make(map[net.Addr]WebsocketUserInfo),
2023-06-13 00:07:14 +03:00
}
return
}
//go:embed html/*
var embedHTML embed.FS
var ErrMediaFolderNotSpecified = errors.New("Please specify media folder to use")
func parseFlags() (config Config, err error) {
flag.StringVar(&config.OutDir, "out", "stream_cache", "location to dump hls/m3u8 livestream files")
2023-06-13 00:07:14 +03:00
flag.StringVar(&config.FallbackFile, "fallback", "family_guy_wheel.mp4", "Fallback video that is played when main stream is down")
2024-02-23 19:42:04 +02:00
flag.StringVar(&config.MediaFolder, "media", "", "Folder to play random media from")
flag.IntVar(&config.PublicPort, "public_port", 3000, "Port to run public website on")
flag.IntVar(&config.AdminApiPort, "admin_api_port", 3001, "Port to run admin api on")
2023-06-13 00:07:14 +03:00
flag.BoolVar(&config.IgnoreHiddenFiles, "hidden_files", true, "Ignores hidden files in random media picking")
flag.Parse()
if config.MediaFolder == "" {
err = ErrMediaFolderNotSpecified
}
return
}
func main() {
config, err := parseFlags()
if err != nil {
log.Fatal(err)
}
2024-02-24 16:54:27 +02:00
if err = RecreateDir(config.OutDir); err != nil {
log.Fatal(err)
}
2023-06-13 00:07:14 +03:00
state, err := newState(config)
if err != nil {
log.Fatal(err)
}
state.setupWebsocket()
if err = state.playFallback(); err != nil {
log.Fatal(err)
}
public := gin.Default()
public.GET("/", func(c *gin.Context) {
ready, err := state.IsLivestreamReady()
if err != nil {
c.String(http.StatusInternalServerError, "Something went wrong")
return
}
if ready {
c.FileFromFS("html/stream.html", http.FS(embedHTML))
2023-06-13 00:07:14 +03:00
} else {
c.FileFromFS("html/loading.html", http.FS(embedHTML))
}
})
2023-06-13 00:07:14 +03:00
public.Static("/static", "static")
public.Static("/media", config.OutDir)
public.Any("/ws", func(c *gin.Context) {
state.Websocket.HandleRequest(c.Writer, c.Request)
})
publicAPI := public.Group("/api")
publicAPI.GET("now_playing", state.NowPlayingAPI)
publicAPI.GET("is_livestream_ready", state.IsLivestreamReadyAPI)
private := gin.Default()
// TODO: autoplay feature that keeps playing random things after current one ends
privateAPI := private.Group("/api")
privateAPI.GET("play", state.PlayAPI)
privateAPI.GET("random", state.PlayRandomAPI)
privateAPI.GET("stop", state.StopPlayingAPI)
privateAPI.GET("now_playing", state.NowPlayingAPI)
fmt.Println("Starting public server on port", fmt.Sprint(config.PublicPort))
go public.Run(fmt.Sprintf(":%d", config.PublicPort))
2024-02-23 19:42:04 +02:00
fmt.Println("Starting private server on port", fmt.Sprint(config.AdminApiPort))
log.Fatal(private.Run(fmt.Sprintf(":%d", config.AdminApiPort)))
2023-06-13 00:07:14 +03:00
}