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