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  }