127fb4c3fa
Rearranged how the RTMP and HTTP servers are started so they could be gracefully shutdown easier. Currently, only the HTTP server is gracefully shutdown. I'm unsure if it is even possible for the RTMP server. The documentation for the RTMP library is basically non-existent, so it's impossible to know for sure without diving into the library code.
186 lines
4.7 KiB
Go
186 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"time"
|
|
|
|
"github.com/gorilla/sessions"
|
|
"github.com/nareix/joy4/format"
|
|
"github.com/nareix/joy4/format/rtmp"
|
|
"github.com/zorchenhimer/MovieNight/common"
|
|
)
|
|
|
|
var (
|
|
pullEmotes bool
|
|
addr string
|
|
rtmpAddr string
|
|
sKey string
|
|
stats = newStreamStats()
|
|
sAdminPass string
|
|
confFile string
|
|
)
|
|
|
|
func setupSettings() error {
|
|
var err error
|
|
settings, err = LoadSettings(confFile)
|
|
if err != nil {
|
|
return fmt.Errorf("Unable to load settings: %s", err)
|
|
}
|
|
if len(settings.StreamKey) == 0 {
|
|
return fmt.Errorf("Missing stream key is settings.json")
|
|
}
|
|
|
|
if sAdminPass != "" {
|
|
fmt.Println("Password provided at runtime; ignoring password in set in settings.")
|
|
settings.AdminPassword = sAdminPass
|
|
}
|
|
|
|
sstore = sessions.NewCookieStore([]byte(settings.SessionKey))
|
|
sstore.Options = &sessions.Options{
|
|
Path: "/",
|
|
MaxAge: 60 * 60 * 24, // one day
|
|
SameSite: http.SameSiteStrictMode,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
flag.StringVar(&addr, "l", "", "host:port of the HTTP server")
|
|
flag.StringVar(&rtmpAddr, "r", "", "host:port of the RTMP server")
|
|
flag.StringVar(&sKey, "k", "", "Stream key, to protect your stream")
|
|
flag.StringVar(&sAdminPass, "a", "", "Set admin password. Overrides configuration in settings.json. This will not write the password to settings.json.")
|
|
flag.BoolVar(&pullEmotes, "e", false, "Pull emotes")
|
|
flag.StringVar(&confFile, "f", "./settings.json", "URI of the conf file")
|
|
flag.Parse()
|
|
|
|
format.RegisterAll()
|
|
|
|
if err := setupSettings(); err != nil {
|
|
fmt.Printf("Error loading settings: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if pullEmotes {
|
|
common.LogInfoln("Pulling emotes")
|
|
err := getEmotes(settings.ApprovedEmotes)
|
|
if err != nil {
|
|
common.LogErrorf("Error downloading emotes: %+v\n", err)
|
|
common.LogErrorf("Error downloading emotes: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
if err := common.InitTemplates(); err != nil {
|
|
common.LogErrorln(err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
exit := make(chan bool)
|
|
go handleInterrupt(exit)
|
|
|
|
// Load emotes before starting server.
|
|
var err error
|
|
chat, err = newChatRoom()
|
|
if err != nil {
|
|
common.LogErrorln(err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if addr == "" {
|
|
addr = settings.ListenAddress
|
|
}
|
|
|
|
if rtmpAddr == "" {
|
|
rtmpAddr = settings.RtmpListenAddress
|
|
}
|
|
|
|
// A stream key was passed on the command line. Use it, but don't save
|
|
// it over the stream key in the settings.json file.
|
|
if sKey != "" {
|
|
settings.SetTempKey(sKey)
|
|
}
|
|
|
|
common.LogInfoln("Stream key: ", settings.GetStreamKey())
|
|
common.LogInfoln("Admin password: ", settings.AdminPassword)
|
|
common.LogInfoln("HTTP server listening on: ", addr)
|
|
common.LogInfoln("RTMP server listening on: ", rtmpAddr)
|
|
common.LogInfoln("RoomAccess: ", settings.RoomAccess)
|
|
common.LogInfoln("RoomAccessPin: ", settings.RoomAccessPin)
|
|
|
|
rtmpServer := &rtmp.Server{
|
|
HandlePlay: handlePlay,
|
|
HandlePublish: handlePublish,
|
|
Addr: rtmpAddr,
|
|
}
|
|
|
|
router := http.NewServeMux()
|
|
|
|
router.HandleFunc("/ws", wsHandler) // Chat websocket
|
|
router.HandleFunc("/static/js/", wsStaticFiles)
|
|
router.HandleFunc("/static/css/", wsStaticFiles)
|
|
router.HandleFunc("/static/img/", wsImages)
|
|
router.HandleFunc("/static/main.wasm", wsWasmFile)
|
|
router.HandleFunc("/emotes/", wsEmotes)
|
|
router.HandleFunc("/favicon.ico", wsStaticFiles)
|
|
router.HandleFunc("/chat", handleIndexTemplate)
|
|
router.HandleFunc("/video", handleIndexTemplate)
|
|
router.HandleFunc("/help", handleHelpTemplate)
|
|
router.HandleFunc("/emotes", handleEmoteTemplate)
|
|
|
|
router.HandleFunc("/live", handleLive)
|
|
router.HandleFunc("/", handleDefault)
|
|
|
|
httpServer := &http.Server{
|
|
Addr: addr,
|
|
Handler: router,
|
|
}
|
|
|
|
// RTMP Server
|
|
go func() {
|
|
err := rtmpServer.ListenAndServe()
|
|
if err != nil {
|
|
// If the server cannot start, don't pretend we can continue.
|
|
panic("Error trying to start rtmp server: " + err.Error())
|
|
}
|
|
}()
|
|
|
|
// HTTP Server
|
|
go func() {
|
|
err := httpServer.ListenAndServe()
|
|
if err != nil && err != http.ErrServerClosed {
|
|
// If the server cannot start, don't pretend we can continue.
|
|
panic("Error trying to start chat/http server: " + err.Error())
|
|
}
|
|
}()
|
|
|
|
<-exit
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
if err := httpServer.Shutdown(ctx); err != nil && err != http.ErrServerClosed {
|
|
panic("Gracefull HTTP server shutdown failed: " + err.Error())
|
|
}
|
|
|
|
// I don't think the RTMP server can be shutdown cleanly. Apparently the author
|
|
// of joy4 want's everyone to use joy5, but that one doesn't seem to allow clean
|
|
// shutdowns either? Idk, the documentation on joy4 and joy5 are non-existent.
|
|
}
|
|
|
|
func handleInterrupt(exit chan bool) {
|
|
ch := make(chan os.Signal)
|
|
signal.Notify(ch, os.Interrupt)
|
|
<-ch
|
|
common.LogInfoln("Closing server")
|
|
if settings.StreamStats {
|
|
stats.Print()
|
|
}
|
|
exit <- true
|
|
}
|