Skip to content

Commit 7124c22

Browse files
Sean-DerhugoArregui
authored andcommitted
Add pion-to-pion-trickle
Demonstrate how to use Trickle ICE when communicating pion-to-pion
1 parent 7c18bbc commit 7124c22

File tree

4 files changed

+335
-0
lines changed

4 files changed

+335
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# pion-to-pion-trickle
2+
pion-to-pion-trickle is an example of two pion instances communicating directly!
3+
This example uses Trickle ICE, this allows communication to begin before gathering
4+
has completed.
5+
6+
See `pion-to-pion` example of a non-Trickle version of this.
7+
8+
The SDP offer and answer are exchanged automatically over HTTP.
9+
The `answer` side acts like a HTTP server and should therefore be ran first.
10+
11+
## Instructions
12+
First run `answer`:
13+
```sh
14+
go install github.com/pion/webrtc/examples/pion-to-pion/answer
15+
answer
16+
```
17+
Next, run `offer`:
18+
```sh
19+
go install github.com/pion/webrtc/examples/pion-to-pion/offer
20+
offer
21+
```
22+
23+
You should see them connect and start to exchange messages.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"flag"
7+
"fmt"
8+
"io/ioutil"
9+
"net/http"
10+
"time"
11+
12+
"github.com/pion/webrtc/v2"
13+
14+
"github.com/pion/webrtc/v2/examples/internal/signal"
15+
)
16+
17+
func main() {
18+
offerAddr := flag.String("offer-address", "localhost:50000", "Address that the Offer HTTP server is hosted on.")
19+
answerAddr := flag.String("answer-address", ":60000", "Address that the Answer HTTP server is hosted on.")
20+
flag.Parse()
21+
22+
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
23+
24+
// Prepare the configuration
25+
config := webrtc.Configuration{
26+
ICEServers: []webrtc.ICEServer{
27+
{
28+
URLs: []string{"stun:stun.l.google.com:19302"},
29+
},
30+
},
31+
}
32+
33+
// Create a new API with Trickle ICE enabled
34+
// This SettingEngine allows non-standard WebRTC behavior
35+
s := webrtc.SettingEngine{}
36+
s.SetTrickle(true)
37+
api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
38+
39+
// Create a new RTCPeerConnection
40+
peerConnection, err := api.NewPeerConnection(config)
41+
if err != nil {
42+
panic(err)
43+
}
44+
45+
// When an ICE candidate is available send to the other Pion instance
46+
// the other Pion instance will add this candidate by calling AddICECandidate
47+
peerConnection.OnICECandidate(func(c *webrtc.ICECandidate) {
48+
if c == nil {
49+
return
50+
}
51+
52+
payload := []byte(c.ToJSON().Candidate)
53+
resp, onICECandidateErr := http.Post(fmt.Sprintf("http://%s/candidate", *answerAddr), "application/json; charset=utf-8", bytes.NewReader(payload))
54+
if onICECandidateErr != nil {
55+
panic(onICECandidateErr)
56+
}
57+
defer func() {
58+
closeErr := resp.Body.Close()
59+
if closeErr != nil {
60+
panic(closeErr)
61+
}
62+
}()
63+
})
64+
65+
// A HTTP handler that allows the other Pion instance to send us ICE candidates
66+
// This allows us to add ICE candidates faster, we don't have to wait for STUN or TURN
67+
// candidates which may be slower
68+
http.HandleFunc("/candidate", func(w http.ResponseWriter, r *http.Request) {
69+
candidate, candidateErr := ioutil.ReadAll(r.Body)
70+
if candidateErr != nil {
71+
panic(candidateErr)
72+
}
73+
if candidateErr := peerConnection.AddICECandidate(webrtc.ICECandidateInit{Candidate: string(candidate)}); candidateErr != nil {
74+
panic(candidateErr)
75+
}
76+
})
77+
78+
// A HTTP handler that processes a SessionDescription given to us from the other Pion process
79+
http.HandleFunc("/sdp", func(w http.ResponseWriter, r *http.Request) {
80+
sdp := webrtc.SessionDescription{}
81+
if err := json.NewDecoder(r.Body).Decode(&sdp); err != nil {
82+
panic(err)
83+
}
84+
85+
if err := peerConnection.SetRemoteDescription(sdp); err != nil {
86+
panic(err)
87+
}
88+
89+
// Create an answer to send to the other process
90+
answer, err := peerConnection.CreateAnswer(nil)
91+
if err != nil {
92+
panic(err)
93+
}
94+
95+
// Sets the LocalDescription, and starts our UDP listeners
96+
err = peerConnection.SetLocalDescription(answer)
97+
if err != nil {
98+
panic(err)
99+
}
100+
101+
// Send our answer to the HTTP server listening in the other process
102+
payload, err := json.Marshal(answer)
103+
if err != nil {
104+
panic(err)
105+
}
106+
resp, err := http.Post(fmt.Sprintf("http://%s/sdp", *offerAddr), "application/json; charset=utf-8", bytes.NewReader(payload))
107+
if err != nil {
108+
panic(err)
109+
}
110+
defer func() {
111+
closeErr := resp.Body.Close()
112+
if closeErr != nil {
113+
panic(closeErr)
114+
}
115+
}()
116+
})
117+
// Start HTTP server that accepts requests from the offer process
118+
go func() { panic(http.ListenAndServe(*answerAddr, nil)) }()
119+
120+
// Set the handler for ICE connection state
121+
// This will notify you when the peer has connected/disconnected
122+
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
123+
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
124+
})
125+
126+
// Register data channel creation handling
127+
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
128+
fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID())
129+
130+
// Register channel opening handling
131+
d.OnOpen(func() {
132+
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", d.Label(), d.ID())
133+
134+
for range time.NewTicker(5 * time.Second).C {
135+
message := signal.RandSeq(15)
136+
fmt.Printf("Sending '%s'\n", message)
137+
138+
// Send the message as text
139+
sendTextErr := d.SendText(message)
140+
if sendTextErr != nil {
141+
panic(sendTextErr)
142+
}
143+
}
144+
})
145+
146+
// Register text message handling
147+
d.OnMessage(func(msg webrtc.DataChannelMessage) {
148+
fmt.Printf("Message from DataChannel '%s': '%s'\n", d.Label(), string(msg.Data))
149+
})
150+
})
151+
152+
// Block forever
153+
select {}
154+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"flag"
7+
"fmt"
8+
"io/ioutil"
9+
"net/http"
10+
"time"
11+
12+
"github.com/pion/webrtc/v2"
13+
14+
"github.com/pion/webrtc/v2/examples/internal/signal"
15+
)
16+
17+
func main() {
18+
offerAddr := flag.String("offer-address", ":50000", "Address that the Offer HTTP server is hosted on.")
19+
answerAddr := flag.String("answer-address", "127.0.0.1:60000", "Address that the Answer HTTP server is hosted on.")
20+
flag.Parse()
21+
22+
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
23+
24+
// Prepare the configuration
25+
config := webrtc.Configuration{
26+
ICEServers: []webrtc.ICEServer{
27+
{
28+
URLs: []string{"stun:stun.l.google.com:19302"},
29+
},
30+
},
31+
}
32+
33+
// Create a new API with Trickle ICE enabled
34+
// This SettingEngine allows non-standard WebRTC behavior
35+
s := webrtc.SettingEngine{}
36+
s.SetTrickle(true)
37+
api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
38+
39+
// Create a new RTCPeerConnection
40+
peerConnection, err := api.NewPeerConnection(config)
41+
if err != nil {
42+
panic(err)
43+
}
44+
45+
// When an ICE candidate is available send to the other Pion instance
46+
// the other Pion instance will add this candidate by calling AddICECandidate
47+
peerConnection.OnICECandidate(func(c *webrtc.ICECandidate) {
48+
if c == nil {
49+
return
50+
}
51+
52+
payload := []byte(c.ToJSON().Candidate)
53+
resp, onICECandidateErr := http.Post(fmt.Sprintf("http://%s/candidate", *answerAddr), "application/json; charset=utf-8", bytes.NewReader(payload))
54+
if onICECandidateErr != nil {
55+
panic(onICECandidateErr)
56+
}
57+
defer func() {
58+
closeErr := resp.Body.Close()
59+
if closeErr != nil {
60+
panic(closeErr)
61+
}
62+
}()
63+
})
64+
65+
// A HTTP handler that allows the other Pion instance to send us ICE candidates
66+
// This allows us to add ICE candidates faster, we don't have to wait for STUN or TURN
67+
// candidates which may be slower
68+
http.HandleFunc("/candidate", func(w http.ResponseWriter, r *http.Request) {
69+
candidate, candidateErr := ioutil.ReadAll(r.Body)
70+
if candidateErr != nil {
71+
panic(candidateErr)
72+
}
73+
if candidateErr := peerConnection.AddICECandidate(webrtc.ICECandidateInit{Candidate: string(candidate)}); candidateErr != nil {
74+
panic(candidateErr)
75+
}
76+
})
77+
78+
// A HTTP handler that processes a SessionDescription given to us from the other Pion process
79+
http.HandleFunc("/sdp", func(w http.ResponseWriter, r *http.Request) {
80+
sdp := webrtc.SessionDescription{}
81+
if sdpErr := json.NewDecoder(r.Body).Decode(&sdp); sdpErr != nil {
82+
panic(sdpErr)
83+
}
84+
85+
if sdpErr := peerConnection.SetRemoteDescription(sdp); sdpErr != nil {
86+
panic(sdpErr)
87+
}
88+
})
89+
// Start HTTP server that accepts requests from the answer process
90+
go func() { panic(http.ListenAndServe(*offerAddr, nil)) }()
91+
92+
// Create a datachannel with label 'data'
93+
dataChannel, err := peerConnection.CreateDataChannel("data", nil)
94+
if err != nil {
95+
panic(err)
96+
}
97+
98+
// Set the handler for ICE connection state
99+
// This will notify you when the peer has connected/disconnected
100+
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
101+
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
102+
})
103+
104+
// Register channel opening handling
105+
dataChannel.OnOpen(func() {
106+
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", dataChannel.Label(), dataChannel.ID())
107+
108+
for range time.NewTicker(5 * time.Second).C {
109+
message := signal.RandSeq(15)
110+
fmt.Printf("Sending '%s'\n", message)
111+
112+
// Send the message as text
113+
sendTextErr := dataChannel.SendText(message)
114+
if sendTextErr != nil {
115+
panic(sendTextErr)
116+
}
117+
}
118+
})
119+
120+
// Register text message handling
121+
dataChannel.OnMessage(func(msg webrtc.DataChannelMessage) {
122+
fmt.Printf("Message from DataChannel '%s': '%s'\n", dataChannel.Label(), string(msg.Data))
123+
})
124+
125+
// Create an offer to send to the other process
126+
offer, err := peerConnection.CreateOffer(nil)
127+
if err != nil {
128+
panic(err)
129+
}
130+
131+
// Sets the LocalDescription, and starts our UDP listeners
132+
if err = peerConnection.SetLocalDescription(offer); err != nil {
133+
panic(err)
134+
}
135+
136+
// Send our offer to the HTTP server listening in the other process
137+
payload, err := json.Marshal(offer)
138+
if err != nil {
139+
panic(err)
140+
}
141+
resp, err := http.Post(fmt.Sprintf("http://%s/sdp", *answerAddr), "application/json; charset=utf-8", bytes.NewReader(payload))
142+
if err != nil {
143+
panic(err)
144+
}
145+
defer func() {
146+
closeErr := resp.Body.Close()
147+
if closeErr != nil {
148+
panic(closeErr)
149+
}
150+
}()
151+
152+
// Block forever
153+
select {}
154+
}

examples/pion-to-pion/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# pion-to-pion
22
pion-to-pion is an example of two pion instances communicating directly!
33

4+
To see an example of `pion-to-pion` that uses Trickle ICE see `pion-to-pion-trickle`.
5+
This may connect faster (and will eventually become the default API) but requires
6+
more code.
7+
48
The SDP offer and answer are exchanged automatically over HTTP.
59
The `answer` side acts like a HTTP server and should therefore be ran first.
610

0 commit comments

Comments
 (0)