github.com/pion/webrtc/v4@v4.0.1/examples/data-channels-detach/jsfiddle/main.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  //go:build js && wasm
     5  // +build js,wasm
     6  
     7  package main
     8  
     9  import (
    10  	"bufio"
    11  	"encoding/base64"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"os"
    17  	"strings"
    18  	"syscall/js"
    19  	"time"
    20  
    21  	"github.com/pion/randutil"
    22  	"github.com/pion/webrtc/v4"
    23  )
    24  
    25  const messageSize = 15
    26  
    27  func main() {
    28  	// Since this behavior diverges from the WebRTC API it has to be
    29  	// enabled using a settings engine. Mixing both detached and the
    30  	// OnMessage DataChannel API is not supported.
    31  
    32  	// Create a SettingEngine and enable Detach
    33  	s := webrtc.SettingEngine{}
    34  	s.DetachDataChannels()
    35  
    36  	// Create an API object with the engine
    37  	api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
    38  
    39  	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
    40  
    41  	// Prepare the configuration
    42  	config := webrtc.Configuration{
    43  		ICEServers: []webrtc.ICEServer{
    44  			{
    45  				URLs: []string{"stun:stun.l.google.com:19302"},
    46  			},
    47  		},
    48  	}
    49  
    50  	// Create a new RTCPeerConnection using the API object
    51  	peerConnection, err := api.NewPeerConnection(config)
    52  	if err != nil {
    53  		handleError(err)
    54  	}
    55  
    56  	// Create a datachannel with label 'data'
    57  	dataChannel, err := peerConnection.CreateDataChannel("data", nil)
    58  	if err != nil {
    59  		handleError(err)
    60  	}
    61  
    62  	// Set the handler for ICE connection state
    63  	// This will notify you when the peer has connected/disconnected
    64  	peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
    65  		log(fmt.Sprintf("ICE Connection State has changed: %s\n", connectionState.String()))
    66  	})
    67  
    68  	// Register channel opening handling
    69  	dataChannel.OnOpen(func() {
    70  		log(fmt.Sprintf("Data channel '%s'-'%d' open.\n", dataChannel.Label(), dataChannel.ID()))
    71  
    72  		// Detach the data channel
    73  		raw, dErr := dataChannel.Detach()
    74  		if dErr != nil {
    75  			handleError(dErr)
    76  		}
    77  
    78  		// Handle reading from the data channel
    79  		go ReadLoop(raw)
    80  
    81  		// Handle writing to the data channel
    82  		go WriteLoop(raw)
    83  	})
    84  
    85  	// Create an offer to send to the browser
    86  	offer, err := peerConnection.CreateOffer(nil)
    87  	if err != nil {
    88  		handleError(err)
    89  	}
    90  
    91  	// Sets the LocalDescription, and starts our UDP listeners
    92  	err = peerConnection.SetLocalDescription(offer)
    93  	if err != nil {
    94  		handleError(err)
    95  	}
    96  
    97  	// Add handlers for setting up the connection.
    98  	peerConnection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
    99  		log(fmt.Sprint(state))
   100  	})
   101  	peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
   102  		if candidate != nil {
   103  			encodedDescr := encode(peerConnection.LocalDescription())
   104  			el := getElementByID("localSessionDescription")
   105  			el.Set("value", encodedDescr)
   106  		}
   107  	})
   108  
   109  	// Set up global callbacks which will be triggered on button clicks.
   110  	/*js.Global().Set("sendMessage", js.FuncOf(func(_ js.Value, _ []js.Value) interface{} {
   111  		go func() {
   112  			el := getElementByID("message")
   113  			message := el.Get("value").String()
   114  			if message == "" {
   115  				js.Global().Call("alert", "Message must not be empty")
   116  				return
   117  			}
   118  			if err := sendChannel.SendText(message); err != nil {
   119  				handleError(err)
   120  			}
   121  		}()
   122  		return js.Undefined()
   123  	}))*/
   124  	js.Global().Set("startSession", js.FuncOf(func(_ js.Value, _ []js.Value) interface{} {
   125  		go func() {
   126  			el := getElementByID("remoteSessionDescription")
   127  			sd := el.Get("value").String()
   128  			if sd == "" {
   129  				js.Global().Call("alert", "Session Description must not be empty")
   130  				return
   131  			}
   132  
   133  			descr := webrtc.SessionDescription{}
   134  			decode(sd, &descr)
   135  			if err := peerConnection.SetRemoteDescription(descr); err != nil {
   136  				handleError(err)
   137  			}
   138  		}()
   139  		return js.Undefined()
   140  	}))
   141  
   142  	// Block forever
   143  	select {}
   144  }
   145  
   146  // ReadLoop shows how to read from the datachannel directly
   147  func ReadLoop(d io.Reader) {
   148  	for {
   149  		buffer := make([]byte, messageSize)
   150  		n, err := d.Read(buffer)
   151  		if err != nil {
   152  			log(fmt.Sprintf("Datachannel closed; Exit the readloop: %v", err))
   153  			return
   154  		}
   155  
   156  		log(fmt.Sprintf("Message from DataChannel: %s\n", string(buffer[:n])))
   157  	}
   158  }
   159  
   160  // WriteLoop shows how to write to the datachannel directly
   161  func WriteLoop(d io.Writer) {
   162  	ticker := time.NewTicker(5 * time.Second)
   163  	defer ticker.Stop()
   164  	for range ticker.C {
   165  		message, err := randutil.GenerateCryptoRandomString(messageSize, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
   166  		if err != nil {
   167  			handleError(err)
   168  		}
   169  
   170  		log(fmt.Sprintf("Sending %s \n", message))
   171  		if _, err := d.Write([]byte(message)); err != nil {
   172  			handleError(err)
   173  		}
   174  	}
   175  }
   176  
   177  func log(msg string) {
   178  	el := getElementByID("logs")
   179  	el.Set("innerHTML", el.Get("innerHTML").String()+msg+"<br>")
   180  }
   181  
   182  func handleError(err error) {
   183  	log("Unexpected error. Check console.")
   184  	panic(err)
   185  }
   186  
   187  func getElementByID(id string) js.Value {
   188  	return js.Global().Get("document").Call("getElementById", id)
   189  }
   190  
   191  // Read from stdin until we get a newline
   192  func readUntilNewline() (in string) {
   193  	var err error
   194  
   195  	r := bufio.NewReader(os.Stdin)
   196  	for {
   197  		in, err = r.ReadString('\n')
   198  		if err != nil && !errors.Is(err, io.EOF) {
   199  			panic(err)
   200  		}
   201  
   202  		if in = strings.TrimSpace(in); len(in) > 0 {
   203  			break
   204  		}
   205  	}
   206  
   207  	fmt.Println("")
   208  	return
   209  }
   210  
   211  // JSON encode + base64 a SessionDescription
   212  func encode(obj *webrtc.SessionDescription) string {
   213  	b, err := json.Marshal(obj)
   214  	if err != nil {
   215  		panic(err)
   216  	}
   217  
   218  	return base64.StdEncoding.EncodeToString(b)
   219  }
   220  
   221  // Decode a base64 and unmarshal JSON into a SessionDescription
   222  func decode(in string, obj *webrtc.SessionDescription) {
   223  	b, err := base64.StdEncoding.DecodeString(in)
   224  	if err != nil {
   225  		panic(err)
   226  	}
   227  
   228  	if err = json.Unmarshal(b, obj); err != nil {
   229  		panic(err)
   230  	}
   231  }