github.com/pion/webrtc/v4@v4.0.1/examples/data-channels-detach/main.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 // data-channels-detach is an example that shows how you can detach a data channel. This allows direct access the underlying [pion/datachannel](https://github.com/pion/datachannel). This allows you to interact with the data channel using a more idiomatic API based on the `io.ReadWriteCloser` interface. 5 package main 6 7 import ( 8 "bufio" 9 "encoding/base64" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "io" 14 "os" 15 "strings" 16 "time" 17 18 "github.com/pion/randutil" 19 "github.com/pion/webrtc/v4" 20 ) 21 22 const messageSize = 15 23 24 func main() { 25 // Since this behavior diverges from the WebRTC API it has to be 26 // enabled using a settings engine. Mixing both detached and the 27 // OnMessage DataChannel API is not supported. 28 29 // Create a SettingEngine and enable Detach 30 s := webrtc.SettingEngine{} 31 s.DetachDataChannels() 32 33 // Create an API object with the engine 34 api := webrtc.NewAPI(webrtc.WithSettingEngine(s)) 35 36 // Everything below is the Pion WebRTC API! Thanks for using it ❤️. 37 38 // Prepare the configuration 39 config := webrtc.Configuration{ 40 ICEServers: []webrtc.ICEServer{ 41 { 42 URLs: []string{"stun:stun.l.google.com:19302"}, 43 }, 44 }, 45 } 46 47 // Create a new RTCPeerConnection using the API object 48 peerConnection, err := api.NewPeerConnection(config) 49 if err != nil { 50 panic(err) 51 } 52 defer func() { 53 if cErr := peerConnection.Close(); cErr != nil { 54 fmt.Printf("cannot close peerConnection: %v\n", cErr) 55 } 56 }() 57 58 // Set the handler for Peer connection state 59 // This will notify you when the peer has connected/disconnected 60 peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { 61 fmt.Printf("Peer Connection State has changed: %s\n", s.String()) 62 63 if s == webrtc.PeerConnectionStateFailed { 64 // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 65 // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 66 // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 67 fmt.Println("Peer Connection has gone to failed exiting") 68 os.Exit(0) 69 } 70 71 if s == webrtc.PeerConnectionStateClosed { 72 // PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify 73 fmt.Println("Peer Connection has gone to closed exiting") 74 os.Exit(0) 75 } 76 }) 77 78 // Register data channel creation handling 79 peerConnection.OnDataChannel(func(d *webrtc.DataChannel) { 80 fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID()) 81 82 // Register channel opening handling 83 d.OnOpen(func() { 84 fmt.Printf("Data channel '%s'-'%d' open.\n", d.Label(), d.ID()) 85 86 // Detach the data channel 87 raw, dErr := d.Detach() 88 if dErr != nil { 89 panic(dErr) 90 } 91 92 // Handle reading from the data channel 93 go ReadLoop(raw) 94 95 // Handle writing to the data channel 96 go WriteLoop(raw) 97 }) 98 }) 99 100 // Wait for the offer to be pasted 101 offer := webrtc.SessionDescription{} 102 decode(readUntilNewline(), &offer) 103 104 // Set the remote SessionDescription 105 err = peerConnection.SetRemoteDescription(offer) 106 if err != nil { 107 panic(err) 108 } 109 110 // Create answer 111 answer, err := peerConnection.CreateAnswer(nil) 112 if err != nil { 113 panic(err) 114 } 115 116 // Create channel that is blocked until ICE Gathering is complete 117 gatherComplete := webrtc.GatheringCompletePromise(peerConnection) 118 119 // Sets the LocalDescription, and starts our UDP listeners 120 err = peerConnection.SetLocalDescription(answer) 121 if err != nil { 122 panic(err) 123 } 124 125 // Block until ICE Gathering is complete, disabling trickle ICE 126 // we do this because we only can exchange one signaling message 127 // in a production application you should exchange ICE Candidates via OnICECandidate 128 <-gatherComplete 129 130 // Output the answer in base64 so we can paste it in browser 131 fmt.Println(encode(peerConnection.LocalDescription())) 132 133 // Block forever 134 select {} 135 } 136 137 // ReadLoop shows how to read from the datachannel directly 138 func ReadLoop(d io.Reader) { 139 for { 140 buffer := make([]byte, messageSize) 141 n, err := d.Read(buffer) 142 if err != nil { 143 fmt.Println("Datachannel closed; Exit the readloop:", err) 144 return 145 } 146 147 fmt.Printf("Message from DataChannel: %s\n", string(buffer[:n])) 148 } 149 } 150 151 // WriteLoop shows how to write to the datachannel directly 152 func WriteLoop(d io.Writer) { 153 ticker := time.NewTicker(5 * time.Second) 154 defer ticker.Stop() 155 for range ticker.C { 156 message, err := randutil.GenerateCryptoRandomString(messageSize, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 157 if err != nil { 158 panic(err) 159 } 160 161 fmt.Printf("Sending %s \n", message) 162 if _, err := d.Write([]byte(message)); err != nil { 163 panic(err) 164 } 165 } 166 } 167 168 // Read from stdin until we get a newline 169 func readUntilNewline() (in string) { 170 var err error 171 172 r := bufio.NewReader(os.Stdin) 173 for { 174 in, err = r.ReadString('\n') 175 if err != nil && !errors.Is(err, io.EOF) { 176 panic(err) 177 } 178 179 if in = strings.TrimSpace(in); len(in) > 0 { 180 break 181 } 182 } 183 184 fmt.Println("") 185 return 186 } 187 188 // JSON encode + base64 a SessionDescription 189 func encode(obj *webrtc.SessionDescription) string { 190 b, err := json.Marshal(obj) 191 if err != nil { 192 panic(err) 193 } 194 195 return base64.StdEncoding.EncodeToString(b) 196 } 197 198 // Decode a base64 and unmarshal JSON into a SessionDescription 199 func decode(in string, obj *webrtc.SessionDescription) { 200 b, err := base64.StdEncoding.DecodeString(in) 201 if err != nil { 202 panic(err) 203 } 204 205 if err = json.Unmarshal(b, obj); err != nil { 206 panic(err) 207 } 208 }