github.com/pion/webrtc/v3@v3.2.24/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/ioutil" 13 "net/http" 14 "os" 15 "sync" 16 "time" 17 18 "github.com/pion/webrtc/v3" 19 "github.com/pion/webrtc/v3/examples/internal/signal" 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) { 84 candidate, candidateErr := ioutil.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) { 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 137 // Register channel opening handling 138 dataChannel.OnOpen(func() { 139 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()) 140 141 for range time.NewTicker(5 * time.Second).C { 142 message := signal.RandSeq(15) 143 fmt.Printf("Sending '%s'\n", message) 144 145 // Send the message as text 146 sendTextErr := dataChannel.SendText(message) 147 if sendTextErr != nil { 148 panic(sendTextErr) 149 } 150 } 151 }) 152 153 // Register text message handling 154 dataChannel.OnMessage(func(msg webrtc.DataChannelMessage) { 155 fmt.Printf("Message from DataChannel '%s': '%s'\n", dataChannel.Label(), string(msg.Data)) 156 }) 157 158 // Create an offer to send to the other process 159 offer, err := peerConnection.CreateOffer(nil) 160 if err != nil { 161 panic(err) 162 } 163 164 // Sets the LocalDescription, and starts our UDP listeners 165 // Note: this will start the gathering of ICE candidates 166 if err = peerConnection.SetLocalDescription(offer); err != nil { 167 panic(err) 168 } 169 170 // Send our offer to the HTTP server listening in the other process 171 payload, err := json.Marshal(offer) 172 if err != nil { 173 panic(err) 174 } 175 resp, err := http.Post(fmt.Sprintf("http://%s/sdp", *answerAddr), "application/json; charset=utf-8", bytes.NewReader(payload)) // nolint:noctx 176 if err != nil { 177 panic(err) 178 } else if err := resp.Body.Close(); err != nil { 179 panic(err) 180 } 181 182 // Block forever 183 select {} 184 }