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