58 changed files with 2856 additions and 3115 deletions
@ -1,309 +0,0 @@ |
|||||||
package core |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"fmt" |
|
||||||
"sort" |
|
||||||
"sync" |
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf" |
|
||||||
"github.com/bluenviron/mediamtx/internal/defs" |
|
||||||
"github.com/bluenviron/mediamtx/internal/logger" |
|
||||||
) |
|
||||||
|
|
||||||
type hlsManagerAPIMuxersListRes struct { |
|
||||||
data *defs.APIHLSMuxerList |
|
||||||
err error |
|
||||||
} |
|
||||||
|
|
||||||
type hlsManagerAPIMuxersListReq struct { |
|
||||||
res chan hlsManagerAPIMuxersListRes |
|
||||||
} |
|
||||||
|
|
||||||
type hlsManagerAPIMuxersGetRes struct { |
|
||||||
data *defs.APIHLSMuxer |
|
||||||
err error |
|
||||||
} |
|
||||||
|
|
||||||
type hlsManagerAPIMuxersGetReq struct { |
|
||||||
name string |
|
||||||
res chan hlsManagerAPIMuxersGetRes |
|
||||||
} |
|
||||||
|
|
||||||
type hlsManagerParent interface { |
|
||||||
logger.Writer |
|
||||||
} |
|
||||||
|
|
||||||
type hlsManager struct { |
|
||||||
externalAuthenticationURL string |
|
||||||
alwaysRemux bool |
|
||||||
variant conf.HLSVariant |
|
||||||
segmentCount int |
|
||||||
segmentDuration conf.StringDuration |
|
||||||
partDuration conf.StringDuration |
|
||||||
segmentMaxSize conf.StringSize |
|
||||||
directory string |
|
||||||
writeQueueSize int |
|
||||||
pathManager *pathManager |
|
||||||
parent hlsManagerParent |
|
||||||
|
|
||||||
ctx context.Context |
|
||||||
ctxCancel func() |
|
||||||
wg sync.WaitGroup |
|
||||||
httpServer *hlsHTTPServer |
|
||||||
muxers map[string]*hlsMuxer |
|
||||||
|
|
||||||
// in
|
|
||||||
chPathReady chan *path |
|
||||||
chPathNotReady chan *path |
|
||||||
chHandleRequest chan hlsMuxerHandleRequestReq |
|
||||||
chCloseMuxer chan *hlsMuxer |
|
||||||
chAPIMuxerList chan hlsManagerAPIMuxersListReq |
|
||||||
chAPIMuxerGet chan hlsManagerAPIMuxersGetReq |
|
||||||
} |
|
||||||
|
|
||||||
func newHLSManager( |
|
||||||
address string, |
|
||||||
encryption bool, |
|
||||||
serverKey string, |
|
||||||
serverCert string, |
|
||||||
externalAuthenticationURL string, |
|
||||||
alwaysRemux bool, |
|
||||||
variant conf.HLSVariant, |
|
||||||
segmentCount int, |
|
||||||
segmentDuration conf.StringDuration, |
|
||||||
partDuration conf.StringDuration, |
|
||||||
segmentMaxSize conf.StringSize, |
|
||||||
allowOrigin string, |
|
||||||
trustedProxies conf.IPsOrCIDRs, |
|
||||||
directory string, |
|
||||||
readTimeout conf.StringDuration, |
|
||||||
writeQueueSize int, |
|
||||||
pathManager *pathManager, |
|
||||||
parent hlsManagerParent, |
|
||||||
) (*hlsManager, error) { |
|
||||||
ctx, ctxCancel := context.WithCancel(context.Background()) |
|
||||||
|
|
||||||
m := &hlsManager{ |
|
||||||
externalAuthenticationURL: externalAuthenticationURL, |
|
||||||
alwaysRemux: alwaysRemux, |
|
||||||
variant: variant, |
|
||||||
segmentCount: segmentCount, |
|
||||||
segmentDuration: segmentDuration, |
|
||||||
partDuration: partDuration, |
|
||||||
segmentMaxSize: segmentMaxSize, |
|
||||||
directory: directory, |
|
||||||
writeQueueSize: writeQueueSize, |
|
||||||
pathManager: pathManager, |
|
||||||
parent: parent, |
|
||||||
ctx: ctx, |
|
||||||
ctxCancel: ctxCancel, |
|
||||||
muxers: make(map[string]*hlsMuxer), |
|
||||||
chPathReady: make(chan *path), |
|
||||||
chPathNotReady: make(chan *path), |
|
||||||
chHandleRequest: make(chan hlsMuxerHandleRequestReq), |
|
||||||
chCloseMuxer: make(chan *hlsMuxer), |
|
||||||
chAPIMuxerList: make(chan hlsManagerAPIMuxersListReq), |
|
||||||
chAPIMuxerGet: make(chan hlsManagerAPIMuxersGetReq), |
|
||||||
} |
|
||||||
|
|
||||||
var err error |
|
||||||
m.httpServer, err = newHLSHTTPServer( |
|
||||||
address, |
|
||||||
encryption, |
|
||||||
serverKey, |
|
||||||
serverCert, |
|
||||||
allowOrigin, |
|
||||||
trustedProxies, |
|
||||||
readTimeout, |
|
||||||
m.pathManager, |
|
||||||
m, |
|
||||||
) |
|
||||||
if err != nil { |
|
||||||
ctxCancel() |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
m.Log(logger.Info, "listener opened on "+address) |
|
||||||
|
|
||||||
m.wg.Add(1) |
|
||||||
go m.run() |
|
||||||
|
|
||||||
return m, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Log is the main logging function.
|
|
||||||
func (m *hlsManager) Log(level logger.Level, format string, args ...interface{}) { |
|
||||||
m.parent.Log(level, "[HLS] "+format, args...) |
|
||||||
} |
|
||||||
|
|
||||||
func (m *hlsManager) close() { |
|
||||||
m.Log(logger.Info, "listener is closing") |
|
||||||
m.ctxCancel() |
|
||||||
m.wg.Wait() |
|
||||||
} |
|
||||||
|
|
||||||
func (m *hlsManager) run() { |
|
||||||
defer m.wg.Done() |
|
||||||
|
|
||||||
outer: |
|
||||||
for { |
|
||||||
select { |
|
||||||
case pa := <-m.chPathReady: |
|
||||||
if m.alwaysRemux && !pa.conf.SourceOnDemand { |
|
||||||
if _, ok := m.muxers[pa.name]; !ok { |
|
||||||
m.createMuxer(pa.name, "") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
case pa := <-m.chPathNotReady: |
|
||||||
c, ok := m.muxers[pa.name] |
|
||||||
if ok && c.remoteAddr == "" { // created with "always remux"
|
|
||||||
c.close() |
|
||||||
delete(m.muxers, pa.name) |
|
||||||
} |
|
||||||
|
|
||||||
case req := <-m.chHandleRequest: |
|
||||||
r, ok := m.muxers[req.path] |
|
||||||
switch { |
|
||||||
case ok: |
|
||||||
r.processRequest(&req) |
|
||||||
|
|
||||||
default: |
|
||||||
r := m.createMuxer(req.path, req.ctx.ClientIP()) |
|
||||||
r.processRequest(&req) |
|
||||||
} |
|
||||||
|
|
||||||
case c := <-m.chCloseMuxer: |
|
||||||
if c2, ok := m.muxers[c.PathName()]; !ok || c2 != c { |
|
||||||
continue |
|
||||||
} |
|
||||||
delete(m.muxers, c.PathName()) |
|
||||||
|
|
||||||
case req := <-m.chAPIMuxerList: |
|
||||||
data := &defs.APIHLSMuxerList{ |
|
||||||
Items: []*defs.APIHLSMuxer{}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, muxer := range m.muxers { |
|
||||||
data.Items = append(data.Items, muxer.apiItem()) |
|
||||||
} |
|
||||||
|
|
||||||
sort.Slice(data.Items, func(i, j int) bool { |
|
||||||
return data.Items[i].Created.Before(data.Items[j].Created) |
|
||||||
}) |
|
||||||
|
|
||||||
req.res <- hlsManagerAPIMuxersListRes{ |
|
||||||
data: data, |
|
||||||
} |
|
||||||
|
|
||||||
case req := <-m.chAPIMuxerGet: |
|
||||||
muxer, ok := m.muxers[req.name] |
|
||||||
if !ok { |
|
||||||
req.res <- hlsManagerAPIMuxersGetRes{err: fmt.Errorf("muxer not found")} |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
req.res <- hlsManagerAPIMuxersGetRes{data: muxer.apiItem()} |
|
||||||
|
|
||||||
case <-m.ctx.Done(): |
|
||||||
break outer |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
m.ctxCancel() |
|
||||||
|
|
||||||
m.httpServer.close() |
|
||||||
} |
|
||||||
|
|
||||||
func (m *hlsManager) createMuxer(pathName string, remoteAddr string) *hlsMuxer { |
|
||||||
r := newHLSMuxer( |
|
||||||
m.ctx, |
|
||||||
remoteAddr, |
|
||||||
m.externalAuthenticationURL, |
|
||||||
m.variant, |
|
||||||
m.segmentCount, |
|
||||||
m.segmentDuration, |
|
||||||
m.partDuration, |
|
||||||
m.segmentMaxSize, |
|
||||||
m.directory, |
|
||||||
m.writeQueueSize, |
|
||||||
&m.wg, |
|
||||||
pathName, |
|
||||||
m.pathManager, |
|
||||||
m) |
|
||||||
m.muxers[pathName] = r |
|
||||||
return r |
|
||||||
} |
|
||||||
|
|
||||||
// closeMuxer is called by hlsMuxer.
|
|
||||||
func (m *hlsManager) closeMuxer(c *hlsMuxer) { |
|
||||||
select { |
|
||||||
case m.chCloseMuxer <- c: |
|
||||||
case <-m.ctx.Done(): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// pathReady is called by pathManager.
|
|
||||||
func (m *hlsManager) pathReady(pa *path) { |
|
||||||
select { |
|
||||||
case m.chPathReady <- pa: |
|
||||||
case <-m.ctx.Done(): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// pathNotReady is called by pathManager.
|
|
||||||
func (m *hlsManager) pathNotReady(pa *path) { |
|
||||||
select { |
|
||||||
case m.chPathNotReady <- pa: |
|
||||||
case <-m.ctx.Done(): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// apiMuxersList is called by api.
|
|
||||||
func (m *hlsManager) apiMuxersList() (*defs.APIHLSMuxerList, error) { |
|
||||||
req := hlsManagerAPIMuxersListReq{ |
|
||||||
res: make(chan hlsManagerAPIMuxersListRes), |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case m.chAPIMuxerList <- req: |
|
||||||
res := <-req.res |
|
||||||
return res.data, res.err |
|
||||||
|
|
||||||
case <-m.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// apiMuxersGet is called by api.
|
|
||||||
func (m *hlsManager) apiMuxersGet(name string) (*defs.APIHLSMuxer, error) { |
|
||||||
req := hlsManagerAPIMuxersGetReq{ |
|
||||||
name: name, |
|
||||||
res: make(chan hlsManagerAPIMuxersGetRes), |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case m.chAPIMuxerGet <- req: |
|
||||||
res := <-req.res |
|
||||||
return res.data, res.err |
|
||||||
|
|
||||||
case <-m.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (m *hlsManager) handleRequest(req hlsMuxerHandleRequestReq) { |
|
||||||
req.res = make(chan *hlsMuxer) |
|
||||||
|
|
||||||
select { |
|
||||||
case m.chHandleRequest <- req: |
|
||||||
muxer := <-req.res |
|
||||||
if muxer != nil { |
|
||||||
req.ctx.Request.URL.Path = req.file |
|
||||||
muxer.handleRequest(req.ctx) |
|
||||||
} |
|
||||||
|
|
||||||
case <-m.ctx.Done(): |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
package core |
|
||||||
|
|
||||||
// publisher is an entity that can publish a stream.
|
|
||||||
type publisher interface { |
|
||||||
source |
|
||||||
close() |
|
||||||
} |
|
||||||
@ -1,17 +0,0 @@ |
|||||||
package core |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/bluenviron/mediamtx/internal/asyncwriter" |
|
||||||
"github.com/bluenviron/mediamtx/internal/defs" |
|
||||||
"github.com/bluenviron/mediamtx/internal/stream" |
|
||||||
) |
|
||||||
|
|
||||||
// reader is an entity that can read a stream.
|
|
||||||
type reader interface { |
|
||||||
close() |
|
||||||
apiReaderDescribe() defs.APIPathSourceOrReader |
|
||||||
} |
|
||||||
|
|
||||||
func readerMediaInfo(r *asyncwriter.Writer, stream *stream.Stream) string { |
|
||||||
return mediaInfo(stream.MediasForReader(r)) |
|
||||||
} |
|
||||||
@ -1,48 +0,0 @@ |
|||||||
package core |
|
||||||
|
|
||||||
import ( |
|
||||||
"net" |
|
||||||
"sync" |
|
||||||
) |
|
||||||
|
|
||||||
type rtmpListener struct { |
|
||||||
ln net.Listener |
|
||||||
wg *sync.WaitGroup |
|
||||||
parent *rtmpServer |
|
||||||
} |
|
||||||
|
|
||||||
func newRTMPListener( |
|
||||||
ln net.Listener, |
|
||||||
wg *sync.WaitGroup, |
|
||||||
parent *rtmpServer, |
|
||||||
) *rtmpListener { |
|
||||||
l := &rtmpListener{ |
|
||||||
ln: ln, |
|
||||||
wg: wg, |
|
||||||
parent: parent, |
|
||||||
} |
|
||||||
|
|
||||||
l.wg.Add(1) |
|
||||||
go l.run() |
|
||||||
|
|
||||||
return l |
|
||||||
} |
|
||||||
|
|
||||||
func (l *rtmpListener) run() { |
|
||||||
defer l.wg.Done() |
|
||||||
|
|
||||||
err := l.runInner() |
|
||||||
|
|
||||||
l.parent.acceptError(err) |
|
||||||
} |
|
||||||
|
|
||||||
func (l *rtmpListener) runInner() error { |
|
||||||
for { |
|
||||||
conn, err := l.ln.Accept() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
l.parent.newConn(conn) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,327 +0,0 @@ |
|||||||
package core |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"crypto/tls" |
|
||||||
"fmt" |
|
||||||
"net" |
|
||||||
"sort" |
|
||||||
"sync" |
|
||||||
|
|
||||||
"github.com/google/uuid" |
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf" |
|
||||||
"github.com/bluenviron/mediamtx/internal/defs" |
|
||||||
"github.com/bluenviron/mediamtx/internal/externalcmd" |
|
||||||
"github.com/bluenviron/mediamtx/internal/logger" |
|
||||||
"github.com/bluenviron/mediamtx/internal/restrictnetwork" |
|
||||||
) |
|
||||||
|
|
||||||
type rtmpServerAPIConnsListRes struct { |
|
||||||
data *defs.APIRTMPConnList |
|
||||||
err error |
|
||||||
} |
|
||||||
|
|
||||||
type rtmpServerAPIConnsListReq struct { |
|
||||||
res chan rtmpServerAPIConnsListRes |
|
||||||
} |
|
||||||
|
|
||||||
type rtmpServerAPIConnsGetRes struct { |
|
||||||
data *defs.APIRTMPConn |
|
||||||
err error |
|
||||||
} |
|
||||||
|
|
||||||
type rtmpServerAPIConnsGetReq struct { |
|
||||||
uuid uuid.UUID |
|
||||||
res chan rtmpServerAPIConnsGetRes |
|
||||||
} |
|
||||||
|
|
||||||
type rtmpServerAPIConnsKickRes struct { |
|
||||||
err error |
|
||||||
} |
|
||||||
|
|
||||||
type rtmpServerAPIConnsKickReq struct { |
|
||||||
uuid uuid.UUID |
|
||||||
res chan rtmpServerAPIConnsKickRes |
|
||||||
} |
|
||||||
|
|
||||||
type rtmpServerParent interface { |
|
||||||
logger.Writer |
|
||||||
} |
|
||||||
|
|
||||||
type rtmpServer struct { |
|
||||||
readTimeout conf.StringDuration |
|
||||||
writeTimeout conf.StringDuration |
|
||||||
writeQueueSize int |
|
||||||
isTLS bool |
|
||||||
rtspAddress string |
|
||||||
runOnConnect string |
|
||||||
runOnConnectRestart bool |
|
||||||
runOnDisconnect string |
|
||||||
externalCmdPool *externalcmd.Pool |
|
||||||
pathManager *pathManager |
|
||||||
parent rtmpServerParent |
|
||||||
|
|
||||||
ctx context.Context |
|
||||||
ctxCancel func() |
|
||||||
wg sync.WaitGroup |
|
||||||
ln net.Listener |
|
||||||
conns map[*rtmpConn]struct{} |
|
||||||
|
|
||||||
// in
|
|
||||||
chNewConn chan net.Conn |
|
||||||
chAcceptErr chan error |
|
||||||
chCloseConn chan *rtmpConn |
|
||||||
chAPIConnsList chan rtmpServerAPIConnsListReq |
|
||||||
chAPIConnsGet chan rtmpServerAPIConnsGetReq |
|
||||||
chAPIConnsKick chan rtmpServerAPIConnsKickReq |
|
||||||
} |
|
||||||
|
|
||||||
func newRTMPServer( |
|
||||||
address string, |
|
||||||
readTimeout conf.StringDuration, |
|
||||||
writeTimeout conf.StringDuration, |
|
||||||
writeQueueSize int, |
|
||||||
isTLS bool, |
|
||||||
serverCert string, |
|
||||||
serverKey string, |
|
||||||
rtspAddress string, |
|
||||||
runOnConnect string, |
|
||||||
runOnConnectRestart bool, |
|
||||||
runOnDisconnect string, |
|
||||||
externalCmdPool *externalcmd.Pool, |
|
||||||
pathManager *pathManager, |
|
||||||
parent rtmpServerParent, |
|
||||||
) (*rtmpServer, error) { |
|
||||||
ln, err := func() (net.Listener, error) { |
|
||||||
if !isTLS { |
|
||||||
return net.Listen(restrictnetwork.Restrict("tcp", address)) |
|
||||||
} |
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair(serverCert, serverKey) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
network, address := restrictnetwork.Restrict("tcp", address) |
|
||||||
return tls.Listen(network, address, &tls.Config{Certificates: []tls.Certificate{cert}}) |
|
||||||
}() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
ctx, ctxCancel := context.WithCancel(context.Background()) |
|
||||||
|
|
||||||
s := &rtmpServer{ |
|
||||||
readTimeout: readTimeout, |
|
||||||
writeTimeout: writeTimeout, |
|
||||||
writeQueueSize: writeQueueSize, |
|
||||||
rtspAddress: rtspAddress, |
|
||||||
runOnConnect: runOnConnect, |
|
||||||
runOnConnectRestart: runOnConnectRestart, |
|
||||||
runOnDisconnect: runOnDisconnect, |
|
||||||
isTLS: isTLS, |
|
||||||
externalCmdPool: externalCmdPool, |
|
||||||
pathManager: pathManager, |
|
||||||
parent: parent, |
|
||||||
ctx: ctx, |
|
||||||
ctxCancel: ctxCancel, |
|
||||||
ln: ln, |
|
||||||
conns: make(map[*rtmpConn]struct{}), |
|
||||||
chNewConn: make(chan net.Conn), |
|
||||||
chAcceptErr: make(chan error), |
|
||||||
chCloseConn: make(chan *rtmpConn), |
|
||||||
chAPIConnsList: make(chan rtmpServerAPIConnsListReq), |
|
||||||
chAPIConnsGet: make(chan rtmpServerAPIConnsGetReq), |
|
||||||
chAPIConnsKick: make(chan rtmpServerAPIConnsKickReq), |
|
||||||
} |
|
||||||
|
|
||||||
s.Log(logger.Info, "listener opened on %s", address) |
|
||||||
|
|
||||||
newRTMPListener( |
|
||||||
s.ln, |
|
||||||
&s.wg, |
|
||||||
s, |
|
||||||
) |
|
||||||
|
|
||||||
s.wg.Add(1) |
|
||||||
go s.run() |
|
||||||
|
|
||||||
return s, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtmpServer) Log(level logger.Level, format string, args ...interface{}) { |
|
||||||
label := func() string { |
|
||||||
if s.isTLS { |
|
||||||
return "RTMPS" |
|
||||||
} |
|
||||||
return "RTMP" |
|
||||||
}() |
|
||||||
s.parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtmpServer) close() { |
|
||||||
s.Log(logger.Info, "listener is closing") |
|
||||||
s.ctxCancel() |
|
||||||
s.wg.Wait() |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtmpServer) run() { |
|
||||||
defer s.wg.Done() |
|
||||||
|
|
||||||
outer: |
|
||||||
for { |
|
||||||
select { |
|
||||||
case err := <-s.chAcceptErr: |
|
||||||
s.Log(logger.Error, "%s", err) |
|
||||||
break outer |
|
||||||
|
|
||||||
case nconn := <-s.chNewConn: |
|
||||||
c := newRTMPConn( |
|
||||||
s.ctx, |
|
||||||
s.isTLS, |
|
||||||
s.rtspAddress, |
|
||||||
s.readTimeout, |
|
||||||
s.writeTimeout, |
|
||||||
s.writeQueueSize, |
|
||||||
s.runOnConnect, |
|
||||||
s.runOnConnectRestart, |
|
||||||
s.runOnDisconnect, |
|
||||||
&s.wg, |
|
||||||
nconn, |
|
||||||
s.externalCmdPool, |
|
||||||
s.pathManager, |
|
||||||
s) |
|
||||||
s.conns[c] = struct{}{} |
|
||||||
|
|
||||||
case c := <-s.chCloseConn: |
|
||||||
delete(s.conns, c) |
|
||||||
|
|
||||||
case req := <-s.chAPIConnsList: |
|
||||||
data := &defs.APIRTMPConnList{ |
|
||||||
Items: []*defs.APIRTMPConn{}, |
|
||||||
} |
|
||||||
|
|
||||||
for c := range s.conns { |
|
||||||
data.Items = append(data.Items, c.apiItem()) |
|
||||||
} |
|
||||||
|
|
||||||
sort.Slice(data.Items, func(i, j int) bool { |
|
||||||
return data.Items[i].Created.Before(data.Items[j].Created) |
|
||||||
}) |
|
||||||
|
|
||||||
req.res <- rtmpServerAPIConnsListRes{data: data} |
|
||||||
|
|
||||||
case req := <-s.chAPIConnsGet: |
|
||||||
c := s.findConnByUUID(req.uuid) |
|
||||||
if c == nil { |
|
||||||
req.res <- rtmpServerAPIConnsGetRes{err: fmt.Errorf("connection not found")} |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
req.res <- rtmpServerAPIConnsGetRes{data: c.apiItem()} |
|
||||||
|
|
||||||
case req := <-s.chAPIConnsKick: |
|
||||||
c := s.findConnByUUID(req.uuid) |
|
||||||
if c == nil { |
|
||||||
req.res <- rtmpServerAPIConnsKickRes{err: fmt.Errorf("connection not found")} |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
delete(s.conns, c) |
|
||||||
c.close() |
|
||||||
req.res <- rtmpServerAPIConnsKickRes{} |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
break outer |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
s.ctxCancel() |
|
||||||
|
|
||||||
s.ln.Close() |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtmpServer) findConnByUUID(uuid uuid.UUID) *rtmpConn { |
|
||||||
for c := range s.conns { |
|
||||||
if c.uuid == uuid { |
|
||||||
return c |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// newConn is called by rtmpListener.
|
|
||||||
func (s *rtmpServer) newConn(conn net.Conn) { |
|
||||||
select { |
|
||||||
case s.chNewConn <- conn: |
|
||||||
case <-s.ctx.Done(): |
|
||||||
conn.Close() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// acceptError is called by rtmpListener.
|
|
||||||
func (s *rtmpServer) acceptError(err error) { |
|
||||||
select { |
|
||||||
case s.chAcceptErr <- err: |
|
||||||
case <-s.ctx.Done(): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// closeConn is called by rtmpConn.
|
|
||||||
func (s *rtmpServer) closeConn(c *rtmpConn) { |
|
||||||
select { |
|
||||||
case s.chCloseConn <- c: |
|
||||||
case <-s.ctx.Done(): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// apiConnsList is called by api.
|
|
||||||
func (s *rtmpServer) apiConnsList() (*defs.APIRTMPConnList, error) { |
|
||||||
req := rtmpServerAPIConnsListReq{ |
|
||||||
res: make(chan rtmpServerAPIConnsListRes), |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case s.chAPIConnsList <- req: |
|
||||||
res := <-req.res |
|
||||||
return res.data, res.err |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// apiConnsGet is called by api.
|
|
||||||
func (s *rtmpServer) apiConnsGet(uuid uuid.UUID) (*defs.APIRTMPConn, error) { |
|
||||||
req := rtmpServerAPIConnsGetReq{ |
|
||||||
uuid: uuid, |
|
||||||
res: make(chan rtmpServerAPIConnsGetRes), |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case s.chAPIConnsGet <- req: |
|
||||||
res := <-req.res |
|
||||||
return res.data, res.err |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// apiConnsKick is called by api.
|
|
||||||
func (s *rtmpServer) apiConnsKick(uuid uuid.UUID) error { |
|
||||||
req := rtmpServerAPIConnsKickReq{ |
|
||||||
uuid: uuid, |
|
||||||
res: make(chan rtmpServerAPIConnsKickRes), |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case s.chAPIConnsKick <- req: |
|
||||||
res := <-req.res |
|
||||||
return res.err |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
return fmt.Errorf("terminated") |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,454 +0,0 @@ |
|||||||
package core |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"crypto/tls" |
|
||||||
"fmt" |
|
||||||
"sort" |
|
||||||
"strings" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4" |
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/base" |
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/headers" |
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/liberrors" |
|
||||||
"github.com/google/uuid" |
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf" |
|
||||||
"github.com/bluenviron/mediamtx/internal/defs" |
|
||||||
"github.com/bluenviron/mediamtx/internal/externalcmd" |
|
||||||
"github.com/bluenviron/mediamtx/internal/logger" |
|
||||||
) |
|
||||||
|
|
||||||
type rtspServerParent interface { |
|
||||||
logger.Writer |
|
||||||
} |
|
||||||
|
|
||||||
func printAddresses(srv *gortsplib.Server) string { |
|
||||||
var ret []string |
|
||||||
|
|
||||||
ret = append(ret, fmt.Sprintf("%s (TCP)", srv.RTSPAddress)) |
|
||||||
|
|
||||||
if srv.UDPRTPAddress != "" { |
|
||||||
ret = append(ret, fmt.Sprintf("%s (UDP/RTP)", srv.UDPRTPAddress)) |
|
||||||
} |
|
||||||
|
|
||||||
if srv.UDPRTCPAddress != "" { |
|
||||||
ret = append(ret, fmt.Sprintf("%s (UDP/RTCP)", srv.UDPRTCPAddress)) |
|
||||||
} |
|
||||||
|
|
||||||
return strings.Join(ret, ", ") |
|
||||||
} |
|
||||||
|
|
||||||
type rtspServer struct { |
|
||||||
authMethods []headers.AuthMethod |
|
||||||
readTimeout conf.StringDuration |
|
||||||
isTLS bool |
|
||||||
rtspAddress string |
|
||||||
protocols map[conf.Protocol]struct{} |
|
||||||
runOnConnect string |
|
||||||
runOnConnectRestart bool |
|
||||||
runOnDisconnect string |
|
||||||
externalCmdPool *externalcmd.Pool |
|
||||||
pathManager *pathManager |
|
||||||
parent rtspServerParent |
|
||||||
|
|
||||||
ctx context.Context |
|
||||||
ctxCancel func() |
|
||||||
wg sync.WaitGroup |
|
||||||
srv *gortsplib.Server |
|
||||||
mutex sync.RWMutex |
|
||||||
conns map[*gortsplib.ServerConn]*rtspConn |
|
||||||
sessions map[*gortsplib.ServerSession]*rtspSession |
|
||||||
} |
|
||||||
|
|
||||||
func newRTSPServer( |
|
||||||
address string, |
|
||||||
authMethods []headers.AuthMethod, |
|
||||||
readTimeout conf.StringDuration, |
|
||||||
writeTimeout conf.StringDuration, |
|
||||||
writeQueueSize int, |
|
||||||
useUDP bool, |
|
||||||
useMulticast bool, |
|
||||||
rtpAddress string, |
|
||||||
rtcpAddress string, |
|
||||||
multicastIPRange string, |
|
||||||
multicastRTPPort int, |
|
||||||
multicastRTCPPort int, |
|
||||||
isTLS bool, |
|
||||||
serverCert string, |
|
||||||
serverKey string, |
|
||||||
rtspAddress string, |
|
||||||
protocols map[conf.Protocol]struct{}, |
|
||||||
runOnConnect string, |
|
||||||
runOnConnectRestart bool, |
|
||||||
runOnDisconnect string, |
|
||||||
externalCmdPool *externalcmd.Pool, |
|
||||||
pathManager *pathManager, |
|
||||||
parent rtspServerParent, |
|
||||||
) (*rtspServer, error) { |
|
||||||
ctx, ctxCancel := context.WithCancel(context.Background()) |
|
||||||
|
|
||||||
s := &rtspServer{ |
|
||||||
authMethods: authMethods, |
|
||||||
readTimeout: readTimeout, |
|
||||||
isTLS: isTLS, |
|
||||||
rtspAddress: rtspAddress, |
|
||||||
protocols: protocols, |
|
||||||
runOnConnect: runOnConnect, |
|
||||||
runOnConnectRestart: runOnConnectRestart, |
|
||||||
runOnDisconnect: runOnDisconnect, |
|
||||||
externalCmdPool: externalCmdPool, |
|
||||||
pathManager: pathManager, |
|
||||||
parent: parent, |
|
||||||
ctx: ctx, |
|
||||||
ctxCancel: ctxCancel, |
|
||||||
conns: make(map[*gortsplib.ServerConn]*rtspConn), |
|
||||||
sessions: make(map[*gortsplib.ServerSession]*rtspSession), |
|
||||||
} |
|
||||||
|
|
||||||
s.srv = &gortsplib.Server{ |
|
||||||
Handler: s, |
|
||||||
ReadTimeout: time.Duration(readTimeout), |
|
||||||
WriteTimeout: time.Duration(writeTimeout), |
|
||||||
WriteQueueSize: writeQueueSize, |
|
||||||
RTSPAddress: address, |
|
||||||
} |
|
||||||
|
|
||||||
if useUDP { |
|
||||||
s.srv.UDPRTPAddress = rtpAddress |
|
||||||
s.srv.UDPRTCPAddress = rtcpAddress |
|
||||||
} |
|
||||||
|
|
||||||
if useMulticast { |
|
||||||
s.srv.MulticastIPRange = multicastIPRange |
|
||||||
s.srv.MulticastRTPPort = multicastRTPPort |
|
||||||
s.srv.MulticastRTCPPort = multicastRTCPPort |
|
||||||
} |
|
||||||
|
|
||||||
if isTLS { |
|
||||||
cert, err := tls.LoadX509KeyPair(serverCert, serverKey) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
s.srv.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}} |
|
||||||
} |
|
||||||
|
|
||||||
err := s.srv.Start() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
s.Log(logger.Info, "listener opened on %s", printAddresses(s.srv)) |
|
||||||
|
|
||||||
s.wg.Add(1) |
|
||||||
go s.run() |
|
||||||
|
|
||||||
return s, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtspServer) Log(level logger.Level, format string, args ...interface{}) { |
|
||||||
label := func() string { |
|
||||||
if s.isTLS { |
|
||||||
return "RTSPS" |
|
||||||
} |
|
||||||
return "RTSP" |
|
||||||
}() |
|
||||||
s.parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtspServer) getISTLS() bool { |
|
||||||
return s.isTLS |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtspServer) getServer() *gortsplib.Server { |
|
||||||
return s.srv |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtspServer) close() { |
|
||||||
s.Log(logger.Info, "listener is closing") |
|
||||||
s.ctxCancel() |
|
||||||
s.wg.Wait() |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtspServer) run() { |
|
||||||
defer s.wg.Done() |
|
||||||
|
|
||||||
serverErr := make(chan error) |
|
||||||
go func() { |
|
||||||
serverErr <- s.srv.Wait() |
|
||||||
}() |
|
||||||
|
|
||||||
outer: |
|
||||||
select { |
|
||||||
case err := <-serverErr: |
|
||||||
s.Log(logger.Error, "%s", err) |
|
||||||
break outer |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
s.srv.Close() |
|
||||||
<-serverErr |
|
||||||
break outer |
|
||||||
} |
|
||||||
|
|
||||||
s.ctxCancel() |
|
||||||
} |
|
||||||
|
|
||||||
// OnConnOpen implements gortsplib.ServerHandlerOnConnOpen.
|
|
||||||
func (s *rtspServer) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) { |
|
||||||
c := newRTSPConn( |
|
||||||
s.isTLS, |
|
||||||
s.rtspAddress, |
|
||||||
s.authMethods, |
|
||||||
s.readTimeout, |
|
||||||
s.runOnConnect, |
|
||||||
s.runOnConnectRestart, |
|
||||||
s.runOnDisconnect, |
|
||||||
s.externalCmdPool, |
|
||||||
s.pathManager, |
|
||||||
ctx.Conn, |
|
||||||
s) |
|
||||||
s.mutex.Lock() |
|
||||||
s.conns[ctx.Conn] = c |
|
||||||
s.mutex.Unlock() |
|
||||||
|
|
||||||
ctx.Conn.SetUserData(c) |
|
||||||
} |
|
||||||
|
|
||||||
// OnConnClose implements gortsplib.ServerHandlerOnConnClose.
|
|
||||||
func (s *rtspServer) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) { |
|
||||||
s.mutex.Lock() |
|
||||||
c := s.conns[ctx.Conn] |
|
||||||
delete(s.conns, ctx.Conn) |
|
||||||
s.mutex.Unlock() |
|
||||||
c.onClose(ctx.Error) |
|
||||||
} |
|
||||||
|
|
||||||
// OnRequest implements gortsplib.ServerHandlerOnRequest.
|
|
||||||
func (s *rtspServer) OnRequest(sc *gortsplib.ServerConn, req *base.Request) { |
|
||||||
c := sc.UserData().(*rtspConn) |
|
||||||
c.onRequest(req) |
|
||||||
} |
|
||||||
|
|
||||||
// OnResponse implements gortsplib.ServerHandlerOnResponse.
|
|
||||||
func (s *rtspServer) OnResponse(sc *gortsplib.ServerConn, res *base.Response) { |
|
||||||
c := sc.UserData().(*rtspConn) |
|
||||||
c.OnResponse(res) |
|
||||||
} |
|
||||||
|
|
||||||
// OnSessionOpen implements gortsplib.ServerHandlerOnSessionOpen.
|
|
||||||
func (s *rtspServer) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) { |
|
||||||
se := newRTSPSession( |
|
||||||
s.isTLS, |
|
||||||
s.protocols, |
|
||||||
ctx.Session, |
|
||||||
ctx.Conn, |
|
||||||
s.externalCmdPool, |
|
||||||
s.pathManager, |
|
||||||
s) |
|
||||||
s.mutex.Lock() |
|
||||||
s.sessions[ctx.Session] = se |
|
||||||
s.mutex.Unlock() |
|
||||||
ctx.Session.SetUserData(se) |
|
||||||
} |
|
||||||
|
|
||||||
// OnSessionClose implements gortsplib.ServerHandlerOnSessionClose.
|
|
||||||
func (s *rtspServer) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) { |
|
||||||
s.mutex.Lock() |
|
||||||
se := s.sessions[ctx.Session] |
|
||||||
delete(s.sessions, ctx.Session) |
|
||||||
s.mutex.Unlock() |
|
||||||
|
|
||||||
if se != nil { |
|
||||||
se.onClose(ctx.Error) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// OnDescribe implements gortsplib.ServerHandlerOnDescribe.
|
|
||||||
func (s *rtspServer) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx, |
|
||||||
) (*base.Response, *gortsplib.ServerStream, error) { |
|
||||||
c := ctx.Conn.UserData().(*rtspConn) |
|
||||||
return c.onDescribe(ctx) |
|
||||||
} |
|
||||||
|
|
||||||
// OnAnnounce implements gortsplib.ServerHandlerOnAnnounce.
|
|
||||||
func (s *rtspServer) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) { |
|
||||||
c := ctx.Conn.UserData().(*rtspConn) |
|
||||||
se := ctx.Session.UserData().(*rtspSession) |
|
||||||
return se.onAnnounce(c, ctx) |
|
||||||
} |
|
||||||
|
|
||||||
// OnSetup implements gortsplib.ServerHandlerOnSetup.
|
|
||||||
func (s *rtspServer) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) { |
|
||||||
c := ctx.Conn.UserData().(*rtspConn) |
|
||||||
se := ctx.Session.UserData().(*rtspSession) |
|
||||||
return se.onSetup(c, ctx) |
|
||||||
} |
|
||||||
|
|
||||||
// OnPlay implements gortsplib.ServerHandlerOnPlay.
|
|
||||||
func (s *rtspServer) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) { |
|
||||||
se := ctx.Session.UserData().(*rtspSession) |
|
||||||
return se.onPlay(ctx) |
|
||||||
} |
|
||||||
|
|
||||||
// OnRecord implements gortsplib.ServerHandlerOnRecord.
|
|
||||||
func (s *rtspServer) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) { |
|
||||||
se := ctx.Session.UserData().(*rtspSession) |
|
||||||
return se.onRecord(ctx) |
|
||||||
} |
|
||||||
|
|
||||||
// OnPause implements gortsplib.ServerHandlerOnPause.
|
|
||||||
func (s *rtspServer) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) { |
|
||||||
se := ctx.Session.UserData().(*rtspSession) |
|
||||||
return se.onPause(ctx) |
|
||||||
} |
|
||||||
|
|
||||||
// OnPacketLost implements gortsplib.ServerHandlerOnDecodeError.
|
|
||||||
func (s *rtspServer) OnPacketLost(ctx *gortsplib.ServerHandlerOnPacketLostCtx) { |
|
||||||
se := ctx.Session.UserData().(*rtspSession) |
|
||||||
se.onPacketLost(ctx) |
|
||||||
} |
|
||||||
|
|
||||||
// OnDecodeError implements gortsplib.ServerHandlerOnDecodeError.
|
|
||||||
func (s *rtspServer) OnDecodeError(ctx *gortsplib.ServerHandlerOnDecodeErrorCtx) { |
|
||||||
se := ctx.Session.UserData().(*rtspSession) |
|
||||||
se.onDecodeError(ctx) |
|
||||||
} |
|
||||||
|
|
||||||
// OnDecodeError implements gortsplib.ServerHandlerOnStreamWriteError.
|
|
||||||
func (s *rtspServer) OnStreamWriteError(ctx *gortsplib.ServerHandlerOnStreamWriteErrorCtx) { |
|
||||||
se := ctx.Session.UserData().(*rtspSession) |
|
||||||
se.onStreamWriteError(ctx) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtspServer) findConnByUUID(uuid uuid.UUID) *rtspConn { |
|
||||||
for _, c := range s.conns { |
|
||||||
if c.uuid == uuid { |
|
||||||
return c |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (s *rtspServer) findSessionByUUID(uuid uuid.UUID) (*gortsplib.ServerSession, *rtspSession) { |
|
||||||
for key, sx := range s.sessions { |
|
||||||
if sx.uuid == uuid { |
|
||||||
return key, sx |
|
||||||
} |
|
||||||
} |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
|
|
||||||
// apiConnsList is called by api and metrics.
|
|
||||||
func (s *rtspServer) apiConnsList() (*defs.APIRTSPConnsList, error) { |
|
||||||
select { |
|
||||||
case <-s.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
default: |
|
||||||
} |
|
||||||
|
|
||||||
s.mutex.RLock() |
|
||||||
defer s.mutex.RUnlock() |
|
||||||
|
|
||||||
data := &defs.APIRTSPConnsList{ |
|
||||||
Items: []*defs.APIRTSPConn{}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, c := range s.conns { |
|
||||||
data.Items = append(data.Items, c.apiItem()) |
|
||||||
} |
|
||||||
|
|
||||||
sort.Slice(data.Items, func(i, j int) bool { |
|
||||||
return data.Items[i].Created.Before(data.Items[j].Created) |
|
||||||
}) |
|
||||||
|
|
||||||
return data, nil |
|
||||||
} |
|
||||||
|
|
||||||
// apiConnsGet is called by api.
|
|
||||||
func (s *rtspServer) apiConnsGet(uuid uuid.UUID) (*defs.APIRTSPConn, error) { |
|
||||||
select { |
|
||||||
case <-s.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
default: |
|
||||||
} |
|
||||||
|
|
||||||
s.mutex.RLock() |
|
||||||
defer s.mutex.RUnlock() |
|
||||||
|
|
||||||
conn := s.findConnByUUID(uuid) |
|
||||||
if conn == nil { |
|
||||||
return nil, fmt.Errorf("connection not found") |
|
||||||
} |
|
||||||
|
|
||||||
return conn.apiItem(), nil |
|
||||||
} |
|
||||||
|
|
||||||
// apiSessionsList is called by api and metrics.
|
|
||||||
func (s *rtspServer) apiSessionsList() (*defs.APIRTSPSessionList, error) { |
|
||||||
select { |
|
||||||
case <-s.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
default: |
|
||||||
} |
|
||||||
|
|
||||||
s.mutex.RLock() |
|
||||||
defer s.mutex.RUnlock() |
|
||||||
|
|
||||||
data := &defs.APIRTSPSessionList{ |
|
||||||
Items: []*defs.APIRTSPSession{}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, s := range s.sessions { |
|
||||||
data.Items = append(data.Items, s.apiItem()) |
|
||||||
} |
|
||||||
|
|
||||||
sort.Slice(data.Items, func(i, j int) bool { |
|
||||||
return data.Items[i].Created.Before(data.Items[j].Created) |
|
||||||
}) |
|
||||||
|
|
||||||
return data, nil |
|
||||||
} |
|
||||||
|
|
||||||
// apiSessionsGet is called by api.
|
|
||||||
func (s *rtspServer) apiSessionsGet(uuid uuid.UUID) (*defs.APIRTSPSession, error) { |
|
||||||
select { |
|
||||||
case <-s.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
default: |
|
||||||
} |
|
||||||
|
|
||||||
s.mutex.RLock() |
|
||||||
defer s.mutex.RUnlock() |
|
||||||
|
|
||||||
_, sx := s.findSessionByUUID(uuid) |
|
||||||
if sx == nil { |
|
||||||
return nil, fmt.Errorf("session not found") |
|
||||||
} |
|
||||||
|
|
||||||
return sx.apiItem(), nil |
|
||||||
} |
|
||||||
|
|
||||||
// apiSessionsKick is called by api.
|
|
||||||
func (s *rtspServer) apiSessionsKick(uuid uuid.UUID) error { |
|
||||||
select { |
|
||||||
case <-s.ctx.Done(): |
|
||||||
return fmt.Errorf("terminated") |
|
||||||
default: |
|
||||||
} |
|
||||||
|
|
||||||
s.mutex.RLock() |
|
||||||
defer s.mutex.RUnlock() |
|
||||||
|
|
||||||
key, sx := s.findSessionByUUID(uuid) |
|
||||||
if sx == nil { |
|
||||||
return fmt.Errorf("session not found") |
|
||||||
} |
|
||||||
|
|
||||||
sx.close() |
|
||||||
delete(s.sessions, key) |
|
||||||
sx.onClose(liberrors.ErrServerTerminated{}) |
|
||||||
return nil |
|
||||||
} |
|
||||||
@ -1,330 +0,0 @@ |
|||||||
package core |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"fmt" |
|
||||||
"sort" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
|
|
||||||
srt "github.com/datarhei/gosrt" |
|
||||||
"github.com/google/uuid" |
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf" |
|
||||||
"github.com/bluenviron/mediamtx/internal/defs" |
|
||||||
"github.com/bluenviron/mediamtx/internal/externalcmd" |
|
||||||
"github.com/bluenviron/mediamtx/internal/logger" |
|
||||||
) |
|
||||||
|
|
||||||
func srtMaxPayloadSize(u int) int { |
|
||||||
return ((u - 16) / 188) * 188 // 16 = SRT header, 188 = MPEG-TS packet
|
|
||||||
} |
|
||||||
|
|
||||||
type srtNewConnReq struct { |
|
||||||
connReq srt.ConnRequest |
|
||||||
res chan *srtConn |
|
||||||
} |
|
||||||
|
|
||||||
type srtServerAPIConnsListRes struct { |
|
||||||
data *defs.APISRTConnList |
|
||||||
err error |
|
||||||
} |
|
||||||
|
|
||||||
type srtServerAPIConnsListReq struct { |
|
||||||
res chan srtServerAPIConnsListRes |
|
||||||
} |
|
||||||
|
|
||||||
type srtServerAPIConnsGetRes struct { |
|
||||||
data *defs.APISRTConn |
|
||||||
err error |
|
||||||
} |
|
||||||
|
|
||||||
type srtServerAPIConnsGetReq struct { |
|
||||||
uuid uuid.UUID |
|
||||||
res chan srtServerAPIConnsGetRes |
|
||||||
} |
|
||||||
|
|
||||||
type srtServerAPIConnsKickRes struct { |
|
||||||
err error |
|
||||||
} |
|
||||||
|
|
||||||
type srtServerAPIConnsKickReq struct { |
|
||||||
uuid uuid.UUID |
|
||||||
res chan srtServerAPIConnsKickRes |
|
||||||
} |
|
||||||
|
|
||||||
type srtServerParent interface { |
|
||||||
logger.Writer |
|
||||||
} |
|
||||||
|
|
||||||
type srtServer struct { |
|
||||||
rtspAddress string |
|
||||||
readTimeout conf.StringDuration |
|
||||||
writeTimeout conf.StringDuration |
|
||||||
writeQueueSize int |
|
||||||
udpMaxPayloadSize int |
|
||||||
runOnConnect string |
|
||||||
runOnConnectRestart bool |
|
||||||
runOnDisconnect string |
|
||||||
externalCmdPool *externalcmd.Pool |
|
||||||
pathManager *pathManager |
|
||||||
parent srtServerParent |
|
||||||
|
|
||||||
ctx context.Context |
|
||||||
ctxCancel func() |
|
||||||
wg sync.WaitGroup |
|
||||||
ln srt.Listener |
|
||||||
conns map[*srtConn]struct{} |
|
||||||
|
|
||||||
// in
|
|
||||||
chNewConnRequest chan srtNewConnReq |
|
||||||
chAcceptErr chan error |
|
||||||
chCloseConn chan *srtConn |
|
||||||
chAPIConnsList chan srtServerAPIConnsListReq |
|
||||||
chAPIConnsGet chan srtServerAPIConnsGetReq |
|
||||||
chAPIConnsKick chan srtServerAPIConnsKickReq |
|
||||||
} |
|
||||||
|
|
||||||
func newSRTServer( |
|
||||||
address string, |
|
||||||
rtspAddress string, |
|
||||||
readTimeout conf.StringDuration, |
|
||||||
writeTimeout conf.StringDuration, |
|
||||||
writeQueueSize int, |
|
||||||
udpMaxPayloadSize int, |
|
||||||
runOnConnect string, |
|
||||||
runOnConnectRestart bool, |
|
||||||
runOnDisconnect string, |
|
||||||
externalCmdPool *externalcmd.Pool, |
|
||||||
pathManager *pathManager, |
|
||||||
parent srtServerParent, |
|
||||||
) (*srtServer, error) { |
|
||||||
conf := srt.DefaultConfig() |
|
||||||
conf.ConnectionTimeout = time.Duration(readTimeout) |
|
||||||
conf.PayloadSize = uint32(srtMaxPayloadSize(udpMaxPayloadSize)) |
|
||||||
|
|
||||||
ln, err := srt.Listen("srt", address, conf) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
ctx, ctxCancel := context.WithCancel(context.Background()) |
|
||||||
|
|
||||||
s := &srtServer{ |
|
||||||
rtspAddress: rtspAddress, |
|
||||||
readTimeout: readTimeout, |
|
||||||
writeTimeout: writeTimeout, |
|
||||||
writeQueueSize: writeQueueSize, |
|
||||||
udpMaxPayloadSize: udpMaxPayloadSize, |
|
||||||
runOnConnect: runOnConnect, |
|
||||||
runOnConnectRestart: runOnConnectRestart, |
|
||||||
runOnDisconnect: runOnDisconnect, |
|
||||||
externalCmdPool: externalCmdPool, |
|
||||||
pathManager: pathManager, |
|
||||||
parent: parent, |
|
||||||
ctx: ctx, |
|
||||||
ctxCancel: ctxCancel, |
|
||||||
ln: ln, |
|
||||||
conns: make(map[*srtConn]struct{}), |
|
||||||
chNewConnRequest: make(chan srtNewConnReq), |
|
||||||
chAcceptErr: make(chan error), |
|
||||||
chCloseConn: make(chan *srtConn), |
|
||||||
chAPIConnsList: make(chan srtServerAPIConnsListReq), |
|
||||||
chAPIConnsGet: make(chan srtServerAPIConnsGetReq), |
|
||||||
chAPIConnsKick: make(chan srtServerAPIConnsKickReq), |
|
||||||
} |
|
||||||
|
|
||||||
s.Log(logger.Info, "listener opened on "+address+" (UDP)") |
|
||||||
|
|
||||||
newSRTListener( |
|
||||||
s.ln, |
|
||||||
&s.wg, |
|
||||||
s, |
|
||||||
) |
|
||||||
|
|
||||||
s.wg.Add(1) |
|
||||||
go s.run() |
|
||||||
|
|
||||||
return s, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Log is the main logging function.
|
|
||||||
func (s *srtServer) Log(level logger.Level, format string, args ...interface{}) { |
|
||||||
s.parent.Log(level, "[SRT] "+format, args...) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *srtServer) close() { |
|
||||||
s.Log(logger.Info, "listener is closing") |
|
||||||
s.ctxCancel() |
|
||||||
s.wg.Wait() |
|
||||||
} |
|
||||||
|
|
||||||
func (s *srtServer) run() { |
|
||||||
defer s.wg.Done() |
|
||||||
|
|
||||||
outer: |
|
||||||
for { |
|
||||||
select { |
|
||||||
case err := <-s.chAcceptErr: |
|
||||||
s.Log(logger.Error, "%s", err) |
|
||||||
break outer |
|
||||||
|
|
||||||
case req := <-s.chNewConnRequest: |
|
||||||
c := newSRTConn( |
|
||||||
s.ctx, |
|
||||||
s.rtspAddress, |
|
||||||
s.readTimeout, |
|
||||||
s.writeTimeout, |
|
||||||
s.writeQueueSize, |
|
||||||
s.udpMaxPayloadSize, |
|
||||||
req.connReq, |
|
||||||
s.runOnConnect, |
|
||||||
s.runOnConnectRestart, |
|
||||||
s.runOnDisconnect, |
|
||||||
&s.wg, |
|
||||||
s.externalCmdPool, |
|
||||||
s.pathManager, |
|
||||||
s) |
|
||||||
s.conns[c] = struct{}{} |
|
||||||
req.res <- c |
|
||||||
|
|
||||||
case c := <-s.chCloseConn: |
|
||||||
delete(s.conns, c) |
|
||||||
|
|
||||||
case req := <-s.chAPIConnsList: |
|
||||||
data := &defs.APISRTConnList{ |
|
||||||
Items: []*defs.APISRTConn{}, |
|
||||||
} |
|
||||||
|
|
||||||
for c := range s.conns { |
|
||||||
data.Items = append(data.Items, c.apiItem()) |
|
||||||
} |
|
||||||
|
|
||||||
sort.Slice(data.Items, func(i, j int) bool { |
|
||||||
return data.Items[i].Created.Before(data.Items[j].Created) |
|
||||||
}) |
|
||||||
|
|
||||||
req.res <- srtServerAPIConnsListRes{data: data} |
|
||||||
|
|
||||||
case req := <-s.chAPIConnsGet: |
|
||||||
c := s.findConnByUUID(req.uuid) |
|
||||||
if c == nil { |
|
||||||
req.res <- srtServerAPIConnsGetRes{err: fmt.Errorf("connection not found")} |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
req.res <- srtServerAPIConnsGetRes{data: c.apiItem()} |
|
||||||
|
|
||||||
case req := <-s.chAPIConnsKick: |
|
||||||
c := s.findConnByUUID(req.uuid) |
|
||||||
if c == nil { |
|
||||||
req.res <- srtServerAPIConnsKickRes{err: fmt.Errorf("connection not found")} |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
delete(s.conns, c) |
|
||||||
c.close() |
|
||||||
req.res <- srtServerAPIConnsKickRes{} |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
break outer |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
s.ctxCancel() |
|
||||||
|
|
||||||
s.ln.Close() |
|
||||||
} |
|
||||||
|
|
||||||
func (s *srtServer) findConnByUUID(uuid uuid.UUID) *srtConn { |
|
||||||
for sx := range s.conns { |
|
||||||
if sx.uuid == uuid { |
|
||||||
return sx |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// newConnRequest is called by srtListener.
|
|
||||||
func (s *srtServer) newConnRequest(connReq srt.ConnRequest) *srtConn { |
|
||||||
req := srtNewConnReq{ |
|
||||||
connReq: connReq, |
|
||||||
res: make(chan *srtConn), |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case s.chNewConnRequest <- req: |
|
||||||
c := <-req.res |
|
||||||
|
|
||||||
return c.new(req) |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// acceptError is called by srtListener.
|
|
||||||
func (s *srtServer) acceptError(err error) { |
|
||||||
select { |
|
||||||
case s.chAcceptErr <- err: |
|
||||||
case <-s.ctx.Done(): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// closeConn is called by srtConn.
|
|
||||||
func (s *srtServer) closeConn(c *srtConn) { |
|
||||||
select { |
|
||||||
case s.chCloseConn <- c: |
|
||||||
case <-s.ctx.Done(): |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// apiConnsList is called by api.
|
|
||||||
func (s *srtServer) apiConnsList() (*defs.APISRTConnList, error) { |
|
||||||
req := srtServerAPIConnsListReq{ |
|
||||||
res: make(chan srtServerAPIConnsListRes), |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case s.chAPIConnsList <- req: |
|
||||||
res := <-req.res |
|
||||||
return res.data, res.err |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// apiConnsGet is called by api.
|
|
||||||
func (s *srtServer) apiConnsGet(uuid uuid.UUID) (*defs.APISRTConn, error) { |
|
||||||
req := srtServerAPIConnsGetReq{ |
|
||||||
uuid: uuid, |
|
||||||
res: make(chan srtServerAPIConnsGetRes), |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case s.chAPIConnsGet <- req: |
|
||||||
res := <-req.res |
|
||||||
return res.data, res.err |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
return nil, fmt.Errorf("terminated") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// apiConnsKick is called by api.
|
|
||||||
func (s *srtServer) apiConnsKick(uuid uuid.UUID) error { |
|
||||||
req := srtServerAPIConnsKickReq{ |
|
||||||
uuid: uuid, |
|
||||||
res: make(chan srtServerAPIConnsKickRes), |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case s.chAPIConnsKick <- req: |
|
||||||
res := <-req.res |
|
||||||
return res.err |
|
||||||
|
|
||||||
case <-s.ctx.Done(): |
|
||||||
return fmt.Errorf("terminated") |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,23 @@ |
|||||||
|
package defs |
||||||
|
|
||||||
|
// AuthProtocol is a authentication protocol.
|
||||||
|
type AuthProtocol string |
||||||
|
|
||||||
|
// authentication protocols.
|
||||||
|
const ( |
||||||
|
AuthProtocolRTSP AuthProtocol = "rtsp" |
||||||
|
AuthProtocolRTMP AuthProtocol = "rtmp" |
||||||
|
AuthProtocolHLS AuthProtocol = "hls" |
||||||
|
AuthProtocolWebRTC AuthProtocol = "webrtc" |
||||||
|
AuthProtocolSRT AuthProtocol = "srt" |
||||||
|
) |
||||||
|
|
||||||
|
// ErrAuthentication is a authentication error.
|
||||||
|
type ErrAuthentication struct { |
||||||
|
Message string |
||||||
|
} |
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e *ErrAuthentication) Error() string { |
||||||
|
return "authentication failed: " + e.Message |
||||||
|
} |
||||||
@ -1,25 +1,156 @@ |
|||||||
package defs |
package defs |
||||||
|
|
||||||
import ( |
import ( |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/base" |
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description" |
"github.com/bluenviron/gortsplib/v4/pkg/description" |
||||||
|
"github.com/google/uuid" |
||||||
|
|
||||||
|
"github.com/bluenviron/mediamtx/internal/conf" |
||||||
|
"github.com/bluenviron/mediamtx/internal/externalcmd" |
||||||
"github.com/bluenviron/mediamtx/internal/stream" |
"github.com/bluenviron/mediamtx/internal/stream" |
||||||
) |
) |
||||||
|
|
||||||
// PathSourceStaticSetReadyRes is a set ready response to a static source.
|
// ErrPathNoOnePublishing is returned when no one is publishing.
|
||||||
|
type ErrPathNoOnePublishing struct { |
||||||
|
PathName string |
||||||
|
} |
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e ErrPathNoOnePublishing) Error() string { |
||||||
|
return fmt.Sprintf("no one is publishing to path '%s'", e.PathName) |
||||||
|
} |
||||||
|
|
||||||
|
// Path is a path.
|
||||||
|
type Path interface { |
||||||
|
Name() string |
||||||
|
SafeConf() *conf.Path |
||||||
|
ExternalCmdEnv() externalcmd.Environment |
||||||
|
StartPublisher(req PathStartPublisherReq) PathStartPublisherRes |
||||||
|
StopPublisher(req PathStopPublisherReq) |
||||||
|
RemovePublisher(req PathRemovePublisherReq) |
||||||
|
RemoveReader(req PathRemoveReaderReq) |
||||||
|
} |
||||||
|
|
||||||
|
// PathAccessRequest is an access request.
|
||||||
|
type PathAccessRequest struct { |
||||||
|
Name string |
||||||
|
Query string |
||||||
|
Publish bool |
||||||
|
SkipAuth bool |
||||||
|
|
||||||
|
// only if skipAuth = false
|
||||||
|
IP net.IP |
||||||
|
User string |
||||||
|
Pass string |
||||||
|
Proto AuthProtocol |
||||||
|
ID *uuid.UUID |
||||||
|
RTSPRequest *base.Request |
||||||
|
RTSPBaseURL *base.URL |
||||||
|
RTSPNonce string |
||||||
|
} |
||||||
|
|
||||||
|
// PathGetConfForPathRes contains the response of GetConfForPath().
|
||||||
|
type PathGetConfForPathRes struct { |
||||||
|
Conf *conf.Path |
||||||
|
Err error |
||||||
|
} |
||||||
|
|
||||||
|
// PathGetConfForPathReq contains arguments of GetConfForPath().
|
||||||
|
type PathGetConfForPathReq struct { |
||||||
|
AccessRequest PathAccessRequest |
||||||
|
Res chan PathGetConfForPathRes |
||||||
|
} |
||||||
|
|
||||||
|
// PathDescribeRes contains the response of Describe().
|
||||||
|
type PathDescribeRes struct { |
||||||
|
Path Path |
||||||
|
Stream *stream.Stream |
||||||
|
Redirect string |
||||||
|
Err error |
||||||
|
} |
||||||
|
|
||||||
|
// PathDescribeReq contains arguments of Describe().
|
||||||
|
type PathDescribeReq struct { |
||||||
|
AccessRequest PathAccessRequest |
||||||
|
Res chan PathDescribeRes |
||||||
|
} |
||||||
|
|
||||||
|
// PathAddPublisherRes contains the response of AddPublisher().
|
||||||
|
type PathAddPublisherRes struct { |
||||||
|
Path Path |
||||||
|
Err error |
||||||
|
} |
||||||
|
|
||||||
|
// PathAddPublisherReq contains arguments of AddPublisher().
|
||||||
|
type PathAddPublisherReq struct { |
||||||
|
Author Publisher |
||||||
|
AccessRequest PathAccessRequest |
||||||
|
Res chan PathAddPublisherRes |
||||||
|
} |
||||||
|
|
||||||
|
// PathRemovePublisherReq contains arguments of RemovePublisher().
|
||||||
|
type PathRemovePublisherReq struct { |
||||||
|
Author Publisher |
||||||
|
Res chan struct{} |
||||||
|
} |
||||||
|
|
||||||
|
// PathStartPublisherRes contains the response of StartPublisher().
|
||||||
|
type PathStartPublisherRes struct { |
||||||
|
Stream *stream.Stream |
||||||
|
Err error |
||||||
|
} |
||||||
|
|
||||||
|
// PathStartPublisherReq contains arguments of StartPublisher().
|
||||||
|
type PathStartPublisherReq struct { |
||||||
|
Author Publisher |
||||||
|
Desc *description.Session |
||||||
|
GenerateRTPPackets bool |
||||||
|
Res chan PathStartPublisherRes |
||||||
|
} |
||||||
|
|
||||||
|
// PathStopPublisherReq contains arguments of StopPublisher().
|
||||||
|
type PathStopPublisherReq struct { |
||||||
|
Author Publisher |
||||||
|
Res chan struct{} |
||||||
|
} |
||||||
|
|
||||||
|
// PathAddReaderRes contains the response of AddReader().
|
||||||
|
type PathAddReaderRes struct { |
||||||
|
Path Path |
||||||
|
Stream *stream.Stream |
||||||
|
Err error |
||||||
|
} |
||||||
|
|
||||||
|
// PathAddReaderReq contains arguments of AddReader().
|
||||||
|
type PathAddReaderReq struct { |
||||||
|
Author Reader |
||||||
|
AccessRequest PathAccessRequest |
||||||
|
Res chan PathAddReaderRes |
||||||
|
} |
||||||
|
|
||||||
|
// PathRemoveReaderReq contains arguments of RemoveReader().
|
||||||
|
type PathRemoveReaderReq struct { |
||||||
|
Author Reader |
||||||
|
Res chan struct{} |
||||||
|
} |
||||||
|
|
||||||
|
// PathSourceStaticSetReadyRes contains the response of SetReadu().
|
||||||
type PathSourceStaticSetReadyRes struct { |
type PathSourceStaticSetReadyRes struct { |
||||||
Stream *stream.Stream |
Stream *stream.Stream |
||||||
Err error |
Err error |
||||||
} |
} |
||||||
|
|
||||||
// PathSourceStaticSetReadyReq is a set ready request from a static source.
|
// PathSourceStaticSetReadyReq contains arguments of SetReady().
|
||||||
type PathSourceStaticSetReadyReq struct { |
type PathSourceStaticSetReadyReq struct { |
||||||
Desc *description.Session |
Desc *description.Session |
||||||
GenerateRTPPackets bool |
GenerateRTPPackets bool |
||||||
Res chan PathSourceStaticSetReadyRes |
Res chan PathSourceStaticSetReadyRes |
||||||
} |
} |
||||||
|
|
||||||
// PathSourceStaticSetNotReadyReq is a set not ready request from a static source.
|
// PathSourceStaticSetNotReadyReq contains arguments of SetNotReady().
|
||||||
type PathSourceStaticSetNotReadyReq struct { |
type PathSourceStaticSetNotReadyReq struct { |
||||||
Res chan struct{} |
Res chan struct{} |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,9 @@ |
|||||||
|
package defs |
||||||
|
|
||||||
|
// PathManager is a path manager.
|
||||||
|
type PathManager interface { |
||||||
|
GetConfForPath(req PathGetConfForPathReq) PathGetConfForPathRes |
||||||
|
Describe(req PathDescribeReq) PathDescribeRes |
||||||
|
AddPublisher(req PathAddPublisherReq) PathAddPublisherRes |
||||||
|
AddReader(req PathAddReaderReq) PathAddReaderRes |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
package defs |
||||||
|
|
||||||
|
// Publisher is an entity that can publish a stream.
|
||||||
|
type Publisher interface { |
||||||
|
Source |
||||||
|
Close() |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
package defs |
||||||
|
|
||||||
|
// Reader is an entity that can read a stream.
|
||||||
|
type Reader interface { |
||||||
|
Close() |
||||||
|
APIReaderDescribe() APIPathSourceOrReader |
||||||
|
} |
||||||
@ -0,0 +1,290 @@ |
|||||||
|
// Package hls contains a HLS server.
|
||||||
|
package hls |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"sort" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/bluenviron/mediamtx/internal/conf" |
||||||
|
"github.com/bluenviron/mediamtx/internal/defs" |
||||||
|
"github.com/bluenviron/mediamtx/internal/logger" |
||||||
|
) |
||||||
|
|
||||||
|
type serverAPIMuxersListRes struct { |
||||||
|
data *defs.APIHLSMuxerList |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIMuxersListReq struct { |
||||||
|
res chan serverAPIMuxersListRes |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIMuxersGetRes struct { |
||||||
|
data *defs.APIHLSMuxer |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIMuxersGetReq struct { |
||||||
|
name string |
||||||
|
res chan serverAPIMuxersGetRes |
||||||
|
} |
||||||
|
|
||||||
|
type serverParent interface { |
||||||
|
logger.Writer |
||||||
|
} |
||||||
|
|
||||||
|
// Server is a HLS server.
|
||||||
|
type Server struct { |
||||||
|
Address string |
||||||
|
Encryption bool |
||||||
|
ServerKey string |
||||||
|
ServerCert string |
||||||
|
ExternalAuthenticationURL string |
||||||
|
AlwaysRemux bool |
||||||
|
Variant conf.HLSVariant |
||||||
|
SegmentCount int |
||||||
|
SegmentDuration conf.StringDuration |
||||||
|
PartDuration conf.StringDuration |
||||||
|
SegmentMaxSize conf.StringSize |
||||||
|
AllowOrigin string |
||||||
|
TrustedProxies conf.IPsOrCIDRs |
||||||
|
Directory string |
||||||
|
ReadTimeout conf.StringDuration |
||||||
|
WriteQueueSize int |
||||||
|
PathManager defs.PathManager |
||||||
|
Parent serverParent |
||||||
|
|
||||||
|
ctx context.Context |
||||||
|
ctxCancel func() |
||||||
|
wg sync.WaitGroup |
||||||
|
httpServer *httpServer |
||||||
|
muxers map[string]*muxer |
||||||
|
|
||||||
|
// in
|
||||||
|
chPathReady chan defs.Path |
||||||
|
chPathNotReady chan defs.Path |
||||||
|
chHandleRequest chan muxerHandleRequestReq |
||||||
|
chCloseMuxer chan *muxer |
||||||
|
chAPIMuxerList chan serverAPIMuxersListReq |
||||||
|
chAPIMuxerGet chan serverAPIMuxersGetReq |
||||||
|
} |
||||||
|
|
||||||
|
// Initialize initializes the server.
|
||||||
|
func (s *Server) Initialize() error { |
||||||
|
ctx, ctxCancel := context.WithCancel(context.Background()) |
||||||
|
|
||||||
|
s.ctx = ctx |
||||||
|
s.ctxCancel = ctxCancel |
||||||
|
s.muxers = make(map[string]*muxer) |
||||||
|
s.chPathReady = make(chan defs.Path) |
||||||
|
s.chPathNotReady = make(chan defs.Path) |
||||||
|
s.chHandleRequest = make(chan muxerHandleRequestReq) |
||||||
|
s.chCloseMuxer = make(chan *muxer) |
||||||
|
s.chAPIMuxerList = make(chan serverAPIMuxersListReq) |
||||||
|
s.chAPIMuxerGet = make(chan serverAPIMuxersGetReq) |
||||||
|
|
||||||
|
s.httpServer = &httpServer{ |
||||||
|
address: s.Address, |
||||||
|
encryption: s.Encryption, |
||||||
|
serverKey: s.ServerKey, |
||||||
|
serverCert: s.ServerCert, |
||||||
|
allowOrigin: s.AllowOrigin, |
||||||
|
trustedProxies: s.TrustedProxies, |
||||||
|
readTimeout: s.ReadTimeout, |
||||||
|
pathManager: s.PathManager, |
||||||
|
parent: s, |
||||||
|
} |
||||||
|
err := s.httpServer.initialize() |
||||||
|
if err != nil { |
||||||
|
ctxCancel() |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
s.Log(logger.Info, "listener opened on "+s.Address) |
||||||
|
|
||||||
|
s.wg.Add(1) |
||||||
|
go s.run() |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Log implements logger.Writer.
|
||||||
|
func (s *Server) Log(level logger.Level, format string, args ...interface{}) { |
||||||
|
s.Parent.Log(level, "[HLS] "+format, args...) |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes the server.
|
||||||
|
func (s *Server) Close() { |
||||||
|
s.Log(logger.Info, "listener is closing") |
||||||
|
s.ctxCancel() |
||||||
|
s.wg.Wait() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) run() { |
||||||
|
defer s.wg.Done() |
||||||
|
|
||||||
|
outer: |
||||||
|
for { |
||||||
|
select { |
||||||
|
case pa := <-s.chPathReady: |
||||||
|
if s.AlwaysRemux && !pa.SafeConf().SourceOnDemand { |
||||||
|
if _, ok := s.muxers[pa.Name()]; !ok { |
||||||
|
s.createMuxer(pa.Name(), "") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
case pa := <-s.chPathNotReady: |
||||||
|
c, ok := s.muxers[pa.Name()] |
||||||
|
if ok && c.remoteAddr == "" { // created with "always remux"
|
||||||
|
c.Close() |
||||||
|
delete(s.muxers, pa.Name()) |
||||||
|
} |
||||||
|
|
||||||
|
case req := <-s.chHandleRequest: |
||||||
|
r, ok := s.muxers[req.path] |
||||||
|
switch { |
||||||
|
case ok: |
||||||
|
r.processRequest(&req) |
||||||
|
|
||||||
|
default: |
||||||
|
r := s.createMuxer(req.path, req.ctx.ClientIP()) |
||||||
|
r.processRequest(&req) |
||||||
|
} |
||||||
|
|
||||||
|
case c := <-s.chCloseMuxer: |
||||||
|
if c2, ok := s.muxers[c.PathName()]; !ok || c2 != c { |
||||||
|
continue |
||||||
|
} |
||||||
|
delete(s.muxers, c.PathName()) |
||||||
|
|
||||||
|
case req := <-s.chAPIMuxerList: |
||||||
|
data := &defs.APIHLSMuxerList{ |
||||||
|
Items: []*defs.APIHLSMuxer{}, |
||||||
|
} |
||||||
|
|
||||||
|
for _, muxer := range s.muxers { |
||||||
|
data.Items = append(data.Items, muxer.apiItem()) |
||||||
|
} |
||||||
|
|
||||||
|
sort.Slice(data.Items, func(i, j int) bool { |
||||||
|
return data.Items[i].Created.Before(data.Items[j].Created) |
||||||
|
}) |
||||||
|
|
||||||
|
req.res <- serverAPIMuxersListRes{ |
||||||
|
data: data, |
||||||
|
} |
||||||
|
|
||||||
|
case req := <-s.chAPIMuxerGet: |
||||||
|
muxer, ok := s.muxers[req.name] |
||||||
|
if !ok { |
||||||
|
req.res <- serverAPIMuxersGetRes{err: fmt.Errorf("muxer not found")} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
req.res <- serverAPIMuxersGetRes{data: muxer.apiItem()} |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
break outer |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
s.ctxCancel() |
||||||
|
|
||||||
|
s.httpServer.close() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) createMuxer(pathName string, remoteAddr string) *muxer { |
||||||
|
r := &muxer{ |
||||||
|
parentCtx: s.ctx, |
||||||
|
remoteAddr: remoteAddr, |
||||||
|
externalAuthenticationURL: s.ExternalAuthenticationURL, |
||||||
|
variant: s.Variant, |
||||||
|
segmentCount: s.SegmentCount, |
||||||
|
segmentDuration: s.SegmentDuration, |
||||||
|
partDuration: s.PartDuration, |
||||||
|
segmentMaxSize: s.SegmentMaxSize, |
||||||
|
directory: s.Directory, |
||||||
|
writeQueueSize: s.WriteQueueSize, |
||||||
|
wg: &s.wg, |
||||||
|
pathName: pathName, |
||||||
|
pathManager: s.PathManager, |
||||||
|
parent: s, |
||||||
|
} |
||||||
|
r.initialize() |
||||||
|
s.muxers[pathName] = r |
||||||
|
return r |
||||||
|
} |
||||||
|
|
||||||
|
// closeMuxer is called by muxer.
|
||||||
|
func (s *Server) closeMuxer(c *muxer) { |
||||||
|
select { |
||||||
|
case s.chCloseMuxer <- c: |
||||||
|
case <-s.ctx.Done(): |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) handleRequest(req muxerHandleRequestReq) { |
||||||
|
req.res = make(chan *muxer) |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chHandleRequest <- req: |
||||||
|
muxer := <-req.res |
||||||
|
if muxer != nil { |
||||||
|
req.ctx.Request.URL.Path = req.file |
||||||
|
muxer.handleRequest(req.ctx) |
||||||
|
} |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// PathReady is called by pathManager.
|
||||||
|
func (s *Server) PathReady(pa defs.Path) { |
||||||
|
select { |
||||||
|
case s.chPathReady <- pa: |
||||||
|
case <-s.ctx.Done(): |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// PathNotReady is called by pathManager.
|
||||||
|
func (s *Server) PathNotReady(pa defs.Path) { |
||||||
|
select { |
||||||
|
case s.chPathNotReady <- pa: |
||||||
|
case <-s.ctx.Done(): |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// APIMuxersList is called by api.
|
||||||
|
func (s *Server) APIMuxersList() (*defs.APIHLSMuxerList, error) { |
||||||
|
req := serverAPIMuxersListReq{ |
||||||
|
res: make(chan serverAPIMuxersListRes), |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chAPIMuxerList <- req: |
||||||
|
res := <-req.res |
||||||
|
return res.data, res.err |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// APIMuxersGet is called by api.
|
||||||
|
func (s *Server) APIMuxersGet(name string) (*defs.APIHLSMuxer, error) { |
||||||
|
req := serverAPIMuxersGetReq{ |
||||||
|
name: name, |
||||||
|
res: make(chan serverAPIMuxersGetRes), |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chAPIMuxerGet <- req: |
||||||
|
res := <-req.res |
||||||
|
return res.data, res.err |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
package rtmp |
||||||
|
|
||||||
|
import ( |
||||||
|
"net" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
type listener struct { |
||||||
|
ln net.Listener |
||||||
|
wg *sync.WaitGroup |
||||||
|
parent *Server |
||||||
|
} |
||||||
|
|
||||||
|
func (l *listener) initialize() { |
||||||
|
l.wg.Add(1) |
||||||
|
go l.run() |
||||||
|
} |
||||||
|
|
||||||
|
func (l *listener) run() { |
||||||
|
defer l.wg.Done() |
||||||
|
|
||||||
|
err := l.runInner() |
||||||
|
|
||||||
|
l.parent.acceptError(err) |
||||||
|
} |
||||||
|
|
||||||
|
func (l *listener) runInner() error { |
||||||
|
for { |
||||||
|
conn, err := l.ln.Accept() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
l.parent.newConn(conn) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,308 @@ |
|||||||
|
// Package rtmp contains a RTMP server.
|
||||||
|
package rtmp |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"crypto/tls" |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
"sort" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/google/uuid" |
||||||
|
|
||||||
|
"github.com/bluenviron/mediamtx/internal/conf" |
||||||
|
"github.com/bluenviron/mediamtx/internal/defs" |
||||||
|
"github.com/bluenviron/mediamtx/internal/externalcmd" |
||||||
|
"github.com/bluenviron/mediamtx/internal/logger" |
||||||
|
"github.com/bluenviron/mediamtx/internal/restrictnetwork" |
||||||
|
) |
||||||
|
|
||||||
|
type serverAPIConnsListRes struct { |
||||||
|
data *defs.APIRTMPConnList |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsListReq struct { |
||||||
|
res chan serverAPIConnsListRes |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsGetRes struct { |
||||||
|
data *defs.APIRTMPConn |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsGetReq struct { |
||||||
|
uuid uuid.UUID |
||||||
|
res chan serverAPIConnsGetRes |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsKickRes struct { |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsKickReq struct { |
||||||
|
uuid uuid.UUID |
||||||
|
res chan serverAPIConnsKickRes |
||||||
|
} |
||||||
|
|
||||||
|
type serverParent interface { |
||||||
|
logger.Writer |
||||||
|
} |
||||||
|
|
||||||
|
// Server is a RTMP server.
|
||||||
|
type Server struct { |
||||||
|
Address string |
||||||
|
ReadTimeout conf.StringDuration |
||||||
|
WriteTimeout conf.StringDuration |
||||||
|
WriteQueueSize int |
||||||
|
IsTLS bool |
||||||
|
ServerCert string |
||||||
|
ServerKey string |
||||||
|
RTSPAddress string |
||||||
|
RunOnConnect string |
||||||
|
RunOnConnectRestart bool |
||||||
|
RunOnDisconnect string |
||||||
|
ExternalCmdPool *externalcmd.Pool |
||||||
|
PathManager defs.PathManager |
||||||
|
Parent serverParent |
||||||
|
|
||||||
|
ctx context.Context |
||||||
|
ctxCancel func() |
||||||
|
wg sync.WaitGroup |
||||||
|
ln net.Listener |
||||||
|
conns map[*conn]struct{} |
||||||
|
|
||||||
|
// in
|
||||||
|
chNewConn chan net.Conn |
||||||
|
chAcceptErr chan error |
||||||
|
chCloseConn chan *conn |
||||||
|
chAPIConnsList chan serverAPIConnsListReq |
||||||
|
chAPIConnsGet chan serverAPIConnsGetReq |
||||||
|
chAPIConnsKick chan serverAPIConnsKickReq |
||||||
|
} |
||||||
|
|
||||||
|
// Initialize initializes the server.
|
||||||
|
func (s *Server) Initialize() error { |
||||||
|
ln, err := func() (net.Listener, error) { |
||||||
|
if !s.IsTLS { |
||||||
|
return net.Listen(restrictnetwork.Restrict("tcp", s.Address)) |
||||||
|
} |
||||||
|
|
||||||
|
cert, err := tls.LoadX509KeyPair(s.ServerCert, s.ServerKey) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
network, address := restrictnetwork.Restrict("tcp", s.Address) |
||||||
|
return tls.Listen(network, address, &tls.Config{Certificates: []tls.Certificate{cert}}) |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
s.ctx, s.ctxCancel = context.WithCancel(context.Background()) |
||||||
|
|
||||||
|
s.ln = ln |
||||||
|
s.conns = make(map[*conn]struct{}) |
||||||
|
s.chNewConn = make(chan net.Conn) |
||||||
|
s.chAcceptErr = make(chan error) |
||||||
|
s.chCloseConn = make(chan *conn) |
||||||
|
s.chAPIConnsList = make(chan serverAPIConnsListReq) |
||||||
|
s.chAPIConnsGet = make(chan serverAPIConnsGetReq) |
||||||
|
s.chAPIConnsKick = make(chan serverAPIConnsKickReq) |
||||||
|
|
||||||
|
s.Log(logger.Info, "listener opened on %s", s.Address) |
||||||
|
|
||||||
|
l := &listener{ |
||||||
|
ln: s.ln, |
||||||
|
wg: &s.wg, |
||||||
|
parent: s, |
||||||
|
} |
||||||
|
l.initialize() |
||||||
|
|
||||||
|
s.wg.Add(1) |
||||||
|
go s.run() |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Log implements logger.Writer.
|
||||||
|
func (s *Server) Log(level logger.Level, format string, args ...interface{}) { |
||||||
|
label := func() string { |
||||||
|
if s.IsTLS { |
||||||
|
return "RTMPS" |
||||||
|
} |
||||||
|
return "RTMP" |
||||||
|
}() |
||||||
|
s.Parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...) |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes the server.
|
||||||
|
func (s *Server) Close() { |
||||||
|
s.Log(logger.Info, "listener is closing") |
||||||
|
s.ctxCancel() |
||||||
|
s.wg.Wait() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) run() { |
||||||
|
defer s.wg.Done() |
||||||
|
|
||||||
|
outer: |
||||||
|
for { |
||||||
|
select { |
||||||
|
case err := <-s.chAcceptErr: |
||||||
|
s.Log(logger.Error, "%s", err) |
||||||
|
break outer |
||||||
|
|
||||||
|
case nconn := <-s.chNewConn: |
||||||
|
c := &conn{ |
||||||
|
parentCtx: s.ctx, |
||||||
|
isTLS: s.IsTLS, |
||||||
|
rtspAddress: s.RTSPAddress, |
||||||
|
readTimeout: s.ReadTimeout, |
||||||
|
writeTimeout: s.WriteTimeout, |
||||||
|
writeQueueSize: s.WriteQueueSize, |
||||||
|
runOnConnect: s.RunOnConnect, |
||||||
|
runOnConnectRestart: s.RunOnConnectRestart, |
||||||
|
runOnDisconnect: s.RunOnDisconnect, |
||||||
|
wg: &s.wg, |
||||||
|
nconn: nconn, |
||||||
|
externalCmdPool: s.ExternalCmdPool, |
||||||
|
pathManager: s.PathManager, |
||||||
|
parent: s, |
||||||
|
} |
||||||
|
c.initialize() |
||||||
|
s.conns[c] = struct{}{} |
||||||
|
|
||||||
|
case c := <-s.chCloseConn: |
||||||
|
delete(s.conns, c) |
||||||
|
|
||||||
|
case req := <-s.chAPIConnsList: |
||||||
|
data := &defs.APIRTMPConnList{ |
||||||
|
Items: []*defs.APIRTMPConn{}, |
||||||
|
} |
||||||
|
|
||||||
|
for c := range s.conns { |
||||||
|
data.Items = append(data.Items, c.apiItem()) |
||||||
|
} |
||||||
|
|
||||||
|
sort.Slice(data.Items, func(i, j int) bool { |
||||||
|
return data.Items[i].Created.Before(data.Items[j].Created) |
||||||
|
}) |
||||||
|
|
||||||
|
req.res <- serverAPIConnsListRes{data: data} |
||||||
|
|
||||||
|
case req := <-s.chAPIConnsGet: |
||||||
|
c := s.findConnByUUID(req.uuid) |
||||||
|
if c == nil { |
||||||
|
req.res <- serverAPIConnsGetRes{err: fmt.Errorf("connection not found")} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
req.res <- serverAPIConnsGetRes{data: c.apiItem()} |
||||||
|
|
||||||
|
case req := <-s.chAPIConnsKick: |
||||||
|
c := s.findConnByUUID(req.uuid) |
||||||
|
if c == nil { |
||||||
|
req.res <- serverAPIConnsKickRes{err: fmt.Errorf("connection not found")} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
delete(s.conns, c) |
||||||
|
c.Close() |
||||||
|
req.res <- serverAPIConnsKickRes{} |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
break outer |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
s.ctxCancel() |
||||||
|
|
||||||
|
s.ln.Close() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) findConnByUUID(uuid uuid.UUID) *conn { |
||||||
|
for c := range s.conns { |
||||||
|
if c.uuid == uuid { |
||||||
|
return c |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// newConn is called by rtmpListener.
|
||||||
|
func (s *Server) newConn(conn net.Conn) { |
||||||
|
select { |
||||||
|
case s.chNewConn <- conn: |
||||||
|
case <-s.ctx.Done(): |
||||||
|
conn.Close() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// acceptError is called by rtmpListener.
|
||||||
|
func (s *Server) acceptError(err error) { |
||||||
|
select { |
||||||
|
case s.chAcceptErr <- err: |
||||||
|
case <-s.ctx.Done(): |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// closeConn is called by conn.
|
||||||
|
func (s *Server) closeConn(c *conn) { |
||||||
|
select { |
||||||
|
case s.chCloseConn <- c: |
||||||
|
case <-s.ctx.Done(): |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// APIConnsList is called by api.
|
||||||
|
func (s *Server) APIConnsList() (*defs.APIRTMPConnList, error) { |
||||||
|
req := serverAPIConnsListReq{ |
||||||
|
res: make(chan serverAPIConnsListRes), |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chAPIConnsList <- req: |
||||||
|
res := <-req.res |
||||||
|
return res.data, res.err |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// APIConnsGet is called by api.
|
||||||
|
func (s *Server) APIConnsGet(uuid uuid.UUID) (*defs.APIRTMPConn, error) { |
||||||
|
req := serverAPIConnsGetReq{ |
||||||
|
uuid: uuid, |
||||||
|
res: make(chan serverAPIConnsGetRes), |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chAPIConnsGet <- req: |
||||||
|
res := <-req.res |
||||||
|
return res.data, res.err |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// APIConnsKick is called by api.
|
||||||
|
func (s *Server) APIConnsKick(uuid uuid.UUID) error { |
||||||
|
req := serverAPIConnsKickReq{ |
||||||
|
uuid: uuid, |
||||||
|
res: make(chan serverAPIConnsKickRes), |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chAPIConnsKick <- req: |
||||||
|
res := <-req.res |
||||||
|
return res.err |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
return fmt.Errorf("terminated") |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,430 @@ |
|||||||
|
// Package rtsp contains a RTSP server.
|
||||||
|
package rtsp |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"crypto/tls" |
||||||
|
"fmt" |
||||||
|
"sort" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/bluenviron/gortsplib/v4" |
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/base" |
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/headers" |
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/liberrors" |
||||||
|
"github.com/google/uuid" |
||||||
|
|
||||||
|
"github.com/bluenviron/mediamtx/internal/conf" |
||||||
|
"github.com/bluenviron/mediamtx/internal/defs" |
||||||
|
"github.com/bluenviron/mediamtx/internal/externalcmd" |
||||||
|
"github.com/bluenviron/mediamtx/internal/logger" |
||||||
|
) |
||||||
|
|
||||||
|
func printAddresses(srv *gortsplib.Server) string { |
||||||
|
var ret []string |
||||||
|
|
||||||
|
ret = append(ret, fmt.Sprintf("%s (TCP)", srv.RTSPAddress)) |
||||||
|
|
||||||
|
if srv.UDPRTPAddress != "" { |
||||||
|
ret = append(ret, fmt.Sprintf("%s (UDP/RTP)", srv.UDPRTPAddress)) |
||||||
|
} |
||||||
|
|
||||||
|
if srv.UDPRTCPAddress != "" { |
||||||
|
ret = append(ret, fmt.Sprintf("%s (UDP/RTCP)", srv.UDPRTCPAddress)) |
||||||
|
} |
||||||
|
|
||||||
|
return strings.Join(ret, ", ") |
||||||
|
} |
||||||
|
|
||||||
|
type serverParent interface { |
||||||
|
logger.Writer |
||||||
|
} |
||||||
|
|
||||||
|
// Server is a RTSP server.
|
||||||
|
type Server struct { |
||||||
|
Address string |
||||||
|
AuthMethods []headers.AuthMethod |
||||||
|
ReadTimeout conf.StringDuration |
||||||
|
WriteTimeout conf.StringDuration |
||||||
|
WriteQueueSize int |
||||||
|
UseUDP bool |
||||||
|
UseMulticast bool |
||||||
|
RTPAddress string |
||||||
|
RTCPAddress string |
||||||
|
MulticastIPRange string |
||||||
|
MulticastRTPPort int |
||||||
|
MulticastRTCPPort int |
||||||
|
IsTLS bool |
||||||
|
ServerCert string |
||||||
|
ServerKey string |
||||||
|
RTSPAddress string |
||||||
|
Protocols map[conf.Protocol]struct{} |
||||||
|
RunOnConnect string |
||||||
|
RunOnConnectRestart bool |
||||||
|
RunOnDisconnect string |
||||||
|
ExternalCmdPool *externalcmd.Pool |
||||||
|
PathManager defs.PathManager |
||||||
|
Parent serverParent |
||||||
|
|
||||||
|
ctx context.Context |
||||||
|
ctxCancel func() |
||||||
|
wg sync.WaitGroup |
||||||
|
srv *gortsplib.Server |
||||||
|
mutex sync.RWMutex |
||||||
|
conns map[*gortsplib.ServerConn]*conn |
||||||
|
sessions map[*gortsplib.ServerSession]*session |
||||||
|
} |
||||||
|
|
||||||
|
// Initialize initializes the server.
|
||||||
|
func (s *Server) Initialize() error { |
||||||
|
s.ctx, s.ctxCancel = context.WithCancel(context.Background()) |
||||||
|
|
||||||
|
s.conns = make(map[*gortsplib.ServerConn]*conn) |
||||||
|
s.sessions = make(map[*gortsplib.ServerSession]*session) |
||||||
|
|
||||||
|
s.srv = &gortsplib.Server{ |
||||||
|
Handler: s, |
||||||
|
ReadTimeout: time.Duration(s.ReadTimeout), |
||||||
|
WriteTimeout: time.Duration(s.WriteTimeout), |
||||||
|
WriteQueueSize: s.WriteQueueSize, |
||||||
|
RTSPAddress: s.Address, |
||||||
|
} |
||||||
|
|
||||||
|
if s.UseUDP { |
||||||
|
s.srv.UDPRTPAddress = s.RTPAddress |
||||||
|
s.srv.UDPRTCPAddress = s.RTCPAddress |
||||||
|
} |
||||||
|
|
||||||
|
if s.UseMulticast { |
||||||
|
s.srv.MulticastIPRange = s.MulticastIPRange |
||||||
|
s.srv.MulticastRTPPort = s.MulticastRTPPort |
||||||
|
s.srv.MulticastRTCPPort = s.MulticastRTCPPort |
||||||
|
} |
||||||
|
|
||||||
|
if s.IsTLS { |
||||||
|
cert, err := tls.LoadX509KeyPair(s.ServerCert, s.ServerKey) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
s.srv.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}} |
||||||
|
} |
||||||
|
|
||||||
|
err := s.srv.Start() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
s.Log(logger.Info, "listener opened on %s", printAddresses(s.srv)) |
||||||
|
|
||||||
|
s.wg.Add(1) |
||||||
|
go s.run() |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Log implements logger.Writer.
|
||||||
|
func (s *Server) Log(level logger.Level, format string, args ...interface{}) { |
||||||
|
label := func() string { |
||||||
|
if s.IsTLS { |
||||||
|
return "RTSPS" |
||||||
|
} |
||||||
|
return "RTSP" |
||||||
|
}() |
||||||
|
s.Parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...) |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes the server.
|
||||||
|
func (s *Server) Close() { |
||||||
|
s.Log(logger.Info, "listener is closing") |
||||||
|
s.ctxCancel() |
||||||
|
s.wg.Wait() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) run() { |
||||||
|
defer s.wg.Done() |
||||||
|
|
||||||
|
serverErr := make(chan error) |
||||||
|
go func() { |
||||||
|
serverErr <- s.srv.Wait() |
||||||
|
}() |
||||||
|
|
||||||
|
outer: |
||||||
|
select { |
||||||
|
case err := <-serverErr: |
||||||
|
s.Log(logger.Error, "%s", err) |
||||||
|
break outer |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
s.srv.Close() |
||||||
|
<-serverErr |
||||||
|
break outer |
||||||
|
} |
||||||
|
|
||||||
|
s.ctxCancel() |
||||||
|
} |
||||||
|
|
||||||
|
// OnConnOpen implements gortsplib.ServerHandlerOnConnOpen.
|
||||||
|
func (s *Server) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) { |
||||||
|
c := &conn{ |
||||||
|
isTLS: s.IsTLS, |
||||||
|
rtspAddress: s.RTSPAddress, |
||||||
|
authMethods: s.AuthMethods, |
||||||
|
readTimeout: s.ReadTimeout, |
||||||
|
runOnConnect: s.RunOnConnect, |
||||||
|
runOnConnectRestart: s.RunOnConnectRestart, |
||||||
|
runOnDisconnect: s.RunOnDisconnect, |
||||||
|
externalCmdPool: s.ExternalCmdPool, |
||||||
|
pathManager: s.PathManager, |
||||||
|
rconn: ctx.Conn, |
||||||
|
rserver: s.srv, |
||||||
|
parent: s, |
||||||
|
} |
||||||
|
c.initialize() |
||||||
|
s.mutex.Lock() |
||||||
|
s.conns[ctx.Conn] = c |
||||||
|
s.mutex.Unlock() |
||||||
|
|
||||||
|
ctx.Conn.SetUserData(c) |
||||||
|
} |
||||||
|
|
||||||
|
// OnConnClose implements gortsplib.ServerHandlerOnConnClose.
|
||||||
|
func (s *Server) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) { |
||||||
|
s.mutex.Lock() |
||||||
|
c := s.conns[ctx.Conn] |
||||||
|
delete(s.conns, ctx.Conn) |
||||||
|
s.mutex.Unlock() |
||||||
|
c.onClose(ctx.Error) |
||||||
|
} |
||||||
|
|
||||||
|
// OnRequest implements gortsplib.ServerHandlerOnRequest.
|
||||||
|
func (s *Server) OnRequest(sc *gortsplib.ServerConn, req *base.Request) { |
||||||
|
c := sc.UserData().(*conn) |
||||||
|
c.onRequest(req) |
||||||
|
} |
||||||
|
|
||||||
|
// OnResponse implements gortsplib.ServerHandlerOnResponse.
|
||||||
|
func (s *Server) OnResponse(sc *gortsplib.ServerConn, res *base.Response) { |
||||||
|
c := sc.UserData().(*conn) |
||||||
|
c.OnResponse(res) |
||||||
|
} |
||||||
|
|
||||||
|
// OnSessionOpen implements gortsplib.ServerHandlerOnSessionOpen.
|
||||||
|
func (s *Server) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) { |
||||||
|
se := &session{ |
||||||
|
isTLS: s.IsTLS, |
||||||
|
protocols: s.Protocols, |
||||||
|
rsession: ctx.Session, |
||||||
|
rconn: ctx.Conn, |
||||||
|
rserver: s.srv, |
||||||
|
externalCmdPool: s.ExternalCmdPool, |
||||||
|
pathManager: s.PathManager, |
||||||
|
parent: s, |
||||||
|
} |
||||||
|
se.initialize() |
||||||
|
s.mutex.Lock() |
||||||
|
s.sessions[ctx.Session] = se |
||||||
|
s.mutex.Unlock() |
||||||
|
ctx.Session.SetUserData(se) |
||||||
|
} |
||||||
|
|
||||||
|
// OnSessionClose implements gortsplib.ServerHandlerOnSessionClose.
|
||||||
|
func (s *Server) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) { |
||||||
|
s.mutex.Lock() |
||||||
|
se := s.sessions[ctx.Session] |
||||||
|
delete(s.sessions, ctx.Session) |
||||||
|
s.mutex.Unlock() |
||||||
|
|
||||||
|
if se != nil { |
||||||
|
se.onClose(ctx.Error) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// OnDescribe implements gortsplib.ServerHandlerOnDescribe.
|
||||||
|
func (s *Server) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx, |
||||||
|
) (*base.Response, *gortsplib.ServerStream, error) { |
||||||
|
c := ctx.Conn.UserData().(*conn) |
||||||
|
return c.onDescribe(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
// OnAnnounce implements gortsplib.ServerHandlerOnAnnounce.
|
||||||
|
func (s *Server) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) { |
||||||
|
c := ctx.Conn.UserData().(*conn) |
||||||
|
se := ctx.Session.UserData().(*session) |
||||||
|
return se.onAnnounce(c, ctx) |
||||||
|
} |
||||||
|
|
||||||
|
// OnSetup implements gortsplib.ServerHandlerOnSetup.
|
||||||
|
func (s *Server) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) { |
||||||
|
c := ctx.Conn.UserData().(*conn) |
||||||
|
se := ctx.Session.UserData().(*session) |
||||||
|
return se.onSetup(c, ctx) |
||||||
|
} |
||||||
|
|
||||||
|
// OnPlay implements gortsplib.ServerHandlerOnPlay.
|
||||||
|
func (s *Server) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) { |
||||||
|
se := ctx.Session.UserData().(*session) |
||||||
|
return se.onPlay(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
// OnRecord implements gortsplib.ServerHandlerOnRecord.
|
||||||
|
func (s *Server) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) { |
||||||
|
se := ctx.Session.UserData().(*session) |
||||||
|
return se.onRecord(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
// OnPause implements gortsplib.ServerHandlerOnPause.
|
||||||
|
func (s *Server) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) { |
||||||
|
se := ctx.Session.UserData().(*session) |
||||||
|
return se.onPause(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
// OnPacketLost implements gortsplib.ServerHandlerOnDecodeError.
|
||||||
|
func (s *Server) OnPacketLost(ctx *gortsplib.ServerHandlerOnPacketLostCtx) { |
||||||
|
se := ctx.Session.UserData().(*session) |
||||||
|
se.onPacketLost(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
// OnDecodeError implements gortsplib.ServerHandlerOnDecodeError.
|
||||||
|
func (s *Server) OnDecodeError(ctx *gortsplib.ServerHandlerOnDecodeErrorCtx) { |
||||||
|
se := ctx.Session.UserData().(*session) |
||||||
|
se.onDecodeError(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
// OnStreamWriteError implements gortsplib.ServerHandlerOnStreamWriteError.
|
||||||
|
func (s *Server) OnStreamWriteError(ctx *gortsplib.ServerHandlerOnStreamWriteErrorCtx) { |
||||||
|
se := ctx.Session.UserData().(*session) |
||||||
|
se.onStreamWriteError(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) findConnByUUID(uuid uuid.UUID) *conn { |
||||||
|
for _, c := range s.conns { |
||||||
|
if c.uuid == uuid { |
||||||
|
return c |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) findSessionByUUID(uuid uuid.UUID) (*gortsplib.ServerSession, *session) { |
||||||
|
for key, sx := range s.sessions { |
||||||
|
if sx.uuid == uuid { |
||||||
|
return key, sx |
||||||
|
} |
||||||
|
} |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
// APIConnsList is called by api and metrics.
|
||||||
|
func (s *Server) APIConnsList() (*defs.APIRTSPConnsList, error) { |
||||||
|
select { |
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
default: |
||||||
|
} |
||||||
|
|
||||||
|
s.mutex.RLock() |
||||||
|
defer s.mutex.RUnlock() |
||||||
|
|
||||||
|
data := &defs.APIRTSPConnsList{ |
||||||
|
Items: []*defs.APIRTSPConn{}, |
||||||
|
} |
||||||
|
|
||||||
|
for _, c := range s.conns { |
||||||
|
data.Items = append(data.Items, c.apiItem()) |
||||||
|
} |
||||||
|
|
||||||
|
sort.Slice(data.Items, func(i, j int) bool { |
||||||
|
return data.Items[i].Created.Before(data.Items[j].Created) |
||||||
|
}) |
||||||
|
|
||||||
|
return data, nil |
||||||
|
} |
||||||
|
|
||||||
|
// APIConnsGet is called by api.
|
||||||
|
func (s *Server) APIConnsGet(uuid uuid.UUID) (*defs.APIRTSPConn, error) { |
||||||
|
select { |
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
default: |
||||||
|
} |
||||||
|
|
||||||
|
s.mutex.RLock() |
||||||
|
defer s.mutex.RUnlock() |
||||||
|
|
||||||
|
conn := s.findConnByUUID(uuid) |
||||||
|
if conn == nil { |
||||||
|
return nil, fmt.Errorf("connection not found") |
||||||
|
} |
||||||
|
|
||||||
|
return conn.apiItem(), nil |
||||||
|
} |
||||||
|
|
||||||
|
// APISessionsList is called by api and metrics.
|
||||||
|
func (s *Server) APISessionsList() (*defs.APIRTSPSessionList, error) { |
||||||
|
select { |
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
default: |
||||||
|
} |
||||||
|
|
||||||
|
s.mutex.RLock() |
||||||
|
defer s.mutex.RUnlock() |
||||||
|
|
||||||
|
data := &defs.APIRTSPSessionList{ |
||||||
|
Items: []*defs.APIRTSPSession{}, |
||||||
|
} |
||||||
|
|
||||||
|
for _, s := range s.sessions { |
||||||
|
data.Items = append(data.Items, s.apiItem()) |
||||||
|
} |
||||||
|
|
||||||
|
sort.Slice(data.Items, func(i, j int) bool { |
||||||
|
return data.Items[i].Created.Before(data.Items[j].Created) |
||||||
|
}) |
||||||
|
|
||||||
|
return data, nil |
||||||
|
} |
||||||
|
|
||||||
|
// APISessionsGet is called by api.
|
||||||
|
func (s *Server) APISessionsGet(uuid uuid.UUID) (*defs.APIRTSPSession, error) { |
||||||
|
select { |
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
default: |
||||||
|
} |
||||||
|
|
||||||
|
s.mutex.RLock() |
||||||
|
defer s.mutex.RUnlock() |
||||||
|
|
||||||
|
_, sx := s.findSessionByUUID(uuid) |
||||||
|
if sx == nil { |
||||||
|
return nil, fmt.Errorf("session not found") |
||||||
|
} |
||||||
|
|
||||||
|
return sx.apiItem(), nil |
||||||
|
} |
||||||
|
|
||||||
|
// APISessionsKick is called by api.
|
||||||
|
func (s *Server) APISessionsKick(uuid uuid.UUID) error { |
||||||
|
select { |
||||||
|
case <-s.ctx.Done(): |
||||||
|
return fmt.Errorf("terminated") |
||||||
|
default: |
||||||
|
} |
||||||
|
|
||||||
|
s.mutex.RLock() |
||||||
|
defer s.mutex.RUnlock() |
||||||
|
|
||||||
|
key, sx := s.findSessionByUUID(uuid) |
||||||
|
if sx == nil { |
||||||
|
return fmt.Errorf("session not found") |
||||||
|
} |
||||||
|
|
||||||
|
sx.Close() |
||||||
|
delete(s.sessions, key) |
||||||
|
sx.onClose(liberrors.ErrServerTerminated{}) |
||||||
|
return nil |
||||||
|
} |
||||||
@ -0,0 +1,310 @@ |
|||||||
|
// Package srt contains a SRT server.
|
||||||
|
package srt |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"sort" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
srt "github.com/datarhei/gosrt" |
||||||
|
"github.com/google/uuid" |
||||||
|
|
||||||
|
"github.com/bluenviron/mediamtx/internal/conf" |
||||||
|
"github.com/bluenviron/mediamtx/internal/defs" |
||||||
|
"github.com/bluenviron/mediamtx/internal/externalcmd" |
||||||
|
"github.com/bluenviron/mediamtx/internal/logger" |
||||||
|
) |
||||||
|
|
||||||
|
func srtMaxPayloadSize(u int) int { |
||||||
|
return ((u - 16) / 188) * 188 // 16 = SRT header, 188 = MPEG-TS packet
|
||||||
|
} |
||||||
|
|
||||||
|
type srtNewConnReq struct { |
||||||
|
connReq srt.ConnRequest |
||||||
|
res chan *conn |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsListRes struct { |
||||||
|
data *defs.APISRTConnList |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsListReq struct { |
||||||
|
res chan serverAPIConnsListRes |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsGetRes struct { |
||||||
|
data *defs.APISRTConn |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsGetReq struct { |
||||||
|
uuid uuid.UUID |
||||||
|
res chan serverAPIConnsGetRes |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsKickRes struct { |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
type serverAPIConnsKickReq struct { |
||||||
|
uuid uuid.UUID |
||||||
|
res chan serverAPIConnsKickRes |
||||||
|
} |
||||||
|
|
||||||
|
type serverParent interface { |
||||||
|
logger.Writer |
||||||
|
} |
||||||
|
|
||||||
|
// Server is a SRT server.
|
||||||
|
type Server struct { |
||||||
|
Address string |
||||||
|
RTSPAddress string |
||||||
|
ReadTimeout conf.StringDuration |
||||||
|
WriteTimeout conf.StringDuration |
||||||
|
WriteQueueSize int |
||||||
|
UDPMaxPayloadSize int |
||||||
|
RunOnConnect string |
||||||
|
RunOnConnectRestart bool |
||||||
|
RunOnDisconnect string |
||||||
|
ExternalCmdPool *externalcmd.Pool |
||||||
|
PathManager defs.PathManager |
||||||
|
Parent serverParent |
||||||
|
|
||||||
|
ctx context.Context |
||||||
|
ctxCancel func() |
||||||
|
wg sync.WaitGroup |
||||||
|
ln srt.Listener |
||||||
|
conns map[*conn]struct{} |
||||||
|
|
||||||
|
// in
|
||||||
|
chNewConnRequest chan srtNewConnReq |
||||||
|
chAcceptErr chan error |
||||||
|
chCloseConn chan *conn |
||||||
|
chAPIConnsList chan serverAPIConnsListReq |
||||||
|
chAPIConnsGet chan serverAPIConnsGetReq |
||||||
|
chAPIConnsKick chan serverAPIConnsKickReq |
||||||
|
} |
||||||
|
|
||||||
|
// Initialize initializes the server.
|
||||||
|
func (s *Server) Initialize() error { |
||||||
|
conf := srt.DefaultConfig() |
||||||
|
conf.ConnectionTimeout = time.Duration(s.ReadTimeout) |
||||||
|
conf.PayloadSize = uint32(srtMaxPayloadSize(s.UDPMaxPayloadSize)) |
||||||
|
|
||||||
|
var err error |
||||||
|
s.ln, err = srt.Listen("srt", s.Address, conf) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
s.ctx, s.ctxCancel = context.WithCancel(context.Background()) |
||||||
|
|
||||||
|
s.conns = make(map[*conn]struct{}) |
||||||
|
s.chNewConnRequest = make(chan srtNewConnReq) |
||||||
|
s.chAcceptErr = make(chan error) |
||||||
|
s.chCloseConn = make(chan *conn) |
||||||
|
s.chAPIConnsList = make(chan serverAPIConnsListReq) |
||||||
|
s.chAPIConnsGet = make(chan serverAPIConnsGetReq) |
||||||
|
s.chAPIConnsKick = make(chan serverAPIConnsKickReq) |
||||||
|
|
||||||
|
s.Log(logger.Info, "listener opened on "+s.Address+" (UDP)") |
||||||
|
|
||||||
|
l := &listener{ |
||||||
|
ln: s.ln, |
||||||
|
wg: &s.wg, |
||||||
|
parent: s, |
||||||
|
} |
||||||
|
l.initialize() |
||||||
|
|
||||||
|
s.wg.Add(1) |
||||||
|
go s.run() |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Log implements logger.Writer.
|
||||||
|
func (s *Server) Log(level logger.Level, format string, args ...interface{}) { |
||||||
|
s.Parent.Log(level, "[SRT] "+format, args...) |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes the server.
|
||||||
|
func (s *Server) Close() { |
||||||
|
s.Log(logger.Info, "listener is closing") |
||||||
|
s.ctxCancel() |
||||||
|
s.wg.Wait() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) run() { |
||||||
|
defer s.wg.Done() |
||||||
|
|
||||||
|
outer: |
||||||
|
for { |
||||||
|
select { |
||||||
|
case err := <-s.chAcceptErr: |
||||||
|
s.Log(logger.Error, "%s", err) |
||||||
|
break outer |
||||||
|
|
||||||
|
case req := <-s.chNewConnRequest: |
||||||
|
c := &conn{ |
||||||
|
parentCtx: s.ctx, |
||||||
|
rtspAddress: s.RTSPAddress, |
||||||
|
readTimeout: s.ReadTimeout, |
||||||
|
writeTimeout: s.WriteTimeout, |
||||||
|
writeQueueSize: s.WriteQueueSize, |
||||||
|
udpMaxPayloadSize: s.UDPMaxPayloadSize, |
||||||
|
connReq: req.connReq, |
||||||
|
runOnConnect: s.RunOnConnect, |
||||||
|
runOnConnectRestart: s.RunOnConnectRestart, |
||||||
|
runOnDisconnect: s.RunOnDisconnect, |
||||||
|
wg: &s.wg, |
||||||
|
externalCmdPool: s.ExternalCmdPool, |
||||||
|
pathManager: s.PathManager, |
||||||
|
parent: s, |
||||||
|
} |
||||||
|
c.initialize() |
||||||
|
s.conns[c] = struct{}{} |
||||||
|
req.res <- c |
||||||
|
|
||||||
|
case c := <-s.chCloseConn: |
||||||
|
delete(s.conns, c) |
||||||
|
|
||||||
|
case req := <-s.chAPIConnsList: |
||||||
|
data := &defs.APISRTConnList{ |
||||||
|
Items: []*defs.APISRTConn{}, |
||||||
|
} |
||||||
|
|
||||||
|
for c := range s.conns { |
||||||
|
data.Items = append(data.Items, c.apiItem()) |
||||||
|
} |
||||||
|
|
||||||
|
sort.Slice(data.Items, func(i, j int) bool { |
||||||
|
return data.Items[i].Created.Before(data.Items[j].Created) |
||||||
|
}) |
||||||
|
|
||||||
|
req.res <- serverAPIConnsListRes{data: data} |
||||||
|
|
||||||
|
case req := <-s.chAPIConnsGet: |
||||||
|
c := s.findConnByUUID(req.uuid) |
||||||
|
if c == nil { |
||||||
|
req.res <- serverAPIConnsGetRes{err: fmt.Errorf("connection not found")} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
req.res <- serverAPIConnsGetRes{data: c.apiItem()} |
||||||
|
|
||||||
|
case req := <-s.chAPIConnsKick: |
||||||
|
c := s.findConnByUUID(req.uuid) |
||||||
|
if c == nil { |
||||||
|
req.res <- serverAPIConnsKickRes{err: fmt.Errorf("connection not found")} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
delete(s.conns, c) |
||||||
|
c.Close() |
||||||
|
req.res <- serverAPIConnsKickRes{} |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
break outer |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
s.ctxCancel() |
||||||
|
|
||||||
|
s.ln.Close() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) findConnByUUID(uuid uuid.UUID) *conn { |
||||||
|
for sx := range s.conns { |
||||||
|
if sx.uuid == uuid { |
||||||
|
return sx |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// newConnRequest is called by srtListener.
|
||||||
|
func (s *Server) newConnRequest(connReq srt.ConnRequest) *conn { |
||||||
|
req := srtNewConnReq{ |
||||||
|
connReq: connReq, |
||||||
|
res: make(chan *conn), |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chNewConnRequest <- req: |
||||||
|
c := <-req.res |
||||||
|
|
||||||
|
return c.new(req) |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// acceptError is called by srtListener.
|
||||||
|
func (s *Server) acceptError(err error) { |
||||||
|
select { |
||||||
|
case s.chAcceptErr <- err: |
||||||
|
case <-s.ctx.Done(): |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// closeConn is called by conn.
|
||||||
|
func (s *Server) closeConn(c *conn) { |
||||||
|
select { |
||||||
|
case s.chCloseConn <- c: |
||||||
|
case <-s.ctx.Done(): |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// APIConnsList is called by api.
|
||||||
|
func (s *Server) APIConnsList() (*defs.APISRTConnList, error) { |
||||||
|
req := serverAPIConnsListReq{ |
||||||
|
res: make(chan serverAPIConnsListRes), |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chAPIConnsList <- req: |
||||||
|
res := <-req.res |
||||||
|
return res.data, res.err |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// APIConnsGet is called by api.
|
||||||
|
func (s *Server) APIConnsGet(uuid uuid.UUID) (*defs.APISRTConn, error) { |
||||||
|
req := serverAPIConnsGetReq{ |
||||||
|
uuid: uuid, |
||||||
|
res: make(chan serverAPIConnsGetRes), |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chAPIConnsGet <- req: |
||||||
|
res := <-req.res |
||||||
|
return res.data, res.err |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
return nil, fmt.Errorf("terminated") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// APIConnsKick is called by api.
|
||||||
|
func (s *Server) APIConnsKick(uuid uuid.UUID) error { |
||||||
|
req := serverAPIConnsKickReq{ |
||||||
|
uuid: uuid, |
||||||
|
res: make(chan serverAPIConnsKickRes), |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case s.chAPIConnsKick <- req: |
||||||
|
res := <-req.res |
||||||
|
return res.err |
||||||
|
|
||||||
|
case <-s.ctx.Done(): |
||||||
|
return fmt.Errorf("terminated") |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue