2021-08-23 00:45:44 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
2022-01-13 14:09:42 +00:00
|
|
|
"git.t-juice.club/torjus/dogtamer/m3u"
|
2021-09-14 20:51:36 +00:00
|
|
|
"github.com/go-chi/chi/v5"
|
2021-11-01 20:31:24 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2021-08-23 00:45:44 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
type WebServer struct {
|
2021-11-01 20:31:24 +00:00
|
|
|
Logger *zap.SugaredLogger
|
|
|
|
EnableMetrics bool
|
|
|
|
ListenAddr string
|
|
|
|
ctx context.Context
|
|
|
|
rtmpServer *RTMPServer
|
|
|
|
httpServer *http.Server
|
2021-08-23 00:45:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewWebServer(ctx context.Context, rs *RTMPServer) *WebServer {
|
|
|
|
return &WebServer{
|
|
|
|
ctx: ctx,
|
|
|
|
rtmpServer: rs,
|
|
|
|
Logger: zap.NewNop().Sugar(),
|
2021-09-02 00:49:11 +00:00
|
|
|
ListenAddr: ":8077",
|
2021-08-23 00:45:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ws *WebServer) Serve() error {
|
2021-09-14 20:51:36 +00:00
|
|
|
r := chi.NewRouter()
|
|
|
|
r.Get("/", ws.IndexHandler)
|
2021-11-01 20:31:24 +00:00
|
|
|
if ws.EnableMetrics {
|
|
|
|
r.Handle("/metrics", promhttp.Handler())
|
|
|
|
}
|
2021-09-14 20:51:36 +00:00
|
|
|
r.Get("/playlist/{name}", ws.PlaylistHandler)
|
2021-08-23 00:45:44 +00:00
|
|
|
ws.httpServer = &http.Server{
|
2021-09-02 00:49:11 +00:00
|
|
|
Addr: ws.ListenAddr,
|
2021-09-14 20:51:36 +00:00
|
|
|
Handler: r,
|
2021-08-23 00:45:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
<-ws.ctx.Done()
|
2021-09-02 00:49:11 +00:00
|
|
|
ws.Logger.Debugw("HTTP shutdown signal received.")
|
2021-08-23 00:45:44 +00:00
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
_ = ws.httpServer.Shutdown(shutdownCtx)
|
|
|
|
}()
|
|
|
|
|
2021-09-02 00:49:11 +00:00
|
|
|
err := ws.httpServer.ListenAndServe()
|
|
|
|
if err != nil && err != http.ErrServerClosed {
|
|
|
|
ws.Logger.Warnw("HTTP Server stopped with error.", "error", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ws.Logger.Info("HTTP server stopped.")
|
|
|
|
|
|
|
|
return nil
|
2021-08-23 00:45:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ws *WebServer) IndexHandler(w http.ResponseWriter, r *http.Request) {
|
2021-09-14 20:51:36 +00:00
|
|
|
|
|
|
|
var data struct {
|
2021-08-23 00:45:44 +00:00
|
|
|
Streams []struct {
|
|
|
|
Name string
|
2021-09-14 20:51:36 +00:00
|
|
|
Path template.URL
|
2021-08-23 00:45:44 +00:00
|
|
|
}
|
2021-09-14 20:51:36 +00:00
|
|
|
}
|
|
|
|
streams := ws.rtmpServer.List()
|
|
|
|
for _, stream := range streams {
|
|
|
|
data.Streams = append(data.Streams, struct {
|
2021-08-23 00:45:44 +00:00
|
|
|
Name string
|
2021-09-14 20:51:36 +00:00
|
|
|
Path template.URL
|
|
|
|
}{Name: stream.Name, Path: template.URL(fmt.Sprintf("/playlist/%s", stream.Name))})
|
2021-08-23 00:45:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tmpl := template.Must(template.ParseFiles("server/templates/index.html"))
|
|
|
|
|
|
|
|
w.Header().Add("Content-Type", "text/html")
|
|
|
|
|
|
|
|
tmpl.Execute(w, data)
|
|
|
|
}
|
2021-09-14 20:51:36 +00:00
|
|
|
|
|
|
|
func (ws *WebServer) PlaylistHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
streamName := chi.URLParam(r, "name")
|
|
|
|
if streamName == "" {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
streamInfo, err := ws.rtmpServer.GetInfo(streamName)
|
|
|
|
if err != nil {
|
|
|
|
if err == ErrNoSuchItem {
|
|
|
|
ws.Logger.Debugw("Client requested non-existing playlist", "stream_name", streamName, "error", err)
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ws.Logger.Warnw("Error getting stream info", "stream_name", streamName, "error", err)
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var p m3u.Playlist
|
|
|
|
p.Add(&m3u.PlaylistItem{Name: streamInfo.Name, Time: -1, Path: streamInfo.Path})
|
|
|
|
|
|
|
|
w.Header().Add("Content-Type", "application/mpegurl")
|
|
|
|
w.Header().Add("Content-Disposition", "attachment; filename=stream.m3u")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
|
|
|
_, err = p.WriteTo(w)
|
|
|
|
if err != nil {
|
|
|
|
ws.Logger.Warnw("error generating playlist", "stream_name", streamName, "error", err)
|
|
|
|
}
|
|
|
|
}
|