Skip to content

Commit 781ff73

Browse files
committed
Create examples/data-channels-detach-create
Pion <-> Pion DataChannels example Resolves #2706
1 parent f5fd0fa commit 781ff73

File tree

2 files changed

+241
-0
lines changed

2 files changed

+241
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# data-channels-detach-create
2+
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.
3+
4+
The example is meant to be used with data-channels-detach. This demonstrates two Go Pion processes communicating directly.
5+
6+
## Run data-channels-detach-create and make an offer to data-channels-detach via stdin
7+
```
8+
go run data-channels-detach-create/*.go | go run data-channels-detach/*.go
9+
```
10+
11+
## post the answer from data-channels-detach back to data-channels-detach-create
12+
You will see a base64 SDP printed to your console. You now need to communicate this back to `data-channels-detach-create` this can be done via a HTTP endpoint
13+
14+
`curl localhost:8080/sdp -d "BASE_64_SDP"`
15+
16+
## Output
17+
18+
On sucess you will get output like the following
19+
20+
```
21+
Peer Connection State has changed: connecting
22+
(Long base64 SDP that you should POST)
23+
Peer Connection State has changed: connected
24+
New DataChannel 1374394845054
25+
Data channel ''-'1374394845054' open.
26+
Message from DataChannel: kvmWkjYodyQcIlv
27+
Sending aMDnwlTfDYnfoUy
28+
Sending htqQtnbvygZKlmy
29+
Message from DataChannel: CMjZiNtsmIBpCaN
30+
```
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
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.
5+
// This allows direct access the underlying [pion/datachannel](https://github.com/pion/datachannel).
6+
// This allows you to interact with the data channel using a more idiomatic API based on
7+
// the `io.ReadWriteCloser` interface.
8+
package main
9+
10+
import (
11+
"encoding/base64"
12+
"encoding/json"
13+
"fmt"
14+
"io"
15+
"net/http"
16+
"os"
17+
"strconv"
18+
"time"
19+
20+
"github.com/pion/randutil"
21+
"github.com/pion/webrtc/v4"
22+
)
23+
24+
const messageSize = 15
25+
26+
func main() {
27+
sdpChan := httpSDPServer(8080)
28+
29+
// Since this behavior diverges from the WebRTC API it has to be
30+
// enabled using a settings engine. Mixing both detached and the
31+
// OnMessage DataChannel API is not supported.
32+
33+
// Create a SettingEngine and enable Detach
34+
s := webrtc.SettingEngine{}
35+
s.DetachDataChannels()
36+
37+
// Create an API object with the engine
38+
api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
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 using the API object
52+
peerConnection, err := api.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+
// Set the handler for Peer connection state
63+
// This will notify you when the peer has connected/disconnected
64+
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
65+
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
66+
67+
if state == webrtc.PeerConnectionStateFailed {
68+
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
69+
// It may be reconnected using an ICE Restart.
70+
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
71+
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
72+
fmt.Println("Peer Connection has gone to failed exiting")
73+
os.Exit(0)
74+
}
75+
76+
if state == webrtc.PeerConnectionStateClosed {
77+
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
78+
fmt.Println("Peer Connection has gone to closed exiting")
79+
os.Exit(0)
80+
}
81+
})
82+
83+
dataChannel, err := peerConnection.CreateDataChannel("", nil)
84+
if err != nil {
85+
panic(err)
86+
}
87+
88+
dataChannel.OnOpen(func() {
89+
fmt.Printf("Data channel '%s'-'%d' open.\n", dataChannel.Label(), dataChannel.ID())
90+
91+
// Detach the data channel
92+
raw, dErr := dataChannel.Detach()
93+
if dErr != nil {
94+
panic(dErr)
95+
}
96+
97+
// Handle reading from the data channel
98+
go ReadLoop(raw)
99+
100+
// Handle writing to the data channel
101+
go WriteLoop(raw)
102+
})
103+
104+
// Create an offer to send to the browser
105+
offer, err := peerConnection.CreateOffer(nil)
106+
if err != nil {
107+
panic(err)
108+
}
109+
110+
// Create channel that is blocked until ICE Gathering is complete
111+
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
112+
113+
// Sets the LocalDescription, and starts our UDP listeners
114+
if err = peerConnection.SetLocalDescription(offer); err != nil {
115+
panic(err)
116+
}
117+
118+
// Block until ICE Gathering is complete, disabling trickle ICE
119+
// we do this because we only can exchange one signaling message
120+
// in a production application you should exchange ICE Candidates via OnICECandidate
121+
<-gatherComplete
122+
123+
// Output the offer in base64 so we can paste it in browser
124+
fmt.Println(encode(peerConnection.LocalDescription()))
125+
126+
// Wait for the answer to be submitted via HTTP
127+
answer := webrtc.SessionDescription{}
128+
decode(<-sdpChan, &answer)
129+
130+
// Set the remote SessionDescription
131+
err = peerConnection.SetRemoteDescription(answer)
132+
if err != nil {
133+
panic(err)
134+
}
135+
136+
// Block forever
137+
select {}
138+
}
139+
140+
// ReadLoop shows how to read from the datachannel directly.
141+
func ReadLoop(d io.Reader) {
142+
for {
143+
buffer := make([]byte, messageSize)
144+
n, err := d.Read(buffer)
145+
if err != nil {
146+
fmt.Println("Datachannel closed; Exit the readloop:", err)
147+
148+
return
149+
}
150+
151+
fmt.Printf("Message from DataChannel: %s\n", string(buffer[:n]))
152+
}
153+
}
154+
155+
// WriteLoop shows how to write to the datachannel directly.
156+
func WriteLoop(d io.Writer) {
157+
ticker := time.NewTicker(5 * time.Second)
158+
defer ticker.Stop()
159+
for range ticker.C {
160+
message, err := randutil.GenerateCryptoRandomString(
161+
messageSize, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
162+
)
163+
if err != nil {
164+
panic(err)
165+
}
166+
167+
fmt.Printf("Sending %s \n", message)
168+
if _, err := d.Write([]byte(message)); err != nil {
169+
panic(err)
170+
}
171+
}
172+
}
173+
174+
// httpSDPServer starts a HTTP Server that consumes SDPs.
175+
func httpSDPServer(port int) chan string {
176+
sdpChan := make(chan string)
177+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
178+
body, _ := io.ReadAll(r.Body)
179+
fmt.Fprintf(w, "done") //nolint: errcheck
180+
sdpChan <- string(body)
181+
})
182+
183+
go func() {
184+
// nolint: gosec
185+
panic(http.ListenAndServe(":"+strconv.Itoa(port), nil))
186+
}()
187+
188+
return sdpChan
189+
}
190+
191+
// JSON encode + base64 a SessionDescription.
192+
func encode(obj *webrtc.SessionDescription) string {
193+
b, err := json.Marshal(obj)
194+
if err != nil {
195+
panic(err)
196+
}
197+
198+
return base64.StdEncoding.EncodeToString(b)
199+
}
200+
201+
// Decode a base64 and unmarshal JSON into a SessionDescription.
202+
func decode(in string, obj *webrtc.SessionDescription) {
203+
b, err := base64.StdEncoding.DecodeString(in)
204+
if err != nil {
205+
panic(err)
206+
}
207+
208+
if err = json.Unmarshal(b, obj); err != nil {
209+
panic(err)
210+
}
211+
}

0 commit comments

Comments
 (0)