client: use a single goroutine during play / record

This commit is contained in:
aler9
2021-11-12 13:03:38 +01:00
committed by Alessandro Ros
parent f6601580db
commit 0440a926b8
3 changed files with 349 additions and 472 deletions

796
client.go
View File

@@ -38,9 +38,10 @@ const (
clientUDPKeepalivePeriod = 30 * time.Second clientUDPKeepalivePeriod = 30 * time.Second
) )
func isErrNOUDPPacketsReceivedRecently(err error) bool { func emptyTimer() *time.Timer {
_, ok := err.(liberrors.ErrClientNoUDPPacketsRecently) t := time.NewTimer(0)
return ok <-t.C
return t
} }
func isAnyPort(p int) bool { func isAnyPort(p int) bool {
@@ -197,44 +198,47 @@ type Client struct {
senderReportPeriod time.Duration senderReportPeriod time.Duration
receiverReportPeriod time.Duration receiverReportPeriod time.Duration
scheme string scheme string
host string host string
ctx context.Context ctx context.Context
ctxCancel func() ctxCancel func()
state clientState state clientState
nconn net.Conn nconn net.Conn
br *bufio.Reader br *bufio.Reader
bw *bufio.Writer bw *bufio.Writer
session string session string
sender *auth.Sender sender *auth.Sender
cseq int cseq int
useGetParameter bool useGetParameter bool
streamBaseURL *base.URL streamBaseURL *base.URL
protocol *Transport protocol *Transport
tracks map[int]clientTrack tracks map[int]clientTrack
tracksByChannel map[int]int tracksByChannel map[int]int
lastRange *headers.Range lastRange *headers.Range
backgroundRunning bool tcpFrameBuffer *multibuffer.MultiBuffer // tcp
backgroundErr error tcpWriteMutex sync.Mutex // tcp
tcpFrameBuffer *multibuffer.MultiBuffer // tcp writeMutex sync.RWMutex // write
tcpWriteMutex sync.Mutex // tcp writeFrameAllowed bool // write
writeMutex sync.RWMutex // write reportTimer *time.Timer
writeFrameAllowed bool // write checkStreamTimer *time.Timer
checkStreamInitial bool
tcpLastFrameTime int64
keepaliveTimer *time.Timer
readerRunning bool
finalErr error
// in // in
options chan optionsReq options chan optionsReq
describe chan describeReq describe chan describeReq
announce chan announceReq announce chan announceReq
setup chan setupReq setup chan setupReq
play chan playReq play chan playReq
record chan recordReq record chan recordReq
pause chan pauseReq pause chan pauseReq
backgroundTerminate chan struct{}
// out // out
backgroundInnerDone chan error readerErr chan error
backgroundDone chan struct{} done chan struct{}
done chan struct{}
} }
// Dial connects to a server. // Dial connects to a server.
@@ -291,6 +295,9 @@ func (c *Client) Dial(scheme string, host string) error {
c.host = host c.host = host
c.ctx = ctx c.ctx = ctx
c.ctxCancel = ctxCancel c.ctxCancel = ctxCancel
c.reportTimer = emptyTimer()
c.checkStreamTimer = emptyTimer()
c.keepaliveTimer = emptyTimer()
c.options = make(chan optionsReq) c.options = make(chan optionsReq)
c.describe = make(chan describeReq) c.describe = make(chan describeReq)
c.announce = make(chan announceReq) c.announce = make(chan announceReq)
@@ -416,65 +423,159 @@ func (c *Client) Tracks() Tracks {
func (c *Client) run() { func (c *Client) run() {
defer close(c.done) defer close(c.done)
outer: c.finalErr = func() error {
for { for {
select { select {
case req := <-c.options: case req := <-c.options:
res, err := c.doOptions(req.url) res, err := c.doOptions(req.url)
req.res <- clientRes{res: res, err: err} req.res <- clientRes{res: res, err: err}
case req := <-c.describe: case req := <-c.describe:
tracks, baseURL, res, err := c.doDescribe(req.url) tracks, baseURL, res, err := c.doDescribe(req.url)
req.res <- clientRes{tracks: tracks, baseURL: baseURL, res: res, err: err} req.res <- clientRes{tracks: tracks, baseURL: baseURL, res: res, err: err}
case req := <-c.announce: case req := <-c.announce:
res, err := c.doAnnounce(req.url, req.tracks) res, err := c.doAnnounce(req.url, req.tracks)
req.res <- clientRes{res: res, err: err} req.res <- clientRes{res: res, err: err}
case req := <-c.setup: case req := <-c.setup:
res, err := c.doSetup(req.mode, req.baseURL, req.track, req.rtpPort, req.rtcpPort) res, err := c.doSetup(req.mode, req.baseURL, req.track, req.rtpPort, req.rtcpPort)
req.res <- clientRes{res: res, err: err} req.res <- clientRes{res: res, err: err}
case req := <-c.play: case req := <-c.play:
res, err := c.doPlay(req.ra, false) res, err := c.doPlay(req.ra, false)
req.res <- clientRes{res: res, err: err} req.res <- clientRes{res: res, err: err}
case req := <-c.record: case req := <-c.record:
res, err := c.doRecord() res, err := c.doRecord()
req.res <- clientRes{res: res, err: err} req.res <- clientRes{res: res, err: err}
case req := <-c.pause: case req := <-c.pause:
res, err := c.doPause() res, err := c.doPause()
req.res <- clientRes{res: res, err: err} req.res <- clientRes{res: res, err: err}
case err := <-c.backgroundInnerDone: case <-c.reportTimer.C:
c.backgroundRunning = false if c.state == clientStatePlay {
err = c.switchProtocolIfTimeout(err) now := time.Now()
if err != nil { for trackID, cct := range c.tracks {
c.backgroundErr = err rr := cct.rtcpReceiver.Report(now)
close(c.backgroundDone) c.WritePacketRTCP(trackID, rr)
}
c.writeMutex.Lock() c.reportTimer = time.NewTimer(c.receiverReportPeriod)
c.writeFrameAllowed = false } else { // Record
c.writeMutex.Unlock() now := time.Now()
for trackID, cct := range c.tracks {
sr := cct.rtcpSender.Report(now)
if sr != nil {
c.WritePacketRTCP(trackID, sr)
}
}
c.reportTimer = time.NewTimer(c.senderReportPeriod)
}
case <-c.checkStreamTimer.C:
if *c.protocol == TransportUDP ||
*c.protocol == TransportUDPMulticast {
if c.checkStreamInitial {
c.checkStreamInitial = false
// check that at least one packet has been received
inTimeout := func() bool {
for _, cct := range c.tracks {
lft := atomic.LoadInt64(cct.udpRTPListener.lastFrameTime)
if lft != 0 {
return false
}
lft = atomic.LoadInt64(cct.udpRTCPListener.lastFrameTime)
if lft != 0 {
return false
}
}
return true
}()
if inTimeout {
err := c.trySwitchingProtocol()
if err != nil {
return err
}
}
c.checkStreamTimer = time.NewTimer(clientCheckStreamPeriod)
} else {
inTimeout := func() bool {
now := time.Now()
for _, cct := range c.tracks {
lft := time.Unix(atomic.LoadInt64(cct.udpRTPListener.lastFrameTime), 0)
if now.Sub(lft) < c.ReadTimeout {
return false
}
lft = time.Unix(atomic.LoadInt64(cct.udpRTCPListener.lastFrameTime), 0)
if now.Sub(lft) < c.ReadTimeout {
return false
}
}
return true
}()
if inTimeout {
return liberrors.ErrClientUDPTimeout{}
}
c.checkStreamTimer = time.NewTimer(clientCheckStreamPeriod)
}
} else { // TCP
inTimeout := func() bool {
now := time.Now()
lft := time.Unix(atomic.LoadInt64(&c.tcpLastFrameTime), 0)
return now.Sub(lft) >= c.ReadTimeout
}()
if inTimeout {
return liberrors.ErrClientTCPTimeout{}
}
c.checkStreamTimer = time.NewTimer(clientCheckStreamPeriod)
}
case <-c.keepaliveTimer.C:
_, err := c.do(&base.Request{
Method: func() base.Method {
// the vlc integrated rtsp server requires GET_PARAMETER
if c.useGetParameter {
return base.GetParameter
}
return base.Options
}(),
// use the stream base URL, otherwise some cameras do not reply
URL: c.streamBaseURL,
}, true)
if err != nil {
return err
}
c.keepaliveTimer = time.NewTimer(clientUDPKeepalivePeriod)
case err := <-c.readerErr:
c.readerRunning = false
return err
case <-c.ctx.Done():
return liberrors.ErrClientTerminated{}
} }
case <-c.ctx.Done():
break outer
} }
} }()
c.ctxCancel() c.ctxCancel()
c.doClose(false) c.doClose()
} }
func (c *Client) doClose(isSwitchingProtocol bool) { func (c *Client) doClose() {
if c.backgroundRunning {
c.backgroundClose(isSwitchingProtocol)
}
if c.state == clientStatePlay || c.state == clientStateRecord { if c.state == clientStatePlay || c.state == clientStateRecord {
c.playRecordClose()
c.do(&base.Request{ c.do(&base.Request{
Method: base.Teardown, Method: base.Teardown,
URL: c.streamBaseURL, URL: c.streamBaseURL,
@@ -494,8 +595,8 @@ func (c *Client) doClose(isSwitchingProtocol bool) {
} }
} }
func (c *Client) reset(isSwitchingProtocol bool) { func (c *Client) reset() {
c.doClose(isSwitchingProtocol) c.doClose()
c.state = clientStateInitial c.state = clientStateInitial
c.session = "" c.session = ""
@@ -524,19 +625,12 @@ func (c *Client) checkState(allowed map[clientState]struct{}) error {
return liberrors.ErrClientInvalidState{AllowedList: allowedList, State: c.state} return liberrors.ErrClientInvalidState{AllowedList: allowedList, State: c.state}
} }
func (c *Client) switchProtocolIfTimeout(err error) error { func (c *Client) trySwitchingProtocol() error {
if *c.protocol != TransportUDP ||
c.state != clientStatePlay ||
!isErrNOUDPPacketsReceivedRecently(err) ||
c.Transport != nil {
return err
}
prevBaseURL := c.streamBaseURL prevBaseURL := c.streamBaseURL
oldUseGetParameter := c.useGetParameter oldUseGetParameter := c.useGetParameter
prevTracks := c.tracks prevTracks := c.tracks
c.reset(true) c.reset()
v := TransportTCP v := TransportTCP
c.protocol = &v c.protocol = &v
@@ -551,7 +645,7 @@ func (c *Client) switchProtocolIfTimeout(err error) error {
} }
} }
_, err = c.doPlay(c.lastRange, true) _, err := c.doPlay(c.lastRange, true)
if err != nil { if err != nil {
return err return err
} }
@@ -559,373 +653,153 @@ func (c *Client) switchProtocolIfTimeout(err error) error {
return nil return nil
} }
func (c *Client) backgroundStart(isSwitchingProtocol bool) { func (c *Client) playRecordStart() {
// start timers
if c.state == clientStatePlay {
c.reportTimer = time.NewTimer(c.receiverReportPeriod)
switch *c.protocol {
case TransportUDP:
c.checkStreamTimer = time.NewTimer(c.InitialUDPReadTimeout)
c.checkStreamInitial = true
c.keepaliveTimer = time.NewTimer(clientUDPKeepalivePeriod)
case TransportUDPMulticast:
c.checkStreamTimer = time.NewTimer(clientCheckStreamPeriod)
c.keepaliveTimer = time.NewTimer(clientUDPKeepalivePeriod)
default: // TCP
c.checkStreamTimer = time.NewTimer(clientCheckStreamPeriod)
c.tcpLastFrameTime = time.Now().Unix()
}
} else {
c.reportTimer = time.NewTimer(c.senderReportPeriod)
}
// allow writing
c.writeMutex.Lock() c.writeMutex.Lock()
c.writeFrameAllowed = true c.writeFrameAllowed = true
c.writeMutex.Unlock() c.writeMutex.Unlock()
c.backgroundRunning = true // start UDP listeners
c.backgroundTerminate = make(chan struct{}) if *c.protocol == TransportUDP || *c.protocol == TransportUDPMulticast {
c.backgroundInnerDone = make(chan error) for _, cct := range c.tracks {
cct.udpRTPListener.start()
if !isSwitchingProtocol { cct.udpRTCPListener.start()
c.backgroundDone = make(chan struct{}) }
} }
go c.runBackground() // for some reason, SetReadDeadline() must always be called in the same
// goroutine, otherwise Read() freezes.
// therefore, we disable the deadline and perform a check with a ticker.
c.nconn.SetReadDeadline(time.Time{})
// start reader
c.readerRunning = true
c.readerErr = make(chan error)
go func() {
c.readerErr <- c.runReader()
}()
} }
func (c *Client) backgroundClose(isSwitchingProtocol bool) { func (c *Client) runReader() error {
close(c.backgroundTerminate) if *c.protocol == TransportUDP || *c.protocol == TransportUDPMulticast {
err := <-c.backgroundInnerDone for {
c.backgroundRunning = false var res base.Response
err := res.Read(c.br)
if err != nil {
return err
}
}
} else {
if c.state == clientStatePlay {
for {
frame := base.InterleavedFrame{
Payload: c.tcpFrameBuffer.Next(),
}
err := frame.Read(c.br)
if err != nil {
return err
}
if !isSwitchingProtocol { channel := frame.Channel
c.backgroundErr = err isRTP := true
close(c.backgroundDone) if (channel % 2) != 0 {
channel--
isRTP = false
}
trackID, ok := c.tracksByChannel[channel]
if !ok {
continue
}
now := time.Now()
atomic.StoreInt64(&c.tcpLastFrameTime, now.Unix())
if isRTP {
c.tracks[trackID].rtcpReceiver.ProcessPacketRTP(now, frame.Payload)
c.OnPacketRTP(c, trackID, frame.Payload)
} else {
c.tracks[trackID].rtcpReceiver.ProcessPacketRTCP(now, frame.Payload)
c.OnPacketRTCP(c, trackID, frame.Payload)
}
}
} else { // Record
for {
frame := base.InterleavedFrame{
Payload: c.tcpFrameBuffer.Next(),
}
err := frame.Read(c.br)
if err != nil {
return err
}
channel := frame.Channel
isRTP := true
if (channel % 2) != 0 {
channel--
isRTP = false
}
trackID, ok := c.tracksByChannel[channel]
if !ok {
continue
}
if !isRTP {
c.OnPacketRTCP(c, trackID, frame.Payload)
}
}
}
}
}
func (c *Client) playRecordClose() {
// stop reader
if c.readerRunning {
c.nconn.SetReadDeadline(time.Now())
<-c.readerErr
} }
// stop UDP listeners
if *c.protocol == TransportUDP || *c.protocol == TransportUDPMulticast {
for _, cct := range c.tracks {
cct.udpRTPListener.stop()
cct.udpRTCPListener.stop()
}
}
// forbid writing
c.writeMutex.Lock() c.writeMutex.Lock()
c.writeFrameAllowed = false c.writeFrameAllowed = false
c.writeMutex.Unlock() c.writeMutex.Unlock()
}
func (c *Client) runBackground() { // stop timers
c.backgroundInnerDone <- func() error { c.reportTimer = emptyTimer()
if c.state == clientStatePlay { c.checkStreamTimer = emptyTimer()
if *c.protocol == TransportUDP || *c.protocol == TransportUDPMulticast { c.keepaliveTimer = emptyTimer()
return c.runBackgroundPlayUDP()
}
return c.runBackgroundPlayTCP()
}
if *c.protocol == TransportUDP {
return c.runBackgroundRecordUDP()
}
return c.runBackgroundRecordTCP()
}()
}
func (c *Client) runBackgroundPlayUDP() error {
for _, cct := range c.tracks {
cct.udpRTPListener.start()
cct.udpRTCPListener.start()
}
defer func() {
for _, cct := range c.tracks {
cct.udpRTPListener.stop()
cct.udpRTCPListener.stop()
}
}()
// disable deadline
c.nconn.SetReadDeadline(time.Time{})
readerDone := make(chan error)
go func() {
for {
var res base.Response
err := res.Read(c.br)
if err != nil {
readerDone <- err
return
}
}
}()
reportTicker := time.NewTicker(c.receiverReportPeriod)
defer reportTicker.Stop()
keepaliveTicker := time.NewTicker(clientUDPKeepalivePeriod)
defer keepaliveTicker.Stop()
checkStreamInitial := true
checkStreamTicker := time.NewTicker(c.InitialUDPReadTimeout)
defer func() {
checkStreamTicker.Stop()
}()
for {
select {
case <-c.backgroundTerminate:
c.nconn.SetReadDeadline(time.Now())
<-readerDone
return fmt.Errorf("terminated")
case <-reportTicker.C:
now := time.Now()
for trackID, cct := range c.tracks {
rr := cct.rtcpReceiver.Report(now)
c.WritePacketRTCP(trackID, rr)
}
case <-keepaliveTicker.C:
_, err := c.do(&base.Request{
Method: func() base.Method {
// the vlc integrated rtsp server requires GET_PARAMETER
if c.useGetParameter {
return base.GetParameter
}
return base.Options
}(),
// use the stream base URL, otherwise some cameras do not reply
URL: c.streamBaseURL,
}, true)
if err != nil {
c.nconn.SetReadDeadline(time.Now())
<-readerDone
return err
}
case <-checkStreamTicker.C:
if checkStreamInitial {
// check that at least one packet has been received
inTimeout := func() bool {
for _, cct := range c.tracks {
lft := atomic.LoadInt64(cct.udpRTPListener.lastFrameTime)
if lft != 0 {
return false
}
lft = atomic.LoadInt64(cct.udpRTCPListener.lastFrameTime)
if lft != 0 {
return false
}
}
return true
}()
if inTimeout {
c.nconn.SetReadDeadline(time.Now())
<-readerDone
return liberrors.ErrClientNoUDPPacketsRecently{}
}
checkStreamInitial = false
checkStreamTicker.Stop()
checkStreamTicker = time.NewTicker(clientCheckStreamPeriod)
} else {
inTimeout := func() bool {
now := time.Now()
for _, cct := range c.tracks {
lft := time.Unix(atomic.LoadInt64(cct.udpRTPListener.lastFrameTime), 0)
if now.Sub(lft) < c.ReadTimeout {
return false
}
lft = time.Unix(atomic.LoadInt64(cct.udpRTCPListener.lastFrameTime), 0)
if now.Sub(lft) < c.ReadTimeout {
return false
}
}
return true
}()
if inTimeout {
c.nconn.SetReadDeadline(time.Now())
<-readerDone
return liberrors.ErrClientUDPTimeout{}
}
}
case err := <-readerDone:
return err
}
}
}
func (c *Client) runBackgroundPlayTCP() error {
// for some reason, SetReadDeadline() must always be called in the same
// goroutine, otherwise Read() freezes.
// therefore, we disable the deadline and perform check with a ticker.
c.nconn.SetReadDeadline(time.Time{})
lastFrameTime := time.Now().Unix()
readerDone := make(chan error)
go func() {
for {
frame := base.InterleavedFrame{
Payload: c.tcpFrameBuffer.Next(),
}
err := frame.Read(c.br)
if err != nil {
readerDone <- err
return
}
channel := frame.Channel
isRTP := true
if (channel % 2) != 0 {
channel--
isRTP = false
}
trackID, ok := c.tracksByChannel[channel]
if !ok {
continue
}
now := time.Now()
atomic.StoreInt64(&lastFrameTime, now.Unix())
if isRTP {
c.tracks[trackID].rtcpReceiver.ProcessPacketRTP(now, frame.Payload)
c.OnPacketRTP(c, trackID, frame.Payload)
} else {
c.tracks[trackID].rtcpReceiver.ProcessPacketRTCP(now, frame.Payload)
c.OnPacketRTCP(c, trackID, frame.Payload)
}
}
}()
reportTicker := time.NewTicker(c.receiverReportPeriod)
defer reportTicker.Stop()
checkStreamTicker := time.NewTicker(clientCheckStreamPeriod)
defer checkStreamTicker.Stop()
for {
select {
case <-c.backgroundTerminate:
c.nconn.SetReadDeadline(time.Now())
<-readerDone
return fmt.Errorf("terminated")
case <-reportTicker.C:
now := time.Now()
for trackID, cct := range c.tracks {
rr := cct.rtcpReceiver.Report(now)
c.WritePacketRTCP(trackID, rr)
}
case <-checkStreamTicker.C:
inTimeout := func() bool {
now := time.Now()
lft := time.Unix(atomic.LoadInt64(&lastFrameTime), 0)
return now.Sub(lft) >= c.ReadTimeout
}()
if inTimeout {
c.nconn.SetReadDeadline(time.Now())
<-readerDone
return liberrors.ErrClientTCPTimeout{}
}
case err := <-readerDone:
return err
}
}
}
func (c *Client) runBackgroundRecordUDP() error {
for _, cct := range c.tracks {
cct.udpRTPListener.start()
cct.udpRTCPListener.start()
}
defer func() {
for _, cct := range c.tracks {
cct.udpRTPListener.stop()
cct.udpRTCPListener.stop()
}
}()
// disable deadline
c.nconn.SetReadDeadline(time.Time{})
readerDone := make(chan error)
go func() {
for {
var res base.Response
err := res.Read(c.br)
if err != nil {
readerDone <- err
return
}
}
}()
reportTicker := time.NewTicker(c.senderReportPeriod)
defer reportTicker.Stop()
for {
select {
case <-c.backgroundTerminate:
c.nconn.SetReadDeadline(time.Now())
<-readerDone
return fmt.Errorf("terminated")
case <-reportTicker.C:
now := time.Now()
for trackID, cct := range c.tracks {
sr := cct.rtcpSender.Report(now)
if sr != nil {
c.WritePacketRTCP(trackID, sr)
}
}
case err := <-readerDone:
return err
}
}
}
func (c *Client) runBackgroundRecordTCP() error {
// disable deadline
c.nconn.SetReadDeadline(time.Time{})
readerDone := make(chan error)
go func() {
for {
frame := base.InterleavedFrame{
Payload: c.tcpFrameBuffer.Next(),
}
err := frame.Read(c.br)
if err != nil {
readerDone <- err
return
}
channel := frame.Channel
isRTP := true
if (channel % 2) != 0 {
channel--
isRTP = false
}
trackID, ok := c.tracksByChannel[channel]
if !ok {
continue
}
if !isRTP {
c.OnPacketRTCP(c, trackID, frame.Payload)
}
}
}()
reportTicker := time.NewTicker(c.senderReportPeriod)
defer reportTicker.Stop()
for {
select {
case <-c.backgroundTerminate:
c.nconn.SetReadDeadline(time.Now())
<-readerDone
return fmt.Errorf("terminated")
case <-reportTicker.C:
now := time.Now()
for trackID, cct := range c.tracks {
sr := cct.rtcpSender.Report(now)
if sr != nil {
c.WritePacketRTCP(trackID, sr)
}
}
case err := <-readerDone:
return err
}
}
} }
func (c *Client) connOpen() error { func (c *Client) connOpen() error {
@@ -1164,7 +1038,7 @@ func (c *Client) doDescribe(u *base.URL) (Tracks, *base.URL, *base.Response, err
res.StatusCode >= base.StatusMovedPermanently && res.StatusCode >= base.StatusMovedPermanently &&
res.StatusCode <= base.StatusUseProxy && res.StatusCode <= base.StatusUseProxy &&
len(res.Header["Location"]) == 1 { len(res.Header["Location"]) == 1 {
c.reset(false) c.reset()
u, err := base.ParseURL(res.Header["Location"][0]) u, err := base.ParseURL(res.Header["Location"][0])
if err != nil { if err != nil {
@@ -1336,7 +1210,7 @@ func (c *Client) doSetup(
} }
proto := func() Transport { proto := func() Transport {
// protocol set by previous Setup() or switchProtocolIfTimeout() // protocol set by previous Setup() or trySwitchingProtocol()
if c.protocol != nil { if c.protocol != nil {
return *c.protocol return *c.protocol
} }
@@ -1676,7 +1550,7 @@ func (c *Client) doPlay(ra *headers.Range, isSwitchingProtocol bool) (*base.Resp
c.state = clientStatePlay c.state = clientStatePlay
c.lastRange = ra c.lastRange = ra
c.backgroundStart(isSwitchingProtocol) c.playRecordStart()
return res, nil return res, nil
} }
@@ -1719,7 +1593,7 @@ func (c *Client) doRecord() (*base.Response, error) {
c.state = clientStateRecord c.state = clientStateRecord
c.backgroundStart(false) c.playRecordStart()
return nil, nil return nil, nil
} }
@@ -1747,7 +1621,7 @@ func (c *Client) doPause() (*base.Response, error) {
return nil, err return nil, err
} }
c.backgroundClose(false) c.playRecordClose()
res, err := c.do(&base.Request{ res, err := c.do(&base.Request{
Method: base.Pause, Method: base.Pause,
@@ -1799,21 +1673,27 @@ func (c *Client) Seek(ra *headers.Range) (*base.Response, error) {
// ReadFrames starts reading frames. // ReadFrames starts reading frames.
func (c *Client) ReadFrames() error { func (c *Client) ReadFrames() error {
<-c.backgroundDone <-c.done
return c.backgroundErr return c.finalErr
} }
// WritePacketRTP writes a RTP packet. // WritePacketRTP writes a RTP packet.
func (c *Client) WritePacketRTP(trackID int, payload []byte) error { func (c *Client) WritePacketRTP(trackID int, payload []byte) error {
now := time.Now() select {
case <-c.done:
return c.finalErr
default:
}
c.writeMutex.RLock() c.writeMutex.RLock()
defer c.writeMutex.RUnlock() defer c.writeMutex.RUnlock()
if !c.writeFrameAllowed { if !c.writeFrameAllowed {
return c.backgroundErr return liberrors.ErrClientWriteNotAllowed{}
} }
now := time.Now()
if c.tracks[trackID].rtcpSender != nil { if c.tracks[trackID].rtcpSender != nil {
c.tracks[trackID].rtcpSender.ProcessPacketRTP(now, payload) c.tracks[trackID].rtcpSender.ProcessPacketRTP(now, payload)
} }
@@ -1838,15 +1718,21 @@ func (c *Client) WritePacketRTP(trackID int, payload []byte) error {
// WritePacketRTCP writes a RTCP packet. // WritePacketRTCP writes a RTCP packet.
func (c *Client) WritePacketRTCP(trackID int, payload []byte) error { func (c *Client) WritePacketRTCP(trackID int, payload []byte) error {
now := time.Now() select {
case <-c.done:
return c.finalErr
default:
}
c.writeMutex.RLock() c.writeMutex.RLock()
defer c.writeMutex.RUnlock() defer c.writeMutex.RUnlock()
if !c.writeFrameAllowed { if !c.writeFrameAllowed {
return c.backgroundErr return liberrors.ErrClientWriteNotAllowed{}
} }
now := time.Now()
if c.tracks[trackID].rtcpSender != nil { if c.tracks[trackID].rtcpSender != nil {
c.tracks[trackID].rtcpSender.ProcessPacketRTCP(now, payload) c.tracks[trackID].rtcpSender.ProcessPacketRTCP(now, payload)
} }

View File

@@ -1768,9 +1768,6 @@ func TestClientReadPause(t *testing.T) {
<-frameRecv <-frameRecv
_, err = c.Pause() _, err = c.Pause()
require.NoError(t, err) require.NoError(t, err)
<-done
c.ReadFrames()
firstFrame = int32(0) firstFrame = int32(0)
frameRecv = make(chan struct{}) frameRecv = make(chan struct{})
@@ -1778,12 +1775,6 @@ func TestClientReadPause(t *testing.T) {
_, err = c.Play(nil) _, err = c.Play(nil)
require.NoError(t, err) require.NoError(t, err)
done = make(chan struct{})
go func() {
defer close(done)
c.ReadFrames()
}()
<-frameRecv <-frameRecv
c.Close() c.Close()
<-done <-done

View File

@@ -163,14 +163,6 @@ func (e ErrClientTransportHeaderInterleavedIDsAlreadyUsed) Error() string {
return "interleaved IDs already used" return "interleaved IDs already used"
} }
// ErrClientNoUDPPacketsRecently is an error that can be returned by a client.
type ErrClientNoUDPPacketsRecently struct{}
// Error implements the error interface.
func (e ErrClientNoUDPPacketsRecently) Error() string {
return "no UDP packets received (maybe there's a firewall/NAT in between)"
}
// ErrClientUDPTimeout is an error that can be returned by a client. // ErrClientUDPTimeout is an error that can be returned by a client.
type ErrClientUDPTimeout struct{} type ErrClientUDPTimeout struct{}
@@ -196,3 +188,11 @@ type ErrClientRTPInfoInvalid struct {
func (e ErrClientRTPInfoInvalid) Error() string { func (e ErrClientRTPInfoInvalid) Error() string {
return fmt.Sprintf("invalid RTP-Info: %v", e.Err) return fmt.Sprintf("invalid RTP-Info: %v", e.Err)
} }
// ErrClientWriteNotAllowed is an error that can be returned by a client.
type ErrClientWriteNotAllowed struct{}
// Error implements the error interface.
func (e ErrClientWriteNotAllowed) Error() string {
return "writing is not allowed at the moment"
}