mirror of
https://github.com/pion/webrtc.git
synced 2025-09-26 19:21:12 +08:00
Add JavaScript/WASM bindings
Resolves #478. Adds minimal JavaScript/WASM bindings. This makes it possible to compile core parts of pions/webrtc to WASM and run it in the browser. Only data channels are supported for now and there is limited/no support for certificates and credentials.
This commit is contained in:

committed by
Michiel De Backker

parent
6a68ca04a8
commit
0f1ddf0825
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,3 +17,4 @@ vendor/
|
||||
tags
|
||||
cover.out
|
||||
*.sw[poe]
|
||||
*.wasm
|
||||
|
@@ -89,6 +89,7 @@ Check out the **[contributing wiki](https://github.com/pions/webrtc/wiki/Contrib
|
||||
* [Antoine Baché](https://github.com/Antonito) - *OGG Opus export*
|
||||
* [frank](https://github.com/feixiao) - *Building examples on OSX*
|
||||
* [mxmCherry](https://github.com/mxmCherry)
|
||||
* [Alex Browne](https://github.com/albrow) - *JavaScript/WASM bindings*
|
||||
|
||||
### License
|
||||
MIT License - see [LICENSE](LICENSE) for full text
|
||||
|
10
constants.go
Normal file
10
constants.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package webrtc
|
||||
|
||||
const (
|
||||
// Unknown defines default public constant to use for "enum" like struct
|
||||
// comparisons when no value was defined.
|
||||
Unknown = iota
|
||||
unknownStr = "unknown"
|
||||
|
||||
receiveMTU = 8192
|
||||
)
|
@@ -1,3 +1,5 @@
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
@@ -261,15 +263,6 @@ func (d *DataChannel) onClose() (done chan struct{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// DataChannelMessage represents a message received from the
|
||||
// data channel. IsString will be set to true if the incoming
|
||||
// message is of the string type. Otherwise the message is of
|
||||
// a binary type.
|
||||
type DataChannelMessage struct {
|
||||
IsString bool
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// OnMessage sets an event handler which is invoked on a binary
|
||||
// message arrival over the sctp transport from a remote peer.
|
||||
// OnMessage can currently receive messages up to 16384 bytes
|
||||
|
256
datachannel_js.go
Normal file
256
datachannel_js.go
Normal file
@@ -0,0 +1,256 @@
|
||||
// +build js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
const dataChannelBufferSize = 16384 // Lowest common denominator among browsers
|
||||
|
||||
// DataChannel represents a WebRTC DataChannel
|
||||
// The DataChannel interface represents a network channel
|
||||
// which can be used for bidirectional peer-to-peer transfers of arbitrary data
|
||||
type DataChannel struct {
|
||||
// Pointer to the underlying JavaScript RTCPeerConnection object.
|
||||
underlying js.Value
|
||||
|
||||
// Keep track of handlers/callbacks so we can call Release as required by the
|
||||
// syscall/js API. Initially nil.
|
||||
onOpenHandler *js.Func
|
||||
onCloseHandler *js.Func
|
||||
onMessageHandler *js.Func
|
||||
}
|
||||
|
||||
// OnOpen sets an event handler which is invoked when
|
||||
// the underlying data transport has been established (or re-established).
|
||||
func (d *DataChannel) OnOpen(f func()) {
|
||||
if d.onOpenHandler != nil {
|
||||
oldHandler := d.onOpenHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onOpenHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go f()
|
||||
return js.Undefined()
|
||||
})
|
||||
d.onOpenHandler = &onOpenHandler
|
||||
d.underlying.Set("onopen", onOpenHandler)
|
||||
}
|
||||
|
||||
// OnClose sets an event handler which is invoked when
|
||||
// the underlying data transport has been closed.
|
||||
func (d *DataChannel) OnClose(f func()) {
|
||||
if d.onCloseHandler != nil {
|
||||
oldHandler := d.onCloseHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onCloseHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go f()
|
||||
return js.Undefined()
|
||||
})
|
||||
d.onCloseHandler = &onCloseHandler
|
||||
d.underlying.Set("onclose", onCloseHandler)
|
||||
}
|
||||
|
||||
// OnMessage sets an event handler which is invoked on a binary message arrival
|
||||
// from a remote peer. Note that browsers may place limitations on message size.
|
||||
func (d *DataChannel) OnMessage(f func(msg DataChannelMessage)) {
|
||||
if d.onMessageHandler != nil {
|
||||
oldHandler := d.onMessageHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onMessageHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
msg := valueToDataChannelMessage(args[0].Get("data"))
|
||||
go f(msg)
|
||||
return js.Undefined()
|
||||
})
|
||||
d.onMessageHandler = &onMessageHandler
|
||||
d.underlying.Set("onmessage", onMessageHandler)
|
||||
}
|
||||
|
||||
// Send sends the binary message to the DataChannel peer
|
||||
func (d *DataChannel) Send(data []byte) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
array := js.TypedArrayOf([]uint8(data))
|
||||
defer array.Release()
|
||||
d.underlying.Call("send", array.Value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendText sends the text message to the DataChannel peer
|
||||
func (d *DataChannel) SendText(s string) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
d.underlying.Call("send", s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close Closes the DataChannel. It may be called regardless of whether
|
||||
// the DataChannel object was created by this peer or the remote peer.
|
||||
func (d *DataChannel) Close() (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
|
||||
d.underlying.Call("close")
|
||||
|
||||
// Release any handlers as required by the syscall/js API.
|
||||
if d.onOpenHandler != nil {
|
||||
d.onOpenHandler.Release()
|
||||
}
|
||||
if d.onCloseHandler != nil {
|
||||
d.onCloseHandler.Release()
|
||||
}
|
||||
if d.onMessageHandler != nil {
|
||||
d.onMessageHandler.Release()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Label represents a label that can be used to distinguish this
|
||||
// DataChannel object from other DataChannel objects. Scripts are
|
||||
// allowed to create multiple DataChannel objects with the same label.
|
||||
func (d *DataChannel) Label() string {
|
||||
return d.underlying.Get("label").String()
|
||||
}
|
||||
|
||||
// Ordered represents if the DataChannel is ordered, and false if
|
||||
// out-of-order delivery is allowed.
|
||||
func (d *DataChannel) Ordered() bool {
|
||||
return d.underlying.Get("ordered").Bool()
|
||||
}
|
||||
|
||||
// MaxPacketLifeTime represents the length of the time window (msec) during
|
||||
// which transmissions and retransmissions may occur in unreliable mode.
|
||||
func (d *DataChannel) MaxPacketLifeTime() *uint16 {
|
||||
return valueToUint16Pointer(d.underlying.Get("maxPacketLifeTime"))
|
||||
}
|
||||
|
||||
// MaxRetransmits represents the maximum number of retransmissions that are
|
||||
// attempted in unreliable mode.
|
||||
func (d *DataChannel) MaxRetransmits() *uint16 {
|
||||
return valueToUint16Pointer(d.underlying.Get("maxRetransmits"))
|
||||
}
|
||||
|
||||
// Protocol represents the name of the sub-protocol used with this
|
||||
// DataChannel.
|
||||
func (d *DataChannel) Protocol() string {
|
||||
return d.underlying.Get("protocol").String()
|
||||
}
|
||||
|
||||
// Negotiated represents whether this DataChannel was negotiated by the
|
||||
// application (true), or not (false).
|
||||
func (d *DataChannel) Negotiated() bool {
|
||||
return d.underlying.Get("negotiated").Bool()
|
||||
}
|
||||
|
||||
// ID represents the ID for this DataChannel. The value is initially
|
||||
// null, which is what will be returned if the ID was not provided at
|
||||
// channel creation time. Otherwise, it will return the ID that was either
|
||||
// selected by the script or generated. After the ID is set to a non-null
|
||||
// value, it will not change.
|
||||
func (d *DataChannel) ID() *uint16 {
|
||||
return valueToUint16Pointer(d.underlying.Get("id"))
|
||||
}
|
||||
|
||||
// NOTE(albrow): Apparently not part of the JavaScript WebRTC API
|
||||
// // Priority represents the priority for this DataChannel. The priority is
|
||||
// // assigned at channel creation time.
|
||||
// Priority PriorityType
|
||||
|
||||
// ReadyState represents the state of the DataChannel object.
|
||||
func (d *DataChannel) ReadyState() DataChannelState {
|
||||
return newDataChannelState(d.underlying.Get("readyState").String())
|
||||
}
|
||||
|
||||
// BufferedAmount represents the number of bytes of application data
|
||||
// (UTF-8 text and binary data) that have been queued using send(). Even
|
||||
// though the data transmission can occur in parallel, the returned value
|
||||
// MUST NOT be decreased before the current task yielded back to the event
|
||||
// loop to prevent race conditions. The value does not include framing
|
||||
// overhead incurred by the protocol, or buffering done by the operating
|
||||
// system or network hardware. The value of BufferedAmount slot will only
|
||||
// increase with each call to the send() method as long as the ReadyState is
|
||||
// open; however, BufferedAmount does not reset to zero once the channel
|
||||
// closes.
|
||||
func (d *DataChannel) BufferedAmount() uint64 {
|
||||
return uint64(d.underlying.Get("bufferedAmount").Int())
|
||||
}
|
||||
|
||||
// BufferedAmountLowThreshold represents the threshold at which the
|
||||
// bufferedAmount is considered to be low. When the bufferedAmount decreases
|
||||
// from above this threshold to equal or below it, the bufferedamountlow
|
||||
// event fires. BufferedAmountLowThreshold is initially zero on each new
|
||||
// DataChannel, but the application may change its value at any time.
|
||||
func (d *DataChannel) BufferedAmountLowThreshold() uint64 {
|
||||
return uint64(d.underlying.Get("bufferedAmountLowThreshold").Int())
|
||||
}
|
||||
|
||||
// valueToDataChannelMessage converts the given value to a DataChannelMessage.
|
||||
// val should be obtained from MessageEvent.data where MessageEvent is received
|
||||
// via the RTCDataChannel.onmessage callback.
|
||||
func valueToDataChannelMessage(val js.Value) DataChannelMessage {
|
||||
// If val is of type string, the conversion is straightforward.
|
||||
if val.Type() == js.TypeString {
|
||||
return DataChannelMessage{
|
||||
IsString: true,
|
||||
Data: []byte(val.String()),
|
||||
}
|
||||
}
|
||||
|
||||
// For other types, we need to first determine val.constructor.name.
|
||||
constructorName := val.Get("constructor").Get("name").String()
|
||||
var data []byte
|
||||
switch constructorName {
|
||||
case "Uint8Array":
|
||||
// We can easily convert Uint8Array to []byte
|
||||
data = uint8ArrayValueToBytes(val)
|
||||
case "Blob":
|
||||
// Convert the Blob to an ArrayBuffer and then convert the ArrayBuffer
|
||||
// to a Uint8Array.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/Blob
|
||||
|
||||
// The JavaScript API for reading from the Blob is asynchronous. We use a
|
||||
// channel to signal when reading is done.
|
||||
reader := js.Global().Get("FileReader").New()
|
||||
doneChan := make(chan struct{})
|
||||
reader.Call("addEventListener", "loadend", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go func() {
|
||||
// Signal that the FileReader is done reading/loading by sending through
|
||||
// the doneChan.
|
||||
doneChan <- struct{}{}
|
||||
}()
|
||||
return js.Undefined()
|
||||
}))
|
||||
|
||||
// Wait for the FileReader to finish reading/loading.
|
||||
<-doneChan
|
||||
|
||||
// At this point buffer.result is a typed array, which we know how to
|
||||
// handle.
|
||||
buffer := reader.Get("result")
|
||||
uint8Array := js.Global().Get("Uint8Array").New(buffer)
|
||||
data = uint8ArrayValueToBytes(uint8Array)
|
||||
default:
|
||||
// Assume we have an ArrayBufferView type which we can convert to a
|
||||
// Uint8Array in JavaScript.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView
|
||||
uint8Array := js.Global().Get("Uint8Array").New(val)
|
||||
data = uint8ArrayValueToBytes(uint8Array)
|
||||
}
|
||||
|
||||
return DataChannelMessage{
|
||||
IsString: false,
|
||||
Data: data,
|
||||
}
|
||||
}
|
10
datachannelmessage.go
Normal file
10
datachannelmessage.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package webrtc
|
||||
|
||||
// DataChannelMessage represents a message received from the
|
||||
// data channel. IsString will be set to true if the incoming
|
||||
// message is of the string type. Otherwise the message is of
|
||||
// a binary type.
|
||||
type DataChannelMessage struct {
|
||||
IsString bool
|
||||
Data []byte
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
|
110
examples/data-channels/jsfiddle/main.go
Normal file
110
examples/data-channels/jsfiddle/main.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// +build js
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/pions/webrtc"
|
||||
"github.com/pions/webrtc/examples/internal/signal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure and create a new PeerConnection.
|
||||
config := webrtc.Configuration{
|
||||
ICEServers: []webrtc.ICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
pc, err := webrtc.NewPeerConnection(config)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
|
||||
// Create DataChannel.
|
||||
sendChannel, err := pc.CreateDataChannel("foo", nil)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
sendChannel.OnClose(func() {
|
||||
fmt.Println("sendChannel has closed")
|
||||
})
|
||||
sendChannel.OnOpen(func() {
|
||||
fmt.Println("sendChannel has opened")
|
||||
})
|
||||
sendChannel.OnMessage(func(msg webrtc.DataChannelMessage) {
|
||||
log(fmt.Sprintf("Message from DataChannel %s payload %s", sendChannel.Label(), string(msg.Data)))
|
||||
})
|
||||
|
||||
// Add handlers for setting up the connection.
|
||||
pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
|
||||
log(fmt.Sprint(state))
|
||||
})
|
||||
pc.OnICECandidate(func(candidate *string) {
|
||||
if candidate != nil {
|
||||
encodedDescr := signal.Encode(pc.LocalDescription())
|
||||
el := getElementByID("localSessionDescription")
|
||||
el.Set("value", encodedDescr)
|
||||
}
|
||||
})
|
||||
pc.OnNegotiationNeeded(func() {
|
||||
offer, err := pc.CreateOffer(nil)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
pc.SetLocalDescription(offer)
|
||||
})
|
||||
|
||||
// Set up global callbacks which will be triggered on button clicks.
|
||||
js.Global().Set("sendMessage", js.FuncOf(func(_ js.Value, _ []js.Value) interface{} {
|
||||
go func() {
|
||||
el := getElementByID("message")
|
||||
message := el.Get("value").String()
|
||||
if message == "" {
|
||||
js.Global().Call("alert", "Message must not be empty")
|
||||
return
|
||||
}
|
||||
if err := sendChannel.SendText(message); err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
}()
|
||||
return js.Undefined()
|
||||
}))
|
||||
js.Global().Set("startSession", js.FuncOf(func(_ js.Value, _ []js.Value) interface{} {
|
||||
go func() {
|
||||
el := getElementByID("remoteSessionDescription")
|
||||
sd := el.Get("value").String()
|
||||
if sd == "" {
|
||||
js.Global().Call("alert", "Session Description must not be empty")
|
||||
return
|
||||
}
|
||||
|
||||
descr := webrtc.SessionDescription{}
|
||||
signal.Decode(sd, &descr)
|
||||
if err := pc.SetRemoteDescription(descr); err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
}()
|
||||
return js.Undefined()
|
||||
}))
|
||||
|
||||
// Stay alive
|
||||
select {}
|
||||
}
|
||||
|
||||
func log(msg string) {
|
||||
el := getElementByID("logs")
|
||||
el.Set("innerHTML", el.Get("innerHTML").String()+msg+"<br>")
|
||||
}
|
||||
|
||||
func handleError(err error) {
|
||||
log("Unexpected error. Check console.")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
func getElementByID(id string) js.Value {
|
||||
return js.Global().Get("document").Call("getElementById", id)
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
|
155
js_utils.go
Normal file
155
js_utils.go
Normal file
@@ -0,0 +1,155 @@
|
||||
// +build js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// awaitPromise accepts a js.Value representing a Promise. If the promise
|
||||
// resolves, it returns (result, nil). If the promise rejects, it returns
|
||||
// (js.Undefined, error). awaitPromise has a synchronous-like API but does not
|
||||
// block the JavaScript event loop.
|
||||
func awaitPromise(promise js.Value) (js.Value, error) {
|
||||
resultsChan := make(chan js.Value)
|
||||
errChan := make(chan js.Error)
|
||||
|
||||
thenFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go func() {
|
||||
resultsChan <- args[0]
|
||||
}()
|
||||
return js.Undefined()
|
||||
})
|
||||
defer thenFunc.Release()
|
||||
promise.Call("then", thenFunc)
|
||||
|
||||
catchFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go func() {
|
||||
errChan <- js.Error{args[0]}
|
||||
}()
|
||||
return js.Undefined()
|
||||
})
|
||||
defer catchFunc.Release()
|
||||
promise.Call("catch", catchFunc)
|
||||
|
||||
select {
|
||||
case result := <-resultsChan:
|
||||
return result, nil
|
||||
case err := <-errChan:
|
||||
return js.Undefined(), err
|
||||
}
|
||||
}
|
||||
|
||||
func valueToUint16Pointer(val js.Value) *uint16 {
|
||||
if val == js.Null() || val == js.Undefined() {
|
||||
return nil
|
||||
}
|
||||
convertedVal := uint16(val.Int())
|
||||
return &convertedVal
|
||||
}
|
||||
|
||||
func valueToStringPointer(val js.Value) *string {
|
||||
if val == js.Null() || val == js.Undefined() {
|
||||
return nil
|
||||
}
|
||||
stringVal := val.String()
|
||||
return &stringVal
|
||||
}
|
||||
|
||||
func stringToValueOrUndefined(val string) js.Value {
|
||||
if val == "" {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(val)
|
||||
}
|
||||
|
||||
func uint8ToValueOrUndefined(val uint8) js.Value {
|
||||
if val == 0 {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(val)
|
||||
}
|
||||
|
||||
func interfaceToValueOrUndefined(val interface{}) js.Value {
|
||||
if val == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(val)
|
||||
}
|
||||
|
||||
func valueToStringOrZero(val js.Value) string {
|
||||
if val == js.Undefined() || val == js.Null() {
|
||||
return ""
|
||||
}
|
||||
return val.String()
|
||||
}
|
||||
|
||||
func valueToUint8OrZero(val js.Value) uint8 {
|
||||
if val == js.Undefined() || val == js.Null() {
|
||||
return 0
|
||||
}
|
||||
return uint8(val.Int())
|
||||
}
|
||||
|
||||
func valueToStrings(val js.Value) []string {
|
||||
result := make([]string, val.Length())
|
||||
for i := 0; i < val.Length(); i++ {
|
||||
result[i] = val.Index(i).String()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func stringPointerToValue(val *string) js.Value {
|
||||
if val == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(*val)
|
||||
}
|
||||
|
||||
func uint16PointerToValue(val *uint16) js.Value {
|
||||
if val == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(*val)
|
||||
}
|
||||
|
||||
func boolPointerToValue(val *bool) js.Value {
|
||||
if val == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(*val)
|
||||
}
|
||||
|
||||
func stringsToValue(strings []string) js.Value {
|
||||
val := make([]interface{}, len(strings))
|
||||
for i, s := range strings {
|
||||
val[i] = s
|
||||
}
|
||||
return js.ValueOf(val)
|
||||
}
|
||||
|
||||
func stringEnumToValueOrUndefined(s string) js.Value {
|
||||
if s == "unknown" {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(s)
|
||||
}
|
||||
|
||||
// Converts the return value of recover() to an error.
|
||||
func recoveryToError(e interface{}) error {
|
||||
switch e := e.(type) {
|
||||
case error:
|
||||
return e
|
||||
default:
|
||||
return fmt.Errorf("recovered with non-error value: (%T) %s", e, e)
|
||||
}
|
||||
}
|
||||
|
||||
func uint8ArrayValueToBytes(val js.Value) []byte {
|
||||
result := make([]byte, val.Length())
|
||||
for i := 0; i < val.Length(); i++ {
|
||||
result[i] = byte(val.Index(i).Int())
|
||||
}
|
||||
return result
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
// +build !js
|
||||
|
||||
// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document.
|
||||
package webrtc
|
||||
|
||||
@@ -22,15 +24,6 @@ import (
|
||||
|
||||
var pcLog = logging.NewScopedLogger("pc")
|
||||
|
||||
const (
|
||||
// Unknown defines default public constant to use for "enum" like struct
|
||||
// comparisons when no value was defined.
|
||||
Unknown = iota
|
||||
unknownStr = "unknown"
|
||||
|
||||
receiveMTU = 8192
|
||||
)
|
||||
|
||||
// PeerConnection represents a WebRTC connection that establishes a
|
||||
// peer-to-peer communications with another PeerConnection instance in a
|
||||
// browser, or to another endpoint implementing the required protocols.
|
||||
|
536
peerconnection_js.go
Normal file
536
peerconnection_js.go
Normal file
@@ -0,0 +1,536 @@
|
||||
// +build js
|
||||
|
||||
// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document.
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// PeerConnection represents a WebRTC connection that establishes a
|
||||
// peer-to-peer communications with another PeerConnection instance in a
|
||||
// browser, or to another endpoint implementing the required protocols.
|
||||
type PeerConnection struct {
|
||||
// Pointer to the underlying JavaScript RTCPeerConnection object.
|
||||
underlying js.Value
|
||||
|
||||
// Keep track of handlers/callbacks so we can call Release as required by the
|
||||
// syscall/js API. Initially nil.
|
||||
onSignalingStateChangeHandler *js.Func
|
||||
onDataChannelHandler *js.Func
|
||||
onICEConectionStateChangeHandler *js.Func
|
||||
onICECandidateHandler *js.Func
|
||||
onNegotiationNeededHandler *js.Func
|
||||
}
|
||||
|
||||
// NewPeerConnection creates a peerconnection with the default
|
||||
// codecs.
|
||||
func NewPeerConnection(configuration Configuration) (_ *PeerConnection, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
configMap := configurationToValue(configuration)
|
||||
underlying := js.Global().Get("window").Get("RTCPeerConnection").New(configMap)
|
||||
return &PeerConnection{
|
||||
underlying: underlying,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OnSignalingStateChange sets an event handler which is invoked when the
|
||||
// peer connection's signaling state changes
|
||||
func (pc *PeerConnection) OnSignalingStateChange(f func(SignalingState)) {
|
||||
if pc.onSignalingStateChangeHandler != nil {
|
||||
oldHandler := pc.onSignalingStateChangeHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onSignalingStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
// TODO(albrow): Protect args access; recover from panics.
|
||||
state := newSignalingState(args[0].String())
|
||||
go f(state)
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onSignalingStateChangeHandler = &onSignalingStateChangeHandler
|
||||
pc.underlying.Set("onsignalingstatechange", onSignalingStateChangeHandler)
|
||||
}
|
||||
|
||||
// OnDataChannel sets an event handler which is invoked when a data
|
||||
// channel message arrives from a remote peer.
|
||||
func (pc *PeerConnection) OnDataChannel(f func(*DataChannel)) {
|
||||
if pc.onDataChannelHandler != nil {
|
||||
oldHandler := pc.onDataChannelHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onDataChannelHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
// BUG(albrow): This reference to the underlying DataChannel doesn't know
|
||||
// about any other references to the same DataChannel. This might result in
|
||||
// memory leaks where we don't clean up handler functions. Could possibly
|
||||
// fix by keeping a mutex-protected list of all DataChannel references as a
|
||||
// property of this PeerConnection, but at the cost of additional overhead.
|
||||
dataChannel := &DataChannel{
|
||||
underlying: args[0],
|
||||
}
|
||||
go f(dataChannel)
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onDataChannelHandler = &onDataChannelHandler
|
||||
pc.underlying.Set("onsignalingstatechange", onDataChannelHandler)
|
||||
}
|
||||
|
||||
// OnTrack sets an event handler which is called when remote track
|
||||
// arrives from a remote peer.
|
||||
// func (pc *PeerConnection) OnTrack(f func(*Track, *RTPReceiver)) {
|
||||
// }
|
||||
|
||||
// OnICEConnectionStateChange sets an event handler which is called
|
||||
// when an ICE connection state is changed.
|
||||
func (pc *PeerConnection) OnICEConnectionStateChange(f func(ICEConnectionState)) {
|
||||
if pc.onICEConectionStateChangeHandler != nil {
|
||||
oldHandler := pc.onICEConectionStateChangeHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onICEConectionStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
connectionState := newICEConnectionState(pc.underlying.Get("iceConnectionState").String())
|
||||
go f(connectionState)
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onICEConectionStateChangeHandler = &onICEConectionStateChangeHandler
|
||||
pc.underlying.Set("oniceconnectionstatechange", onICEConectionStateChangeHandler)
|
||||
}
|
||||
|
||||
// SetConfiguration updates the configuration of this PeerConnection object.
|
||||
func (pc *PeerConnection) SetConfiguration(configuration Configuration) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
configMap := configurationToValue(configuration)
|
||||
pc.underlying.Call("setConfiguration", configMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfiguration returns a Configuration object representing the current
|
||||
// configuration of this PeerConnection object. The returned object is a
|
||||
// copy and direct mutation on it will not take affect until SetConfiguration
|
||||
// has been called with Configuration passed as its only argument.
|
||||
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration
|
||||
func (pc *PeerConnection) GetConfiguration() Configuration {
|
||||
return valueToConfiguration(pc.underlying.Call("getConfiguration"))
|
||||
}
|
||||
|
||||
// CreateOffer starts the PeerConnection and generates the localDescription
|
||||
func (pc *PeerConnection) CreateOffer(options *OfferOptions) (_ SessionDescription, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("createOffer", offerOptionsToValue(options))
|
||||
desc, err := awaitPromise(promise)
|
||||
if err != nil {
|
||||
return SessionDescription{}, err
|
||||
}
|
||||
return *valueToSessionDescription(desc), nil
|
||||
}
|
||||
|
||||
// CreateAnswer starts the PeerConnection and generates the localDescription
|
||||
func (pc *PeerConnection) CreateAnswer(options *AnswerOptions) (_ SessionDescription, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("createAnswer", answerOptionsToValue(options))
|
||||
desc, err := awaitPromise(promise)
|
||||
if err != nil {
|
||||
return SessionDescription{}, err
|
||||
}
|
||||
return *valueToSessionDescription(desc), nil
|
||||
}
|
||||
|
||||
// SetLocalDescription sets the SessionDescription of the local peer
|
||||
func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("setLocalDescription", sessionDescriptionToValue(&desc))
|
||||
_, err = awaitPromise(promise)
|
||||
return err
|
||||
}
|
||||
|
||||
// LocalDescription returns PendingLocalDescription if it is not null and
|
||||
// otherwise it returns CurrentLocalDescription. This property is used to
|
||||
// determine if setLocalDescription has already been called.
|
||||
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-localdescription
|
||||
func (pc *PeerConnection) LocalDescription() *SessionDescription {
|
||||
return valueToSessionDescription(pc.underlying.Get("localDescription"))
|
||||
}
|
||||
|
||||
// SetRemoteDescription sets the SessionDescription of the remote peer
|
||||
func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("setRemoteDescription", sessionDescriptionToValue(&desc))
|
||||
_, err = awaitPromise(promise)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoteDescription returns PendingRemoteDescription if it is not null and
|
||||
// otherwise it returns CurrentRemoteDescription. This property is used to
|
||||
// determine if setRemoteDescription has already been called.
|
||||
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-remotedescription
|
||||
func (pc *PeerConnection) RemoteDescription() *SessionDescription {
|
||||
return valueToSessionDescription(pc.underlying.Get("remoteDescription"))
|
||||
}
|
||||
|
||||
// AddICECandidate accepts an ICE candidate string and adds it
|
||||
// to the existing set of candidates
|
||||
func (pc *PeerConnection) AddICECandidate(candidate ICECandidateInit) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
promise := pc.underlying.Call("addIceCandidate", iceCandidateInitToValue(candidate))
|
||||
_, err = awaitPromise(promise)
|
||||
return err
|
||||
}
|
||||
|
||||
// ICEConnectionState returns the ICE connection state of the
|
||||
// PeerConnection instance.
|
||||
func (pc *PeerConnection) ICEConnectionState() ICEConnectionState {
|
||||
return newICEConnectionState(pc.underlying.Get("iceConnectionState").String())
|
||||
}
|
||||
|
||||
// TODO(albrow): This function doesn't exist in the Go implementation.
|
||||
// TODO(albrow): Follow the spec more closely. Handler should accept
|
||||
// RTCPeerConnectionIceEvent instead of *string.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onicecandidate
|
||||
func (pc *PeerConnection) OnICECandidate(f func(candidate *string)) {
|
||||
if pc.onICECandidateHandler != nil {
|
||||
oldHandler := pc.onICECandidateHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onICECandidateHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
// TODO(albrow): Protect args access; recover from panics.
|
||||
candidate := valueToStringPointer(args[0].Get("candidate"))
|
||||
go f(candidate)
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onICECandidateHandler = &onICECandidateHandler
|
||||
pc.underlying.Set("onicecandidate", onICECandidateHandler)
|
||||
}
|
||||
|
||||
// TODO(albrow): This function doesn't exist in the Go implementation.
|
||||
func (pc *PeerConnection) OnNegotiationNeeded(f func()) {
|
||||
if pc.onNegotiationNeededHandler != nil {
|
||||
oldHandler := pc.onNegotiationNeededHandler
|
||||
defer oldHandler.Release()
|
||||
}
|
||||
onNegotiationNeededHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
go f()
|
||||
return js.Undefined()
|
||||
})
|
||||
pc.onNegotiationNeededHandler = &onNegotiationNeededHandler
|
||||
pc.underlying.Set("onnegotiationneeded", onNegotiationNeededHandler)
|
||||
}
|
||||
|
||||
// // GetSenders returns the RTPSender that are currently attached to this PeerConnection
|
||||
// func (pc *PeerConnection) GetSenders() []*RTPSender {
|
||||
// }
|
||||
|
||||
// // GetReceivers returns the RTPReceivers that are currently attached to this RTCPeerConnection
|
||||
// func (pc *PeerConnection) GetReceivers() []*RTPReceiver {
|
||||
// }
|
||||
|
||||
// // GetTransceivers returns the RTCRtpTransceiver that are currently attached to this RTCPeerConnection
|
||||
// func (pc *PeerConnection) GetTransceivers() []*RTPTransceiver {
|
||||
// }
|
||||
|
||||
// // AddTrack adds a Track to the PeerConnection
|
||||
// func (pc *PeerConnection) AddTrack(track *Track) (*RTPSender, error) {
|
||||
// }
|
||||
|
||||
// func (pc *PeerConnection) RemoveTrack() {
|
||||
// }
|
||||
|
||||
// func (pc *PeerConnection) AddTransceiver() RTPTransceiver {
|
||||
// }
|
||||
|
||||
// CreateDataChannel creates a new DataChannel object with the given label
|
||||
// and optional DataChannelInit used to configure properties of the
|
||||
// underlying channel such as data reliability.
|
||||
func (pc *PeerConnection) CreateDataChannel(label string, options *DataChannelInit) (_ *DataChannel, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
channel := pc.underlying.Call("createDataChannel", label, dataChannelInitToValue(options))
|
||||
return &DataChannel{
|
||||
underlying: channel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetIdentityProvider is used to configure an identity provider to generate identity assertions
|
||||
func (pc *PeerConnection) SetIdentityProvider(provider string) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
pc.underlying.Call("setIdentityProvider", provider)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note(albrow) SendRTCP is not included in MDN WebRTC documentation.
|
||||
// SendRTCP sends a user provided RTCP packet to the connected peer
|
||||
// If no peer is connected the packet is discarded
|
||||
// func (pc *PeerConnection) SendRTCP(pkt rtcp.Packet) error {
|
||||
// return errors.New("Not yet implemented")
|
||||
// }
|
||||
|
||||
// Close ends the PeerConnection
|
||||
func (pc *PeerConnection) Close() (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = recoveryToError(e)
|
||||
}
|
||||
}()
|
||||
|
||||
pc.underlying.Call("close")
|
||||
|
||||
// Release any handlers as required by the syscall/js API.
|
||||
if pc.onSignalingStateChangeHandler != nil {
|
||||
pc.onSignalingStateChangeHandler.Release()
|
||||
}
|
||||
if pc.onDataChannelHandler != nil {
|
||||
pc.onDataChannelHandler.Release()
|
||||
}
|
||||
if pc.onICEConectionStateChangeHandler != nil {
|
||||
pc.onICEConectionStateChangeHandler.Release()
|
||||
}
|
||||
if pc.onICECandidateHandler != nil {
|
||||
pc.onICECandidateHandler.Release()
|
||||
}
|
||||
if pc.onNegotiationNeededHandler != nil {
|
||||
pc.onNegotiationNeededHandler.Release()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTrack Creates a new Track
|
||||
// func (pc *PeerConnection) NewTrack(payloadType uint8, ssrc uint32, id, label string) (*Track, error) {
|
||||
// return nil, errors.New("Not yet implemented")
|
||||
// }
|
||||
|
||||
// CurrentLocalDescription represents the local description that was
|
||||
// successfully negotiated the last time the PeerConnection transitioned
|
||||
// into the stable state plus any local candidates that have been generated
|
||||
// by the ICEAgent since the offer or answer was created.
|
||||
func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription {
|
||||
desc := pc.underlying.Get("currentLocalDescription")
|
||||
return valueToSessionDescription(desc)
|
||||
}
|
||||
|
||||
// PendingLocalDescription represents a local description that is in the
|
||||
// process of being negotiated plus any local candidates that have been
|
||||
// generated by the ICEAgent since the offer or answer was created. If the
|
||||
// PeerConnection is in the stable state, the value is null.
|
||||
func (pc *PeerConnection) PendingLocalDescription() *SessionDescription {
|
||||
desc := pc.underlying.Get("pendingLocalDescription")
|
||||
return valueToSessionDescription(desc)
|
||||
}
|
||||
|
||||
// CurrentRemoteDescription represents the last remote description that was
|
||||
// successfully negotiated the last time the PeerConnection transitioned
|
||||
// into the stable state plus any remote candidates that have been supplied
|
||||
// via AddICECandidate() since the offer or answer was created.
|
||||
func (pc *PeerConnection) CurrentRemoteDescription() *SessionDescription {
|
||||
desc := pc.underlying.Get("currentRemoteDescription")
|
||||
return valueToSessionDescription(desc)
|
||||
}
|
||||
|
||||
// PendingRemoteDescription represents a remote description that is in the
|
||||
// process of being negotiated, complete with any remote candidates that
|
||||
// have been supplied via AddICECandidate() since the offer or answer was
|
||||
// created. If the PeerConnection is in the stable state, the value is
|
||||
// null.
|
||||
func (pc *PeerConnection) PendingRemoteDescription() *SessionDescription {
|
||||
desc := pc.underlying.Get("pendingRemoteDescription")
|
||||
return valueToSessionDescription(desc)
|
||||
}
|
||||
|
||||
// SignalingState returns the signaling state of the PeerConnection instance.
|
||||
func (pc *PeerConnection) SignalingState() SignalingState {
|
||||
rawState := pc.underlying.Get("signalingState").String()
|
||||
return newSignalingState(rawState)
|
||||
}
|
||||
|
||||
// ICEGatheringState attribute the ICE gathering state of the PeerConnection
|
||||
// instance.
|
||||
func (pc *PeerConnection) ICEGatheringState() ICEGatheringState {
|
||||
rawState := pc.underlying.Get("iceGatheringState").String()
|
||||
return newICEGatheringState(rawState)
|
||||
}
|
||||
|
||||
// ConnectionState attribute the connection state of the PeerConnection
|
||||
// instance.
|
||||
func (pc *PeerConnection) ConnectionState() PeerConnectionState {
|
||||
rawState := pc.underlying.Get("connectionState").String()
|
||||
return newPeerConnectionState(rawState)
|
||||
}
|
||||
|
||||
// Converts a Configuration to js.Value so it can be passed
|
||||
// through to the JavaScript WebRTC API. Any zero values are converted to
|
||||
// js.Undefined(), which will result in the default value being used.
|
||||
func configurationToValue(configuration Configuration) js.Value {
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"iceServers": iceServersToValue(configuration.ICEServers),
|
||||
"iceTransportPolicy": stringEnumToValueOrUndefined(configuration.ICETransportPolicy.String()),
|
||||
"bundlePolicy": stringEnumToValueOrUndefined(configuration.BundlePolicy.String()),
|
||||
"rtcpMuxPolicy": stringEnumToValueOrUndefined(configuration.RTCPMuxPolicy.String()),
|
||||
"peerIdentity": stringToValueOrUndefined(configuration.PeerIdentity),
|
||||
"iceCandidatePoolSize": uint8ToValueOrUndefined(configuration.ICECandidatePoolSize),
|
||||
|
||||
// TODO(albrow): Docs for RTCCertificate are underspecified.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/RTCCertificate How
|
||||
// should we handle this?
|
||||
// "certificates": configuration.Certificates,
|
||||
})
|
||||
}
|
||||
|
||||
func iceServersToValue(iceServers []ICEServer) js.Value {
|
||||
if len(iceServers) == 0 {
|
||||
return js.Undefined()
|
||||
}
|
||||
maps := make([]interface{}, len(iceServers))
|
||||
for i, server := range iceServers {
|
||||
maps[i] = iceServerToValue(server)
|
||||
}
|
||||
return js.ValueOf(maps)
|
||||
}
|
||||
|
||||
func iceServerToValue(server ICEServer) js.Value {
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"urls": stringsToValue(server.URLs), // required
|
||||
"username": stringToValueOrUndefined(server.Username),
|
||||
"credential": interfaceToValueOrUndefined(server.Credential),
|
||||
"credentialType": stringEnumToValueOrUndefined(server.CredentialType.String()),
|
||||
})
|
||||
}
|
||||
|
||||
func valueToConfiguration(configValue js.Value) Configuration {
|
||||
if configValue == js.Null() || configValue == js.Undefined() {
|
||||
return Configuration{}
|
||||
}
|
||||
return Configuration{
|
||||
ICEServers: valueToICEServers(configValue.Get("iceServers")),
|
||||
ICETransportPolicy: newICETransportPolicy(valueToStringOrZero(configValue.Get("iceTransportPolicy"))),
|
||||
BundlePolicy: newBundlePolicy(valueToStringOrZero(configValue.Get("bundlePolicy"))),
|
||||
RTCPMuxPolicy: newRTCPMuxPolicy(valueToStringOrZero(configValue.Get("bundlePolicy"))),
|
||||
PeerIdentity: valueToStringOrZero(configValue.Get("peerIdentity")),
|
||||
ICECandidatePoolSize: valueToUint8OrZero(configValue.Get("iceCandidatePoolSize")),
|
||||
|
||||
// TODO(albrow): Docs for RTCCertificate are underspecified.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/RTCCertificate How
|
||||
// should we handle this?
|
||||
// Certificates []Certificate
|
||||
}
|
||||
}
|
||||
|
||||
func valueToICEServers(iceServersValue js.Value) []ICEServer {
|
||||
if iceServersValue == js.Null() || iceServersValue == js.Undefined() {
|
||||
return nil
|
||||
}
|
||||
iceServers := make([]ICEServer, iceServersValue.Length())
|
||||
for i := 0; i < iceServersValue.Length(); i++ {
|
||||
iceServers[i] = valueToICEServer(iceServersValue.Index(i))
|
||||
}
|
||||
return iceServers
|
||||
}
|
||||
|
||||
func valueToICEServer(iceServerValue js.Value) ICEServer {
|
||||
return ICEServer{
|
||||
URLs: valueToStrings(iceServerValue.Get("urls")), // required
|
||||
Username: valueToStringOrZero(iceServerValue.Get("username")),
|
||||
// TODO(albrow): Handle Credential which might have different types.
|
||||
// Credential: iceServerValue.Get("credential"),
|
||||
CredentialType: newICECredentialType(valueToStringOrZero(iceServerValue.Get("credentialType"))),
|
||||
}
|
||||
}
|
||||
|
||||
func sessionDescriptionToValue(desc *SessionDescription) js.Value {
|
||||
if desc == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"type": desc.Type.String(),
|
||||
"sdp": desc.SDP,
|
||||
})
|
||||
}
|
||||
|
||||
func valueToSessionDescription(descValue js.Value) *SessionDescription {
|
||||
if descValue == js.Null() || descValue == js.Undefined() {
|
||||
return nil
|
||||
}
|
||||
return &SessionDescription{
|
||||
Type: newSDPType(descValue.Get("type").String()),
|
||||
SDP: descValue.Get("sdp").String(),
|
||||
}
|
||||
}
|
||||
|
||||
func offerOptionsToValue(offerOptions *OfferOptions) js.Value {
|
||||
if offerOptions == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"iceRestart": offerOptions.ICERestart,
|
||||
"voiceActivityDetection": offerOptions.VoiceActivityDetection,
|
||||
})
|
||||
}
|
||||
|
||||
func answerOptionsToValue(answerOptions *AnswerOptions) js.Value {
|
||||
if answerOptions == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"voiceActivityDetection": answerOptions.VoiceActivityDetection,
|
||||
})
|
||||
}
|
||||
|
||||
func iceCandidateInitToValue(candidate ICECandidateInit) js.Value {
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"candidate": candidate.Candidate,
|
||||
"sdpMid": stringPointerToValue(candidate.SDPMid),
|
||||
"sdpMLineIndex": uint16PointerToValue(candidate.SDPMLineIndex),
|
||||
"usernameFragment": candidate.UsernameFragment,
|
||||
})
|
||||
}
|
||||
|
||||
func dataChannelInitToValue(options *DataChannelInit) js.Value {
|
||||
if options == nil {
|
||||
return js.Undefined()
|
||||
}
|
||||
|
||||
return js.ValueOf(map[string]interface{}{
|
||||
"ordered": boolPointerToValue(options.Ordered),
|
||||
"maxPacketLifeTime": uint16PointerToValue(options.MaxPacketLifeTime),
|
||||
"maxRetransmits": uint16PointerToValue(options.MaxRetransmits),
|
||||
"protocol": stringPointerToValue(options.Protocol),
|
||||
"negotiated": boolPointerToValue(options.Negotiated),
|
||||
"id": uint16PointerToValue(options.ID),
|
||||
|
||||
// Note(albrow) Priority is not included in MDN WebRTC documentation. Should
|
||||
// we include it here?
|
||||
// "priority": options.Priority
|
||||
})
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import "fmt"
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// +build !js
|
||||
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
|
Reference in New Issue
Block a user