1package main
2
3import (
4	"bytes"
5	"encoding/json"
6	"flag"
7	"fmt"
8	"io/ioutil"
9	"net/http"
10	"sync"
11	"time"
12
13	"github.com/pion/webrtc/v3"
14	"github.com/pion/webrtc/v3/examples/internal/signal"
15)
16
17func signalCandidate(addr string, c *webrtc.ICECandidate) error {
18	payload := []byte(c.ToJSON().Candidate)
19	resp, err := http.Post(fmt.Sprintf("http://%s/candidate", addr), // nolint:noctx
20		"application/json; charset=utf-8", bytes.NewReader(payload))
21	if err != nil {
22		return err
23	}
24
25	if closeErr := resp.Body.Close(); closeErr != nil {
26		return closeErr
27	}
28
29	return nil
30}
31
32func main() { // nolint:gocognit
33	offerAddr := flag.String("offer-address", "localhost:50000", "Address that the Offer HTTP server is hosted on.")
34	answerAddr := flag.String("answer-address", ":60000", "Address that the Answer HTTP server is hosted on.")
35	flag.Parse()
36
37	var candidatesMux sync.Mutex
38	pendingCandidates := make([]*webrtc.ICECandidate, 0)
39	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
40
41	// Prepare the configuration
42	config := webrtc.Configuration{
43		ICEServers: []webrtc.ICEServer{
44			{
45				URLs: []string{"stun:stun.l.google.com:19302"},
46			},
47		},
48	}
49
50	// Create a new RTCPeerConnection
51	peerConnection, err := webrtc.NewPeerConnection(config)
52	if err != nil {
53		panic(err)
54	}
55
56	// When an ICE candidate is available send to the other Pion instance
57	// the other Pion instance will add this candidate by calling AddICECandidate
58	peerConnection.OnICECandidate(func(c *webrtc.ICECandidate) {
59		if c == nil {
60			return
61		}
62
63		candidatesMux.Lock()
64		defer candidatesMux.Unlock()
65
66		desc := peerConnection.RemoteDescription()
67		if desc == nil {
68			pendingCandidates = append(pendingCandidates, c)
69		} else if onICECandidateErr := signalCandidate(*offerAddr, c); onICECandidateErr != nil {
70			panic(onICECandidateErr)
71		}
72	})
73
74	// A HTTP handler that allows the other Pion instance to send us ICE candidates
75	// This allows us to add ICE candidates faster, we don't have to wait for STUN or TURN
76	// candidates which may be slower
77	http.HandleFunc("/candidate", func(w http.ResponseWriter, r *http.Request) {
78		candidate, candidateErr := ioutil.ReadAll(r.Body)
79		if candidateErr != nil {
80			panic(candidateErr)
81		}
82		if candidateErr := peerConnection.AddICECandidate(webrtc.ICECandidateInit{Candidate: string(candidate)}); candidateErr != nil {
83			panic(candidateErr)
84		}
85	})
86
87	// A HTTP handler that processes a SessionDescription given to us from the other Pion process
88	http.HandleFunc("/sdp", func(w http.ResponseWriter, r *http.Request) {
89		sdp := webrtc.SessionDescription{}
90		if err := json.NewDecoder(r.Body).Decode(&sdp); err != nil {
91			panic(err)
92		}
93
94		if err := peerConnection.SetRemoteDescription(sdp); err != nil {
95			panic(err)
96		}
97
98		// Create an answer to send to the other process
99		answer, err := peerConnection.CreateAnswer(nil)
100		if err != nil {
101			panic(err)
102		}
103
104		// Send our answer to the HTTP server listening in the other process
105		payload, err := json.Marshal(answer)
106		if err != nil {
107			panic(err)
108		}
109		resp, err := http.Post(fmt.Sprintf("http://%s/sdp", *offerAddr), "application/json; charset=utf-8", bytes.NewReader(payload)) // nolint:noctx
110		if err != nil {
111			panic(err)
112		} else if closeErr := resp.Body.Close(); closeErr != nil {
113			panic(closeErr)
114		}
115
116		// Sets the LocalDescription, and starts our UDP listeners
117		err = peerConnection.SetLocalDescription(answer)
118		if err != nil {
119			panic(err)
120		}
121
122		candidatesMux.Lock()
123		for _, c := range pendingCandidates {
124			onICECandidateErr := signalCandidate(*offerAddr, c)
125			if onICECandidateErr != nil {
126				panic(onICECandidateErr)
127			}
128		}
129		candidatesMux.Unlock()
130	})
131
132	// Set the handler for ICE connection state
133	// This will notify you when the peer has connected/disconnected
134	peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
135		fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
136	})
137
138	// Register data channel creation handling
139	peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
140		fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID())
141
142		// Register channel opening handling
143		d.OnOpen(func() {
144			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())
145
146			for range time.NewTicker(5 * time.Second).C {
147				message := signal.RandSeq(15)
148				fmt.Printf("Sending '%s'\n", message)
149
150				// Send the message as text
151				sendTextErr := d.SendText(message)
152				if sendTextErr != nil {
153					panic(sendTextErr)
154				}
155			}
156		})
157
158		// Register text message handling
159		d.OnMessage(func(msg webrtc.DataChannelMessage) {
160			fmt.Printf("Message from DataChannel '%s': '%s'\n", d.Label(), string(msg.Data))
161		})
162	})
163
164	// Start HTTP server that accepts requests from the offer process to exchange SDP and Candidates
165	panic(http.ListenAndServe(*answerAddr, nil))
166}
167