github.com/pion/webrtc/v4@v4.0.1/examples/ortc/main.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build !js 5 // +build !js 6 7 // ortc demonstrates Pion WebRTC's ORTC capabilities. 8 package main 9 10 import ( 11 "bufio" 12 "encoding/base64" 13 "encoding/json" 14 "errors" 15 "flag" 16 "fmt" 17 "io" 18 "net/http" 19 "os" 20 "strconv" 21 "strings" 22 "time" 23 24 "github.com/pion/randutil" 25 "github.com/pion/webrtc/v4" 26 ) 27 28 func main() { 29 isOffer := flag.Bool("offer", false, "Act as the offerer if set") 30 port := flag.Int("port", 8080, "http server port") 31 flag.Parse() 32 33 // Everything below is the Pion WebRTC (ORTC) API! Thanks for using it ❤️. 34 35 // Prepare ICE gathering options 36 iceOptions := webrtc.ICEGatherOptions{ 37 ICEServers: []webrtc.ICEServer{ 38 {URLs: []string{"stun:stun.l.google.com:19302"}}, 39 }, 40 } 41 42 // Create an API object 43 api := webrtc.NewAPI() 44 45 // Create the ICE gatherer 46 gatherer, err := api.NewICEGatherer(iceOptions) 47 if err != nil { 48 panic(err) 49 } 50 51 // Construct the ICE transport 52 ice := api.NewICETransport(gatherer) 53 54 // Construct the DTLS transport 55 dtls, err := api.NewDTLSTransport(ice, nil) 56 if err != nil { 57 panic(err) 58 } 59 60 // Construct the SCTP transport 61 sctp := api.NewSCTPTransport(dtls) 62 63 // Handle incoming data channels 64 sctp.OnDataChannel(func(channel *webrtc.DataChannel) { 65 fmt.Printf("New DataChannel %s %d\n", channel.Label(), channel.ID()) 66 67 // Register the handlers 68 channel.OnOpen(handleOnOpen(channel)) 69 channel.OnMessage(func(msg webrtc.DataChannelMessage) { 70 fmt.Printf("Message from DataChannel '%s': '%s'\n", channel.Label(), string(msg.Data)) 71 }) 72 }) 73 74 gatherFinished := make(chan struct{}) 75 gatherer.OnLocalCandidate(func(i *webrtc.ICECandidate) { 76 if i == nil { 77 close(gatherFinished) 78 } 79 }) 80 81 // Gather candidates 82 if err = gatherer.Gather(); err != nil { 83 panic(err) 84 } 85 86 <-gatherFinished 87 88 iceCandidates, err := gatherer.GetLocalCandidates() 89 if err != nil { 90 panic(err) 91 } 92 93 iceParams, err := gatherer.GetLocalParameters() 94 if err != nil { 95 panic(err) 96 } 97 98 dtlsParams, err := dtls.GetLocalParameters() 99 if err != nil { 100 panic(err) 101 } 102 103 sctpCapabilities := sctp.GetCapabilities() 104 105 s := Signal{ 106 ICECandidates: iceCandidates, 107 ICEParameters: iceParams, 108 DTLSParameters: dtlsParams, 109 SCTPCapabilities: sctpCapabilities, 110 } 111 112 iceRole := webrtc.ICERoleControlled 113 114 // Exchange the information 115 fmt.Println(encode(s)) 116 remoteSignal := Signal{} 117 118 if *isOffer { 119 signalingChan := httpSDPServer(*port) 120 decode(<-signalingChan, &remoteSignal) 121 122 iceRole = webrtc.ICERoleControlling 123 } else { 124 decode(readUntilNewline(), &remoteSignal) 125 } 126 127 if err = ice.SetRemoteCandidates(remoteSignal.ICECandidates); err != nil { 128 panic(err) 129 } 130 131 // Start the ICE transport 132 err = ice.Start(nil, remoteSignal.ICEParameters, &iceRole) 133 if err != nil { 134 panic(err) 135 } 136 137 // Start the DTLS transport 138 if err = dtls.Start(remoteSignal.DTLSParameters); err != nil { 139 panic(err) 140 } 141 142 // Start the SCTP transport 143 if err = sctp.Start(remoteSignal.SCTPCapabilities); err != nil { 144 panic(err) 145 } 146 147 // Construct the data channel as the offerer 148 if *isOffer { 149 var id uint16 = 1 150 151 dcParams := &webrtc.DataChannelParameters{ 152 Label: "Foo", 153 ID: &id, 154 } 155 var channel *webrtc.DataChannel 156 channel, err = api.NewDataChannel(sctp, dcParams) 157 if err != nil { 158 panic(err) 159 } 160 161 // Register the handlers 162 // channel.OnOpen(handleOnOpen(channel)) // TODO: OnOpen on handle ChannelAck 163 go handleOnOpen(channel)() // Temporary alternative 164 channel.OnMessage(func(msg webrtc.DataChannelMessage) { 165 fmt.Printf("Message from DataChannel '%s': '%s'\n", channel.Label(), string(msg.Data)) 166 }) 167 } 168 169 select {} 170 } 171 172 // Signal is used to exchange signaling info. 173 // This is not part of the ORTC spec. You are free 174 // to exchange this information any way you want. 175 type Signal struct { 176 ICECandidates []webrtc.ICECandidate `json:"iceCandidates"` 177 ICEParameters webrtc.ICEParameters `json:"iceParameters"` 178 DTLSParameters webrtc.DTLSParameters `json:"dtlsParameters"` 179 SCTPCapabilities webrtc.SCTPCapabilities `json:"sctpCapabilities"` 180 } 181 182 func handleOnOpen(channel *webrtc.DataChannel) func() { 183 return func() { 184 fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", channel.Label(), channel.ID()) 185 186 ticker := time.NewTicker(5 * time.Second) 187 defer ticker.Stop() 188 for range ticker.C { 189 message, err := randutil.GenerateCryptoRandomString(15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 190 if err != nil { 191 panic(err) 192 } 193 194 fmt.Printf("Sending %s \n", message) 195 if err := channel.SendText(message); err != nil { 196 panic(err) 197 } 198 } 199 } 200 } 201 202 // Read from stdin until we get a newline 203 func readUntilNewline() (in string) { 204 var err error 205 206 r := bufio.NewReader(os.Stdin) 207 for { 208 in, err = r.ReadString('\n') 209 if err != nil && !errors.Is(err, io.EOF) { 210 panic(err) 211 } 212 213 if in = strings.TrimSpace(in); len(in) > 0 { 214 break 215 } 216 } 217 218 fmt.Println("") 219 return 220 } 221 222 // JSON encode + base64 a SessionDescription 223 func encode(obj Signal) string { 224 b, err := json.Marshal(obj) 225 if err != nil { 226 panic(err) 227 } 228 229 return base64.StdEncoding.EncodeToString(b) 230 } 231 232 // Decode a base64 and unmarshal JSON into a SessionDescription 233 func decode(in string, obj *Signal) { 234 b, err := base64.StdEncoding.DecodeString(in) 235 if err != nil { 236 panic(err) 237 } 238 239 if err = json.Unmarshal(b, obj); err != nil { 240 panic(err) 241 } 242 } 243 244 // httpSDPServer starts a HTTP Server that consumes SDPs 245 func httpSDPServer(port int) chan string { 246 sdpChan := make(chan string) 247 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 248 body, _ := io.ReadAll(r.Body) 249 fmt.Fprintf(w, "done") //nolint: errcheck 250 sdpChan <- string(body) 251 }) 252 253 go func() { 254 // nolint: gosec 255 panic(http.ListenAndServe(":"+strconv.Itoa(port), nil)) 256 }() 257 258 return sdpChan 259 }