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