cleanup code; remove PAUSE support

This commit is contained in:
aler9
2020-07-11 16:40:19 +02:00
parent c0d94929a6
commit c77d6e57cc
7 changed files with 378 additions and 342 deletions

View File

@@ -5,14 +5,13 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/aler9/rtsp-simple-server)](https://goreportcard.com/report/github.com/aler9/rtsp-simple-server) [![Go Report Card](https://goreportcard.com/badge/github.com/aler9/rtsp-simple-server)](https://goreportcard.com/report/github.com/aler9/rtsp-simple-server)
[![Docker Hub](https://img.shields.io/badge/docker-aler9%2Frtsp--simple--server-blue)](https://hub.docker.com/r/aler9/rtsp-simple-server) [![Docker Hub](https://img.shields.io/badge/docker-aler9%2Frtsp--simple--server-blue)](https://hub.docker.com/r/aler9/rtsp-simple-server)
_rtsp-simple-server_ is a simple, ready-to-use and zero-dependency RTSP server and RTSP proxy, a software that allows multiple users to publish and read live video and audio streams. RTSP is a standardized protocol that defines how to perform these operations with the help of a server, that is contacted by both readers and publishers in order to negotiate a streaming protocol. The server is then responsible of relaying the publisher streams to the readers. _rtsp-simple-server_ is a simple, ready-to-use and zero-dependency RTSP server and RTSP proxy, a software that allows multiple users to publish and read live video and audio streams over time. RTSP, RTP and RTCP are standardized protocol that describe how to perform these operations with the help of a server, that is contacted by both readers and publishers in order to negotiate a streaming protocol. The server is then responsible of relaying the publisher streams to the readers.
Features: Features:
* Read and publish streams via UDP and TCP * Read and publish streams via UDP and TCP
* Pull and serve streams from other RTSP servers (RTSP proxy) * Pull and serve streams from other RTSP servers (RTSP proxy)
* Each stream can have multiple video and audio tracks, encoded in any format * Each stream can have multiple video and audio tracks, encoded in any format
* Publish multiple streams at once, each in a separate path, that can be read by multiple users * Publish multiple streams at once, each in a separate path, that can be read by multiple users
* Supports the RTP/RTCP streaming protocol
* Supports authentication * Supports authentication
* Supports running a script when a client connects or disconnects * Supports running a script when a client connects or disconnects
* Compatible with Linux, Windows and Mac, does not require any dependency or interpreter, it's a single executable * Compatible with Linux, Windows and Mac, does not require any dependency or interpreter, it's a single executable

115
main.go
View File

@@ -104,13 +104,6 @@ type programEventClientPlay2 struct {
func (programEventClientPlay2) isProgramEvent() {} func (programEventClientPlay2) isProgramEvent() {}
type programEventClientPause struct {
res chan error
client *serverClient
}
func (programEventClientPause) isProgramEvent() {}
type programEventClientRecord struct { type programEventClientRecord struct {
res chan error res chan error
client *serverClient client *serverClient
@@ -119,8 +112,8 @@ type programEventClientRecord struct {
func (programEventClientRecord) isProgramEvent() {} func (programEventClientRecord) isProgramEvent() {}
type programEventClientFrameUdp struct { type programEventClientFrameUdp struct {
trackFlowType trackFlowType
addr *net.UDPAddr addr *net.UDPAddr
trackFlowType trackFlowType
buf []byte buf []byte
} }
@@ -544,54 +537,22 @@ outer:
evt.client.state = _CLIENT_STATE_PLAY evt.client.state = _CLIENT_STATE_PLAY
evt.res <- nil evt.res <- nil
case programEventClientPause:
p.receiverCount -= 1
evt.client.state = _CLIENT_STATE_PRE_PLAY
evt.res <- nil
case programEventClientRecord: case programEventClientRecord:
p.publisherCount += 1 p.publisherCount += 1
evt.client.state = _CLIENT_STATE_RECORD evt.client.state = _CLIENT_STATE_RECORD
evt.res <- nil evt.res <- nil
case programEventClientFrameUdp: case programEventClientFrameUdp:
// find publisher and track id from ip and port client, trackId := p.findPublisher(evt.addr, evt.trackFlowType)
cl, trackId := func() (*serverClient, int) { if client == nil {
for _, pub := range p.publishers {
cl, ok := pub.(*serverClient)
if !ok {
continue
}
if cl.streamProtocol != _STREAM_PROTOCOL_UDP ||
cl.state != _CLIENT_STATE_RECORD ||
!cl.ip().Equal(evt.addr.IP) {
continue
}
for i, t := range cl.streamTracks {
if evt.trackFlowType == _TRACK_FLOW_TYPE_RTP {
if t.rtpPort == evt.addr.Port {
return cl, i
}
} else {
if t.rtcpPort == evt.addr.Port {
return cl, i
}
}
}
}
return nil, -1
}()
if cl == nil {
continue continue
} }
cl.udpLastFrameTime = time.Now() client.udpLastFrameTime = time.Now()
p.forwardTrack(cl.path, trackId, evt.trackFlowType, evt.buf) p.forwardFrame(client.path, trackId, evt.trackFlowType, evt.buf)
case programEventClientFrameTcp: case programEventClientFrameTcp:
p.forwardTrack(evt.path, evt.trackId, evt.trackFlowType, evt.buf) p.forwardFrame(evt.path, evt.trackId, evt.trackFlowType, evt.buf)
case programEventStreamerReady: case programEventStreamerReady:
evt.streamer.ready = true evt.streamer.ready = true
@@ -611,7 +572,7 @@ outer:
} }
case programEventStreamerFrame: case programEventStreamerFrame:
p.forwardTrack(evt.streamer.path, evt.trackId, evt.trackFlowType, evt.buf) p.forwardFrame(evt.streamer.path, evt.trackId, evt.trackFlowType, evt.buf)
case programEventTerminate: case programEventTerminate:
break outer break outer
@@ -642,9 +603,6 @@ outer:
case programEventClientPlay2: case programEventClientPlay2:
evt.res <- fmt.Errorf("terminated") evt.res <- fmt.Errorf("terminated")
case programEventClientPause:
evt.res <- fmt.Errorf("terminated")
case programEventClientRecord: case programEventClientRecord:
evt.res <- fmt.Errorf("terminated") evt.res <- fmt.Errorf("terminated")
} }
@@ -672,27 +630,60 @@ func (p *program) close() {
<-p.done <-p.done
} }
func (p *program) forwardTrack(path string, id int, trackFlowType trackFlowType, frame []byte) { func (p *program) findPublisher(addr *net.UDPAddr, trackFlowType trackFlowType) (*serverClient, int) {
for _, pub := range p.publishers {
cl, ok := pub.(*serverClient)
if !ok {
continue
}
if cl.streamProtocol != _STREAM_PROTOCOL_UDP ||
cl.state != _CLIENT_STATE_RECORD ||
!cl.ip().Equal(addr.IP) {
continue
}
for i, t := range cl.streamTracks {
if trackFlowType == _TRACK_FLOW_TYPE_RTP {
if t.rtpPort == addr.Port {
return cl, i
}
} else {
if t.rtcpPort == addr.Port {
return cl, i
}
}
}
}
return nil, -1
}
func (p *program) forwardFrame(path string, trackId int, trackFlowType trackFlowType, frame []byte) {
for c := range p.clients { for c := range p.clients {
if c.path == path && c.state == _CLIENT_STATE_PLAY { if c.path == path && c.state == _CLIENT_STATE_PLAY {
if c.streamProtocol == _STREAM_PROTOCOL_UDP { if c.streamProtocol == _STREAM_PROTOCOL_UDP {
if trackFlowType == _TRACK_FLOW_TYPE_RTP { if trackFlowType == _TRACK_FLOW_TYPE_RTP {
p.udplRtp.write(&net.UDPAddr{ p.udplRtp.write(&udpAddrBufPair{
IP: c.ip(), addr: &net.UDPAddr{
Zone: c.zone(), IP: c.ip(),
Port: c.streamTracks[id].rtpPort, Zone: c.zone(),
}, frame) Port: c.streamTracks[trackId].rtpPort,
},
buf: frame,
})
} else { } else {
p.udplRtcp.write(&net.UDPAddr{ p.udplRtcp.write(&udpAddrBufPair{
IP: c.ip(), addr: &net.UDPAddr{
Zone: c.zone(), IP: c.ip(),
Port: c.streamTracks[id].rtcpPort, Zone: c.zone(),
}, frame) Port: c.streamTracks[trackId].rtcpPort,
},
buf: frame,
})
} }
} else { } else {
c.writeFrame(trackFlowTypeToInterleavedChannel(id, trackFlowType), frame) c.writeFrame(trackFlowTypeToInterleavedChannel(trackId, trackFlowType), frame)
} }
} }
} }

View File

@@ -53,24 +53,23 @@ func (cs serverClientState) String() string {
} }
type serverClient struct { type serverClient struct {
p *program p *program
conn *gortsplib.ConnServer conn *gortsplib.ConnServer
state serverClientState state serverClientState
path string path string
authUser string authUser string
authPass string authPass string
authHelper *gortsplib.AuthServer authHelper *gortsplib.AuthServer
authFailures int authFailures int
streamSdpText []byte // filled only if publisher streamSdpText []byte // only if publisher
streamSdpParsed *sdp.Message // filled only if publisher streamSdpParsed *sdp.Message // only if publisher
streamProtocol streamProtocol streamProtocol streamProtocol
streamTracks []*track streamTracks []*track
udpLastFrameTime time.Time udpLastFrameTime time.Time
udpCheckStreamTicker *time.Ticker readBuf *doubleBuffer
readBuf *doubleBuffer writeBuf *doubleBuffer
writeBuf *doubleBuffer
writeChan chan *gortsplib.InterleavedFrame writeChan chan *gortsplib.InterleavedFrame // only if state = _CLIENT_STATE_PLAY
done chan struct{} done chan struct{}
} }
@@ -82,11 +81,9 @@ func newServerClient(p *program, nconn net.Conn) *serverClient {
ReadTimeout: p.conf.ReadTimeout, ReadTimeout: p.conf.ReadTimeout,
WriteTimeout: p.conf.WriteTimeout, WriteTimeout: p.conf.WriteTimeout,
}), }),
state: _CLIENT_STATE_STARTING, state: _CLIENT_STATE_STARTING,
readBuf: newDoubleBuffer(512 * 1024), readBuf: newDoubleBuffer(512 * 1024),
writeBuf: newDoubleBuffer(2048), done: make(chan struct{}),
writeChan: make(chan *gortsplib.InterleavedFrame),
done: make(chan struct{}),
} }
go c.run() go c.run()
@@ -126,29 +123,30 @@ func (c *serverClient) run() {
} }
} }
outer:
for { for {
req, err := c.conn.ReadRequest() switch c.state {
if err != nil { case _CLIENT_STATE_PLAY:
if err != io.EOF { ok := c.runPlay()
c.log("ERR: %s", err) if !ok {
break outer
} }
break
}
ok := c.handleRequest(req) case _CLIENT_STATE_RECORD:
if !ok { ok := c.runRecord()
break if !ok {
break outer
}
default:
ok := c.runNormal()
if !ok {
break outer
}
} }
} }
if c.udpCheckStreamTicker != nil { c.conn.NetConn().Close() // close socket in case it has not been closed yet
c.udpCheckStreamTicker.Stop()
}
go func() {
for range c.writeChan {
}
}()
func() { func() {
if c.p.conf.PostScript != "" { if c.p.conf.PostScript != "" {
@@ -160,16 +158,185 @@ func (c *serverClient) run() {
} }
}() }()
done := make(chan struct{})
c.p.events <- programEventClientClose{done, c}
<-done
close(c.writeChan)
c.conn.NetConn().Close() // close socket in case it has not been closed yet
close(c.done) // close() never blocks close(c.done) // close() never blocks
} }
var errClientChangeRunMode = errors.New("change run mode")
var errClientTerminate = errors.New("terminate")
func (c *serverClient) runNormal() bool {
var ret bool
outer:
for {
req, err := c.conn.ReadRequest()
if err != nil {
if err != io.EOF {
c.log("ERR: %s", err)
}
ret = false
break outer
}
err = c.handleRequest(req)
switch err {
case errClientChangeRunMode:
ret = true
break outer
case errClientTerminate:
ret = false
break outer
}
}
if !ret {
done := make(chan struct{})
c.p.events <- programEventClientClose{done, c}
<-done
}
return ret
}
func (c *serverClient) runPlay() bool {
if c.streamProtocol == _STREAM_PROTOCOL_TCP {
writeDone := make(chan struct{})
go func() {
defer close(writeDone)
for frame := range c.writeChan {
c.conn.WriteInterleavedFrame(frame)
}
}()
buf := make([]byte, 2048)
for {
_, err := c.conn.NetConn().Read(buf)
if err != nil {
if err != io.EOF {
c.log("ERR: %s", err)
}
break
}
}
done := make(chan struct{})
c.p.events <- programEventClientClose{done, c}
<-done
close(c.writeChan)
<-writeDone
} else {
for {
req, err := c.conn.ReadRequest()
if err != nil {
if err != io.EOF {
c.log("ERR: %s", err)
}
break
}
err = c.handleRequest(req)
if err != nil {
break
}
}
done := make(chan struct{})
c.p.events <- programEventClientClose{done, c}
<-done
}
return false
}
func (c *serverClient) runRecord() bool {
if c.streamProtocol == _STREAM_PROTOCOL_TCP {
frame := &gortsplib.InterleavedFrame{}
outer:
for {
frame.Content = c.readBuf.swap()
frame.Content = frame.Content[:cap(frame.Content)]
recv, err := c.conn.ReadInterleavedFrameOrRequest(frame)
if err != nil {
if err != io.EOF {
c.log("ERR: %s", err)
}
break outer
}
switch recvt := recv.(type) {
case *gortsplib.InterleavedFrame:
trackId, trackFlowType := interleavedChannelToTrackFlowType(frame.Channel)
if trackId >= len(c.streamTracks) {
c.log("ERR: invalid track id '%d'", trackId)
break outer
}
c.p.events <- programEventClientFrameTcp{
c.path,
trackId,
trackFlowType,
frame.Content,
}
case *gortsplib.Request:
err := c.handleRequest(recvt)
if err != nil {
break outer
}
}
}
done := make(chan struct{})
c.p.events <- programEventClientClose{done, c}
<-done
} else {
c.udpLastFrameTime = time.Now()
udpCheckStreamTicker := time.NewTicker(_UDP_CHECK_STREAM_INTERVAL)
udpCheckStreamDone := make(chan struct{})
go func() {
defer close(udpCheckStreamDone)
for range udpCheckStreamTicker.C {
if time.Since(c.udpLastFrameTime) >= _UDP_STREAM_DEAD_AFTER {
c.log("ERR: stream is dead")
c.conn.NetConn().Close()
break
}
}
}()
for {
req, err := c.conn.ReadRequest()
if err != nil {
if err != io.EOF {
c.log("ERR: %s", err)
}
break
}
err = c.handleRequest(req)
if err != nil {
break
}
}
done := make(chan struct{})
c.p.events <- programEventClientClose{done, c}
<-done
udpCheckStreamTicker.Stop()
<-udpCheckStreamDone
}
return false
}
func (c *serverClient) close() { func (c *serverClient) close() {
c.conn.NetConn().Close() c.conn.NetConn().Close()
<-c.done <-c.done
@@ -223,23 +390,12 @@ func (c *serverClient) authenticate(ips []interface{}, user string, pass string,
} }
ip := c.ip() ip := c.ip()
if !ipEqualOrInRange(ip, ips) {
for _, item := range ips { c.log("ERR: ip '%s' not allowed", ip)
switch titem := item.(type) { return errAuthCritical
case net.IP:
if titem.Equal(ip) {
return nil
}
case *net.IPNet:
if titem.Contains(ip) {
return nil
}
}
} }
c.log("ERR: ip '%s' not allowed", ip) return nil
return errAuthCritical
}() }()
if err != nil { if err != nil {
return err return err
@@ -304,13 +460,13 @@ func (c *serverClient) authenticate(ips []interface{}, user string, pass string,
return nil return nil
} }
func (c *serverClient) handleRequest(req *gortsplib.Request) bool { func (c *serverClient) handleRequest(req *gortsplib.Request) error {
c.log(string(req.Method)) c.log(string(req.Method))
cseq, ok := req.Header["CSeq"] cseq, ok := req.Header["CSeq"]
if !ok || len(cseq) != 1 { if !ok || len(cseq) != 1 {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("cseq missing")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("cseq missing"))
return false return errClientTerminate
} }
path := func() string { path := func() string {
@@ -343,34 +499,33 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
string(gortsplib.ANNOUNCE), string(gortsplib.ANNOUNCE),
string(gortsplib.SETUP), string(gortsplib.SETUP),
string(gortsplib.PLAY), string(gortsplib.PLAY),
string(gortsplib.PAUSE),
string(gortsplib.RECORD), string(gortsplib.RECORD),
string(gortsplib.TEARDOWN), string(gortsplib.TEARDOWN),
}, ", ")}, }, ", ")},
}, },
}) })
return true return nil
case gortsplib.DESCRIBE: case gortsplib.DESCRIBE:
if c.state != _CLIENT_STATE_STARTING { if c.state != _CLIENT_STATE_STARTING {
c.writeResError(req, gortsplib.StatusBadRequest, c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_STARTING)) fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_STARTING))
return false return errClientTerminate
} }
pconf := c.findConfForPath(path) pconf := c.findConfForPath(path)
if pconf == nil { if pconf == nil {
c.writeResError(req, gortsplib.StatusBadRequest, c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("unable to find a valid configuration for path '%s'", path)) fmt.Errorf("unable to find a valid configuration for path '%s'", path))
return false return errClientTerminate
} }
err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req) err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req)
if err != nil { if err != nil {
if err == errAuthCritical { if err == errAuthCritical {
return false return errClientTerminate
} }
return true return nil
} }
res := make(chan []byte) res := make(chan []byte)
@@ -378,7 +533,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
sdp := <-res sdp := <-res
if sdp == nil { if sdp == nil {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("no one is streaming on path '%s'", path)) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("no one is streaming on path '%s'", path))
return false return errClientTerminate
} }
c.conn.WriteResponse(&gortsplib.Response{ c.conn.WriteResponse(&gortsplib.Response{
@@ -390,51 +545,51 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
}, },
Content: sdp, Content: sdp,
}) })
return true return nil
case gortsplib.ANNOUNCE: case gortsplib.ANNOUNCE:
if c.state != _CLIENT_STATE_STARTING { if c.state != _CLIENT_STATE_STARTING {
c.writeResError(req, gortsplib.StatusBadRequest, c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_STARTING)) fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_STARTING))
return false return errClientTerminate
} }
pconf := c.findConfForPath(path) pconf := c.findConfForPath(path)
if pconf == nil { if pconf == nil {
c.writeResError(req, gortsplib.StatusBadRequest, c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("unable to find a valid configuration for path '%s'", path)) fmt.Errorf("unable to find a valid configuration for path '%s'", path))
return false return errClientTerminate
} }
err := c.authenticate(pconf.publishIpsParsed, pconf.PublishUser, pconf.PublishPass, req) err := c.authenticate(pconf.publishIpsParsed, pconf.PublishUser, pconf.PublishPass, req)
if err != nil { if err != nil {
if err == errAuthCritical { if err == errAuthCritical {
return false return errClientTerminate
} }
return true return nil
} }
ct, ok := req.Header["Content-Type"] ct, ok := req.Header["Content-Type"]
if !ok || len(ct) != 1 { if !ok || len(ct) != 1 {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("Content-Type header missing")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("Content-Type header missing"))
return false return errClientTerminate
} }
if ct[0] != "application/sdp" { if ct[0] != "application/sdp" {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("unsupported Content-Type '%s'", ct)) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("unsupported Content-Type '%s'", ct))
return false return errClientTerminate
} }
sdpParsed, err := gortsplib.SDPParse(req.Content) sdpParsed, err := gortsplib.SDPParse(req.Content)
if err != nil { if err != nil {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("invalid SDP: %s", err)) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("invalid SDP: %s", err))
return false return errClientTerminate
} }
sdpParsed, req.Content = gortsplib.SDPFilter(sdpParsed, req.Content) sdpParsed, req.Content = gortsplib.SDPFilter(sdpParsed, req.Content)
if len(path) == 0 { if len(path) == 0 {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path can't be empty")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path can't be empty"))
return false return errClientTerminate
} }
res := make(chan error) res := make(chan error)
@@ -442,7 +597,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
err = <-res err = <-res
if err != nil { if err != nil {
c.writeResError(req, gortsplib.StatusBadRequest, err) c.writeResError(req, gortsplib.StatusBadRequest, err)
return false return errClientTerminate
} }
c.streamSdpText = req.Content c.streamSdpText = req.Content
@@ -454,19 +609,19 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
"CSeq": cseq, "CSeq": cseq,
}, },
}) })
return true return nil
case gortsplib.SETUP: case gortsplib.SETUP:
tsRaw, ok := req.Header["Transport"] tsRaw, ok := req.Header["Transport"]
if !ok || len(tsRaw) != 1 { if !ok || len(tsRaw) != 1 {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header missing")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header missing"))
return false return errClientTerminate
} }
th := gortsplib.ReadHeaderTransport(tsRaw[0]) th := gortsplib.ReadHeaderTransport(tsRaw[0])
if _, ok := th["multicast"]; ok { if _, ok := th["multicast"]; ok {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("multicast is not supported")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("multicast is not supported"))
return false return errClientTerminate
} }
switch c.state { switch c.state {
@@ -476,15 +631,15 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
if pconf == nil { if pconf == nil {
c.writeResError(req, gortsplib.StatusBadRequest, c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("unable to find a valid configuration for path '%s'", path)) fmt.Errorf("unable to find a valid configuration for path '%s'", path))
return false return errClientTerminate
} }
err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req) err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req)
if err != nil { if err != nil {
if err == errAuthCritical { if err == errAuthCritical {
return false return errClientTerminate
} }
return true return nil
} }
// play via UDP // play via UDP
@@ -501,23 +656,23 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
}() { }() {
if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok { if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok {
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("UDP streaming is disabled")) c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("UDP streaming is disabled"))
return false return errClientTerminate
} }
rtpPort, rtcpPort := th.GetPorts("client_port") rtpPort, rtcpPort := th.GetPorts("client_port")
if rtpPort == 0 || rtcpPort == 0 { if rtpPort == 0 || rtcpPort == 0 {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not have valid client ports (%s)", tsRaw[0])) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not have valid client ports (%s)", tsRaw[0]))
return false return errClientTerminate
} }
if c.path != "" && path != c.path { if c.path != "" && path != c.path {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
return false return errClientTerminate
} }
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP { if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't receive tracks with different protocols")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't receive tracks with different protocols"))
return false return errClientTerminate
} }
res := make(chan error) res := make(chan error)
@@ -525,7 +680,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
err = <-res err = <-res
if err != nil { if err != nil {
c.writeResError(req, gortsplib.StatusBadRequest, err) c.writeResError(req, gortsplib.StatusBadRequest, err)
return false return errClientTerminate
} }
c.conn.WriteResponse(&gortsplib.Response{ c.conn.WriteResponse(&gortsplib.Response{
@@ -541,23 +696,23 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
"Session": []string{"12345678"}, "Session": []string{"12345678"},
}, },
}) })
return true return nil
// play via TCP // play via TCP
} else if _, ok := th["RTP/AVP/TCP"]; ok { } else if _, ok := th["RTP/AVP/TCP"]; ok {
if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok { if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok {
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("TCP streaming is disabled")) c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("TCP streaming is disabled"))
return false return errClientTerminate
} }
if c.path != "" && path != c.path { if c.path != "" && path != c.path {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
return false return errClientTerminate
} }
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP { if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't receive tracks with different protocols")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't receive tracks with different protocols"))
return false return errClientTerminate
} }
res := make(chan error) res := make(chan error)
@@ -565,7 +720,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
err = <-res err = <-res
if err != nil { if err != nil {
c.writeResError(req, gortsplib.StatusBadRequest, err) c.writeResError(req, gortsplib.StatusBadRequest, err)
return false return errClientTerminate
} }
interleaved := fmt.Sprintf("%d-%d", ((len(c.streamTracks) - 1) * 2), ((len(c.streamTracks)-1)*2)+1) interleaved := fmt.Sprintf("%d-%d", ((len(c.streamTracks) - 1) * 2), ((len(c.streamTracks)-1)*2)+1)
@@ -582,24 +737,24 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
"Session": []string{"12345678"}, "Session": []string{"12345678"},
}, },
}) })
return true return nil
} else { } else {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP, RTP/AVP/UDP or RTP/AVP/TCP) (%s)", tsRaw[0])) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP, RTP/AVP/UDP or RTP/AVP/TCP) (%s)", tsRaw[0]))
return false return errClientTerminate
} }
// record // record
case _CLIENT_STATE_ANNOUNCE, _CLIENT_STATE_PRE_RECORD: case _CLIENT_STATE_ANNOUNCE, _CLIENT_STATE_PRE_RECORD:
if _, ok := th["mode=record"]; !ok { if _, ok := th["mode=record"]; !ok {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain mode=record")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain mode=record"))
return false return errClientTerminate
} }
// after ANNOUNCE, c.path is already set // after ANNOUNCE, c.path is already set
if path != c.path { if path != c.path {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
return false return errClientTerminate
} }
// record via UDP // record via UDP
@@ -616,23 +771,23 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
}() { }() {
if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok { if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok {
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("UDP streaming is disabled")) c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("UDP streaming is disabled"))
return false return errClientTerminate
} }
rtpPort, rtcpPort := th.GetPorts("client_port") rtpPort, rtcpPort := th.GetPorts("client_port")
if rtpPort == 0 || rtcpPort == 0 { if rtpPort == 0 || rtcpPort == 0 {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not have valid client ports (%s)", tsRaw[0])) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not have valid client ports (%s)", tsRaw[0]))
return false return errClientTerminate
} }
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP { if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't publish tracks with different protocols")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't publish tracks with different protocols"))
return false return errClientTerminate
} }
if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) { if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("all the tracks have already been setup")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("all the tracks have already been setup"))
return false return errClientTerminate
} }
res := make(chan error) res := make(chan error)
@@ -640,7 +795,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
err := <-res err := <-res
if err != nil { if err != nil {
c.writeResError(req, gortsplib.StatusBadRequest, err) c.writeResError(req, gortsplib.StatusBadRequest, err)
return false return errClientTerminate
} }
c.conn.WriteResponse(&gortsplib.Response{ c.conn.WriteResponse(&gortsplib.Response{
@@ -656,35 +811,35 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
"Session": []string{"12345678"}, "Session": []string{"12345678"},
}, },
}) })
return true return nil
// record via TCP // record via TCP
} else if _, ok := th["RTP/AVP/TCP"]; ok { } else if _, ok := th["RTP/AVP/TCP"]; ok {
if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok { if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok {
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("TCP streaming is disabled")) c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("TCP streaming is disabled"))
return false return errClientTerminate
} }
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP { if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't publish tracks with different protocols")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't publish tracks with different protocols"))
return false return errClientTerminate
} }
interleaved := th.GetValue("interleaved") interleaved := th.GetValue("interleaved")
if interleaved == "" { if interleaved == "" {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain the interleaved field")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain the interleaved field"))
return false return errClientTerminate
} }
expInterleaved := fmt.Sprintf("%d-%d", 0+len(c.streamTracks)*2, 1+len(c.streamTracks)*2) expInterleaved := fmt.Sprintf("%d-%d", 0+len(c.streamTracks)*2, 1+len(c.streamTracks)*2)
if interleaved != expInterleaved { if interleaved != expInterleaved {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("wrong interleaved value, expected '%s', got '%s'", expInterleaved, interleaved)) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("wrong interleaved value, expected '%s', got '%s'", expInterleaved, interleaved))
return false return errClientTerminate
} }
if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) { if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("all the tracks have already been setup")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("all the tracks have already been setup"))
return false return errClientTerminate
} }
res := make(chan error) res := make(chan error)
@@ -692,7 +847,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
err := <-res err := <-res
if err != nil { if err != nil {
c.writeResError(req, gortsplib.StatusBadRequest, err) c.writeResError(req, gortsplib.StatusBadRequest, err)
return false return errClientTerminate
} }
c.conn.WriteResponse(&gortsplib.Response{ c.conn.WriteResponse(&gortsplib.Response{
@@ -707,28 +862,28 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
"Session": []string{"12345678"}, "Session": []string{"12345678"},
}, },
}) })
return true return nil
} else { } else {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP, RTP/AVP/UDP or RTP/AVP/TCP) (%s)", tsRaw[0])) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP, RTP/AVP/UDP or RTP/AVP/TCP) (%s)", tsRaw[0]))
return false return errClientTerminate
} }
default: default:
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("client is in state '%s'", c.state)) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("client is in state '%s'", c.state))
return false return errClientTerminate
} }
case gortsplib.PLAY: case gortsplib.PLAY:
if c.state != _CLIENT_STATE_PRE_PLAY { if c.state != _CLIENT_STATE_PRE_PLAY {
c.writeResError(req, gortsplib.StatusBadRequest, c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PRE_PLAY)) fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PRE_PLAY))
return false return errClientTerminate
} }
if path != c.path { if path != c.path {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
return false return errClientTerminate
} }
// check publisher existence // check publisher existence
@@ -737,7 +892,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
err := <-res err := <-res
if err != nil { if err != nil {
c.writeResError(req, gortsplib.StatusBadRequest, err) c.writeResError(req, gortsplib.StatusBadRequest, err)
return false return errClientTerminate
} }
// write response before setting state // write response before setting state
@@ -751,6 +906,9 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
}, },
}) })
c.writeBuf = newDoubleBuffer(2048)
c.writeChan = make(chan *gortsplib.InterleavedFrame)
// set state // set state
res = make(chan error) res = make(chan error)
c.p.events <- programEventClientPlay2{res, c} c.p.events <- programEventClientPlay2{res, c}
@@ -763,72 +921,23 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
return "tracks" return "tracks"
}(), c.streamProtocol) }(), c.streamProtocol)
// when protocol is TCP, the RTSP connection becomes a RTP connection return errClientChangeRunMode
if c.streamProtocol == _STREAM_PROTOCOL_TCP {
// write RTP frames sequentially
go func() {
for frame := range c.writeChan {
c.conn.WriteInterleavedFrame(frame)
}
}()
// receive RTP feedback, do not parse it, wait until connection closes
buf := make([]byte, 2048)
for {
_, err := c.conn.NetConn().Read(buf)
if err != nil {
if err != io.EOF {
c.log("ERR: %s", err)
}
return false
}
}
}
return true
case gortsplib.PAUSE:
if c.state != _CLIENT_STATE_PLAY {
c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PLAY))
return false
}
if path != c.path {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
return false
}
c.log("paused")
res := make(chan error)
c.p.events <- programEventClientPause{res, c}
<-res
c.conn.WriteResponse(&gortsplib.Response{
StatusCode: gortsplib.StatusOK,
Header: gortsplib.Header{
"CSeq": cseq,
"Session": []string{"12345678"},
},
})
return true
case gortsplib.RECORD: case gortsplib.RECORD:
if c.state != _CLIENT_STATE_PRE_RECORD { if c.state != _CLIENT_STATE_PRE_RECORD {
c.writeResError(req, gortsplib.StatusBadRequest, c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PRE_RECORD)) fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PRE_RECORD))
return false return errClientTerminate
} }
if path != c.path { if path != c.path {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
return false return errClientTerminate
} }
if len(c.streamTracks) != len(c.streamSdpParsed.Medias) { if len(c.streamTracks) != len(c.streamSdpParsed.Medias) {
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("not all tracks have been setup")) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("not all tracks have been setup"))
return false return errClientTerminate
} }
c.conn.WriteResponse(&gortsplib.Response{ c.conn.WriteResponse(&gortsplib.Response{
@@ -850,79 +959,14 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
return "tracks" return "tracks"
}(), c.streamProtocol) }(), c.streamProtocol)
// when protocol is TCP, the RTSP connection becomes a RTP connection return errClientChangeRunMode
// receive RTP data and parse it
if c.streamProtocol == _STREAM_PROTOCOL_TCP {
frame := &gortsplib.InterleavedFrame{}
for {
frame.Content = c.readBuf.swap()
frame.Content = frame.Content[:cap(frame.Content)]
recv, err := c.conn.ReadInterleavedFrameOrRequest(frame)
if err != nil {
if err != io.EOF {
c.log("ERR: %s", err)
}
return false
}
switch recvt := recv.(type) {
case *gortsplib.InterleavedFrame:
trackId, trackFlowType := interleavedChannelToTrackFlowType(frame.Channel)
if trackId >= len(c.streamTracks) {
c.log("ERR: invalid track id '%d'", trackId)
return false
}
c.p.events <- programEventClientFrameTcp{
c.path,
trackId,
trackFlowType,
frame.Content,
}
case *gortsplib.Request:
cseq, ok := recvt.Header["CSeq"]
if !ok || len(cseq) != 1 {
c.writeResError(recvt, gortsplib.StatusBadRequest, fmt.Errorf("cseq missing"))
return false
}
switch recvt.Method {
case gortsplib.TEARDOWN:
// close connection silently
return false
default:
c.writeResError(recvt, gortsplib.StatusBadRequest, fmt.Errorf("unhandled method '%s'", recvt.Method))
return false
}
}
}
} else {
c.udpLastFrameTime = time.Now()
c.udpCheckStreamTicker = time.NewTicker(_UDP_CHECK_STREAM_INTERVAL)
go func() {
for range c.udpCheckStreamTicker.C {
if time.Since(c.udpLastFrameTime) >= _UDP_STREAM_DEAD_AFTER {
c.log("ERR: stream is dead")
c.conn.NetConn().Close()
break
}
}
}()
}
return true
case gortsplib.TEARDOWN: case gortsplib.TEARDOWN:
// close connection silently // close connection silently
return false return errClientTerminate
default: default:
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("unhandled method '%s'", req.Method)) c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("unhandled method '%s'", req.Method))
return false return errClientTerminate
} }
} }

View File

@@ -5,7 +5,7 @@ import (
"time" "time"
) )
type udpAddrFramePair struct { type udpAddrBufPair struct {
addr *net.UDPAddr addr *net.UDPAddr
buf []byte buf []byte
} }
@@ -17,7 +17,7 @@ type serverUdpListener struct {
readBuf *doubleBuffer readBuf *doubleBuffer
writeBuf *doubleBuffer writeBuf *doubleBuffer
writeChan chan *udpAddrFramePair writeChan chan *udpAddrBufPair
done chan struct{} done chan struct{}
} }
@@ -35,7 +35,7 @@ func newServerUdpListener(p *program, port int, trackFlowType trackFlowType) (*s
trackFlowType: trackFlowType, trackFlowType: trackFlowType,
readBuf: newDoubleBuffer(2048), readBuf: newDoubleBuffer(2048),
writeBuf: newDoubleBuffer(2048), writeBuf: newDoubleBuffer(2048),
writeChan: make(chan *udpAddrFramePair), writeChan: make(chan *udpAddrBufPair),
done: make(chan struct{}), done: make(chan struct{}),
} }
@@ -69,8 +69,8 @@ func (l *serverUdpListener) run() {
} }
l.p.events <- programEventClientFrameUdp{ l.p.events <- programEventClientFrameUdp{
l.trackFlowType,
addr, addr,
l.trackFlowType,
buf[:n], buf[:n],
} }
} }
@@ -85,13 +85,12 @@ func (l *serverUdpListener) close() {
<-l.done <-l.done
} }
func (l *serverUdpListener) write(addr *net.UDPAddr, inbuf []byte) { func (l *serverUdpListener) write(pair *udpAddrBufPair) {
// replace input buffer with write buffer
buf := l.writeBuf.swap() buf := l.writeBuf.swap()
buf = buf[:len(inbuf)] buf = buf[:len(pair.buf)]
copy(buf, inbuf) copy(buf, pair.buf)
pair.buf = buf
l.writeChan <- &udpAddrFramePair{ l.writeChan <- pair
addr: addr,
buf: buf,
}
} }

View File

@@ -15,7 +15,6 @@ type streamerUdpListener struct {
nconn *net.UDPConn nconn *net.UDPConn
running bool running bool
readBuf *doubleBuffer readBuf *doubleBuffer
lastFrameTime time.Time
done chan struct{} done chan struct{}
} }
@@ -37,7 +36,6 @@ func newStreamerUdpListener(p *program, port int, streamer *streamer,
publisherIp: publisherIp, publisherIp: publisherIp,
nconn: nconn, nconn: nconn,
readBuf: newDoubleBuffer(2048), readBuf: newDoubleBuffer(2048),
lastFrameTime: time.Now(),
done: make(chan struct{}), done: make(chan struct{}),
} }
@@ -69,7 +67,7 @@ func (l *streamerUdpListener) run() {
continue continue
} }
l.lastFrameTime = time.Now() l.streamer.udpLastFrameTime = time.Now()
l.p.events <- programEventStreamerFrame{l.streamer, l.trackId, l.trackFlowType, buf[:n]} l.p.events <- programEventStreamerFrame{l.streamer, l.trackId, l.trackFlowType, buf[:n]}
} }

View File

@@ -27,16 +27,17 @@ type streamerUdpListenerPair struct {
} }
type streamer struct { type streamer struct {
p *program p *program
path string path string
ur *url.URL ur *url.URL
proto streamProtocol proto streamProtocol
ready bool ready bool
clientSdpParsed *sdp.Message clientSdpParsed *sdp.Message
serverSdpText []byte serverSdpText []byte
serverSdpParsed *sdp.Message serverSdpParsed *sdp.Message
firstTime bool firstTime bool
readBuf *doubleBuffer udpLastFrameTime time.Time
readBuf *doubleBuffer
terminate chan struct{} terminate chan struct{}
done chan struct{} done chan struct{}
@@ -402,6 +403,7 @@ func (s *streamer) runUdp(conn *gortsplib.ConnClient) bool {
tickerSendKeepalive := time.NewTicker(_KEEPALIVE_INTERVAL) tickerSendKeepalive := time.NewTicker(_KEEPALIVE_INTERVAL)
defer tickerSendKeepalive.Stop() defer tickerSendKeepalive.Stop()
s.udpLastFrameTime = time.Now()
tickerCheckStream := time.NewTicker(_CHECK_STREAM_INTERVAL) tickerCheckStream := time.NewTicker(_CHECK_STREAM_INTERVAL)
defer tickerCheckStream.Stop() defer tickerCheckStream.Stop()
@@ -431,21 +433,7 @@ func (s *streamer) runUdp(conn *gortsplib.ConnClient) bool {
} }
case <-tickerCheckStream.C: case <-tickerCheckStream.C:
lastFrameTime := time.Time{} if time.Since(s.udpLastFrameTime) >= _STREAM_DEAD_AFTER {
for _, pair := range streamerUdpListenerPairs {
lft := pair.udplRtp.lastFrameTime
if lft.After(lastFrameTime) {
lastFrameTime = lft
}
lft = pair.udplRtcp.lastFrameTime
if lft.After(lastFrameTime) {
lastFrameTime = lft
}
}
if time.Since(lastFrameTime) >= _STREAM_DEAD_AFTER {
s.log("ERR: stream is dead") s.log("ERR: stream is dead")
return true return true
} }

View File

@@ -29,6 +29,23 @@ func parseIpCidrList(in []string) ([]interface{}, error) {
return ret, nil return ret, nil
} }
func ipEqualOrInRange(ip net.IP, ips []interface{}) bool {
for _, item := range ips {
switch titem := item.(type) {
case net.IP:
if titem.Equal(ip) {
return true
}
case *net.IPNet:
if titem.Contains(ip) {
return true
}
}
}
return false
}
type trackFlowType int type trackFlowType int
const ( const (