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  }