Add reconnect logic to RTSP client

This commit is contained in:
Alexey Khit
2023-04-16 13:57:16 +03:00
parent 4b4deaaaf2
commit a5c4854aeb
5 changed files with 214 additions and 205 deletions
+24 -139
View File
@@ -31,10 +31,6 @@ func (c *Conn) Dial() (err error) {
c.URL.Host += ":554"
}
// remove UserInfo from URL
c.auth = tcp.NewAuth(c.URL.User)
c.URL.User = nil
c.conn, err = net.DialTimeout("tcp", c.URL.Host, time.Second*5)
if err != nil {
return
@@ -56,55 +52,24 @@ func (c *Conn) Dial() (err error) {
c.conn = tlsConn
}
// remove UserInfo from URL
c.auth = tcp.NewAuth(c.URL.User)
c.URL.User = nil
c.reader = bufio.NewReader(c.conn)
c.session = ""
c.state = StateConn
return nil
}
// Request sends only Request
func (c *Conn) Request(req *tcp.Request) error {
if req.Proto == "" {
req.Proto = ProtoRTSP
}
if req.Header == nil {
req.Header = make(map[string][]string)
}
c.sequence++
// important to send case sensitive CSeq
// https://github.com/AlexxIT/go2rtc/issues/7
req.Header["CSeq"] = []string{strconv.Itoa(c.sequence)}
c.auth.Write(req)
if c.Session != "" {
req.Header.Set("Session", c.Session)
}
if req.Body != nil {
val := strconv.Itoa(len(req.Body))
req.Header.Set("Content-Length", val)
}
c.Fire(req)
return req.Write(c.conn)
}
// Do send Request and receive and process Response
// Do send WriteRequest and receive and process WriteResponse
func (c *Conn) Do(req *tcp.Request) (*tcp.Response, error) {
// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
if err := c.conn.SetDeadline(time.Now().Add(Timeout)); err != nil {
if err := c.WriteRequest(req); err != nil {
return nil, err
}
if err := c.Request(req); err != nil {
return nil, err
}
res, err := tcp.ReadResponse(c.reader)
res, err := c.ReadResponse()
if err != nil {
return nil, err
}
@@ -134,40 +99,6 @@ func (c *Conn) Do(req *tcp.Request) (*tcp.Response, error) {
return res, nil
}
func (c *Conn) Response(res *tcp.Response) error {
if res.Proto == "" {
res.Proto = ProtoRTSP
}
if res.Status == "" {
res.Status = "200 OK"
}
if res.Header == nil {
res.Header = make(map[string][]string)
}
if res.Request != nil && res.Request.Header != nil {
seq := res.Request.Header.Get("CSeq")
if seq != "" {
res.Header.Set("CSeq", seq)
}
}
if c.Session != "" {
res.Header.Set("Session", c.Session)
}
if res.Body != nil {
val := strconv.Itoa(len(res.Body))
res.Header.Set("Content-Length", val)
}
c.Fire(res)
return res.Write(c.conn)
}
func (c *Conn) Options() error {
req := &tcp.Request{Method: MethodOptions, URL: c.URL}
@@ -219,11 +150,18 @@ func (c *Conn) Describe() error {
}
}
c.Medias, err = UnmarshalSDP(res.Body)
medias, err := UnmarshalSDP(res.Body)
if err != nil {
return err
}
// TODO: rewrite more smart
if c.Medias == nil {
c.Medias = medias
} else if len(c.Medias) > len(medias) {
c.Medias = c.Medias[:len(medias)]
}
c.mode = core.ModeActiveProducer
return nil
@@ -250,28 +188,7 @@ func (c *Conn) Announce() (err error) {
return
}
func (c *Conn) Setup() error {
for _, media := range c.Medias {
_, err := c.SetupMedia(media, true)
if err != nil {
return err
}
}
return nil
}
func (c *Conn) SetupMedia(media *core.Media, first bool) (byte, error) {
// TODO: rewrite recoonection and first flag
if first {
c.stateMu.Lock()
defer c.stateMu.Unlock()
}
if c.state != StateConn && c.state != StateSetup {
return 0, fmt.Errorf("RTSP SETUP from wrong state: %s", c.state)
}
func (c *Conn) SetupMedia(media *core.Media) (byte, error) {
var transport string
// try to use media position as channel number
@@ -311,39 +228,28 @@ func (c *Conn) SetupMedia(media *core.Media, first bool) (byte, error) {
},
}
var res *tcp.Response
res, err = c.Do(req)
res, err := c.Do(req)
if err != nil {
// some Dahua/Amcrest cameras fail here because two simultaneous
// backchannel connections
if c.Backchannel {
_ = c.conn.Close()
c.Backchannel = false
if err := c.Dial(); err != nil {
if err = c.Reconnect(); err != nil {
return 0, err
}
if err := c.Describe(); err != nil {
return 0, err
}
for _, m := range c.Medias {
if m.Equal(media) {
return c.SetupMedia(m, false)
}
}
return c.SetupMedia(media)
}
return 0, err
}
if c.Session == "" {
if c.session == "" {
// Session: 216525287999;timeout=60
if s := res.Header.Get("Session"); s != "" {
if j := strings.IndexByte(s, ';'); j > 0 {
s = s[:j]
}
c.Session = s
c.session = s
}
}
@@ -361,8 +267,6 @@ func (c *Conn) SetupMedia(media *core.Media, first bool) (byte, error) {
}
}
c.state = StateSetup
channel := core.Between(transport, "interleaved=", "-")
i, err := strconv.Atoi(channel)
if err != nil {
@@ -373,36 +277,17 @@ func (c *Conn) SetupMedia(media *core.Media, first bool) (byte, error) {
}
func (c *Conn) Play() (err error) {
c.stateMu.Lock()
defer c.stateMu.Unlock()
if c.state != StateSetup {
return fmt.Errorf("RTSP PLAY from wrong state: %s", c.state)
}
req := &tcp.Request{Method: MethodPlay, URL: c.URL}
if err = c.Request(req); err == nil {
c.state = StatePlay
}
return
return c.WriteRequest(req)
}
func (c *Conn) Teardown() (err error) {
// allow TEARDOWN from any state (ex. ANNOUNCE > SETUP)
req := &tcp.Request{Method: MethodTeardown, URL: c.URL}
return c.Request(req)
return c.WriteRequest(req)
}
func (c *Conn) Close() error {
c.stateMu.Lock()
defer c.stateMu.Unlock()
if c.state == StateNone {
return nil
}
_ = c.Teardown()
c.state = StateNone
return c.conn.Close()
}