mirror of
https://github.com/pion/webrtc.git
synced 2025-10-25 16:20:38 +08:00
389 lines
11 KiB
Go
389 lines
11 KiB
Go
package webrtc
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
|
|
"github.com/pions/datachannel"
|
|
sugar "github.com/pions/webrtc/pkg/datachannel"
|
|
"github.com/pions/webrtc/pkg/rtcerr"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const dataChannelBufferSize = 16384 // Lowest common denominator among browsers
|
|
|
|
// RTCDataChannel represents a WebRTC DataChannel
|
|
// The RTCDataChannel interface represents a network channel
|
|
// which can be used for bidirectional peer-to-peer transfers of arbitrary data
|
|
type RTCDataChannel struct {
|
|
mu sync.RWMutex
|
|
|
|
// Label represents a label that can be used to distinguish this
|
|
// RTCDataChannel object from other RTCDataChannel objects. Scripts are
|
|
// allowed to create multiple RTCDataChannel objects with the same label.
|
|
Label string
|
|
|
|
// Ordered represents if the RTCDataChannel is ordered, and false if
|
|
// out-of-order delivery is allowed.
|
|
Ordered bool
|
|
|
|
// MaxPacketLifeTime represents the length of the time window (msec) during
|
|
// which transmissions and retransmissions may occur in unreliable mode.
|
|
MaxPacketLifeTime *uint16
|
|
|
|
// MaxRetransmits represents the maximum number of retransmissions that are
|
|
// attempted in unreliable mode.
|
|
MaxRetransmits *uint16
|
|
|
|
// Protocol represents the name of the sub-protocol used with this
|
|
// RTCDataChannel.
|
|
Protocol string
|
|
|
|
// Negotiated represents whether this RTCDataChannel was negotiated by the
|
|
// application (true), or not (false).
|
|
Negotiated bool
|
|
|
|
// ID represents the ID for this RTCDataChannel. The value is initially
|
|
// null, which is what will be returned if the ID was not provided at
|
|
// channel creation time, and the DTLS role of the SCTP transport has not
|
|
// yet been negotiated. 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.
|
|
ID *uint16
|
|
|
|
// Priority represents the priority for this RTCDataChannel. The priority is
|
|
// assigned at channel creation time.
|
|
Priority RTCPriorityType
|
|
|
|
// ReadyState represents the state of the RTCDataChannel object.
|
|
ReadyState RTCDataChannelState
|
|
|
|
// 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.
|
|
BufferedAmount uint64
|
|
|
|
// 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
|
|
// RTCDataChannel, but the application may change its value at any time.
|
|
BufferedAmountLowThreshold uint64
|
|
|
|
// The binaryType represents attribute MUST, on getting, return the value to
|
|
// which it was last set. On setting, if the new value is either the string
|
|
// "blob" or the string "arraybuffer", then set the IDL attribute to this
|
|
// new value. Otherwise, throw a SyntaxError. When an RTCDataChannel object
|
|
// is created, the binaryType attribute MUST be initialized to the string
|
|
// "blob". This attribute controls how binary data is exposed to scripts.
|
|
// binaryType string
|
|
|
|
// OnOpen func()
|
|
// OnBufferedAmountLow func()
|
|
// OnError func()
|
|
// OnClose func()
|
|
|
|
onMessageHandler func(sugar.Payload)
|
|
onOpenHandler func()
|
|
onCloseHandler func()
|
|
|
|
sctpTransport *RTCSctpTransport
|
|
dataChannel *datachannel.DataChannel
|
|
|
|
// A reference to the associated api object used by this datachannel
|
|
api *API
|
|
}
|
|
|
|
// NewRTCDataChannel creates a new RTCDataChannel.
|
|
// This constructor is part of the ORTC API. It is not
|
|
// meant to be used together with the basic WebRTC API.
|
|
func (api *API) NewRTCDataChannel(transport *RTCSctpTransport, params *RTCDataChannelParameters) (*RTCDataChannel, error) {
|
|
d, err := api.newRTCDataChannel(params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = d.open(transport)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
// newRTCDataChannel is an internal constructor for the data channel used to
|
|
// create the RTCDataChannel object before the networking is set up.
|
|
func (api *API) newRTCDataChannel(params *RTCDataChannelParameters) (*RTCDataChannel, error) {
|
|
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #5)
|
|
if len(params.Label) > 65535 {
|
|
return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit}
|
|
}
|
|
|
|
d := &RTCDataChannel{
|
|
Label: params.Label,
|
|
ID: ¶ms.ID,
|
|
ReadyState: RTCDataChannelStateConnecting,
|
|
api: api,
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
// open opens the datachannel over the sctp transport
|
|
func (d *RTCDataChannel) open(sctpTransport *RTCSctpTransport) error {
|
|
d.mu.RLock()
|
|
d.sctpTransport = sctpTransport
|
|
|
|
if err := d.ensureSCTP(); err != nil {
|
|
d.mu.RUnlock()
|
|
return err
|
|
}
|
|
|
|
cfg := &datachannel.Config{
|
|
ChannelType: datachannel.ChannelTypeReliable, // TODO: Wiring
|
|
Priority: datachannel.ChannelPriorityNormal, // TODO: Wiring
|
|
ReliabilityParameter: 0, // TODO: Wiring
|
|
Label: d.Label,
|
|
}
|
|
|
|
dc, err := datachannel.Dial(d.sctpTransport.association, *d.ID, cfg)
|
|
if err != nil {
|
|
d.mu.RUnlock()
|
|
return err
|
|
}
|
|
|
|
d.ReadyState = RTCDataChannelStateOpen
|
|
d.mu.RUnlock()
|
|
|
|
d.handleOpen(dc)
|
|
return nil
|
|
}
|
|
|
|
func (d *RTCDataChannel) ensureSCTP() error {
|
|
if d.sctpTransport == nil ||
|
|
d.sctpTransport.association == nil {
|
|
return errors.New("SCTP not establisched")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Transport returns the RTCSctpTransport instance the RTCDataChannel is sending over.
|
|
func (d *RTCDataChannel) Transport() *RTCSctpTransport {
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
|
|
return d.sctpTransport
|
|
}
|
|
|
|
// OnOpen sets an event handler which is invoked when
|
|
// the underlying data transport has been established (or re-established).
|
|
func (d *RTCDataChannel) OnOpen(f func()) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.onOpenHandler = f
|
|
}
|
|
|
|
func (d *RTCDataChannel) onOpen() (done chan struct{}) {
|
|
d.mu.RLock()
|
|
hdlr := d.onOpenHandler
|
|
d.mu.RUnlock()
|
|
|
|
done = make(chan struct{})
|
|
if hdlr == nil {
|
|
close(done)
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
hdlr()
|
|
close(done)
|
|
}()
|
|
|
|
return
|
|
}
|
|
|
|
// OnClose sets an event handler which is invoked when
|
|
// the underlying data transport has been closed.
|
|
func (d *RTCDataChannel) OnClose(f func()) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.onCloseHandler = f
|
|
}
|
|
|
|
func (d *RTCDataChannel) onClose() (done chan struct{}) {
|
|
d.mu.RLock()
|
|
hdlr := d.onCloseHandler
|
|
d.mu.RUnlock()
|
|
|
|
done = make(chan struct{})
|
|
if hdlr == nil {
|
|
close(done)
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
hdlr()
|
|
close(done)
|
|
}()
|
|
|
|
return
|
|
}
|
|
|
|
// OnMessage sets an event handler which is invoked on a message
|
|
// arrival over the sctp transport from a remote peer.
|
|
// OnMessage can currently receive messages up to 16384 bytes
|
|
// in size. Check out the detach API if you want to use larger
|
|
// message sizes. Note that browser support for larger messages
|
|
// is also limited.
|
|
func (d *RTCDataChannel) OnMessage(f func(p sugar.Payload)) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.onMessageHandler = f
|
|
}
|
|
|
|
func (d *RTCDataChannel) onMessage(p sugar.Payload) {
|
|
d.mu.RLock()
|
|
hdlr := d.onMessageHandler
|
|
d.mu.RUnlock()
|
|
|
|
if hdlr == nil || p == nil {
|
|
return
|
|
}
|
|
hdlr(p)
|
|
}
|
|
|
|
// Onmessage sets an event handler which is invoked on a message
|
|
// arrival over the sctp transport from a remote peer.
|
|
//
|
|
// Deprecated: use OnMessage instead.
|
|
func (d *RTCDataChannel) Onmessage(f func(p sugar.Payload)) {
|
|
d.OnMessage(f)
|
|
}
|
|
|
|
func (d *RTCDataChannel) handleOpen(dc *datachannel.DataChannel) {
|
|
d.mu.Lock()
|
|
d.dataChannel = dc
|
|
d.mu.Unlock()
|
|
|
|
d.onOpen()
|
|
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if !d.api.settingEngine.detach.DataChannels {
|
|
go d.readLoop()
|
|
}
|
|
}
|
|
|
|
func (d *RTCDataChannel) readLoop() {
|
|
for {
|
|
buffer := make([]byte, dataChannelBufferSize)
|
|
n, isString, err := d.dataChannel.ReadDataChannel(buffer)
|
|
if err == io.ErrShortBuffer {
|
|
pcLog.Warnf("Failed to read from data channel: The message is larger than %d bytes.\n", dataChannelBufferSize)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
d.mu.Lock()
|
|
d.ReadyState = RTCDataChannelStateClosed
|
|
d.mu.Unlock()
|
|
if err != io.EOF {
|
|
// TODO: Throw OnError
|
|
fmt.Println("Failed to read from data channel", err)
|
|
}
|
|
d.onClose()
|
|
return
|
|
}
|
|
|
|
if isString {
|
|
d.onMessage(&sugar.PayloadString{Data: buffer[:n]})
|
|
continue
|
|
}
|
|
d.onMessage(&sugar.PayloadBinary{Data: buffer[:n]})
|
|
}
|
|
}
|
|
|
|
// Send sends the passed message to the DataChannel peer
|
|
func (d *RTCDataChannel) Send(payload sugar.Payload) error {
|
|
err := d.ensureOpen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var data []byte
|
|
isString := false
|
|
|
|
switch p := payload.(type) {
|
|
case sugar.PayloadString:
|
|
data = p.Data
|
|
isString = true
|
|
case sugar.PayloadBinary:
|
|
data = p.Data
|
|
default:
|
|
return errors.Errorf("unknown DataChannel Payload (%s)", payload.PayloadType())
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
data = []byte{0}
|
|
}
|
|
|
|
_, err = d.dataChannel.WriteDataChannel(data, isString)
|
|
return err
|
|
}
|
|
|
|
func (d *RTCDataChannel) ensureOpen() error {
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
if d.ReadyState != RTCDataChannelStateOpen {
|
|
return &rtcerr.InvalidStateError{Err: ErrDataChannelNotOpen}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Detach allows you to detach the underlying datachannel. This provides
|
|
// an idiomatic API to work with, however it disables the OnMessage callback.
|
|
// Before calling Detach you have to enable this behavior by calling
|
|
// webrtc.DetachDataChannels(). Combining detached and normal data channels
|
|
// is not supported.
|
|
// Please reffer to the data-channels-detach example and the
|
|
// pions/datachannel documentation for the correct way to handle the
|
|
// resulting DataChannel object.
|
|
func (d *RTCDataChannel) Detach() (*datachannel.DataChannel, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if !d.api.settingEngine.detach.DataChannels {
|
|
return nil, errors.New("enable detaching by calling webrtc.DetachDataChannels()")
|
|
}
|
|
|
|
if d.dataChannel == nil {
|
|
return nil, errors.New("datachannel not opened yet, try calling Detach from OnOpen")
|
|
}
|
|
|
|
return d.dataChannel, nil
|
|
}
|
|
|
|
// Close Closes the RTCDataChannel. It may be called regardless of whether
|
|
// the RTCDataChannel object was created by this peer or the remote peer.
|
|
func (d *RTCDataChannel) Close() error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
if d.ReadyState == RTCDataChannelStateClosing ||
|
|
d.ReadyState == RTCDataChannelStateClosed {
|
|
return nil
|
|
}
|
|
|
|
d.ReadyState = RTCDataChannelStateClosing
|
|
|
|
return d.dataChannel.Close()
|
|
}
|