main.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
  2. // SPDX-License-Identifier: MIT
  3. //go:build js && wasm
  4. // +build js,wasm
  5. package main
  6. import (
  7. "fmt"
  8. "io"
  9. "syscall/js"
  10. "time"
  11. "github.com/pion/webrtc/v3"
  12. "github.com/pion/webrtc/v3/examples/internal/signal"
  13. )
  14. const messageSize = 15
  15. func main() {
  16. // Since this behavior diverges from the WebRTC API it has to be
  17. // enabled using a settings engine. Mixing both detached and the
  18. // OnMessage DataChannel API is not supported.
  19. // Create a SettingEngine and enable Detach
  20. s := webrtc.SettingEngine{}
  21. s.DetachDataChannels()
  22. // Create an API object with the engine
  23. api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
  24. // Everything below is the Pion WebRTC API! Thanks for using it ❤️.
  25. // Prepare the configuration
  26. config := webrtc.Configuration{
  27. ICEServers: []webrtc.ICEServer{
  28. {
  29. URLs: []string{"stun:stun.l.google.com:19302"},
  30. },
  31. },
  32. }
  33. // Create a new RTCPeerConnection using the API object
  34. peerConnection, err := api.NewPeerConnection(config)
  35. if err != nil {
  36. handleError(err)
  37. }
  38. // Create a datachannel with label 'data'
  39. dataChannel, err := peerConnection.CreateDataChannel("data", nil)
  40. if err != nil {
  41. handleError(err)
  42. }
  43. // Set the handler for ICE connection state
  44. // This will notify you when the peer has connected/disconnected
  45. peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
  46. log(fmt.Sprintf("ICE Connection State has changed: %s\n", connectionState.String()))
  47. })
  48. // Register channel opening handling
  49. dataChannel.OnOpen(func() {
  50. log(fmt.Sprintf("Data channel '%s'-'%d' open.\n", dataChannel.Label(), dataChannel.ID()))
  51. // Detach the data channel
  52. raw, dErr := dataChannel.Detach()
  53. if dErr != nil {
  54. handleError(dErr)
  55. }
  56. // Handle reading from the data channel
  57. go ReadLoop(raw)
  58. // Handle writing to the data channel
  59. go WriteLoop(raw)
  60. })
  61. // Create an offer to send to the browser
  62. offer, err := peerConnection.CreateOffer(nil)
  63. if err != nil {
  64. handleError(err)
  65. }
  66. // Sets the LocalDescription, and starts our UDP listeners
  67. err = peerConnection.SetLocalDescription(offer)
  68. if err != nil {
  69. handleError(err)
  70. }
  71. // Add handlers for setting up the connection.
  72. peerConnection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
  73. log(fmt.Sprint(state))
  74. })
  75. peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
  76. if candidate != nil {
  77. encodedDescr := signal.Encode(peerConnection.LocalDescription())
  78. el := getElementByID("localSessionDescription")
  79. el.Set("value", encodedDescr)
  80. }
  81. })
  82. // Set up global callbacks which will be triggered on button clicks.
  83. /*js.Global().Set("sendMessage", js.FuncOf(func(_ js.Value, _ []js.Value) interface{} {
  84. go func() {
  85. el := getElementByID("message")
  86. message := el.Get("value").String()
  87. if message == "" {
  88. js.Global().Call("alert", "Message must not be empty")
  89. return
  90. }
  91. if err := sendChannel.SendText(message); err != nil {
  92. handleError(err)
  93. }
  94. }()
  95. return js.Undefined()
  96. }))*/
  97. js.Global().Set("startSession", js.FuncOf(func(_ js.Value, _ []js.Value) interface{} {
  98. go func() {
  99. el := getElementByID("remoteSessionDescription")
  100. sd := el.Get("value").String()
  101. if sd == "" {
  102. js.Global().Call("alert", "Session Description must not be empty")
  103. return
  104. }
  105. descr := webrtc.SessionDescription{}
  106. signal.Decode(sd, &descr)
  107. if err := peerConnection.SetRemoteDescription(descr); err != nil {
  108. handleError(err)
  109. }
  110. }()
  111. return js.Undefined()
  112. }))
  113. // Block forever
  114. select {}
  115. }
  116. // ReadLoop shows how to read from the datachannel directly
  117. func ReadLoop(d io.Reader) {
  118. for {
  119. buffer := make([]byte, messageSize)
  120. n, err := d.Read(buffer)
  121. if err != nil {
  122. log(fmt.Sprintf("Datachannel closed; Exit the readloop: %v", err))
  123. return
  124. }
  125. log(fmt.Sprintf("Message from DataChannel: %s\n", string(buffer[:n])))
  126. }
  127. }
  128. // WriteLoop shows how to write to the datachannel directly
  129. func WriteLoop(d io.Writer) {
  130. for range time.NewTicker(5 * time.Second).C {
  131. message := signal.RandSeq(messageSize)
  132. log(fmt.Sprintf("Sending %s \n", message))
  133. _, err := d.Write([]byte(message))
  134. if err != nil {
  135. handleError(err)
  136. }
  137. }
  138. }
  139. func log(msg string) {
  140. el := getElementByID("logs")
  141. el.Set("innerHTML", el.Get("innerHTML").String()+msg+"<br>")
  142. }
  143. func handleError(err error) {
  144. log("Unexpected error. Check console.")
  145. panic(err)
  146. }
  147. func getElementByID(id string) js.Value {
  148. return js.Global().Get("document").Call("getElementById", id)
  149. }