diff --git a/examples/play-from-disk-renegotation/README.md b/examples/play-from-disk-renegotation/README.md
new file mode 100644
index 00000000..32ab6b1b
--- /dev/null
+++ b/examples/play-from-disk-renegotation/README.md
@@ -0,0 +1,29 @@
+# play-from-disk-renegotiation
+play-from-disk-renegotiation demonstrates Pion WebRTC's renegotiation abilities.
+
+For a simpler example of playing a file from disk we also have [examples/play-from-disk](examples/play-from-disk)
+
+## Instructions
+
+### Download play-from-disk-renegotiation
+This example requires you to clone the repo since it is serving static HTML.
+
+```
+mkdir -p $GOPATH/src/github.com/pion
+cd $GOPATH/src/github.com/pion
+git clone https://github.com/pion/webrtc.git
+cd webrtc/examples/play-from-disk-renegotiation
+```
+
+### Create IVF named `output.ivf` that contains a VP8 track
+```
+ffmpeg -i $INPUT_FILE -g 30 output.ivf
+```
+
+### Run play-from-disk-renegotiation
+The `output.ivf` you created should be in the same directory as `play-from-disk-renegotiation`. Execute `go run *.go`
+
+### Open the Web UI
+Open [http://localhost:8080](http://localhost:8080) and you should have a `Add Track` and `Remove Track` button. Press these to add as many tracks as you want, or to remove as many as you wish.
+
+Congrats, you have used Pion WebRTC! Now start building something cool
diff --git a/examples/play-from-disk-renegotation/index.html b/examples/play-from-disk-renegotation/index.html
new file mode 100644
index 00000000..c6ffd726
--- /dev/null
+++ b/examples/play-from-disk-renegotation/index.html
@@ -0,0 +1,71 @@
+
+
+ play-from-disk-renegotation
+
+
+
+
+
+
+
+
Video
+
+
+
Logs
+
+
+
+
+
diff --git a/examples/play-from-disk-renegotation/main.go b/examples/play-from-disk-renegotation/main.go
new file mode 100644
index 00000000..18287d5a
--- /dev/null
+++ b/examples/play-from-disk-renegotation/main.go
@@ -0,0 +1,136 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "math/rand"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/pion/webrtc/v2"
+ "github.com/pion/webrtc/v2/pkg/media"
+ "github.com/pion/webrtc/v2/pkg/media/ivfreader"
+)
+
+var peerConnection *webrtc.PeerConnection //nolint
+
+// doSignaling exchanges all state of the local PeerConnection and is called
+// every time a video is added or removed
+func doSignaling(w http.ResponseWriter, r *http.Request) {
+ var offer webrtc.SessionDescription
+ if err := json.NewDecoder(r.Body).Decode(&offer); err != nil {
+ panic(err)
+ }
+
+ if err := peerConnection.SetRemoteDescription(offer); err != nil {
+ panic(err)
+ }
+
+ answer, err := peerConnection.CreateAnswer(nil)
+ if err != nil {
+ panic(err)
+ } else if err = peerConnection.SetLocalDescription(answer); err != nil {
+ panic(err)
+ }
+
+ response, err := json.Marshal(answer)
+ if err != nil {
+ panic(err)
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ if _, err := w.Write(response); err != nil {
+ panic(err)
+ }
+}
+
+// Add a single video track
+func createPeerConnection(w http.ResponseWriter, r *http.Request) {
+ if peerConnection.ConnectionState() != webrtc.PeerConnectionStateNew {
+ panic(fmt.Sprintf("createPeerConnection called in non-new state (%s)", peerConnection.ConnectionState()))
+ }
+
+ doSignaling(w, r)
+ fmt.Println("PeerConnection has been created")
+}
+
+// Add a single video track
+func addVideo(w http.ResponseWriter, r *http.Request) {
+ videoTrack, err := peerConnection.NewTrack(webrtc.DefaultPayloadTypeVP8, rand.Uint32(), fmt.Sprintf("video-%d", rand.Uint32()), fmt.Sprintf("video-%d", rand.Uint32()))
+ if err != nil {
+ panic(err)
+ }
+ if _, err = peerConnection.AddTrack(videoTrack); err != nil {
+ panic(err)
+ }
+
+ go writeVideoToTrack(videoTrack)
+ doSignaling(w, r)
+ fmt.Println("Video track has been added")
+}
+
+// Remove a single sender
+func removeVideo(w http.ResponseWriter, r *http.Request) {
+ if senders := peerConnection.GetSenders(); len(senders) != 0 {
+ if err := peerConnection.RemoveTrack(senders[0]); err != nil {
+ panic(err)
+ }
+ }
+
+ doSignaling(w, r)
+ fmt.Println("Video track has been removed")
+}
+
+func main() {
+ rand.Seed(time.Now().UTC().UnixNano())
+
+ var err error
+ if peerConnection, err = webrtc.NewPeerConnection(webrtc.Configuration{}); err != nil {
+ panic(err)
+ }
+
+ // Set the handler for ICE connection state
+ // This will notify you when the peer has connected/disconnected
+ peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
+ fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
+ })
+
+ http.Handle("/", http.FileServer(http.Dir(".")))
+ http.HandleFunc("/createPeerConnection", createPeerConnection)
+ http.HandleFunc("/addVideo", addVideo)
+ http.HandleFunc("/removeVideo", removeVideo)
+ panic(http.ListenAndServe(":8080", nil))
+}
+
+// Read a video file from disk and write it to a webrtc.Track
+// When the video has been completely read this exits without error
+func writeVideoToTrack(t *webrtc.Track) {
+ // Open a IVF file and start reading using our IVFReader
+ file, err := os.Open("output.ivf")
+ if err != nil {
+ panic(err)
+ }
+
+ ivf, header, err := ivfreader.NewWith(file)
+ if err != nil {
+ panic(err)
+ }
+
+ // Send our video file frame at a time. Pace our sending so we send it at the same speed it should be played back as.
+ // This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
+ sleepTime := time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000)
+ for {
+ frame, _, err := ivf.ParseNextFrame()
+ if err != nil {
+ fmt.Printf("Finish writing video track: %s ", err)
+ return
+ }
+
+ time.Sleep(sleepTime)
+ if err = t.WriteSample(media.Sample{Data: frame, Samples: 90000}); err != nil {
+ fmt.Printf("Finish writing video track: %s ", err)
+ return
+ }
+ }
+}
diff --git a/rtptransceiver.go b/rtptransceiver.go
index 4b71cc7e..0913baec 100644
--- a/rtptransceiver.go
+++ b/rtptransceiver.go
@@ -71,6 +71,9 @@ func (t *RTPTransceiver) setDirection(d RTPTransceiverDirection) {
func (t *RTPTransceiver) setSendingTrack(track *Track) error {
t.Sender().track = track
+ if track == nil {
+ t.setSender(nil)
+ }
switch {
case track != nil && t.Direction() == RTPTransceiverDirectionRecvonly: