Add various features
* HTTP Server * Config files
This commit is contained in:
@@ -9,8 +9,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/nareix/joy5/av"
|
||||
"github.com/nareix/joy5/format/rtmp"
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -21,9 +23,12 @@ type RTMPClient struct {
|
||||
stream *Stream
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
timestamp time.Time
|
||||
|
||||
keyframeSent bool
|
||||
|
||||
bytesSent atomic.Uint64
|
||||
|
||||
packetsChan chan av.Packet
|
||||
}
|
||||
|
||||
@@ -34,22 +39,25 @@ func NewRTMPClient(rs *RTMPServer, stream *Stream, c *rtmp.Conn, nc net.Conn) *R
|
||||
nc: nc,
|
||||
server: rs,
|
||||
stream: stream,
|
||||
packetsChan: make(chan av.Packet, 10),
|
||||
packetsChan: make(chan av.Packet, 100),
|
||||
ctx: ctx,
|
||||
ctxCancel: cancel,
|
||||
timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
type Stream struct {
|
||||
Name string
|
||||
Clients []*RTMPClient
|
||||
clientsLock sync.Mutex
|
||||
server *RTMPServer
|
||||
nc net.Conn
|
||||
c *rtmp.Conn
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
startTime time.Time
|
||||
Name string
|
||||
Clients []*RTMPClient
|
||||
clientsLock sync.Mutex
|
||||
server *RTMPServer
|
||||
nc net.Conn
|
||||
c *rtmp.Conn
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
startTime time.Time
|
||||
bytesReceived atomic.Uint64
|
||||
bytesSent atomic.Uint64
|
||||
|
||||
metadataPacket *av.Packet
|
||||
h264DecoderConfigPacket *av.Packet
|
||||
@@ -91,7 +99,10 @@ func (s *Stream) handleBroadcaster(c *rtmp.Conn, nc net.Conn) {
|
||||
}
|
||||
// Once all clients have been removed, remove stream from server
|
||||
s.server.RemoveStream(s)
|
||||
s.server.Logger.Infow("Stream has been successfully stopped.", "stream_name", s.Name, "duration_seconds", time.Since(s.startTime))
|
||||
s.server.Logger.Infow("Stream has been successfully stopped.",
|
||||
"stream_name", s.Name,
|
||||
"stream_duration", time.Since(s.startTime),
|
||||
"bytes_received", humanize.Bytes(s.bytesReceived.Load()))
|
||||
}()
|
||||
|
||||
for {
|
||||
@@ -119,6 +130,10 @@ func (s *Stream) handleBroadcaster(c *rtmp.Conn, nc net.Conn) {
|
||||
}
|
||||
nc.Close()
|
||||
}
|
||||
|
||||
// Increase received counter
|
||||
s.bytesReceived.Add(uint64(len(pkt.Data)))
|
||||
|
||||
switch pkt.Type {
|
||||
case av.Metadata:
|
||||
s.server.Logger.Debugw("Got metadata packet for stream.", "stream_name", s.Name)
|
||||
@@ -138,9 +153,10 @@ func (s *Stream) handleBroadcaster(c *rtmp.Conn, nc net.Conn) {
|
||||
for _, client := range s.Clients {
|
||||
select {
|
||||
case client.packetsChan <- pkt:
|
||||
s.bytesSent.Add(uint64(len(pkt.Data)))
|
||||
continue
|
||||
default:
|
||||
logger.Info("Client channel is blocking.")
|
||||
logger.Info("Client channel is blocking.", zap.String("client_addr", client.nc.RemoteAddr().String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,10 +170,14 @@ func (client *RTMPClient) handleClient() {
|
||||
defer func() {
|
||||
client.nc.Close()
|
||||
stream.RemoveClient(client)
|
||||
server.Logger.Infow("Disconnected viewer.", "client_addr", client.nc.RemoteAddr().String(), "stream_name", stream.Name)
|
||||
server.Logger.Infow("Disconnected viewer.",
|
||||
"client_addr", client.nc.RemoteAddr().String(),
|
||||
"stream_name", stream.Name,
|
||||
"watch_duration", time.Since(client.timestamp),
|
||||
"bytes_sent", humanize.Bytes(client.bytesSent.Load()))
|
||||
}()
|
||||
|
||||
server.Logger.Infow("Viewer connected.", "stream", stream.Name, "remote_addr", client.nc.RemoteAddr().String())
|
||||
server.Logger.Infow("Viewer connected.", "stream", stream.Name, "client_addr", client.nc.RemoteAddr().String())
|
||||
// First ensure that metadata, and decoder configs are set
|
||||
// TODO: Ensure stream isnt cancelled before sending metadata and decoder configs
|
||||
for stream.aacDecoderConfigPacket == nil || stream.metadataPacket == nil || stream.h264DecoderConfigPacket == nil {
|
||||
@@ -189,11 +209,11 @@ func (client *RTMPClient) handleClient() {
|
||||
// Wait for keyframe before sending video
|
||||
if !pkt.IsKeyFrame && !client.keyframeSent {
|
||||
continue
|
||||
} else {
|
||||
} else if !client.keyframeSent {
|
||||
// Assume that the packet will get sent
|
||||
client.keyframeSent = true
|
||||
server.Logger.Debugw("Got keyframe. Sending video.", "client_addr", client.nc.RemoteAddr().String(), "stream_name", stream.Name)
|
||||
}
|
||||
server.Logger.Debugw("Got keyframe. Sending video.", "client_addr", client.nc.RemoteAddr().String(), "stream_name", stream.Name)
|
||||
|
||||
client.nc.SetWriteDeadline(time.Now().Add(1 * time.Second))
|
||||
if err := client.c.WritePacket(pkt); err != nil {
|
||||
@@ -212,6 +232,7 @@ func (client *RTMPClient) handleClient() {
|
||||
server.Logger.Debugw("Error while writing packet to viewer.", "client_addr", client.nc.RemoteAddr().String(), "error_message", err)
|
||||
return
|
||||
}
|
||||
client.bytesSent.Add(uint64(len(pkt.Data)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +295,7 @@ func (rs *RTMPServer) Listen() error {
|
||||
for len(rs.streams) > 0 {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
rs.Logger.Infow("Server stopped.")
|
||||
rs.Logger.Infow("RTMP server stopped.")
|
||||
return nil
|
||||
}
|
||||
// Connection not closed, wait and try again
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
type WebServer struct {
|
||||
Logger *zap.SugaredLogger
|
||||
ListenAddr string
|
||||
ctx context.Context
|
||||
rtmpServer *RTMPServer
|
||||
httpServer *http.Server
|
||||
@@ -22,23 +23,32 @@ func NewWebServer(ctx context.Context, rs *RTMPServer) *WebServer {
|
||||
ctx: ctx,
|
||||
rtmpServer: rs,
|
||||
Logger: zap.NewNop().Sugar(),
|
||||
ListenAddr: ":8077",
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *WebServer) Serve() error {
|
||||
ws.httpServer = &http.Server{
|
||||
Addr: ":8077",
|
||||
Addr: ws.ListenAddr,
|
||||
Handler: http.HandlerFunc(ws.IndexHandler),
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ws.ctx.Done()
|
||||
ws.Logger.Debugw("HTTP shutdown signal received.")
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = ws.httpServer.Shutdown(shutdownCtx)
|
||||
}()
|
||||
|
||||
return ws.httpServer.ListenAndServe()
|
||||
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
|
||||
}
|
||||
|
||||
func (ws *WebServer) IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
Reference in New Issue
Block a user