Add playlist-generation

This commit is contained in:
2021-09-14 22:51:36 +02:00
parent 6e15d9835c
commit 584ffffe83
10 changed files with 192 additions and 23 deletions

View File

@@ -3,6 +3,7 @@ package server
import (
"context"
"errors"
"fmt"
"io"
"net"
"regexp"
@@ -16,6 +17,8 @@ import (
"go.uber.org/zap"
)
var ErrNoSuchItem error = fmt.Errorf("no such stream")
type RTMPClient struct {
c *rtmp.Conn
nc net.Conn
@@ -238,6 +241,7 @@ func (client *RTMPClient) handleClient() {
type RTMPServer struct {
ListenAddr string
Hostname string
Logger *zap.SugaredLogger
streamsLock sync.Mutex
streams map[string]*Stream
@@ -248,6 +252,7 @@ type RTMPServer struct {
func NewRTMPServer(ctx context.Context, addr string) *RTMPServer {
serverCtx, cancel := context.WithCancel(ctx)
rs := &RTMPServer{
Hostname: "localhost",
ListenAddr: addr,
Logger: zap.NewNop().Sugar(),
streams: make(map[string]*Stream),
@@ -358,3 +363,31 @@ func (rs *RTMPServer) handleConn(c *rtmp.Conn, nc net.Conn) {
// Stream URL is invalid, disconnect
nc.Close()
}
type StreamInfo struct {
Name string
Path string
}
func (s *RTMPServer) List() []*StreamInfo {
var results []*StreamInfo
_, port, _ := net.SplitHostPort(s.ListenAddr)
for _, stream := range s.streams {
results = append(results, &StreamInfo{Name: stream.Name, Path: fmt.Sprintf("rtmp://%s:%s/view/%s", s.Hostname, port, stream.Name)})
}
return results
}
func (s *RTMPServer) GetInfo(name string) (*StreamInfo, error) {
stream, ok := s.streams[name]
if !ok {
return nil, ErrNoSuchItem
}
_, port, _ := net.SplitHostPort(s.ListenAddr)
return &StreamInfo{
Name: stream.Name,
Path: fmt.Sprintf("rtmp://%s:%s/view/%s", s.Hostname, port, stream.Name),
}, nil
}

View File

@@ -1,8 +1,10 @@
<DOCTYPE html>
<html>
<html>
<ol>
{{ range .Streams }}
<li>{{ .URL }}</li>
{{ end }}
{{ range .Streams }}
<li>
<a href="{{ .Path }}">{{ .Name }}</a>
</li>
{{ end }}
</ol>
</html>

View File

@@ -7,6 +7,8 @@ import (
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.uio.no/torjus/dogtamer/m3u"
"go.uber.org/zap"
)
@@ -28,9 +30,12 @@ func NewWebServer(ctx context.Context, rs *RTMPServer) *WebServer {
}
func (ws *WebServer) Serve() error {
r := chi.NewRouter()
r.Get("/", ws.IndexHandler)
r.Get("/playlist/{name}", ws.PlaylistHandler)
ws.httpServer = &http.Server{
Addr: ws.ListenAddr,
Handler: http.HandlerFunc(ws.IndexHandler),
Handler: r,
}
go func() {
@@ -52,22 +57,19 @@ func (ws *WebServer) Serve() error {
}
func (ws *WebServer) IndexHandler(w http.ResponseWriter, r *http.Request) {
data := struct {
var data struct {
Streams []struct {
Name string
URL template.URL
Path template.URL
}
}{}
for _, s := range ws.rtmpServer.streams {
stream := struct {
}
streams := ws.rtmpServer.List()
for _, stream := range streams {
data.Streams = append(data.Streams, struct {
Name string
URL template.URL
}{
Name: s.Name,
URL: template.URL(fmt.Sprintf("rtmp://localhost:5566/view/%s", s.Name)),
}
data.Streams = append(data.Streams, stream)
Path template.URL
}{Name: stream.Name, Path: template.URL(fmt.Sprintf("/playlist/%s", stream.Name))})
}
tmpl := template.Must(template.ParseFiles("server/templates/index.html"))
@@ -76,3 +78,34 @@ func (ws *WebServer) IndexHandler(w http.ResponseWriter, r *http.Request) {
tmpl.Execute(w, data)
}
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)
}
}