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  }