forked from External/mediamtx
implement log levels; print requests and responses when log level is "debug" (#116)
This commit is contained in:
parent
18fe2d057c
commit
74b592b211
18 changed files with 393 additions and 269 deletions
2
go.mod
2
go.mod
|
|
@ -5,7 +5,7 @@ go 1.15
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
|
||||||
github.com/aler9/gortsplib v0.0.0-20201206210440-3631309f9fc3
|
github.com/aler9/gortsplib v0.0.0-20201208105438-07aefbcd5d11
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -2,8 +2,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/aler9/gortsplib v0.0.0-20201206210440-3631309f9fc3 h1:ord/MYU8Re5dW06oqggQjC0AYrzfGhycWDr8WtJq0H8=
|
github.com/aler9/gortsplib v0.0.0-20201208105438-07aefbcd5d11 h1:as97tV7XyNJurmD1e3iT0AcgxeIwRa+nwMm10gi0vO0=
|
||||||
github.com/aler9/gortsplib v0.0.0-20201206210440-3631309f9fc3/go.mod h1:8P09VjpiPJFyfkVosyF5/TY82jNwkMN165NS/7sc32I=
|
github.com/aler9/gortsplib v0.0.0-20201208105438-07aefbcd5d11/go.mod h1:8P09VjpiPJFyfkVosyF5/TY82jNwkMN165NS/7sc32I=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
|
|
||||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
|
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/serverudp"
|
"github.com/aler9/rtsp-simple-server/internal/serverudp"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/stats"
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
@ -88,7 +89,7 @@ type Path interface {
|
||||||
|
|
||||||
// Parent is implemented by clientman.ClientMan.
|
// Parent is implemented by clientman.ClientMan.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
OnClientClose(*Client)
|
OnClientClose(*Client)
|
||||||
OnClientDescribe(*Client, string, *base.Request) (Path, error)
|
OnClientDescribe(*Client, string, *base.Request) (Path, error)
|
||||||
OnClientAnnounce(*Client, string, gortsplib.Tracks, *base.Request) (Path, error)
|
OnClientAnnounce(*Client, string, gortsplib.Tracks, *base.Request) (Path, error)
|
||||||
|
|
@ -162,7 +163,7 @@ func New(
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddInt64(c.stats.CountClients, 1)
|
atomic.AddInt64(c.stats.CountClients, 1)
|
||||||
c.log("connected")
|
c.log(logger.Info, "connected")
|
||||||
|
|
||||||
c.wg.Add(1)
|
c.wg.Add(1)
|
||||||
go c.run()
|
go c.run()
|
||||||
|
|
@ -178,8 +179,8 @@ func (c *Client) Close() {
|
||||||
// IsSource implements path.source.
|
// IsSource implements path.source.
|
||||||
func (c *Client) IsSource() {}
|
func (c *Client) IsSource() {}
|
||||||
|
|
||||||
func (c *Client) log(format string, args ...interface{}) {
|
func (c *Client) log(level logger.Level, format string, args ...interface{}) {
|
||||||
c.parent.Log("[client %s] "+format, append([]interface{}{c.conn.NetConn().RemoteAddr().String()}, args...)...)
|
c.parent.Log(level, "[client %s] "+format, append([]interface{}{c.conn.NetConn().RemoteAddr().String()}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ip() net.IP {
|
func (c *Client) ip() net.IP {
|
||||||
|
|
@ -198,7 +199,7 @@ var errStateInitial = errors.New("initial")
|
||||||
|
|
||||||
func (c *Client) run() {
|
func (c *Client) run() {
|
||||||
defer c.wg.Done()
|
defer c.wg.Done()
|
||||||
defer c.log("disconnected")
|
defer c.log(logger.Info, "disconnected")
|
||||||
|
|
||||||
if c.runOnConnect != "" {
|
if c.runOnConnect != "" {
|
||||||
onConnectCmd := externalcmd.New(c.runOnConnect, c.runOnConnectRestart, externalcmd.Environment{
|
onConnectCmd := externalcmd.New(c.runOnConnect, c.runOnConnectRestart, externalcmd.Environment{
|
||||||
|
|
@ -243,7 +244,7 @@ func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{
|
||||||
ip := c.ip()
|
ip := c.ip()
|
||||||
|
|
||||||
if !ipEqualOrInRange(ip, ips) {
|
if !ipEqualOrInRange(ip, ips) {
|
||||||
c.log("ERR: ip '%s' not allowed", ip)
|
c.log(logger.Info, "ERR: ip '%s' not allowed", ip)
|
||||||
|
|
||||||
return errAuthCritical{&base.Response{
|
return errAuthCritical{&base.Response{
|
||||||
StatusCode: base.StatusUnauthorized,
|
StatusCode: base.StatusUnauthorized,
|
||||||
|
|
@ -274,7 +275,7 @@ func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{
|
||||||
// 4) with password and username
|
// 4) with password and username
|
||||||
// therefore we must allow up to 3 failures
|
// therefore we must allow up to 3 failures
|
||||||
if c.authFailures > 3 {
|
if c.authFailures > 3 {
|
||||||
c.log("ERR: unauthorized: %s", err)
|
c.log(logger.Info, "ERR: unauthorized: %s", err)
|
||||||
|
|
||||||
return errAuthCritical{&base.Response{
|
return errAuthCritical{&base.Response{
|
||||||
StatusCode: base.StatusUnauthorized,
|
StatusCode: base.StatusUnauthorized,
|
||||||
|
|
@ -286,7 +287,7 @@ func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.authFailures > 1 {
|
if c.authFailures > 1 {
|
||||||
c.log("WARN: unauthorized: %s", err)
|
c.log(logger.Debug, "WARN: unauthorized: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errAuthNotCritical{&base.Response{
|
return errAuthNotCritical{&base.Response{
|
||||||
|
|
@ -318,10 +319,15 @@ func (c *Client) checkState(allowed map[state]struct{}) error {
|
||||||
allowedList, c.state)
|
allowedList, c.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) writeResError(cseq base.HeaderValue, code base.StatusCode, err error) {
|
func (c *Client) writeRes(res *base.Response) {
|
||||||
c.log("ERR: %s", err)
|
c.log(logger.Debug, "s->c %v", res)
|
||||||
|
c.conn.WriteResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&base.Response{
|
func (c *Client) writeResError(cseq base.HeaderValue, code base.StatusCode, err error) {
|
||||||
|
c.log(logger.Info, "ERR: %s", err)
|
||||||
|
|
||||||
|
c.writeRes(&base.Response{
|
||||||
StatusCode: code,
|
StatusCode: code,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -330,7 +336,7 @@ func (c *Client) writeResError(cseq base.HeaderValue, code base.StatusCode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) handleRequest(req *base.Request) error {
|
func (c *Client) handleRequest(req *base.Request) error {
|
||||||
c.log(string(req.Method))
|
c.log(logger.Debug, "[c->s] %v", req)
|
||||||
|
|
||||||
cseq, ok := req.Header["CSeq"]
|
cseq, ok := req.Header["CSeq"]
|
||||||
if !ok || len(cseq) != 1 {
|
if !ok || len(cseq) != 1 {
|
||||||
|
|
@ -340,7 +346,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case base.Options:
|
case base.Options:
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -360,7 +366,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
|
|
||||||
// GET_PARAMETER is used like a ping
|
// GET_PARAMETER is used like a ping
|
||||||
case base.GetParameter:
|
case base.GetParameter:
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -392,12 +398,12 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
switch terr := err.(type) {
|
switch terr := err.(type) {
|
||||||
case errAuthNotCritical:
|
case errAuthNotCritical:
|
||||||
close(c.describeData)
|
close(c.describeData)
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.writeRes(terr.Response)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case errAuthCritical:
|
case errAuthCritical:
|
||||||
close(c.describeData)
|
close(c.describeData)
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.writeRes(terr.Response)
|
||||||
return errStateTerminate
|
return errStateTerminate
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -462,11 +468,11 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch terr := err.(type) {
|
switch terr := err.(type) {
|
||||||
case errAuthNotCritical:
|
case errAuthNotCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.writeRes(terr.Response)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case errAuthCritical:
|
case errAuthCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.writeRes(terr.Response)
|
||||||
return errStateTerminate
|
return errStateTerminate
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -483,7 +489,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
c.path = path
|
c.path = path
|
||||||
c.state = statePreRecord
|
c.state = statePreRecord
|
||||||
|
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -560,11 +566,11 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch terr := err.(type) {
|
switch terr := err.(type) {
|
||||||
case errAuthNotCritical:
|
case errAuthNotCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.writeRes(terr.Response)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case errAuthCritical:
|
case errAuthCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.writeRes(terr.Response)
|
||||||
return errStateTerminate
|
return errStateTerminate
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -592,7 +598,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
ServerPorts: &[2]int{c.serverUDPRtp.Port(), c.serverUDPRtcp.Port()},
|
ServerPorts: &[2]int{c.serverUDPRtp.Port(), c.serverUDPRtcp.Port()},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -618,11 +624,11 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch terr := err.(type) {
|
switch terr := err.(type) {
|
||||||
case errAuthNotCritical:
|
case errAuthNotCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.writeRes(terr.Response)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case errAuthCritical:
|
case errAuthCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.writeRes(terr.Response)
|
||||||
return errStateTerminate
|
return errStateTerminate
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -647,7 +653,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
InterleavedIds: &interleavedIds,
|
InterleavedIds: &interleavedIds,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -709,7 +715,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
ServerPorts: &[2]int{c.serverUDPRtp.Port(), c.serverUDPRtcp.Port()},
|
ServerPorts: &[2]int{c.serverUDPRtp.Port(), c.serverUDPRtcp.Port()},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -760,7 +766,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
InterleavedIds: &interleavedIds,
|
InterleavedIds: &interleavedIds,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -810,7 +816,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
// write response before setting state
|
// write response before setting state
|
||||||
// otherwise, in case of TCP connections, RTP packets could be sent
|
// otherwise, in case of TCP connections, RTP packets could be sent
|
||||||
// before the response
|
// before the response
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -851,7 +857,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
return errStateTerminate
|
return errStateTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -872,7 +878,7 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
return errStateTerminate
|
return errStateTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
|
|
@ -928,7 +934,7 @@ func (c *Client) runInitial() bool {
|
||||||
default:
|
default:
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
if err != io.EOF && err != errStateTerminate {
|
if err != io.EOF && err != errStateTerminate {
|
||||||
c.log("ERR: %s", err)
|
c.log(logger.Info, "ERR: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.parent.OnClientClose(c)
|
c.parent.OnClientClose(c)
|
||||||
|
|
@ -959,7 +965,7 @@ func (c *Client) runWaitingDescribe() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.redirect != "" {
|
if res.redirect != "" {
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusMovedPermanently,
|
StatusCode: base.StatusMovedPermanently,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": c.describeCSeq,
|
"CSeq": c.describeCSeq,
|
||||||
|
|
@ -969,7 +975,7 @@ func (c *Client) runWaitingDescribe() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&base.Response{
|
c.writeRes(&base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": c.describeCSeq,
|
"CSeq": c.describeCSeq,
|
||||||
|
|
@ -1006,7 +1012,7 @@ func (c *Client) runPlay() bool {
|
||||||
c.state = statePlay
|
c.state = statePlay
|
||||||
c.path.OnClientPlay(c)
|
c.path.OnClientPlay(c)
|
||||||
|
|
||||||
c.log("is reading from path '%s', %d %s with %s", c.path.Name(), len(c.streamTracks), func() string {
|
c.log(logger.Info, "is reading from path '%s', %d %s with %s", c.path.Name(), len(c.streamTracks), func() string {
|
||||||
if len(c.streamTracks) == 1 {
|
if len(c.streamTracks) == 1 {
|
||||||
return "track"
|
return "track"
|
||||||
}
|
}
|
||||||
|
|
@ -1059,7 +1065,7 @@ func (c *Client) runPlayUDP() bool {
|
||||||
|
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
if err != io.EOF && err != errStateTerminate {
|
if err != io.EOF && err != errStateTerminate {
|
||||||
c.log("ERR: %s", err)
|
c.log(logger.Info, "ERR: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.path.OnClientRemove(c)
|
c.path.OnClientRemove(c)
|
||||||
|
|
@ -1139,7 +1145,7 @@ func (c *Client) runPlayTCP() bool {
|
||||||
|
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
if err != io.EOF && err != errStateTerminate {
|
if err != io.EOF && err != errStateTerminate {
|
||||||
c.log("ERR: %s", err)
|
c.log(logger.Info, "ERR: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.path.OnClientRemove(c)
|
c.path.OnClientRemove(c)
|
||||||
|
|
@ -1189,7 +1195,7 @@ func (c *Client) runRecord() bool {
|
||||||
c.state = stateRecord
|
c.state = stateRecord
|
||||||
c.path.OnClientRecord(c)
|
c.path.OnClientRecord(c)
|
||||||
|
|
||||||
c.log("is publishing to path '%s', %d %s with %s", c.path.Name(), len(c.streamTracks), func() string {
|
c.log(logger.Info, "is publishing to path '%s', %d %s with %s", c.path.Name(), len(c.streamTracks), func() string {
|
||||||
if len(c.streamTracks) == 1 {
|
if len(c.streamTracks) == 1 {
|
||||||
return "track"
|
return "track"
|
||||||
}
|
}
|
||||||
|
|
@ -1285,7 +1291,7 @@ func (c *Client) runRecordUDP() bool {
|
||||||
|
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
if err != io.EOF && err != errStateTerminate {
|
if err != io.EOF && err != errStateTerminate {
|
||||||
c.log("ERR: %s", err)
|
c.log(logger.Info, "ERR: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, track := range c.streamTracks {
|
for _, track := range c.streamTracks {
|
||||||
|
|
@ -1328,7 +1334,7 @@ func (c *Client) runRecordUDP() bool {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
c.log("ERR: no packets received recently (maybe there's a firewall/NAT in between)")
|
c.log(logger.Info, "ERR: no packets received recently (maybe there's a firewall/NAT in between)")
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
<-readerDone
|
<-readerDone
|
||||||
|
|
||||||
|
|
@ -1426,7 +1432,7 @@ func (c *Client) runRecordTCP() bool {
|
||||||
|
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
if err != io.EOF && err != errStateTerminate {
|
if err != io.EOF && err != errStateTerminate {
|
||||||
c.log("ERR: %s", err)
|
c.log(logger.Info, "ERR: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.path.OnClientRemove(c)
|
c.path.OnClientRemove(c)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/aler9/gortsplib/pkg/base"
|
"github.com/aler9/gortsplib/pkg/base"
|
||||||
|
|
||||||
"github.com/aler9/rtsp-simple-server/internal/client"
|
"github.com/aler9/rtsp-simple-server/internal/client"
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/pathman"
|
"github.com/aler9/rtsp-simple-server/internal/pathman"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/servertcp"
|
"github.com/aler9/rtsp-simple-server/internal/servertcp"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/serverudp"
|
"github.com/aler9/rtsp-simple-server/internal/serverudp"
|
||||||
|
|
@ -16,7 +17,7 @@ import (
|
||||||
|
|
||||||
// Parent is implemented by program.
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientManager is a client.Client manager.
|
// ClientManager is a client.Client manager.
|
||||||
|
|
@ -87,8 +88,8 @@ func (cm *ClientManager) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log is the main logging function.
|
// Log is the main logging function.
|
||||||
func (cm *ClientManager) Log(format string, args ...interface{}) {
|
func (cm *ClientManager) Log(level logger.Level, format string, args ...interface{}) {
|
||||||
cm.parent.Log(format, args...)
|
cm.parent.Log(level, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ClientManager) run() {
|
func (cm *ClientManager) run() {
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,16 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/aler9/rtsp-simple-server/internal/confenv"
|
"github.com/aler9/rtsp-simple-server/internal/confenv"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/loghandler"
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Conf is the main program configuration.
|
// Conf is the main program configuration.
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
|
LogLevel string `yaml:"logLevel"`
|
||||||
|
LogLevelParsed logger.Level `yaml:"-" json:"-"`
|
||||||
|
LogDestinations []string `yaml:"logDestinations"`
|
||||||
|
LogDestinationsParsed map[logger.Destination]struct{} `yaml:"-" json:"-"`
|
||||||
|
LogFile string `yaml:"logFile"`
|
||||||
Protocols []string `yaml:"protocols"`
|
Protocols []string `yaml:"protocols"`
|
||||||
ProtocolsParsed map[gortsplib.StreamProtocol]struct{} `yaml:"-" json:"-"`
|
ProtocolsParsed map[gortsplib.StreamProtocol]struct{} `yaml:"-" json:"-"`
|
||||||
RtspPort int `yaml:"rtspPort"`
|
RtspPort int `yaml:"rtspPort"`
|
||||||
|
|
@ -28,13 +33,49 @@ type Conf struct {
|
||||||
AuthMethodsParsed []headers.AuthMethod `yaml:"-" json:"-"`
|
AuthMethodsParsed []headers.AuthMethod `yaml:"-" json:"-"`
|
||||||
Metrics bool `yaml:"metrics"`
|
Metrics bool `yaml:"metrics"`
|
||||||
Pprof bool `yaml:"pprof"`
|
Pprof bool `yaml:"pprof"`
|
||||||
LogDestinations []string `yaml:"logDestinations"`
|
|
||||||
LogDestinationsParsed map[loghandler.Destination]struct{} `yaml:"-" json:"-"`
|
|
||||||
LogFile string `yaml:"logFile"`
|
|
||||||
Paths map[string]*PathConf `yaml:"paths"`
|
Paths map[string]*PathConf `yaml:"paths"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *Conf) fillAndCheck() error {
|
func (conf *Conf) fillAndCheck() error {
|
||||||
|
switch conf.LogLevel {
|
||||||
|
case "warn":
|
||||||
|
conf.LogLevelParsed = logger.Warn
|
||||||
|
|
||||||
|
case "", "info":
|
||||||
|
conf.LogLevelParsed = logger.Info
|
||||||
|
|
||||||
|
case "debug":
|
||||||
|
conf.LogLevelParsed = logger.Debug
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported log level: %s", conf.LogLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(conf.LogDestinations) == 0 {
|
||||||
|
conf.LogDestinations = []string{"stdout"}
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.LogDestinationsParsed = make(map[logger.Destination]struct{})
|
||||||
|
for _, dest := range conf.LogDestinations {
|
||||||
|
switch dest {
|
||||||
|
case "stdout":
|
||||||
|
conf.LogDestinationsParsed[logger.DestinationStdout] = struct{}{}
|
||||||
|
|
||||||
|
case "file":
|
||||||
|
conf.LogDestinationsParsed[logger.DestinationFile] = struct{}{}
|
||||||
|
|
||||||
|
case "syslog":
|
||||||
|
conf.LogDestinationsParsed[logger.DestinationSyslog] = struct{}{}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported log destination: %s", dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.LogFile == "" {
|
||||||
|
conf.LogFile = "rtsp-simple-server.log"
|
||||||
|
}
|
||||||
|
|
||||||
if len(conf.Protocols) == 0 {
|
if len(conf.Protocols) == 0 {
|
||||||
conf.Protocols = []string{"udp", "tcp"}
|
conf.Protocols = []string{"udp", "tcp"}
|
||||||
}
|
}
|
||||||
|
|
@ -94,29 +135,6 @@ func (conf *Conf) fillAndCheck() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conf.LogDestinations) == 0 {
|
|
||||||
conf.LogDestinations = []string{"stdout"}
|
|
||||||
}
|
|
||||||
conf.LogDestinationsParsed = make(map[loghandler.Destination]struct{})
|
|
||||||
for _, dest := range conf.LogDestinations {
|
|
||||||
switch dest {
|
|
||||||
case "stdout":
|
|
||||||
conf.LogDestinationsParsed[loghandler.DestinationStdout] = struct{}{}
|
|
||||||
|
|
||||||
case "file":
|
|
||||||
conf.LogDestinationsParsed[loghandler.DestinationFile] = struct{}{}
|
|
||||||
|
|
||||||
case "syslog":
|
|
||||||
conf.LogDestinationsParsed[loghandler.DestinationSyslog] = struct{}{}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported log destination: %s", dest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if conf.LogFile == "" {
|
|
||||||
conf.LogFile = "rtsp-simple-server.log"
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(conf.Paths) == 0 {
|
if len(conf.Paths) == 0 {
|
||||||
conf.Paths = map[string]*PathConf{
|
conf.Paths = map[string]*PathConf{
|
||||||
"all": {},
|
"all": {},
|
||||||
|
|
|
||||||
160
internal/logger/logger.go
Normal file
160
internal/logger/logger.go
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Level is a log level.
|
||||||
|
type Level int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Debug Level = iota
|
||||||
|
Info
|
||||||
|
Warn
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log levels.
|
||||||
|
|
||||||
|
// Destination is a log destination.
|
||||||
|
type Destination int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DestinationStdout writes logs to the standard output.
|
||||||
|
DestinationStdout Destination = iota
|
||||||
|
|
||||||
|
// DestinationFile writes logs to a file.
|
||||||
|
DestinationFile
|
||||||
|
|
||||||
|
// DestinationSyslog writes logs to the system logger.
|
||||||
|
DestinationSyslog
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger is a log handler.
|
||||||
|
type Logger struct {
|
||||||
|
level Level
|
||||||
|
destinations map[Destination]struct{}
|
||||||
|
mutex sync.Mutex
|
||||||
|
buffer []byte
|
||||||
|
|
||||||
|
file *os.File
|
||||||
|
syslog io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// New allocates a log handler.
|
||||||
|
func New(level Level, destinations map[Destination]struct{}, filePath string) (*Logger, error) {
|
||||||
|
lh := &Logger{
|
||||||
|
level: level,
|
||||||
|
destinations: destinations,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := destinations[DestinationFile]; ok {
|
||||||
|
var err error
|
||||||
|
lh.file, err = os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
lh.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := destinations[DestinationSyslog]; ok {
|
||||||
|
var err error
|
||||||
|
lh.syslog, err = syslog.New("rtsp-simple-server")
|
||||||
|
if err != nil {
|
||||||
|
lh.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a log handler.
|
||||||
|
func (lh *Logger) Close() {
|
||||||
|
if lh.file != nil {
|
||||||
|
lh.file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if lh.syslog != nil {
|
||||||
|
lh.syslog.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://golang.org/src/log/log.go#L78
|
||||||
|
func itoa(buf *[]byte, i int, wid int) {
|
||||||
|
// Assemble decimal in reverse order.
|
||||||
|
var b [20]byte
|
||||||
|
bp := len(b) - 1
|
||||||
|
for i >= 10 || wid > 1 {
|
||||||
|
wid--
|
||||||
|
q := i / 10
|
||||||
|
b[bp] = byte('0' + i - q*10)
|
||||||
|
bp--
|
||||||
|
i = q
|
||||||
|
}
|
||||||
|
// i < 10
|
||||||
|
b[bp] = byte('0' + i)
|
||||||
|
*buf = append(*buf, b[bp:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lh *Logger) Log(level Level, format string, args ...interface{}) {
|
||||||
|
if level < lh.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lh.mutex.Lock()
|
||||||
|
defer lh.mutex.Unlock()
|
||||||
|
|
||||||
|
lh.buffer = lh.buffer[:0]
|
||||||
|
|
||||||
|
// date
|
||||||
|
now := time.Now()
|
||||||
|
year, month, day := now.Date()
|
||||||
|
itoa(&lh.buffer, year, 4)
|
||||||
|
lh.buffer = append(lh.buffer, '/')
|
||||||
|
itoa(&lh.buffer, int(month), 2)
|
||||||
|
lh.buffer = append(lh.buffer, '/')
|
||||||
|
itoa(&lh.buffer, day, 2)
|
||||||
|
lh.buffer = append(lh.buffer, ' ')
|
||||||
|
|
||||||
|
// time
|
||||||
|
hour, min, sec := now.Clock()
|
||||||
|
itoa(&lh.buffer, hour, 2)
|
||||||
|
lh.buffer = append(lh.buffer, ':')
|
||||||
|
itoa(&lh.buffer, min, 2)
|
||||||
|
lh.buffer = append(lh.buffer, ':')
|
||||||
|
itoa(&lh.buffer, sec, 2)
|
||||||
|
lh.buffer = append(lh.buffer, ' ')
|
||||||
|
|
||||||
|
// level
|
||||||
|
switch level {
|
||||||
|
case Debug:
|
||||||
|
lh.buffer = append(lh.buffer, "[D] "...)
|
||||||
|
|
||||||
|
case Info:
|
||||||
|
lh.buffer = append(lh.buffer, "[I] "...)
|
||||||
|
|
||||||
|
case Warn:
|
||||||
|
lh.buffer = append(lh.buffer, "[W] "...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// content
|
||||||
|
lh.buffer = append(lh.buffer, fmt.Sprintf(format, args...)...)
|
||||||
|
lh.buffer = append(lh.buffer, '\n')
|
||||||
|
|
||||||
|
// output
|
||||||
|
if _, ok := lh.destinations[DestinationStdout]; ok {
|
||||||
|
print(string(lh.buffer))
|
||||||
|
}
|
||||||
|
if _, ok := lh.destinations[DestinationFile]; ok {
|
||||||
|
lh.file.Write(lh.buffer)
|
||||||
|
}
|
||||||
|
if _, ok := lh.destinations[DestinationSyslog]; ok {
|
||||||
|
lh.syslog.Write(lh.buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
package loghandler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/aler9/rtsp-simple-server/internal/syslog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Destination is a log destination.
|
|
||||||
type Destination int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DestinationStdout writes logs to the standard output.
|
|
||||||
DestinationStdout Destination = iota
|
|
||||||
|
|
||||||
// DestinationFile writes logs to a file.
|
|
||||||
DestinationFile
|
|
||||||
|
|
||||||
// DestinationSyslog writes logs to the system logger.
|
|
||||||
DestinationSyslog
|
|
||||||
)
|
|
||||||
|
|
||||||
type writeFunc func(p []byte) (int, error)
|
|
||||||
|
|
||||||
func (f writeFunc) Write(p []byte) (int, error) {
|
|
||||||
return f(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogHandler is a log handler.
|
|
||||||
type LogHandler struct {
|
|
||||||
destinations map[Destination]struct{}
|
|
||||||
|
|
||||||
file *os.File
|
|
||||||
syslog io.WriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
// New allocates a log handler.
|
|
||||||
func New(destinations map[Destination]struct{}, filePath string) (*LogHandler, error) {
|
|
||||||
lh := &LogHandler{
|
|
||||||
destinations: destinations,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := destinations[DestinationFile]; ok {
|
|
||||||
var err error
|
|
||||||
lh.file, err = os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
lh.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := destinations[DestinationSyslog]; ok {
|
|
||||||
var err error
|
|
||||||
lh.syslog, err = syslog.New("rtsp-simple-server")
|
|
||||||
if err != nil {
|
|
||||||
lh.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetOutput(writeFunc(lh.write))
|
|
||||||
|
|
||||||
return lh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes a log handler.
|
|
||||||
func (lh *LogHandler) Close() {
|
|
||||||
if lh.file != nil {
|
|
||||||
lh.file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if lh.syslog != nil {
|
|
||||||
lh.syslog.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lh *LogHandler) write(p []byte) (int, error) {
|
|
||||||
if _, ok := lh.destinations[DestinationStdout]; ok {
|
|
||||||
print(string(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := lh.destinations[DestinationFile]; ok {
|
|
||||||
lh.file.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := lh.destinations[DestinationSyslog]; ok {
|
|
||||||
lh.syslog.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/stats"
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -18,7 +19,7 @@ const (
|
||||||
|
|
||||||
// Parent is implemented by program.
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metrics is a metrics exporter.
|
// Metrics is a metrics exporter.
|
||||||
|
|
@ -49,7 +50,7 @@ func New(stats *stats.Stats, parent Parent) (*Metrics, error) {
|
||||||
Handler: m.mux,
|
Handler: m.mux,
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.Log("[metrics] opened on " + address)
|
parent.Log(logger.Info, "[metrics] opened on "+address)
|
||||||
|
|
||||||
go m.run()
|
go m.run()
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/aler9/rtsp-simple-server/internal/client"
|
"github.com/aler9/rtsp-simple-server/internal/client"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
|
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/sourcertmp"
|
"github.com/aler9/rtsp-simple-server/internal/sourcertmp"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/sourcertsp"
|
"github.com/aler9/rtsp-simple-server/internal/sourcertsp"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/stats"
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
||||||
|
|
@ -27,7 +28,7 @@ func newEmptyTimer() *time.Timer {
|
||||||
|
|
||||||
// Parent is implemented by pathman.PathMan.
|
// Parent is implemented by pathman.PathMan.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
OnPathClose(*Path)
|
OnPathClose(*Path)
|
||||||
OnPathClientClose(*client.Client)
|
OnPathClientClose(*client.Client)
|
||||||
}
|
}
|
||||||
|
|
@ -230,8 +231,8 @@ func (pa *Path) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log is the main logging function.
|
// Log is the main logging function.
|
||||||
func (pa *Path) Log(format string, args ...interface{}) {
|
func (pa *Path) Log(level logger.Level, format string, args ...interface{}) {
|
||||||
pa.parent.Log("[path "+pa.name+"] "+format, args...)
|
pa.parent.Log(level, "[path "+pa.name+"] "+format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *Path) run() {
|
func (pa *Path) run() {
|
||||||
|
|
@ -246,7 +247,7 @@ func (pa *Path) run() {
|
||||||
|
|
||||||
var onInitCmd *externalcmd.Cmd
|
var onInitCmd *externalcmd.Cmd
|
||||||
if pa.conf.RunOnInit != "" {
|
if pa.conf.RunOnInit != "" {
|
||||||
pa.Log("on init command started")
|
pa.Log(logger.Info, "on init command started")
|
||||||
onInitCmd = externalcmd.New(pa.conf.RunOnInit, pa.conf.RunOnInitRestart, externalcmd.Environment{
|
onInitCmd = externalcmd.New(pa.conf.RunOnInit, pa.conf.RunOnInitRestart, externalcmd.Environment{
|
||||||
Path: pa.name,
|
Path: pa.name,
|
||||||
Port: strconv.FormatInt(int64(pa.rtspPort), 10),
|
Port: strconv.FormatInt(int64(pa.rtspPort), 10),
|
||||||
|
|
@ -280,7 +281,7 @@ outer:
|
||||||
|
|
||||||
case <-pa.runOnDemandCloseTimer.C:
|
case <-pa.runOnDemandCloseTimer.C:
|
||||||
pa.runOnDemandCloseTimerStarted = false
|
pa.runOnDemandCloseTimerStarted = false
|
||||||
pa.Log("on demand command stopped")
|
pa.Log(logger.Info, "on demand command stopped")
|
||||||
pa.onDemandCmd.Close()
|
pa.onDemandCmd.Close()
|
||||||
pa.onDemandCmd = nil
|
pa.onDemandCmd = nil
|
||||||
|
|
||||||
|
|
@ -364,7 +365,7 @@ outer:
|
||||||
pa.closeTimer.Stop()
|
pa.closeTimer.Stop()
|
||||||
|
|
||||||
if onInitCmd != nil {
|
if onInitCmd != nil {
|
||||||
pa.Log("on init command stopped")
|
pa.Log(logger.Info, "on init command stopped")
|
||||||
onInitCmd.Close()
|
onInitCmd.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,7 +375,7 @@ outer:
|
||||||
pa.sourceWg.Wait()
|
pa.sourceWg.Wait()
|
||||||
|
|
||||||
if pa.onDemandCmd != nil {
|
if pa.onDemandCmd != nil {
|
||||||
pa.Log("on demand command stopped")
|
pa.Log(logger.Info, "on demand command stopped")
|
||||||
pa.onDemandCmd.Close()
|
pa.onDemandCmd.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -609,7 +610,7 @@ func (pa *Path) onClientDescribe(c *client.Client) {
|
||||||
// start on-demand command
|
// start on-demand command
|
||||||
if pa.conf.RunOnDemand != "" {
|
if pa.conf.RunOnDemand != "" {
|
||||||
if pa.onDemandCmd == nil {
|
if pa.onDemandCmd == nil {
|
||||||
pa.Log("on demand command started")
|
pa.Log(logger.Info, "on demand command started")
|
||||||
pa.onDemandCmd = externalcmd.New(pa.conf.RunOnDemand, pa.conf.RunOnDemandRestart, externalcmd.Environment{
|
pa.onDemandCmd = externalcmd.New(pa.conf.RunOnDemand, pa.conf.RunOnDemandRestart, externalcmd.Environment{
|
||||||
Path: pa.name,
|
Path: pa.name,
|
||||||
Port: strconv.FormatInt(int64(pa.rtspPort), 10),
|
Port: strconv.FormatInt(int64(pa.rtspPort), 10),
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,14 @@ import (
|
||||||
|
|
||||||
"github.com/aler9/rtsp-simple-server/internal/client"
|
"github.com/aler9/rtsp-simple-server/internal/client"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/path"
|
"github.com/aler9/rtsp-simple-server/internal/path"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/stats"
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parent is implemented by program.
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathManager is a path.Path manager.
|
// PathManager is a path.Path manager.
|
||||||
|
|
@ -92,8 +93,8 @@ func (pm *PathManager) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log is the main logging function.
|
// Log is the main logging function.
|
||||||
func (pm *PathManager) Log(format string, args ...interface{}) {
|
func (pm *PathManager) Log(level logger.Level, format string, args ...interface{}) {
|
||||||
pm.parent.Log(format, args...)
|
pm.parent.Log(level, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *PathManager) run() {
|
func (pm *PathManager) run() {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
|
|
||||||
// start pprof
|
// start pprof
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -15,7 +17,7 @@ const (
|
||||||
|
|
||||||
// Parent is implemented by program.
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pprof is a performance metrics exporter.
|
// Pprof is a performance metrics exporter.
|
||||||
|
|
@ -39,7 +41,7 @@ func New(parent Parent) (*Pprof, error) {
|
||||||
Handler: http.DefaultServeMux,
|
Handler: http.DefaultServeMux,
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.Log("[pprof] opened on " + address)
|
parent.Log(logger.Info, "[pprof] opened on "+address)
|
||||||
|
|
||||||
go pp.run()
|
go pp.run()
|
||||||
return pp, nil
|
return pp, nil
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib"
|
"github.com/aler9/gortsplib"
|
||||||
|
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parent is implemented by program.
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server is a RTSP TCP server.
|
// Server is a RTSP TCP server.
|
||||||
|
|
@ -47,7 +49,7 @@ func New(port int,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.Log("[TCP server] opened on :%d", port)
|
parent.Log(logger.Info, "[TCP server] opened on :%d", port)
|
||||||
|
|
||||||
go s.run()
|
go s.run()
|
||||||
return s, nil
|
return s, nil
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/aler9/gortsplib"
|
"github.com/aler9/gortsplib"
|
||||||
"github.com/aler9/gortsplib/pkg/base"
|
"github.com/aler9/gortsplib/pkg/base"
|
||||||
"github.com/aler9/gortsplib/pkg/multibuffer"
|
"github.com/aler9/gortsplib/pkg/multibuffer"
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -29,7 +30,7 @@ type publisherData struct {
|
||||||
|
|
||||||
// Parent is implemented by program.
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type publisherAddr struct {
|
type publisherAddr struct {
|
||||||
|
|
@ -96,7 +97,7 @@ func New(writeTimeout time.Duration,
|
||||||
} else {
|
} else {
|
||||||
label = "RTCP"
|
label = "RTCP"
|
||||||
}
|
}
|
||||||
parent.Log("[UDP/"+label+" server] opened on :%d", port)
|
parent.Log(logger.Info, "[UDP/"+label+" server] opened on :%d", port)
|
||||||
|
|
||||||
go s.run()
|
go s.run()
|
||||||
return s, nil
|
return s, nil
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/notedit/rtmp/codec/h264"
|
"github.com/notedit/rtmp/codec/h264"
|
||||||
"github.com/notedit/rtmp/format/rtmp"
|
"github.com/notedit/rtmp/format/rtmp"
|
||||||
|
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/stats"
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ const (
|
||||||
|
|
||||||
// Parent is implemeneted by path.Path.
|
// Parent is implemeneted by path.Path.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
OnSourceSetReady(gortsplib.Tracks)
|
OnSourceSetReady(gortsplib.Tracks)
|
||||||
OnSourceSetNotReady()
|
OnSourceSetNotReady()
|
||||||
OnFrame(int, gortsplib.StreamType, []byte)
|
OnFrame(int, gortsplib.StreamType, []byte)
|
||||||
|
|
@ -56,7 +57,7 @@ func New(ur string,
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddInt64(s.stats.CountSourcesRtmp, +1)
|
atomic.AddInt64(s.stats.CountSourcesRtmp, +1)
|
||||||
s.parent.Log("rtmp source started")
|
s.log(logger.Info, "started")
|
||||||
|
|
||||||
s.wg.Add(1)
|
s.wg.Add(1)
|
||||||
go s.run()
|
go s.run()
|
||||||
|
|
@ -66,7 +67,7 @@ func New(ur string,
|
||||||
// Close closes a Source.
|
// Close closes a Source.
|
||||||
func (s *Source) Close() {
|
func (s *Source) Close() {
|
||||||
atomic.AddInt64(s.stats.CountSourcesRtmpRunning, -1)
|
atomic.AddInt64(s.stats.CountSourcesRtmpRunning, -1)
|
||||||
s.parent.Log("rtmp source stopped")
|
s.log(logger.Info, "stopped")
|
||||||
close(s.terminate)
|
close(s.terminate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,6 +77,10 @@ func (s *Source) IsSource() {}
|
||||||
// IsSourceExternal implements path.sourceExternal.
|
// IsSourceExternal implements path.sourceExternal.
|
||||||
func (s *Source) IsSourceExternal() {}
|
func (s *Source) IsSourceExternal() {}
|
||||||
|
|
||||||
|
func (s *Source) log(level logger.Level, format string, args ...interface{}) {
|
||||||
|
s.parent.Log(level, "[rtmp source] "+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Source) run() {
|
func (s *Source) run() {
|
||||||
defer s.wg.Done()
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
|
@ -103,7 +108,7 @@ func (s *Source) run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) runInner() bool {
|
func (s *Source) runInner() bool {
|
||||||
s.parent.Log("connecting to rtmp source")
|
s.log(logger.Info, "connecting")
|
||||||
|
|
||||||
var conn *rtmp.Conn
|
var conn *rtmp.Conn
|
||||||
var nconn net.Conn
|
var nconn net.Conn
|
||||||
|
|
@ -121,7 +126,7 @@ func (s *Source) runInner() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.parent.Log("rtmp source ERR: %s", err)
|
s.log(logger.Info, "ERR: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +184,7 @@ func (s *Source) runInner() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.parent.Log("rtmp source ERR: %s", err)
|
s.log(logger.Info, "ERR: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,7 +201,7 @@ func (s *Source) runInner() bool {
|
||||||
if h264Sps != nil {
|
if h264Sps != nil {
|
||||||
videoTrack, err = gortsplib.NewTrackH264(96, h264Sps, h264Pps)
|
videoTrack, err = gortsplib.NewTrackH264(96, h264Sps, h264Pps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.parent.Log("rtmp source ERR: %s", err)
|
s.log(logger.Info, "ERR: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +210,7 @@ func (s *Source) runInner() bool {
|
||||||
|
|
||||||
h264Encoder, err = rtph264.NewEncoder(96)
|
h264Encoder, err = rtph264.NewEncoder(96)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.parent.Log("rtmp source ERR: %s", err)
|
s.log(logger.Info, "ERR: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +220,7 @@ func (s *Source) runInner() bool {
|
||||||
if aacConfig != nil {
|
if aacConfig != nil {
|
||||||
audioTrack, err = gortsplib.NewTrackAAC(96, aacConfig)
|
audioTrack, err = gortsplib.NewTrackAAC(96, aacConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.parent.Log("rtmp source ERR: %s", err)
|
s.log(logger.Info, "ERR: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,7 +229,7 @@ func (s *Source) runInner() bool {
|
||||||
|
|
||||||
aacEncoder, err = rtpaac.NewEncoder(96, clockRate)
|
aacEncoder, err = rtpaac.NewEncoder(96, clockRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.parent.Log("rtmp source ERR: %s", err)
|
s.log(logger.Info, "ERR: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,7 +237,7 @@ func (s *Source) runInner() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tracks) == 0 {
|
if len(tracks) == 0 {
|
||||||
s.parent.Log("rtmp source ERR: no tracks found")
|
s.log(logger.Info, "ERR: no tracks found")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,7 +245,7 @@ func (s *Source) runInner() bool {
|
||||||
t.ID = i
|
t.ID = i
|
||||||
}
|
}
|
||||||
|
|
||||||
s.parent.Log("rtmp source ready")
|
s.log(logger.Info, "ready")
|
||||||
s.parent.OnSourceSetReady(tracks)
|
s.parent.OnSourceSetReady(tracks)
|
||||||
defer s.parent.OnSourceSetNotReady()
|
defer s.parent.OnSourceSetNotReady()
|
||||||
|
|
||||||
|
|
@ -348,7 +353,7 @@ func (s *Source) runInner() bool {
|
||||||
|
|
||||||
case err := <-readerDone:
|
case err := <-readerDone:
|
||||||
nconn.Close()
|
nconn.Close()
|
||||||
s.parent.Log("rtmp source ERR: %s", err)
|
s.log(logger.Info, "ERR: %s", err)
|
||||||
|
|
||||||
close(rtcpTerminate)
|
close(rtcpTerminate)
|
||||||
<-rtcpDone
|
<-rtcpDone
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib"
|
"github.com/aler9/gortsplib"
|
||||||
|
"github.com/aler9/gortsplib/pkg/base"
|
||||||
|
|
||||||
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/stats"
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -16,7 +18,7 @@ const (
|
||||||
|
|
||||||
// Parent is implemented by path.Path.
|
// Parent is implemented by path.Path.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(logger.Level, string, ...interface{})
|
||||||
OnSourceSetReady(gortsplib.Tracks)
|
OnSourceSetReady(gortsplib.Tracks)
|
||||||
OnSourceSetNotReady()
|
OnSourceSetNotReady()
|
||||||
OnFrame(int, gortsplib.StreamType, []byte)
|
OnFrame(int, gortsplib.StreamType, []byte)
|
||||||
|
|
@ -56,7 +58,7 @@ func New(ur string,
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddInt64(s.stats.CountSourcesRtsp, +1)
|
atomic.AddInt64(s.stats.CountSourcesRtsp, +1)
|
||||||
s.parent.Log("rtsp source started")
|
s.log(logger.Info, "started")
|
||||||
|
|
||||||
s.wg.Add(1)
|
s.wg.Add(1)
|
||||||
go s.run()
|
go s.run()
|
||||||
|
|
@ -66,7 +68,7 @@ func New(ur string,
|
||||||
// Close closes a Source.
|
// Close closes a Source.
|
||||||
func (s *Source) Close() {
|
func (s *Source) Close() {
|
||||||
atomic.AddInt64(s.stats.CountSourcesRtsp, -1)
|
atomic.AddInt64(s.stats.CountSourcesRtsp, -1)
|
||||||
s.parent.Log("rtsp source stopped")
|
s.log(logger.Info, "stopped")
|
||||||
close(s.terminate)
|
close(s.terminate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,6 +78,10 @@ func (s *Source) IsSource() {}
|
||||||
// IsSourceExternal implements path.sourceExternal.
|
// IsSourceExternal implements path.sourceExternal.
|
||||||
func (s *Source) IsSourceExternal() {}
|
func (s *Source) IsSourceExternal() {}
|
||||||
|
|
||||||
|
func (s *Source) log(level logger.Level, format string, args ...interface{}) {
|
||||||
|
s.parent.Log(level, "[rtsp source] "+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Source) run() {
|
func (s *Source) run() {
|
||||||
defer s.wg.Done()
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
|
@ -103,7 +109,7 @@ func (s *Source) run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) runInner() bool {
|
func (s *Source) runInner() bool {
|
||||||
s.parent.Log("connecting to rtsp source")
|
s.log(logger.Info, "connecting")
|
||||||
|
|
||||||
var conn *gortsplib.ClientConn
|
var conn *gortsplib.ClientConn
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -111,13 +117,19 @@ func (s *Source) runInner() bool {
|
||||||
go func() {
|
go func() {
|
||||||
defer close(dialDone)
|
defer close(dialDone)
|
||||||
|
|
||||||
dialer := gortsplib.ClientConf{
|
conf := gortsplib.ClientConf{
|
||||||
StreamProtocol: s.proto,
|
StreamProtocol: s.proto,
|
||||||
ReadTimeout: s.readTimeout,
|
ReadTimeout: s.readTimeout,
|
||||||
WriteTimeout: s.writeTimeout,
|
WriteTimeout: s.writeTimeout,
|
||||||
ReadBufferCount: 2,
|
ReadBufferCount: 2,
|
||||||
|
OnRequest: func(req *base.Request) {
|
||||||
|
s.log(logger.Debug, "c->s %v", req)
|
||||||
|
},
|
||||||
|
OnResponse: func(res *base.Response) {
|
||||||
|
s.log(logger.Debug, "s->c %v", res)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
conn, err = dialer.DialRead(s.ur)
|
conn, err = conf.DialRead(s.ur)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|
@ -127,13 +139,13 @@ func (s *Source) runInner() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.parent.Log("rtsp source ERR: %s", err)
|
s.log(logger.Info, "ERR: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks := conn.Tracks()
|
tracks := conn.Tracks()
|
||||||
|
|
||||||
s.parent.Log("rtsp source ready")
|
s.log(logger.Info, "ready")
|
||||||
s.parent.OnSourceSetReady(tracks)
|
s.parent.OnSourceSetReady(tracks)
|
||||||
defer s.parent.OnSourceSetNotReady()
|
defer s.parent.OnSourceSetNotReady()
|
||||||
|
|
||||||
|
|
@ -150,7 +162,7 @@ func (s *Source) runInner() bool {
|
||||||
|
|
||||||
case err := <-readerDone:
|
case err := <-readerDone:
|
||||||
conn.Close()
|
conn.Close()
|
||||||
s.parent.Log("rtsp source ERR: %s", err)
|
s.log(logger.Info, "ERR: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
55
main.go
55
main.go
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -13,7 +12,7 @@ import (
|
||||||
"github.com/aler9/rtsp-simple-server/internal/clientman"
|
"github.com/aler9/rtsp-simple-server/internal/clientman"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/confwatcher"
|
"github.com/aler9/rtsp-simple-server/internal/confwatcher"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/loghandler"
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/metrics"
|
"github.com/aler9/rtsp-simple-server/internal/metrics"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/pathman"
|
"github.com/aler9/rtsp-simple-server/internal/pathman"
|
||||||
"github.com/aler9/rtsp-simple-server/internal/pprof"
|
"github.com/aler9/rtsp-simple-server/internal/pprof"
|
||||||
|
|
@ -29,7 +28,7 @@ type program struct {
|
||||||
conf *conf.Conf
|
conf *conf.Conf
|
||||||
confFound bool
|
confFound bool
|
||||||
stats *stats.Stats
|
stats *stats.Stats
|
||||||
logHandler *loghandler.LogHandler
|
logger *logger.Logger
|
||||||
metrics *metrics.Metrics
|
metrics *metrics.Metrics
|
||||||
pprof *pprof.Pprof
|
pprof *pprof.Pprof
|
||||||
serverUDPRtp *serverudp.Server
|
serverUDPRtp *serverudp.Server
|
||||||
|
|
@ -43,7 +42,7 @@ type program struct {
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProgram(args []string) (*program, error) {
|
func newProgram(args []string) (*program, bool) {
|
||||||
k := kingpin.New("rtsp-simple-server",
|
k := kingpin.New("rtsp-simple-server",
|
||||||
"rtsp-simple-server "+version+"\n\nRTSP server.")
|
"rtsp-simple-server "+version+"\n\nRTSP server.")
|
||||||
|
|
||||||
|
|
@ -66,26 +65,29 @@ func newProgram(args []string) (*program, error) {
|
||||||
var err error
|
var err error
|
||||||
p.conf, p.confFound, err = conf.Load(p.confPath)
|
p.conf, p.confFound, err = conf.Load(p.confPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
p.Log(logger.Info, "ERR: %s", err)
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.createDynamicResources(true)
|
err = p.createDynamicResources(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
p.Log(logger.Info, "ERR: %s", err)
|
||||||
p.closeAllResources()
|
p.closeAllResources()
|
||||||
return nil, err
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.confFound {
|
if p.confFound {
|
||||||
p.confWatcher, err = confwatcher.New(p.confPath)
|
p.confWatcher, err = confwatcher.New(p.confPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
p.Log(logger.Info, "ERR: %s", err)
|
||||||
p.closeAllResources()
|
p.closeAllResources()
|
||||||
return nil, err
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go p.run()
|
go p.run()
|
||||||
|
|
||||||
return p, nil
|
return p, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *program) close() {
|
func (p *program) close() {
|
||||||
|
|
@ -93,12 +95,12 @@ func (p *program) close() {
|
||||||
<-p.done
|
<-p.done
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *program) Log(format string, args ...interface{}) {
|
func (p *program) Log(level logger.Level, format string, args ...interface{}) {
|
||||||
countClients := atomic.LoadInt64(p.stats.CountClients)
|
countClients := atomic.LoadInt64(p.stats.CountClients)
|
||||||
countPublishers := atomic.LoadInt64(p.stats.CountPublishers)
|
countPublishers := atomic.LoadInt64(p.stats.CountPublishers)
|
||||||
countReaders := atomic.LoadInt64(p.stats.CountReaders)
|
countReaders := atomic.LoadInt64(p.stats.CountReaders)
|
||||||
|
|
||||||
log.Printf("[%d/%d/%d] "+format, append([]interface{}{countClients,
|
p.logger.Log(level, "[%d/%d/%d] "+format, append([]interface{}{countClients,
|
||||||
countPublishers, countReaders}, args...)...)
|
countPublishers, countReaders}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,7 +120,7 @@ outer:
|
||||||
case <-confChanged:
|
case <-confChanged:
|
||||||
err := p.reloadConf()
|
err := p.reloadConf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Log("ERR: %s", err)
|
p.Log(logger.Info, "ERR: %s", err)
|
||||||
break outer
|
break outer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,18 +139,19 @@ func (p *program) createDynamicResources(initial bool) error {
|
||||||
p.stats = stats.New()
|
p.stats = stats.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.logHandler == nil {
|
if p.logger == nil {
|
||||||
p.logHandler, err = loghandler.New(p.conf.LogDestinationsParsed, p.conf.LogFile)
|
p.logger, err = logger.New(p.conf.LogLevelParsed, p.conf.LogDestinationsParsed,
|
||||||
|
p.conf.LogFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if initial {
|
if initial {
|
||||||
p.Log("rtsp-simple-server %s", version)
|
p.Log(logger.Info, "rtsp-simple-server %s", version)
|
||||||
|
|
||||||
if !p.confFound {
|
if !p.confFound {
|
||||||
p.Log("configuration file not found, using the default one")
|
p.Log(logger.Warn, "configuration file not found, using the default one")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,23 +248,23 @@ func (p *program) closeAllResources() {
|
||||||
p.pprof.Close()
|
p.pprof.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.logHandler != nil {
|
if p.logger != nil {
|
||||||
p.logHandler.Close()
|
p.logger.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *program) reloadConf() error {
|
func (p *program) reloadConf() error {
|
||||||
p.Log("reloading configuration")
|
p.Log(logger.Info, "reloading configuration")
|
||||||
|
|
||||||
conf, _, err := conf.Load(p.confPath)
|
conf, _, err := conf.Load(p.confPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
closeLogHandler := false
|
closeLogger := false
|
||||||
if !reflect.DeepEqual(conf.LogDestinationsParsed, p.conf.LogDestinationsParsed) ||
|
if !reflect.DeepEqual(conf.LogDestinationsParsed, p.conf.LogDestinationsParsed) ||
|
||||||
conf.LogFile != p.conf.LogFile {
|
conf.LogFile != p.conf.LogFile {
|
||||||
closeLogHandler = true
|
closeLogger = true
|
||||||
}
|
}
|
||||||
|
|
||||||
closeMetrics := false
|
closeMetrics := false
|
||||||
|
|
@ -353,9 +356,9 @@ func (p *program) reloadConf() error {
|
||||||
p.metrics = nil
|
p.metrics = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if closeLogHandler {
|
if closeLogger {
|
||||||
p.logHandler.Close()
|
p.logger.Close()
|
||||||
p.logHandler = nil
|
p.logger = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
p.conf = conf
|
p.conf = conf
|
||||||
|
|
@ -363,9 +366,9 @@ func (p *program) reloadConf() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
p, err := newProgram(os.Args[1:])
|
p, ok := newProgram(os.Args[1:])
|
||||||
if err != nil {
|
if !ok {
|
||||||
log.Fatal("ERR: ", err)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
<-p.done
|
<-p.done
|
||||||
|
|
|
||||||
72
main_test.go
72
main_test.go
|
|
@ -102,14 +102,14 @@ func (c *container) ip() string {
|
||||||
return string(out[:len(out)-1])
|
return string(out[:len(out)-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testProgram(conf string) (*program, error) {
|
func testProgram(conf string) (*program, bool) {
|
||||||
if conf == "" {
|
if conf == "" {
|
||||||
return newProgram([]string{})
|
return newProgram([]string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpf, err := ioutil.TempFile(os.TempDir(), "rtsp-")
|
tmpf, err := ioutil.TempFile(os.TempDir(), "rtsp-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false
|
||||||
}
|
}
|
||||||
defer os.Remove(tmpf.Name())
|
defer os.Remove(tmpf.Name())
|
||||||
|
|
||||||
|
|
@ -158,8 +158,8 @@ func TestEnvironment(t *testing.T) {
|
||||||
os.Setenv("RTSP_PATHS_CAM1_SOURCEONDEMAND", "yes")
|
os.Setenv("RTSP_PATHS_CAM1_SOURCEONDEMAND", "yes")
|
||||||
defer os.Unsetenv("RTSP_PATHS_CAM1_SOURCEONDEMAND")
|
defer os.Unsetenv("RTSP_PATHS_CAM1_SOURCEONDEMAND")
|
||||||
|
|
||||||
p, err := testProgram("")
|
p, ok := testProgram("")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
require.Equal(t, "test=cmd", p.conf.RunOnConnect)
|
require.Equal(t, "test=cmd", p.conf.RunOnConnect)
|
||||||
|
|
@ -217,8 +217,8 @@ func TestEnvironmentNoFile(t *testing.T) {
|
||||||
os.Setenv("RTSP_PATHS_CAM1_SOURCE", "rtsp://testing")
|
os.Setenv("RTSP_PATHS_CAM1_SOURCE", "rtsp://testing")
|
||||||
defer os.Unsetenv("RTSP_PATHS_CAM1_SOURCE")
|
defer os.Unsetenv("RTSP_PATHS_CAM1_SOURCE")
|
||||||
|
|
||||||
p, err := testProgram("{}")
|
p, ok := testProgram("{}")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
pa, ok := p.conf.Paths["cam1"]
|
pa, ok := p.conf.Paths["cam1"]
|
||||||
|
|
@ -244,8 +244,8 @@ func TestPublish(t *testing.T) {
|
||||||
{"gstreamer", "tcp"},
|
{"gstreamer", "tcp"},
|
||||||
} {
|
} {
|
||||||
t.Run(conf.publishSoft+"_"+conf.publishProto, func(t *testing.T) {
|
t.Run(conf.publishSoft+"_"+conf.publishProto, func(t *testing.T) {
|
||||||
p, err := testProgram("")
|
p, ok := testProgram("readTimeout: 20s")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -303,8 +303,8 @@ func TestRead(t *testing.T) {
|
||||||
{"vlc", "tcp"},
|
{"vlc", "tcp"},
|
||||||
} {
|
} {
|
||||||
t.Run(conf.readSoft+"_"+conf.readProto, func(t *testing.T) {
|
t.Run(conf.readSoft+"_"+conf.readProto, func(t *testing.T) {
|
||||||
p, err := testProgram("")
|
p, ok := testProgram("")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -355,8 +355,8 @@ func TestRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTCPOnly(t *testing.T) {
|
func TestTCPOnly(t *testing.T) {
|
||||||
p, err := testProgram("protocols: [tcp]\n")
|
p, ok := testProgram("protocols: [tcp]\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -387,8 +387,8 @@ func TestTCPOnly(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathWithSlash(t *testing.T) {
|
func TestPathWithSlash(t *testing.T) {
|
||||||
p, err := testProgram("")
|
p, ok := testProgram("")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -419,8 +419,8 @@ func TestPathWithSlash(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathWithQuery(t *testing.T) {
|
func TestPathWithQuery(t *testing.T) {
|
||||||
p, err := testProgram("")
|
p, ok := testProgram("")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -452,12 +452,12 @@ func TestPathWithQuery(t *testing.T) {
|
||||||
|
|
||||||
func TestAuth(t *testing.T) {
|
func TestAuth(t *testing.T) {
|
||||||
t.Run("publish", func(t *testing.T) {
|
t.Run("publish", func(t *testing.T) {
|
||||||
p, err := testProgram("paths:\n" +
|
p, ok := testProgram("paths:\n" +
|
||||||
" all:\n" +
|
" all:\n" +
|
||||||
" publishUser: testuser\n" +
|
" publishUser: testuser\n" +
|
||||||
" publishPass: test!$()*+.;<=>[]^_-{}\n" +
|
" publishPass: test!$()*+.;<=>[]^_-{}\n" +
|
||||||
" publishIps: [172.17.0.0/16]\n")
|
" publishIps: [172.17.0.0/16]\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -494,12 +494,12 @@ func TestAuth(t *testing.T) {
|
||||||
"vlc",
|
"vlc",
|
||||||
} {
|
} {
|
||||||
t.Run("read_"+soft, func(t *testing.T) {
|
t.Run("read_"+soft, func(t *testing.T) {
|
||||||
p, err := testProgram("paths:\n" +
|
p, ok := testProgram("paths:\n" +
|
||||||
" all:\n" +
|
" all:\n" +
|
||||||
" readUser: testuser\n" +
|
" readUser: testuser\n" +
|
||||||
" readPass: test!$()*+.;<=>[]^_-{}\n" +
|
" readPass: test!$()*+.;<=>[]^_-{}\n" +
|
||||||
" readIps: [172.17.0.0/16]\n")
|
" readIps: [172.17.0.0/16]\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -545,10 +545,10 @@ func TestAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthIpFail(t *testing.T) {
|
func TestAuthIpFail(t *testing.T) {
|
||||||
p, err := testProgram("paths:\n" +
|
p, ok := testProgram("paths:\n" +
|
||||||
" all:\n" +
|
" all:\n" +
|
||||||
" publishIps: [127.0.0.1/32]\n")
|
" publishIps: [127.0.0.1/32]\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -574,11 +574,11 @@ func TestSourceRtsp(t *testing.T) {
|
||||||
"tcp",
|
"tcp",
|
||||||
} {
|
} {
|
||||||
t.Run(proto, func(t *testing.T) {
|
t.Run(proto, func(t *testing.T) {
|
||||||
p1, err := testProgram("paths:\n" +
|
p1, ok := testProgram("paths:\n" +
|
||||||
" all:\n" +
|
" all:\n" +
|
||||||
" readUser: testuser\n" +
|
" readUser: testuser\n" +
|
||||||
" readPass: testpass\n")
|
" readPass: testpass\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p1.close()
|
defer p1.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -597,7 +597,7 @@ func TestSourceRtsp(t *testing.T) {
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
p2, err := testProgram("rtspPort: 8555\n" +
|
p2, ok := testProgram("rtspPort: 8555\n" +
|
||||||
"rtpPort: 8100\n" +
|
"rtpPort: 8100\n" +
|
||||||
"rtcpPort: 8101\n" +
|
"rtcpPort: 8101\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
|
@ -606,7 +606,7 @@ func TestSourceRtsp(t *testing.T) {
|
||||||
" source: rtsp://testuser:testpass@localhost:8554/teststream\n" +
|
" source: rtsp://testuser:testpass@localhost:8554/teststream\n" +
|
||||||
" sourceProtocol: " + proto + "\n" +
|
" sourceProtocol: " + proto + "\n" +
|
||||||
" sourceOnDemand: yes\n")
|
" sourceOnDemand: yes\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p2.close()
|
defer p2.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -646,11 +646,11 @@ func TestSourceRtmp(t *testing.T) {
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
p, err := testProgram("paths:\n" +
|
p, ok := testProgram("paths:\n" +
|
||||||
" proxied:\n" +
|
" proxied:\n" +
|
||||||
" source: rtmp://" + cnt1.ip() + "/stream/test\n" +
|
" source: rtmp://" + cnt1.ip() + "/stream/test\n" +
|
||||||
" sourceOnDemand: yes\n")
|
" sourceOnDemand: yes\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -669,12 +669,12 @@ func TestSourceRtmp(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedirect(t *testing.T) {
|
func TestRedirect(t *testing.T) {
|
||||||
p1, err := testProgram("paths:\n" +
|
p1, ok := testProgram("paths:\n" +
|
||||||
" path1:\n" +
|
" path1:\n" +
|
||||||
" source: redirect\n" +
|
" source: redirect\n" +
|
||||||
" sourceRedirect: rtsp://" + ownDockerIP + ":8554/path2\n" +
|
" sourceRedirect: rtsp://" + ownDockerIP + ":8554/path2\n" +
|
||||||
" path2:\n")
|
" path2:\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p1.close()
|
defer p1.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -707,11 +707,11 @@ func TestRedirect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFallback(t *testing.T) {
|
func TestFallback(t *testing.T) {
|
||||||
p1, err := testProgram("paths:\n" +
|
p1, ok := testProgram("paths:\n" +
|
||||||
" path1:\n" +
|
" path1:\n" +
|
||||||
" fallback: rtsp://" + ownDockerIP + ":8554/path2\n" +
|
" fallback: rtsp://" + ownDockerIP + ":8554/path2\n" +
|
||||||
" path2:\n")
|
" path2:\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p1.close()
|
defer p1.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -744,10 +744,10 @@ func TestFallback(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunOnDemand(t *testing.T) {
|
func TestRunOnDemand(t *testing.T) {
|
||||||
p1, err := testProgram("paths:\n" +
|
p1, ok := testProgram("paths:\n" +
|
||||||
" all:\n" +
|
" all:\n" +
|
||||||
" runOnDemand: ffmpeg -hide_banner -loglevel error -re -i testimages/ffmpeg/emptyvideo.ts -c copy -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH\n")
|
" runOnDemand: ffmpeg -hide_banner -loglevel error -re -i testimages/ffmpeg/emptyvideo.ts -c copy -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH\n")
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p1.close()
|
defer p1.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
@ -778,8 +778,8 @@ func TestHotReloading(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.Remove(confPath)
|
defer os.Remove(confPath)
|
||||||
|
|
||||||
p, err := newProgram([]string{confPath})
|
p, ok := newProgram([]string{confPath})
|
||||||
require.NoError(t, err)
|
require.Equal(t, true, ok)
|
||||||
defer p.close()
|
defer p.close()
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
# sets the verbosity of the program; available values are "warn", "info", "debug"
|
||||||
|
logLevel: info
|
||||||
# destinations of log messages; available values are "stdout", "file" and "syslog".
|
# destinations of log messages; available values are "stdout", "file" and "syslog".
|
||||||
logDestinations: [stdout]
|
logDestinations: [stdout]
|
||||||
# if "file" is in logDestinations, this is the file which will receive the logs.
|
# if "file" is in logDestinations, this is the file which will receive the logs.
|
||||||
|
|
@ -49,7 +51,9 @@ paths:
|
||||||
|
|
||||||
# if the source is an RTSP url, this is the protocol that will be used to
|
# if the source is an RTSP url, this is the protocol that will be used to
|
||||||
# pull the stream. available options are "automatic", "udp", "tcp".
|
# pull the stream. available options are "automatic", "udp", "tcp".
|
||||||
|
# the tcp protocol can help to overcome the error "no packets received recently".
|
||||||
sourceProtocol: automatic
|
sourceProtocol: automatic
|
||||||
|
|
||||||
# if the source is an RTSP or RTMP url, it will be pulled only when at least
|
# if the source is an RTSP or RTMP url, it will be pulled only when at least
|
||||||
# one reader is connected, saving bandwidth.
|
# one reader is connected, saving bandwidth.
|
||||||
sourceOnDemand: no
|
sourceOnDemand: no
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue