Repository: bluenviron/gortsplib
Pull request #775 (2025-05-04 13:29:51)

client: enable keepalives in most cases

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/bluenviron/gortsplib/pull/775?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bluenviron) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 82.28%. Comparing base [(`2cbdc2a`)](https://app.codecov.io/gh/bluenviron/gortsplib/commit/2cbdc2a0b7a8ee11ba00d185ced10c2b34de4eec?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bluenviron) to head [(`bd83f15`)](https://app.codecov.io/gh/bluenviron/gortsplib/commit/bd83f1508fe15fd5254c74a3c8e19d85b225e706?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bluenviron).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #775   +/-   ##
=======================================
  Coverage   82.28%   82.28%           
=======================================
  Files         117      117           
  Lines       12639    12639           
=======================================
  Hits        10400    10400           
  Misses       1825     1825           
  Partials      414      414           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/bluenviron/gortsplib/pull/775?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bluenviron).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bluenviron).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/client.go b/client.go
index 0879322d..5ccf445b 100644
--- a/client.go
+++ b/client.go
@@ -889,7 +889,8 @@ func (c *Client) startTransportRoutines() {
 		c.tcpBuffer = make([]byte, c.MaxPacketSize+4)
 	}
 
-	if c.state == clientStatePlay {
+	// always enable keepalives unless we are recording with TCP
+	if c.state == clientStatePlay || *c.effectiveTransport != TransportTCP {
 		c.keepAliveTimer = time.NewTimer(c.keepAlivePeriod)
 	}
 


Test patch:
diff --git a/client_play_test.go b/client_play_test.go
index 28864b68..97a1ff41 100644
--- a/client_play_test.go
+++ b/client_play_test.go
@@ -432,7 +432,8 @@ func TestClientPlay(t *testing.T) {
 				require.NoError(t, err2)
 
 				for i := 0; i < 2; i++ {
-					// server -> client (RTP)
+					// server -> client RTP packet
+
 					switch transport {
 					case "udp":
 						_, err2 = l1s[i].WriteTo(testRTPPacketMarshaled, &net.UDPAddr{
@@ -456,7 +457,8 @@ func TestClientPlay(t *testing.T) {
 						require.NoError(t, err2)
 					}
 
-					// client -> server (RTCP)
+					// client -> server RTCP packet
+
 					switch transport {
 					case "udp", "multicast":
 						// skip firewall opening
@@ -2874,7 +2876,6 @@ func TestClientPlayDifferentSource(t *testing.T) {
 		})
 		require.NoError(t, err2)
 
-		// server -> client (RTP)
 		_, err2 = l1.WriteTo(testRTPPacketMarshaled, &net.UDPAddr{
 			IP:   net.ParseIP("127.0.0.1"),
 			Port: th.ClientPorts[0],
diff --git a/client_record_test.go b/client_record_test.go
index e5408ac7..24e9023d 100644
--- a/client_record_test.go
+++ b/client_record_test.go
@@ -235,6 +235,10 @@ func TestClientRecord(t *testing.T) {
 					StatusCode: base.StatusOK,
 					Header: base.Header{
 						"Transport": th.Marshal(),
+						"Session": headers.Session{
+							Session: "ABCDE",
+							Timeout: uintPtr(1),
+						}.Marshal(),
 					},
 				})
 				require.NoError(t, err2)
@@ -249,30 +253,49 @@ func TestClientRecord(t *testing.T) {
 				})
 				require.NoError(t, err2)
 
-				// client -> server (RTP)
+				var pl []byte
+
+				// client -> server RTP packet
+
 				if transport == "udp" {
 					buf := make([]byte, 2048)
 					var n int
 					n, _, err2 = l1.ReadFrom(buf)
 					require.NoError(t, err2)
-
-					var pkt rtp.Packet
-					err2 = pkt.Unmarshal(buf[:n])
-					require.NoError(t, err2)
-					require.Equal(t, testRTPPacket, pkt)
+					pl = buf[:n]
 				} else {
 					var f *base.InterleavedFrame
 					f, err2 = conn.ReadInterleavedFrame()
 					require.NoError(t, err2)
 					require.Equal(t, 0, f.Channel)
+					pl = f.Payload
+				}
 
-					var pkt rtp.Packet
-					err2 = pkt.Unmarshal(f.Payload)
-					require.NoError(t, err2)
-					require.Equal(t, testRTPPacket, pkt)
+				var pkt rtp.Packet
+				err2 = pkt.Unmarshal(pl)
+				require.NoError(t, err2)
+				require.Equal(t, testRTPPacket, pkt)
+
+				// client -> server keepalive (UDP only)
+
+				if transport == "udp" {
+					recv := make(chan struct{})
+					go func() {
+						defer close(recv)
+						req, err2 = conn.ReadRequest()
+						require.NoError(t, err2)
+						require.Equal(t, base.Options, req.Method)
+					}()
+
+					select {
+					case <-recv:
+					case <-time.After(2 * time.Second):
+						t.Errorf("should not happen")
+					}
 				}
 
-				// server -> client (RTCP)
+				// server -> client RTCP packet
+
 				if transport == "udp" {
 					_, err2 = l2.WriteTo(testRTCPPacketMarshaled, &net.UDPAddr{
 						IP:   net.ParseIP("127.0.0.1"),
diff --git a/server_play_test.go b/server_play_test.go
index e87ba4d0..7a3cf7bb 100644
--- a/server_play_test.go
+++ b/server_play_test.go
@@ -848,6 +848,7 @@ func TestServerPlay(t *testing.T) {
 			doPlay(t, conn, "rtsp://"+listenIP+":8554/teststream", session)
 
 			// server -> client (direct)
+
 			switch transport {
 			case "udp":
 				buf := make([]byte, 2048)
@@ -874,6 +875,7 @@ func TestServerPlay(t *testing.T) {
 			}
 
 			// server -> client (through stream)
+
 			if transport == "udp" || transport == "multicast" {
 				buf := make([]byte, 2048)
 				var n int
@@ -904,7 +906,8 @@ func TestServerPlay(t *testing.T) {
 				}
 			}
 
-			// client -> server (RTCP)
+			// client -> server RTCP packet
+
 			switch transport {
 			case "udp":
 				_, err = l2.WriteTo(testRTCPPacketMarshaled, &net.UDPAddr{
diff --git a/server_record_test.go b/server_record_test.go
index 98885171..2ab9b89f 100644
--- a/server_record_test.go
+++ b/server_record_test.go
@@ -728,6 +728,7 @@ func TestServerRecord(t *testing.T) {
 				}
 
 				// server -> client (direct)
+
 				if transport == "udp" {
 					buf := make([]byte, 2048)
 					var n int
@@ -742,7 +743,8 @@ func TestServerRecord(t *testing.T) {
 					require.Equal(t, testRTCPPacketMarshaled, f.Payload)
 				}
 
-				// client -> server
+				// client -> server RTP+RTCP packets
+
 				if transport == "udp" {
 					_, err = l1s[i].WriteTo(testRTPPacketMarshaled, &net.UDPAddr{
 						IP:   net.ParseIP("127.0.0.1"),
@@ -771,7 +773,8 @@ func TestServerRecord(t *testing.T) {
 			}
 
 			for i := 0; i < 2; i++ {
-				// server -> client (RTCP)
+				// server -> client RTCP packet
+
 				if transport == "udp" {
 					buf := make([]byte, 2048)
 					n, _, err := l2s[i].ReadFrom(buf)