github.com/pion/webrtc/v4@v4.0.1/examples/pion-to-pion/offer/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), "application/json; charset=utf-8", bytes.NewReader(payload)) //nolint:noctx 25 if err != nil { 26 return err 27 } 28 29 return resp.Body.Close() 30 } 31 32 func main() { //nolint:gocognit 33 offerAddr := flag.String("offer-address", ":50000", "Address that the Offer HTTP server is hosted on.") 34 answerAddr := flag.String("answer-address", "127.0.0.1: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 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 cErr := peerConnection.Close(); cErr != nil { 58 fmt.Printf("cannot close peerConnection: %v\n", cErr) 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(*answerAddr, 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 sdpErr := json.NewDecoder(r.Body).Decode(&sdp); sdpErr != nil { 97 panic(sdpErr) 98 } 99 100 if sdpErr := peerConnection.SetRemoteDescription(sdp); sdpErr != nil { 101 panic(sdpErr) 102 } 103 104 candidatesMux.Lock() 105 defer candidatesMux.Unlock() 106 107 for _, c := range pendingCandidates { 108 if onICECandidateErr := signalCandidate(*answerAddr, c); onICECandidateErr != nil { 109 panic(onICECandidateErr) 110 } 111 } 112 }) 113 // Start HTTP server that accepts requests from the answer process 114 // nolint: gosec 115 go func() { panic(http.ListenAndServe(*offerAddr, nil)) }() 116 117 // Create a datachannel with label 'data' 118 dataChannel, err := peerConnection.CreateDataChannel("data", nil) 119 if err != nil { 120 panic(err) 121 } 122 123 // Set the handler for Peer connection state 124 // This will notify you when the peer has connected/disconnected 125 peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { 126 fmt.Printf("Peer Connection State has changed: %s\n", s.String()) 127 128 if s == webrtc.PeerConnectionStateFailed { 129 // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 130 // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 131 // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 132 fmt.Println("Peer Connection has gone to failed exiting") 133 os.Exit(0) 134 } 135 136 if s == webrtc.PeerConnectionStateClosed { 137 // PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify 138 fmt.Println("Peer Connection has gone to closed exiting") 139 os.Exit(0) 140 } 141 }) 142 143 // Register channel opening handling 144 dataChannel.OnOpen(func() { 145 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()) 146 147 ticker := time.NewTicker(5 * time.Second) 148 defer ticker.Stop() 149 for range ticker.C { 150 message, sendTextErr := randutil.GenerateCryptoRandomString(15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 151 if sendTextErr != nil { 152 panic(sendTextErr) 153 } 154 155 // Send the message as text 156 fmt.Printf("Sending '%s'\n", message) 157 if sendTextErr = dataChannel.SendText(message); sendTextErr != nil { 158 panic(sendTextErr) 159 } 160 } 161 }) 162 163 // Register text message handling 164 dataChannel.OnMessage(func(msg webrtc.DataChannelMessage) { 165 fmt.Printf("Message from DataChannel '%s': '%s'\n", dataChannel.Label(), string(msg.Data)) 166 }) 167 168 // Create an offer to send to the other process 169 offer, err := peerConnection.CreateOffer(nil) 170 if err != nil { 171 panic(err) 172 } 173 174 // Sets the LocalDescription, and starts our UDP listeners 175 // Note: this will start the gathering of ICE candidates 176 if err = peerConnection.SetLocalDescription(offer); err != nil { 177 panic(err) 178 } 179 180 // Send our offer to the HTTP server listening in the other process 181 payload, err := json.Marshal(offer) 182 if err != nil { 183 panic(err) 184 } 185 resp, err := http.Post(fmt.Sprintf("http://%s/sdp", *answerAddr), "application/json; charset=utf-8", bytes.NewReader(payload)) // nolint:noctx 186 if err != nil { 187 panic(err) 188 } else if err := resp.Body.Close(); err != nil { 189 panic(err) 190 } 191 192 // Block forever 193 select {} 194 }