Repository: pion/webrtc
Pull request #1611 (2020-12-24 12:05:57)

Added SetReadDeadline to RTPReceiver
#### Added SetReadDeadline to RTPReceiver

Previously, Reading RTP or RTCP packets from a peer would block until a packet was received, or the connection was terminated. This change allows you to set a deadline, after which the read function will return a timeout error and you can get on with other things.

codecov[bot]: # [Codecov](https://codecov.io/gh/pion/webrtc/pull/1611?src=pr&el=h1) Report
> Merging [#1611](https://codecov.io/gh/pion/webrtc/pull/1611?src=pr&el=desc) (23b218d) into [master](https://codecov.io/gh/pion/webrtc/commit/5a3d41ed7791041c6fa9af00d58da51aa93a6670?el=desc) (5a3d41e) will **decrease** coverage by `4.79%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/pion/webrtc/pull/1611/graphs/tree.svg?width=650&height=150&src=pr&token=XOOuQo07I2)](https://codecov.io/gh/pion/webrtc/pull/1611?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1611      +/-   ##
==========================================
- Coverage   75.24%   70.45%   -4.80%     
==========================================
  Files          78       57      -21     
  Lines        5611     2112    -3499     
==========================================
- Hits         4222     1488    -2734     
+ Misses       1018      524     -494     
+ Partials      371      100     -271     
```

| Flag | Coverage Δ | |
|---|---|---|
| go | `?` | |
| wasm | `70.45% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/pion/webrtc/pull/1611?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [icetcp.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-aWNldGNwLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [sdpsemantics.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-c2Rwc2VtYW50aWNzLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [rtpcodec.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-cnRwY29kZWMuZ28=) | `0.00% <0.00%> (-89.48%)` | :arrow_down: |
| [icecandidatepair.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-aWNlY2FuZGlkYXRlcGFpci5nbw==) | `0.00% <0.00%> (-85.72%)` | :arrow_down: |
| [track\_local.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-dHJhY2tfbG9jYWwuZ28=) | `0.00% <0.00%> (-80.00%)` | :arrow_down: |
| [stats.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-c3RhdHMuZ28=) | `0.00% <0.00%> (-67.86%)` | :arrow_down: |
| [gathering\_complete\_promise.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-Z2F0aGVyaW5nX2NvbXBsZXRlX3Byb21pc2UuZ28=) | `60.00% <0.00%> (-40.00%)` | :arrow_down: |
| [dtlsrole.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-ZHRsc3JvbGUuZ28=) | `68.96% <0.00%> (-31.04%)` | :arrow_down: |
| [networktype.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-bmV0d29ya3R5cGUuZ28=) | `46.80% <0.00%> (-19.15%)` | :arrow_down: |
| [rtptransceiverdirection.go](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree#diff-cnRwdHJhbnNjZWl2ZXJkaXJlY3Rpb24uZ28=) | `68.75% <0.00%> (-18.75%)` | :arrow_down: |
| ... and [25 more](https://codecov.io/gh/pion/webrtc/pull/1611/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/webrtc/pull/1611?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/webrtc/pull/1611?src=pr&el=footer). Last update [5a3d41e...23b218d](https://codecov.io/gh/pion/webrtc/pull/1611?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

Sean-Der: @Threadnaught @mission-liao I pushed some changes, mind giving it a look over!

* Added the ability to do Deadline for RTP or RTCP (not both at once)
* Added a distinct test
Sean-Der: Merging! @Threadnaught and @mission-liao if you see any issues happy to revert :) 

Patch:
diff --git a/README.md b/README.md
index 71401248..728765ab 100644
--- a/README.md
+++ b/README.md
@@ -257,6 +257,7 @@ Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contribu
 * [Suzuki Takeo](https://github.com/BambooTuna)
 * [baiyufei](https://github.com/baiyufei)
 * [pascal-ace](https://github.com/pascal-ace)
+* [Threadnaught](https://github.com/Threadnaught)
 
 ### License
 MIT License - see [LICENSE](LICENSE) for full text
diff --git a/go.mod b/go.mod
index b616a1cf..a42b0fd8 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
 	github.com/pion/sctp v1.7.11
 	github.com/pion/sdp/v3 v3.0.3
 	github.com/pion/srtp/v2 v2.0.0
-	github.com/pion/transport v0.12.0
+	github.com/pion/transport v0.12.1
 	github.com/sclevine/agouti v3.0.0+incompatible
 	github.com/stretchr/testify v1.6.1
 	golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7
diff --git a/go.sum b/go.sum
index 6955e33c..f736c221 100644
--- a/go.sum
+++ b/go.sum
@@ -66,6 +66,8 @@ github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJ
 github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
 github.com/pion/transport v0.12.0 h1:UFmOBBZkTZ3LgvLRf/NGrfWdZEubcU6zkLU3PsA9YvU=
 github.com/pion/transport v0.12.0/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
+github.com/pion/transport v0.12.1 h1:6v8lxQGVZpwSICEZjhl/CCv6aErINZlrm3O5ncFXj/c=
+github.com/pion/transport v0.12.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
 github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
 github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
 github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
diff --git a/rtpreceiver.go b/rtpreceiver.go
index 4d64ecac..8f6c06aa 100644
--- a/rtpreceiver.go
+++ b/rtpreceiver.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"sync"
+	"time"
 
 	"github.com/pion/interceptor"
 	"github.com/pion/rtcp"
@@ -322,3 +323,30 @@ func (r *RTPReceiver) streamsForSSRC(ssrc SSRC, streamInfo interceptor.StreamInf
 
 	return rtpReadStream, rtpInterceptor, rtcpReadStream, rtcpInterceptor, nil
 }
+
+// SetReadDeadline sets the max amount of time the RTCP stream will block before returning. 0 is forever.
+func (r *RTPReceiver) SetReadDeadline(t time.Time) error {
+	r.mu.RLock()
+	defer r.mu.RUnlock()
+
+	for i := range r.tracks {
+		if err := r.tracks[i].rtcpReadStream.SetReadDeadline(t); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// setRTPReadDeadline sets the max amount of time the RTP stream will block before returning. 0 is forever.
+// This should be fired by calling SetReadDeadline on the TrackRemote
+func (r *RTPReceiver) setRTPReadDeadline(t time.Time) error {
+	r.mu.RLock()
+	defer r.mu.RUnlock()
+
+	for i := range r.tracks {
+		if err := r.tracks[i].rtpReadStream.SetReadDeadline(t); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/track_remote.go b/track_remote.go
index c150a3a7..6cc72ef6 100644
--- a/track_remote.go
+++ b/track_remote.go
@@ -4,6 +4,7 @@ package webrtc
 
 import (
 	"sync"
+	"time"
 
 	"github.com/pion/interceptor"
 	"github.com/pion/rtp"
@@ -175,3 +176,8 @@ func (t *TrackRemote) peek(b []byte) (n int, a interceptor.Attributes, err error
 	t.mu.Unlock()
 	return
 }
+
+// SetReadDeadline sets the max amount of time the RTP stream will block before returning. 0 is forever.
+func (t *TrackRemote) SetReadDeadline(deadline time.Time) error {
+	return t.receiver.setRTPReadDeadline(deadline)
+}


Test patch:
diff --git a/rtpreceiver_test.go b/rtpreceiver_test.go
new file mode 100644
index 00000000..3864dc0f
--- /dev/null
+++ b/rtpreceiver_test.go
@@ -0,0 +1,73 @@
+// +build !js
+
+package webrtc
+
+import (
+	"context"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/pion/transport/packetio"
+	"github.com/pion/transport/test"
+	"github.com/pion/webrtc/v3/pkg/media"
+	"github.com/stretchr/testify/assert"
+)
+
+// Assert that SetReadDeadline works as expected
+// This test uses VNet since we must have zero loss
+func Test_RTPSender_SetReadDeadline(t *testing.T) {
+	lim := test.TimeOut(time.Second * 30)
+	defer lim.Stop()
+
+	report := test.CheckRoutines(t)
+	defer report()
+
+	sender, receiver, wan := createVNetPair(t)
+
+	track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
+	assert.NoError(t, err)
+
+	_, err = sender.AddTrack(track)
+	assert.NoError(t, err)
+
+	seenPacket, seenPacketCancel := context.WithCancel(context.Background())
+	receiver.OnTrack(func(trackRemote *TrackRemote, r *RTPReceiver) {
+		// Set Deadline for both RTP and RTCP Stream
+		assert.NoError(t, r.SetReadDeadline(time.Now().Add(time.Second)))
+		assert.NoError(t, trackRemote.SetReadDeadline(time.Now().Add(time.Second)))
+
+		// First call will not error because we cache for probing
+		_, _, readErr := trackRemote.ReadRTP()
+		assert.NoError(t, readErr)
+
+		_, _, readErr = trackRemote.ReadRTP()
+		assert.Error(t, readErr, packetio.ErrTimeout)
+
+		_, _, readErr = r.ReadRTCP()
+		assert.Error(t, readErr, packetio.ErrTimeout)
+
+		seenPacketCancel()
+	})
+
+	var peerConnectionsConnected sync.WaitGroup
+	peerConnectionsConnected.Add(2)
+
+	hdlr := func(p PeerConnectionState) {
+		if p == PeerConnectionStateConnected {
+			peerConnectionsConnected.Done()
+		}
+	}
+	sender.OnConnectionStateChange(hdlr)
+	receiver.OnConnectionStateChange(hdlr)
+
+	assert.NoError(t, signalPair(sender, receiver))
+
+	peerConnectionsConnected.Wait()
+	assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0xAA}, Duration: time.Second}))
+
+	<-seenPacket.Done()
+	assert.NoError(t, wan.Stop())
+	assert.NoError(t, sender.Close())
+	assert.NoError(t, receiver.Close())
+}
diff --git a/vnet_test.go b/vnet_test.go
index 07d1c8f8..14e8372c 100644
--- a/vnet_test.go
+++ b/vnet_test.go
@@ -44,10 +44,14 @@ func createVNetPair(t *testing.T) (*PeerConnection, *PeerConnection, *vnet.Route
 	// Start the virtual network by calling Start() on the root router
 	assert.NoError(t, wan.Start())
 
-	offerPeerConnection, err := NewAPI(WithSettingEngine(offerSettingEngine)).NewPeerConnection(Configuration{})
+	offerMediaEngine := &MediaEngine{}
+	assert.NoError(t, offerMediaEngine.RegisterDefaultCodecs())
+	offerPeerConnection, err := NewAPI(WithSettingEngine(offerSettingEngine), WithMediaEngine(offerMediaEngine)).NewPeerConnection(Configuration{})
 	assert.NoError(t, err)
 
-	answerPeerConnection, err := NewAPI(WithSettingEngine(answerSettingEngine)).NewPeerConnection(Configuration{})
+	answerMediaEngine := &MediaEngine{}
+	assert.NoError(t, answerMediaEngine.RegisterDefaultCodecs())
+	answerPeerConnection, err := NewAPI(WithSettingEngine(answerSettingEngine), WithMediaEngine(answerMediaEngine)).NewPeerConnection(Configuration{})
 	assert.NoError(t, err)
 
 	return offerPeerConnection, answerPeerConnection, wan