From 5f54b688995a424fbe5764a4e753a7deea62d04e Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Sun, 9 Feb 2020 21:06:40 -0800 Subject: [PATCH] Add examples/play-from-disk-renegotation Resolves #207 --- .../play-from-disk-renegotation/README.md | 29 ++++ .../play-from-disk-renegotation/index.html | 71 +++++++++ examples/play-from-disk-renegotation/main.go | 136 ++++++++++++++++++ rtptransceiver.go | 3 + 4 files changed, 239 insertions(+) create mode 100644 examples/play-from-disk-renegotation/README.md create mode 100644 examples/play-from-disk-renegotation/index.html create mode 100644 examples/play-from-disk-renegotation/main.go 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: