mirror of
				https://github.com/pion/webrtc.git
				synced 2025-10-30 02:12:03 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			1355 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1355 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document.
 | |
| package webrtc
 | |
| 
 | |
| import (
 | |
| 	"crypto/ecdsa"
 | |
| 	"crypto/elliptic"
 | |
| 	"crypto/rand"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"encoding/binary"
 | |
| 
 | |
| 	"github.com/pions/datachannel"
 | |
| 	"github.com/pions/sdp"
 | |
| 	"github.com/pions/webrtc/internal/network"
 | |
| 	"github.com/pions/webrtc/pkg/ice"
 | |
| 	"github.com/pions/webrtc/pkg/media"
 | |
| 	"github.com/pions/webrtc/pkg/rtcerr"
 | |
| 	"github.com/pions/webrtc/pkg/rtcp"
 | |
| 	"github.com/pions/webrtc/pkg/rtp"
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| // Unknown defines default public constant to use for "enum" like struct
 | |
| // comparisons when no value was defined.
 | |
| const Unknown = iota
 | |
| 
 | |
| // RTCPeerConnection represents a WebRTC connection that establishes a
 | |
| // peer-to-peer communications with another RTCPeerConnection instance in a
 | |
| // browser, or to another endpoint implementing the required protocols.
 | |
| type RTCPeerConnection struct {
 | |
| 	sync.RWMutex
 | |
| 
 | |
| 	configuration RTCConfiguration
 | |
| 
 | |
| 	// CurrentLocalDescription represents the local description that was
 | |
| 	// successfully negotiated the last time the RTCPeerConnection transitioned
 | |
| 	// into the stable state plus any local candidates that have been generated
 | |
| 	// by the IceAgent since the offer or answer was created.
 | |
| 	CurrentLocalDescription *RTCSessionDescription
 | |
| 
 | |
| 	// 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
 | |
| 	// RTCPeerConnection is in the stable state, the value is null.
 | |
| 	PendingLocalDescription *RTCSessionDescription
 | |
| 
 | |
| 	// CurrentRemoteDescription represents the last remote description that was
 | |
| 	// successfully negotiated the last time the RTCPeerConnection transitioned
 | |
| 	// into the stable state plus any remote candidates that have been supplied
 | |
| 	// via AddIceCandidate() since the offer or answer was created.
 | |
| 	CurrentRemoteDescription *RTCSessionDescription
 | |
| 
 | |
| 	// 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 RTCPeerConnection is in the stable state, the value is
 | |
| 	// null.
 | |
| 	PendingRemoteDescription *RTCSessionDescription
 | |
| 
 | |
| 	// SignalingState attribute returns the signaling state of the
 | |
| 	// RTCPeerConnection instance.
 | |
| 	SignalingState RTCSignalingState
 | |
| 
 | |
| 	// IceGatheringState attribute returns the ICE gathering state of the
 | |
| 	// RTCPeerConnection instance.
 | |
| 	IceGatheringState RTCIceGatheringState // FIXME NOT-USED
 | |
| 
 | |
| 	// IceConnectionState attribute returns the ICE connection state of the
 | |
| 	// RTCPeerConnection instance.
 | |
| 	// IceConnectionState RTCIceConnectionState  // FIXME SWAP-FOR-THIS
 | |
| 	IceConnectionState ice.ConnectionState // FIXME REMOVE
 | |
| 
 | |
| 	// ConnectionState attribute returns the connection state of the
 | |
| 	// RTCPeerConnection instance.
 | |
| 	ConnectionState RTCPeerConnectionState
 | |
| 
 | |
| 	idpLoginURL *string
 | |
| 
 | |
| 	isClosed          bool
 | |
| 	negotiationNeeded bool
 | |
| 
 | |
| 	lastOffer  string
 | |
| 	lastAnswer string
 | |
| 
 | |
| 	// Media
 | |
| 	mediaEngine     *MediaEngine
 | |
| 	rtpTransceivers []*RTCRtpTransceiver
 | |
| 
 | |
| 	// sctpTransport
 | |
| 	sctpTransport *RTCSctpTransport
 | |
| 
 | |
| 	// DataChannels
 | |
| 	dataChannels map[uint16]*RTCDataChannel
 | |
| 
 | |
| 	// OnNegotiationNeeded        func() // FIXME NOT-USED
 | |
| 	// OnIceCandidate             func() // FIXME NOT-USED
 | |
| 	// OnIceCandidateError        func() // FIXME NOT-USED
 | |
| 
 | |
| 	// OnIceGatheringStateChange  func() // FIXME NOT-USED
 | |
| 	// OnConnectionStateChange    func() // FIXME NOT-USED
 | |
| 
 | |
| 	onSignalingStateChangeHandler     func(RTCSignalingState)
 | |
| 	onICEConnectionStateChangeHandler func(ice.ConnectionState)
 | |
| 	onTrackHandler                    func(*RTCTrack)
 | |
| 	onDataChannelHandler              func(*RTCDataChannel)
 | |
| 
 | |
| 	// Deprecated: Internal mechanism which will be removed.
 | |
| 	networkManager *network.Manager
 | |
| }
 | |
| 
 | |
| // New creates a new RTCPeerConfiguration with the provided configuration
 | |
| func New(configuration RTCConfiguration) (*RTCPeerConnection, error) {
 | |
| 	// https://w3c.github.io/webrtc-pc/#constructor (Step #2)
 | |
| 	// Some variables defined explicitly despite their implicit zero values to
 | |
| 	// allow better readability to understand what is happening.
 | |
| 	pc := RTCPeerConnection{
 | |
| 		configuration: RTCConfiguration{
 | |
| 			IceServers:           []RTCIceServer{},
 | |
| 			IceTransportPolicy:   RTCIceTransportPolicyAll,
 | |
| 			BundlePolicy:         RTCBundlePolicyBalanced,
 | |
| 			RtcpMuxPolicy:        RTCRtcpMuxPolicyRequire,
 | |
| 			Certificates:         []RTCCertificate{},
 | |
| 			IceCandidatePoolSize: 0,
 | |
| 		},
 | |
| 		isClosed:          false,
 | |
| 		negotiationNeeded: false,
 | |
| 		lastOffer:         "",
 | |
| 		lastAnswer:        "",
 | |
| 		SignalingState:    RTCSignalingStateStable,
 | |
| 		// IceConnectionState: RTCIceConnectionStateNew, // FIXME SWAP-FOR-THIS
 | |
| 		IceConnectionState: ice.ConnectionStateNew, // FIXME REMOVE
 | |
| 		IceGatheringState:  RTCIceGatheringStateNew,
 | |
| 		ConnectionState:    RTCPeerConnectionStateNew,
 | |
| 		mediaEngine:        DefaultMediaEngine,
 | |
| 		sctpTransport:      newRTCSctpTransport(),
 | |
| 		dataChannels:       make(map[uint16]*RTCDataChannel),
 | |
| 	}
 | |
| 
 | |
| 	var err error
 | |
| 	if err = pc.initConfiguration(configuration); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var urls []*ice.URL
 | |
| 	for _, server := range pc.configuration.IceServers {
 | |
| 		for _, rawURL := range server.URLs {
 | |
| 			var url *ice.URL
 | |
| 			url, err = ice.ParseURL(rawURL)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 
 | |
| 			urls = append(urls, url)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	pc.networkManager, err = network.NewManager(urls, pc.generateChannel, pc.iceStateChange, defaultSettingEngine.EphemeralUDP.PortMin, defaultSettingEngine.EphemeralUDP.PortMax)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &pc, nil
 | |
| }
 | |
| 
 | |
| // initConfiguration defines validation of the specified RTCConfiguration and
 | |
| // its assignment to the internal configuration variable. This function differs
 | |
| // from its SetConfiguration counterpart because most of the checks do not
 | |
| // include verification statements related to the existing state. Thus the
 | |
| // function describes only minor verification of some the struct variables.
 | |
| func (pc *RTCPeerConnection) initConfiguration(configuration RTCConfiguration) error {
 | |
| 	if configuration.PeerIdentity != "" {
 | |
| 		pc.configuration.PeerIdentity = configuration.PeerIdentity
 | |
| 	}
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#constructor (step #3)
 | |
| 	if len(configuration.Certificates) > 0 {
 | |
| 		now := time.Now()
 | |
| 		for _, x509Cert := range configuration.Certificates {
 | |
| 			if !x509Cert.Expires().IsZero() && now.After(x509Cert.Expires()) {
 | |
| 				return &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}
 | |
| 			}
 | |
| 			pc.configuration.Certificates = append(pc.configuration.Certificates, x509Cert)
 | |
| 		}
 | |
| 	} else {
 | |
| 		sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 		if err != nil {
 | |
| 			return &rtcerr.UnknownError{Err: err}
 | |
| 		}
 | |
| 		certificate, err := GenerateCertificate(sk)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		pc.configuration.Certificates = []RTCCertificate{*certificate}
 | |
| 	}
 | |
| 
 | |
| 	if configuration.BundlePolicy != RTCBundlePolicy(Unknown) {
 | |
| 		pc.configuration.BundlePolicy = configuration.BundlePolicy
 | |
| 	}
 | |
| 
 | |
| 	if configuration.RtcpMuxPolicy != RTCRtcpMuxPolicy(Unknown) {
 | |
| 		pc.configuration.RtcpMuxPolicy = configuration.RtcpMuxPolicy
 | |
| 	}
 | |
| 
 | |
| 	if configuration.IceCandidatePoolSize != 0 {
 | |
| 		pc.configuration.IceCandidatePoolSize = configuration.IceCandidatePoolSize
 | |
| 	}
 | |
| 
 | |
| 	if configuration.IceTransportPolicy != RTCIceTransportPolicy(Unknown) {
 | |
| 		pc.configuration.IceTransportPolicy = configuration.IceTransportPolicy
 | |
| 	}
 | |
| 
 | |
| 	if len(configuration.IceServers) > 0 {
 | |
| 		for _, server := range configuration.IceServers {
 | |
| 			if err := server.validate(); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 		pc.configuration.IceServers = configuration.IceServers
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // OnSignalingStateChange sets an event handler which is invoked when the
 | |
| // peer connection's signaling state changes
 | |
| func (pc *RTCPeerConnection) OnSignalingStateChange(f func(RTCSignalingState)) {
 | |
| 	pc.Lock()
 | |
| 	defer pc.Unlock()
 | |
| 	pc.onSignalingStateChangeHandler = f
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) onSignalingStateChange(newState RTCSignalingState) (done chan struct{}) {
 | |
| 	pc.RLock()
 | |
| 	hdlr := pc.onSignalingStateChangeHandler
 | |
| 	pc.RUnlock()
 | |
| 
 | |
| 	done = make(chan struct{})
 | |
| 	if hdlr == nil {
 | |
| 		close(done)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	go func() {
 | |
| 		hdlr(newState)
 | |
| 		close(done)
 | |
| 	}()
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // OnDataChannel sets an event handler which is invoked when a data
 | |
| // channel message arrives from a remote peer.
 | |
| func (pc *RTCPeerConnection) OnDataChannel(f func(*RTCDataChannel)) {
 | |
| 	pc.Lock()
 | |
| 	defer pc.Unlock()
 | |
| 	pc.onDataChannelHandler = f
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) onDataChannel(dc *RTCDataChannel) (done chan struct{}) {
 | |
| 	pc.RLock()
 | |
| 	hdlr := pc.onDataChannelHandler
 | |
| 	pc.RUnlock()
 | |
| 
 | |
| 	done = make(chan struct{})
 | |
| 	if hdlr == nil || dc == nil {
 | |
| 		close(done)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Run this synchronously to allow setup done in onDataChannelFn()
 | |
| 	// to complete before datachannel event handlers might be called.
 | |
| 	go func() {
 | |
| 		hdlr(dc)
 | |
| 		close(done)
 | |
| 	}()
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // OnTrack sets an event handler which is called when remote track
 | |
| // arrives from a remote peer.
 | |
| func (pc *RTCPeerConnection) OnTrack(f func(*RTCTrack)) {
 | |
| 	pc.Lock()
 | |
| 	defer pc.Unlock()
 | |
| 	pc.onTrackHandler = f
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) onTrack(t *RTCTrack) (done chan struct{}) {
 | |
| 	pc.RLock()
 | |
| 	hdlr := pc.onTrackHandler
 | |
| 	pc.RUnlock()
 | |
| 
 | |
| 	done = make(chan struct{})
 | |
| 	if hdlr == nil || t == nil {
 | |
| 		close(done)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	go func() {
 | |
| 		hdlr(t)
 | |
| 		close(done)
 | |
| 	}()
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // OnICEConnectionStateChange sets an event handler which is called
 | |
| // when an ICE connection state is changed.
 | |
| func (pc *RTCPeerConnection) OnICEConnectionStateChange(f func(ice.ConnectionState)) {
 | |
| 	pc.Lock()
 | |
| 	defer pc.Unlock()
 | |
| 	pc.onICEConnectionStateChangeHandler = f
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) onICEConnectionStateChange(cs ice.ConnectionState) (done chan struct{}) {
 | |
| 	pc.RLock()
 | |
| 	hdlr := pc.onICEConnectionStateChangeHandler
 | |
| 	pc.RUnlock()
 | |
| 
 | |
| 	done = make(chan struct{})
 | |
| 	if hdlr == nil {
 | |
| 		close(done)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	go func() {
 | |
| 		hdlr(cs)
 | |
| 		close(done)
 | |
| 	}()
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // SetConfiguration updates the configuration of this RTCPeerConnection object.
 | |
| func (pc *RTCPeerConnection) SetConfiguration(configuration RTCConfiguration) error {
 | |
| 	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2)
 | |
| 	if pc.isClosed {
 | |
| 		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 | |
| 	}
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #3)
 | |
| 	if configuration.PeerIdentity != "" {
 | |
| 		if configuration.PeerIdentity != pc.configuration.PeerIdentity {
 | |
| 			return &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity}
 | |
| 		}
 | |
| 		pc.configuration.PeerIdentity = configuration.PeerIdentity
 | |
| 	}
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #4)
 | |
| 	if len(configuration.Certificates) > 0 {
 | |
| 		if len(configuration.Certificates) != len(pc.configuration.Certificates) {
 | |
| 			return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}
 | |
| 		}
 | |
| 
 | |
| 		for i, certificate := range configuration.Certificates {
 | |
| 			if !pc.configuration.Certificates[i].Equals(certificate) {
 | |
| 				return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}
 | |
| 			}
 | |
| 		}
 | |
| 		pc.configuration.Certificates = configuration.Certificates
 | |
| 	}
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #5)
 | |
| 	if configuration.BundlePolicy != RTCBundlePolicy(Unknown) {
 | |
| 		if configuration.BundlePolicy != pc.configuration.BundlePolicy {
 | |
| 			return &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy}
 | |
| 		}
 | |
| 		pc.configuration.BundlePolicy = configuration.BundlePolicy
 | |
| 	}
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #6)
 | |
| 	if configuration.RtcpMuxPolicy != RTCRtcpMuxPolicy(Unknown) {
 | |
| 		if configuration.RtcpMuxPolicy != pc.configuration.RtcpMuxPolicy {
 | |
| 			return &rtcerr.InvalidModificationError{Err: ErrModifyingRtcpMuxPolicy}
 | |
| 		}
 | |
| 		pc.configuration.RtcpMuxPolicy = configuration.RtcpMuxPolicy
 | |
| 	}
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #7)
 | |
| 	if configuration.IceCandidatePoolSize != 0 {
 | |
| 		if pc.configuration.IceCandidatePoolSize != configuration.IceCandidatePoolSize &&
 | |
| 			pc.LocalDescription() != nil {
 | |
| 			return &rtcerr.InvalidModificationError{Err: ErrModifyingIceCandidatePoolSize}
 | |
| 		}
 | |
| 		pc.configuration.IceCandidatePoolSize = configuration.IceCandidatePoolSize
 | |
| 	}
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #8)
 | |
| 	if configuration.IceTransportPolicy != RTCIceTransportPolicy(Unknown) {
 | |
| 		pc.configuration.IceTransportPolicy = configuration.IceTransportPolicy
 | |
| 	}
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11)
 | |
| 	if len(configuration.IceServers) > 0 {
 | |
| 		// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3)
 | |
| 		for _, server := range configuration.IceServers {
 | |
| 			if err := server.validate(); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 		pc.configuration.IceServers = configuration.IceServers
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // GetConfiguration returns an RTCConfiguration object representing the current
 | |
| // configuration of this RTCPeerConnection object. The returned object is a
 | |
| // copy and direct mutation on it will not take affect until SetConfiguration
 | |
| // has been called with RTCConfiguration passed as its only argument.
 | |
| // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration
 | |
| func (pc *RTCPeerConnection) GetConfiguration() RTCConfiguration {
 | |
| 	return pc.configuration
 | |
| }
 | |
| 
 | |
| // ------------------------------------------------------------------------
 | |
| // --- FIXME - BELOW CODE NEEDS REVIEW/CLEANUP
 | |
| // ------------------------------------------------------------------------
 | |
| 
 | |
| // CreateOffer starts the RTCPeerConnection and generates the localDescription
 | |
| func (pc *RTCPeerConnection) CreateOffer(options *RTCOfferOptions) (RTCSessionDescription, error) {
 | |
| 	useIdentity := pc.idpLoginURL != nil
 | |
| 	if options != nil {
 | |
| 		return RTCSessionDescription{}, errors.Errorf("TODO handle options")
 | |
| 	} else if useIdentity {
 | |
| 		return RTCSessionDescription{}, errors.Errorf("TODO handle identity provider")
 | |
| 	} else if pc.isClosed {
 | |
| 		return RTCSessionDescription{}, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 | |
| 	}
 | |
| 
 | |
| 	d := sdp.NewJSEPSessionDescription(useIdentity)
 | |
| 	pc.addFingerprint(d)
 | |
| 	candidates, err := pc.generateLocalCandidates()
 | |
| 	if err != nil {
 | |
| 		return RTCSessionDescription{}, err
 | |
| 	}
 | |
| 
 | |
| 	bundleValue := "BUNDLE"
 | |
| 
 | |
| 	if pc.addRTPMediaSection(d, RTCRtpCodecTypeAudio, "audio", RTCRtpTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) {
 | |
| 		bundleValue += " audio"
 | |
| 	}
 | |
| 	if pc.addRTPMediaSection(d, RTCRtpCodecTypeVideo, "video", RTCRtpTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) {
 | |
| 		bundleValue += " video"
 | |
| 	}
 | |
| 
 | |
| 	pc.addDataMediaSection(d, "data", candidates, sdp.ConnectionRoleActpass)
 | |
| 	d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue+" data")
 | |
| 
 | |
| 	for _, m := range d.MediaDescriptions {
 | |
| 		m.WithPropertyAttribute("setup:actpass")
 | |
| 	}
 | |
| 
 | |
| 	desc := RTCSessionDescription{
 | |
| 		Type:   RTCSdpTypeOffer,
 | |
| 		Sdp:    d.Marshal(),
 | |
| 		parsed: d,
 | |
| 	}
 | |
| 	pc.lastOffer = desc.Sdp
 | |
| 
 | |
| 	// FIXME: This doesn't follow the JS API spec, but removing it
 | |
| 	// would mean our examples and existing code have to change
 | |
| 	if err := pc.SetLocalDescription(desc); err != nil {
 | |
| 		return RTCSessionDescription{}, err
 | |
| 	}
 | |
| 	return desc, nil
 | |
| }
 | |
| 
 | |
| // CreateAnswer starts the RTCPeerConnection and generates the localDescription
 | |
| func (pc *RTCPeerConnection) CreateAnswer(options *RTCAnswerOptions) (RTCSessionDescription, error) {
 | |
| 	useIdentity := pc.idpLoginURL != nil
 | |
| 	if options != nil {
 | |
| 		return RTCSessionDescription{}, errors.Errorf("TODO handle options")
 | |
| 	} else if useIdentity {
 | |
| 		return RTCSessionDescription{}, errors.Errorf("TODO handle identity provider")
 | |
| 	} else if pc.isClosed {
 | |
| 		return RTCSessionDescription{}, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 | |
| 	}
 | |
| 
 | |
| 	candidates, err := pc.generateLocalCandidates()
 | |
| 	if err != nil {
 | |
| 		return RTCSessionDescription{}, err
 | |
| 	}
 | |
| 	d := sdp.NewJSEPSessionDescription(useIdentity)
 | |
| 	pc.addFingerprint(d)
 | |
| 
 | |
| 	bundleValue := "BUNDLE"
 | |
| 	for _, remoteMedia := range pc.RemoteDescription().parsed.MediaDescriptions {
 | |
| 		// TODO @trivigy better SDP parser
 | |
| 		var peerDirection RTCRtpTransceiverDirection
 | |
| 		midValue := ""
 | |
| 		for _, a := range remoteMedia.Attributes {
 | |
| 			if strings.HasPrefix(*a.String(), "mid") {
 | |
| 				midValue = (*a.String())[len("mid:"):]
 | |
| 			} else if strings.HasPrefix(*a.String(), "sendrecv") {
 | |
| 				peerDirection = RTCRtpTransceiverDirectionSendrecv
 | |
| 			} else if strings.HasPrefix(*a.String(), "sendonly") {
 | |
| 				peerDirection = RTCRtpTransceiverDirectionSendonly
 | |
| 			} else if strings.HasPrefix(*a.String(), "recvonly") {
 | |
| 				peerDirection = RTCRtpTransceiverDirectionRecvonly
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		appendBundle := func() {
 | |
| 			bundleValue += " " + midValue
 | |
| 		}
 | |
| 
 | |
| 		if strings.HasPrefix(*remoteMedia.MediaName.String(), "audio") {
 | |
| 			if pc.addRTPMediaSection(d, RTCRtpCodecTypeAudio, midValue, peerDirection, candidates, sdp.ConnectionRoleActive) {
 | |
| 				appendBundle()
 | |
| 			}
 | |
| 		} else if strings.HasPrefix(*remoteMedia.MediaName.String(), "video") {
 | |
| 			if pc.addRTPMediaSection(d, RTCRtpCodecTypeVideo, midValue, peerDirection, candidates, sdp.ConnectionRoleActive) {
 | |
| 				appendBundle()
 | |
| 			}
 | |
| 		} else if strings.HasPrefix(*remoteMedia.MediaName.String(), "application") {
 | |
| 			pc.addDataMediaSection(d, midValue, candidates, sdp.ConnectionRoleActive)
 | |
| 			appendBundle()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue)
 | |
| 
 | |
| 	desc := RTCSessionDescription{
 | |
| 		Type:   RTCSdpTypeAnswer,
 | |
| 		Sdp:    d.Marshal(),
 | |
| 		parsed: d,
 | |
| 	}
 | |
| 	pc.lastAnswer = desc.Sdp
 | |
| 
 | |
| 	// FIXME: This doesn't follow the JS API spec, but removing it
 | |
| 	// would mean our examples and existing code have to change
 | |
| 	if err := pc.SetLocalDescription(desc); err != nil {
 | |
| 		return RTCSessionDescription{}, err
 | |
| 	}
 | |
| 	return desc, nil
 | |
| }
 | |
| 
 | |
| // 4.4.1.6 Set the RTCSessionDescription
 | |
| func (pc *RTCPeerConnection) setDescription(sd *RTCSessionDescription, op rtcStateChangeOp) error {
 | |
| 	if pc.isClosed {
 | |
| 		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 | |
| 	}
 | |
| 
 | |
| 	cur := pc.SignalingState
 | |
| 	setLocal := rtcStateChangeOpSetLocal
 | |
| 	setRemote := rtcStateChangeOpSetRemote
 | |
| 	newSdpDoesNotMatchOffer := &rtcerr.InvalidModificationError{Err: errors.New("New sdp does not match previous offer")}
 | |
| 	newSdpDoesNotMatchAnswer := &rtcerr.InvalidModificationError{Err: errors.New("New sdp does not match previous answer")}
 | |
| 
 | |
| 	var nextState RTCSignalingState
 | |
| 	var err error
 | |
| 	switch op {
 | |
| 	case setLocal:
 | |
| 		switch sd.Type {
 | |
| 		// stable->SetLocal(offer)->have-local-offer
 | |
| 		case RTCSdpTypeOffer:
 | |
| 			if sd.Sdp != pc.lastOffer {
 | |
| 				return newSdpDoesNotMatchOffer
 | |
| 			}
 | |
| 			nextState, err = checkNextSignalingState(cur, RTCSignalingStateHaveLocalOffer, setLocal, sd.Type)
 | |
| 			if err == nil {
 | |
| 				pc.PendingLocalDescription = sd
 | |
| 			}
 | |
| 		// have-remote-offer->SetLocal(answer)->stable
 | |
| 		// have-local-pranswer->SetLocal(answer)->stable
 | |
| 		case RTCSdpTypeAnswer:
 | |
| 			if sd.Sdp != pc.lastAnswer {
 | |
| 				return newSdpDoesNotMatchAnswer
 | |
| 			}
 | |
| 			nextState, err = checkNextSignalingState(cur, RTCSignalingStateStable, setLocal, sd.Type)
 | |
| 			if err == nil {
 | |
| 				pc.CurrentLocalDescription = sd
 | |
| 				pc.CurrentRemoteDescription = pc.PendingRemoteDescription
 | |
| 				pc.PendingRemoteDescription = nil
 | |
| 				pc.PendingLocalDescription = nil
 | |
| 			}
 | |
| 		case RTCSdpTypeRollback:
 | |
| 			nextState, err = checkNextSignalingState(cur, RTCSignalingStateStable, setLocal, sd.Type)
 | |
| 			if err == nil {
 | |
| 				pc.PendingLocalDescription = nil
 | |
| 			}
 | |
| 		// have-remote-offer->SetLocal(pranswer)->have-local-pranswer
 | |
| 		case RTCSdpTypePranswer:
 | |
| 			if sd.Sdp != pc.lastAnswer {
 | |
| 				return newSdpDoesNotMatchAnswer
 | |
| 			}
 | |
| 			nextState, err = checkNextSignalingState(cur, RTCSignalingStateHaveLocalPranswer, setLocal, sd.Type)
 | |
| 			if err == nil {
 | |
| 				pc.PendingLocalDescription = sd
 | |
| 			}
 | |
| 		default:
 | |
| 			return &rtcerr.OperationError{Err: fmt.Errorf("Invalid state change op: %s(%s)", op, sd.Type)}
 | |
| 		}
 | |
| 	case setRemote:
 | |
| 		switch sd.Type {
 | |
| 		// stable->SetRemote(offer)->have-remote-offer
 | |
| 		case RTCSdpTypeOffer:
 | |
| 			nextState, err = checkNextSignalingState(cur, RTCSignalingStateHaveRemoteOffer, setRemote, sd.Type)
 | |
| 			if err == nil {
 | |
| 				pc.PendingRemoteDescription = sd
 | |
| 			}
 | |
| 		// have-local-offer->SetRemote(answer)->stable
 | |
| 		// have-remote-pranswer->SetRemote(answer)->stable
 | |
| 		case RTCSdpTypeAnswer:
 | |
| 			nextState, err = checkNextSignalingState(cur, RTCSignalingStateStable, setRemote, sd.Type)
 | |
| 			if err == nil {
 | |
| 				pc.CurrentRemoteDescription = sd
 | |
| 				pc.CurrentLocalDescription = pc.PendingLocalDescription
 | |
| 				pc.PendingRemoteDescription = nil
 | |
| 				pc.PendingLocalDescription = nil
 | |
| 			}
 | |
| 		case RTCSdpTypeRollback:
 | |
| 			nextState, err = checkNextSignalingState(cur, RTCSignalingStateStable, setRemote, sd.Type)
 | |
| 			if err == nil {
 | |
| 				pc.PendingRemoteDescription = nil
 | |
| 			}
 | |
| 		// have-local-offer->SetRemote(pranswer)->have-remote-pranswer
 | |
| 		case RTCSdpTypePranswer:
 | |
| 			nextState, err = checkNextSignalingState(cur, RTCSignalingStateHaveRemotePranswer, setRemote, sd.Type)
 | |
| 			if err == nil {
 | |
| 				pc.PendingRemoteDescription = sd
 | |
| 			}
 | |
| 		default:
 | |
| 			return &rtcerr.OperationError{Err: fmt.Errorf("Invalid state change op: %s(%s)", op, sd.Type)}
 | |
| 		}
 | |
| 	default:
 | |
| 		return &rtcerr.OperationError{Err: fmt.Errorf("Unhandled state change op: %q", op)}
 | |
| 	}
 | |
| 
 | |
| 	if err == nil {
 | |
| 		pc.SignalingState = nextState
 | |
| 		pc.onSignalingStateChange(nextState)
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // SetLocalDescription sets the SessionDescription of the local peer
 | |
| func (pc *RTCPeerConnection) SetLocalDescription(desc RTCSessionDescription) error {
 | |
| 	if pc.isClosed {
 | |
| 		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 | |
| 	}
 | |
| 
 | |
| 	// JSEP 5.4
 | |
| 	if desc.Sdp == "" {
 | |
| 		switch desc.Type {
 | |
| 		case RTCSdpTypeAnswer, RTCSdpTypePranswer:
 | |
| 			desc.Sdp = pc.lastAnswer
 | |
| 		case RTCSdpTypeOffer:
 | |
| 			desc.Sdp = pc.lastOffer
 | |
| 		default:
 | |
| 			return &rtcerr.InvalidModificationError{
 | |
| 				Err: fmt.Errorf("Invalid SDP type supplied to SetLocalDescription(): %s", desc.Type),
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// TODO: Initiate ICE candidate gathering?
 | |
| 
 | |
| 	desc.parsed = &sdp.SessionDescription{}
 | |
| 	if err := desc.parsed.Unmarshal(desc.Sdp); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return pc.setDescription(&desc, rtcStateChangeOpSetLocal)
 | |
| }
 | |
| 
 | |
| // 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 *RTCPeerConnection) LocalDescription() *RTCSessionDescription {
 | |
| 	if pc.PendingLocalDescription != nil {
 | |
| 		return pc.PendingLocalDescription
 | |
| 	}
 | |
| 	return pc.CurrentLocalDescription
 | |
| }
 | |
| 
 | |
| // SetRemoteDescription sets the SessionDescription of the remote peer
 | |
| func (pc *RTCPeerConnection) SetRemoteDescription(desc RTCSessionDescription) error {
 | |
| 	// FIXME: Remove this when renegotiation is supported
 | |
| 	if pc.CurrentRemoteDescription != nil {
 | |
| 		return errors.Errorf("remoteDescription is already defined, SetRemoteDescription can only be called once")
 | |
| 	}
 | |
| 	if pc.isClosed {
 | |
| 		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 | |
| 	}
 | |
| 
 | |
| 	desc.parsed = &sdp.SessionDescription{}
 | |
| 	if err := desc.parsed.Unmarshal(desc.Sdp); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := pc.setDescription(&desc, rtcStateChangeOpSetRemote); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	weOffer := true
 | |
| 	remoteUfrag := ""
 | |
| 	remotePwd := ""
 | |
| 	if desc.Type == RTCSdpTypeOffer {
 | |
| 		weOffer = false
 | |
| 	}
 | |
| 
 | |
| 	for _, m := range pc.RemoteDescription().parsed.MediaDescriptions {
 | |
| 		for _, a := range m.Attributes {
 | |
| 			if strings.HasPrefix(*a.String(), "candidate") {
 | |
| 				c, err := sdp.ICECandidateUnmarshal(*a.String())
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 
 | |
| 				if err := pc.networkManager.IceAgent.AddRemoteCandidate(c); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			} else if strings.HasPrefix(*a.String(), "ice-ufrag") {
 | |
| 				remoteUfrag = (*a.String())[len("ice-ufrag:"):]
 | |
| 			} else if strings.HasPrefix(*a.String(), "ice-pwd") {
 | |
| 				remotePwd = (*a.String())[len("ice-pwd:"):]
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fingerprint, ok := desc.parsed.Attribute("fingerprint")
 | |
| 	if !ok {
 | |
| 		fingerprint, ok = desc.parsed.MediaDescriptions[0].Attribute("fingerprint")
 | |
| 		if !ok {
 | |
| 			return errors.New("could not find fingerprint")
 | |
| 		}
 | |
| 	}
 | |
| 	var fingerprintHash string
 | |
| 	parts := strings.Split(fingerprint, " ")
 | |
| 	if len(parts) != 2 {
 | |
| 		return errors.New("invalid fingerprint")
 | |
| 	}
 | |
| 	fingerprint = parts[1]
 | |
| 	fingerprintHash = parts[0]
 | |
| 
 | |
| 	go func() {
 | |
| 		cert := pc.configuration.Certificates[0] // TODO: handle multiple certs
 | |
| 		err := pc.networkManager.Start(weOffer,
 | |
| 			remoteUfrag, remotePwd,
 | |
| 			cert.x509Cert, cert.privateKey, fingerprint, fingerprintHash)
 | |
| 		if err != nil {
 | |
| 			fmt.Println("Failed to start manager", err)
 | |
| 		}
 | |
| 
 | |
| 		// Temporary data channel glue
 | |
| 		pc.openDataChannels()
 | |
| 		pc.acceptDataChannels()
 | |
| 	}()
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // openDataChannels opens the existing data channels
 | |
| // TODO: Move to RTCDataChannel
 | |
| func (pc *RTCPeerConnection) openDataChannels() {
 | |
| 	pc.RLock()
 | |
| 	defer pc.RUnlock()
 | |
| 	for _, rtcDC := range pc.dataChannels {
 | |
| 		dc, err := pc.networkManager.OpenDataChannel(
 | |
| 			*rtcDC.ID,
 | |
| 			&datachannel.Config{
 | |
| 				ChannelType:          datachannel.ChannelTypeReliable,   // TODO: Wiring
 | |
| 				Priority:             datachannel.ChannelPriorityNormal, // TODO: Wiring
 | |
| 				ReliabilityParameter: 0,                                 // TODO: Wiring
 | |
| 				Label:                rtcDC.Label,
 | |
| 			})
 | |
| 		if err != nil {
 | |
| 			fmt.Println("failed to open data channel", err)
 | |
| 			continue
 | |
| 		}
 | |
| 		rtcDC.ReadyState = RTCDataChannelStateOpen
 | |
| 		rtcDC.handleOpen(dc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // acceptDataChannels accepts data channels
 | |
| // TODO: Move to RTCSctpTransport
 | |
| func (pc *RTCPeerConnection) acceptDataChannels() {
 | |
| 	for {
 | |
| 		dc, err := pc.networkManager.AcceptDataChannel()
 | |
| 		if err != nil {
 | |
| 			fmt.Println("Failed to accept data channel:", err)
 | |
| 			// TODO: Kill DataChannel/PeerConnection?
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		sid := dc.StreamIdentifier()
 | |
| 		rtcDC := &RTCDataChannel{
 | |
| 			ID:                &sid,
 | |
| 			Label:             dc.Config.Label,
 | |
| 			rtcPeerConnection: pc,
 | |
| 			ReadyState:        RTCDataChannelStateOpen,
 | |
| 		}
 | |
| 
 | |
| 		pc.Lock()
 | |
| 		pc.dataChannels[sid] = rtcDC
 | |
| 		pc.Unlock()
 | |
| 
 | |
| 		<-pc.onDataChannel(rtcDC)
 | |
| 		rtcDC.handleOpen(dc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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 *RTCPeerConnection) RemoteDescription() *RTCSessionDescription {
 | |
| 	if pc.PendingRemoteDescription != nil {
 | |
| 		return pc.PendingRemoteDescription
 | |
| 	}
 | |
| 	return pc.CurrentRemoteDescription
 | |
| }
 | |
| 
 | |
| // AddIceCandidate accepts an ICE candidate string and adds it
 | |
| // to the existing set of candidates
 | |
| func (pc *RTCPeerConnection) AddIceCandidate(s string) error {
 | |
| 	c, err := sdp.ICECandidateUnmarshal(s)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return pc.networkManager.IceAgent.AddRemoteCandidate(c)
 | |
| }
 | |
| 
 | |
| // ------------------------------------------------------------------------
 | |
| // --- FIXME - BELOW CODE NEEDS RE-ORGANIZATION - https://w3c.github.io/webrtc-pc/#rtp-media-api
 | |
| // ------------------------------------------------------------------------
 | |
| 
 | |
| // GetSenders returns the RTCRtpSender that are currently attached to this RTCPeerConnection
 | |
| func (pc *RTCPeerConnection) GetSenders() []RTCRtpSender {
 | |
| 	result := make([]RTCRtpSender, len(pc.rtpTransceivers))
 | |
| 	for i, tranceiver := range pc.rtpTransceivers {
 | |
| 		result[i] = *tranceiver.Sender
 | |
| 	}
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| // GetReceivers returns the RTCRtpReceivers that are currently attached to this RTCPeerConnection
 | |
| func (pc *RTCPeerConnection) GetReceivers() []RTCRtpReceiver {
 | |
| 	result := make([]RTCRtpReceiver, len(pc.rtpTransceivers))
 | |
| 	for i, tranceiver := range pc.rtpTransceivers {
 | |
| 		result[i] = *tranceiver.Receiver
 | |
| 	}
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| // GetTransceivers returns the RTCRtpTransceiver that are currently attached to this RTCPeerConnection
 | |
| func (pc *RTCPeerConnection) GetTransceivers() []RTCRtpTransceiver {
 | |
| 	result := make([]RTCRtpTransceiver, len(pc.rtpTransceivers))
 | |
| 	for i, tranceiver := range pc.rtpTransceivers {
 | |
| 		result[i] = *tranceiver
 | |
| 	}
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| // AddTrack adds a RTCTrack to the RTCPeerConnection
 | |
| func (pc *RTCPeerConnection) AddTrack(track *RTCTrack) (*RTCRtpSender, error) {
 | |
| 	if pc.isClosed {
 | |
| 		return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 | |
| 	}
 | |
| 	for _, transceiver := range pc.rtpTransceivers {
 | |
| 		if transceiver.Sender.Track == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		if track.ID == transceiver.Sender.Track.ID {
 | |
| 			return nil, &rtcerr.InvalidAccessError{Err: ErrExistingTrack}
 | |
| 		}
 | |
| 	}
 | |
| 	var transceiver *RTCRtpTransceiver
 | |
| 	for _, t := range pc.rtpTransceivers {
 | |
| 		if !t.stopped &&
 | |
| 			// t.Sender == nil && // TODO: check that the sender has never sent
 | |
| 			t.Sender.Track == nil &&
 | |
| 			t.Receiver.Track != nil &&
 | |
| 			t.Receiver.Track.Kind == track.Kind {
 | |
| 			transceiver = t
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if transceiver != nil {
 | |
| 		if err := transceiver.setSendingTrack(track); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	} else {
 | |
| 		var receiver *RTCRtpReceiver
 | |
| 		sender := newRTCRtpSender(track)
 | |
| 		transceiver = pc.newRTCRtpTransceiver(
 | |
| 			receiver,
 | |
| 			sender,
 | |
| 			RTCRtpTransceiverDirectionSendonly,
 | |
| 		)
 | |
| 	}
 | |
| 
 | |
| 	transceiver.Mid = track.Kind.String() // TODO: Mid generation
 | |
| 
 | |
| 	return transceiver.Sender, nil
 | |
| }
 | |
| 
 | |
| // func (pc *RTCPeerConnection) RemoveTrack() {
 | |
| // 	panic("not implemented yet") // FIXME NOT-IMPLEMENTED nolint
 | |
| // }
 | |
| 
 | |
| // func (pc *RTCPeerConnection) AddTransceiver() RTCRtpTransceiver {
 | |
| // 	panic("not implemented yet") // FIXME NOT-IMPLEMENTED nolint
 | |
| // }
 | |
| 
 | |
| // ------------------------------------------------------------------------
 | |
| // --- FIXME - BELOW CODE NEEDS RE-ORGANIZATION - https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api
 | |
| // ------------------------------------------------------------------------
 | |
| 
 | |
| // CreateDataChannel creates a new RTCDataChannel object with the given label
 | |
| // and optitional RTCDataChannelInit used to configure properties of the
 | |
| // underlying channel such as data reliability.
 | |
| func (pc *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChannelInit) (*RTCDataChannel, error) {
 | |
| 	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #2)
 | |
| 	if pc.isClosed {
 | |
| 		return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 | |
| 	}
 | |
| 
 | |
| 	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #5)
 | |
| 	if len(label) > 65535 {
 | |
| 		return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit}
 | |
| 	}
 | |
| 
 | |
| 	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #3)
 | |
| 	// Some variables defined explicitly despite their implicit zero values to
 | |
| 	// allow better readability to understand what is happening. Additionally,
 | |
| 	// some members are set to a non zero value default due to the default
 | |
| 	// definitions in https://w3c.github.io/webrtc-pc/#dom-rtcdatachannelinit
 | |
| 	// which are later overwriten by the options if any were specified.
 | |
| 	channel := RTCDataChannel{
 | |
| 		rtcPeerConnection: pc,
 | |
| 		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #4)
 | |
| 		Label:             label,
 | |
| 		Ordered:           true,
 | |
| 		MaxPacketLifeTime: nil,
 | |
| 		MaxRetransmits:    nil,
 | |
| 		Protocol:          "",
 | |
| 		Negotiated:        false,
 | |
| 		ID:                nil,
 | |
| 		Priority:          RTCPriorityTypeLow,
 | |
| 		// https://w3c.github.io/webrtc-pc/#dfn-create-an-rtcdatachannel (Step #2)
 | |
| 		ReadyState: RTCDataChannelStateConnecting,
 | |
| 		// https://w3c.github.io/webrtc-pc/#dfn-create-an-rtcdatachannel (Step #3)
 | |
| 		BufferedAmount: 0,
 | |
| 	}
 | |
| 
 | |
| 	if options != nil {
 | |
| 		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #7)
 | |
| 		if options.MaxPacketLifeTime != nil {
 | |
| 			channel.MaxPacketLifeTime = options.MaxPacketLifeTime
 | |
| 		}
 | |
| 
 | |
| 		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #8)
 | |
| 		if options.MaxRetransmits != nil {
 | |
| 			channel.MaxRetransmits = options.MaxRetransmits
 | |
| 		}
 | |
| 
 | |
| 		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #9)
 | |
| 		if options.Ordered != nil {
 | |
| 			channel.Ordered = *options.Ordered
 | |
| 		}
 | |
| 
 | |
| 		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #10)
 | |
| 		if options.Protocol != nil {
 | |
| 			channel.Protocol = *options.Protocol
 | |
| 		}
 | |
| 
 | |
| 		// https://w3c.github.io/webrtc-pc/#peer-to-peer-da	ta-api (Step #12)
 | |
| 		if options.Negotiated != nil {
 | |
| 			channel.Negotiated = *options.Negotiated
 | |
| 		}
 | |
| 
 | |
| 		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #13)
 | |
| 		if options.ID != nil && channel.Negotiated {
 | |
| 			channel.ID = options.ID
 | |
| 		}
 | |
| 
 | |
| 		// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #15)
 | |
| 		if options.Priority != nil {
 | |
| 			channel.Priority = *options.Priority
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #11)
 | |
| 	if len(channel.Protocol) > 65535 {
 | |
| 		return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit}
 | |
| 	}
 | |
| 
 | |
| 	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #14)
 | |
| 	if channel.Negotiated && channel.ID == nil {
 | |
| 		return nil, &rtcerr.TypeError{Err: ErrNegotiatedWithoutID}
 | |
| 	}
 | |
| 
 | |
| 	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #16)
 | |
| 	if channel.MaxPacketLifeTime != nil && channel.MaxRetransmits != nil {
 | |
| 		return nil, &rtcerr.TypeError{Err: ErrRetransmitsOrPacketLifeTime}
 | |
| 	}
 | |
| 
 | |
| 	// FIXME https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createdatachannel (Step #17)
 | |
| 
 | |
| 	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #20)
 | |
| 	channel.Transport = pc.sctpTransport
 | |
| 
 | |
| 	// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #19)
 | |
| 	if channel.ID == nil {
 | |
| 		var err error
 | |
| 		if channel.ID, err = pc.generateDataChannelID(true); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		// if err := channel.generateID(); err != nil {
 | |
| 		// 	return nil, err
 | |
| 		// }
 | |
| 	}
 | |
| 
 | |
| 	// // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #18)
 | |
| 	if *channel.ID > 65534 {
 | |
| 		return nil, &rtcerr.TypeError{Err: ErrMaxDataChannelID}
 | |
| 	}
 | |
| 
 | |
| 	if pc.sctpTransport.State == RTCSctpTransportStateConnected &&
 | |
| 		*channel.ID >= *pc.sctpTransport.MaxChannels {
 | |
| 		return nil, &rtcerr.OperationError{Err: ErrMaxDataChannelID}
 | |
| 	}
 | |
| 
 | |
| 	// Remember datachannel
 | |
| 	pc.dataChannels[*channel.ID] = &channel
 | |
| 
 | |
| 	// Send opening message
 | |
| 	// pc.networkManager.SendOpenChannelMessage(id, label)
 | |
| 
 | |
| 	return &channel, nil
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) generateDataChannelID(client bool) (*uint16, error) {
 | |
| 	var id uint16
 | |
| 	if !client {
 | |
| 		id++
 | |
| 	}
 | |
| 
 | |
| 	for ; id < *pc.sctpTransport.MaxChannels-1; id += 2 {
 | |
| 		_, ok := pc.dataChannels[id]
 | |
| 		if !ok {
 | |
| 			return &id, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return nil, &rtcerr.OperationError{Err: ErrMaxDataChannelID}
 | |
| }
 | |
| 
 | |
| // SetMediaEngine allows overwriting the default media engine used by the RTCPeerConnection
 | |
| // This enables RTCPeerConnection with support for different codecs
 | |
| func (pc *RTCPeerConnection) SetMediaEngine(m *MediaEngine) {
 | |
| 	pc.mediaEngine = m
 | |
| }
 | |
| 
 | |
| // SetIdentityProvider is used to configure an identity provider to generate identity assertions
 | |
| func (pc *RTCPeerConnection) SetIdentityProvider(provider string) error {
 | |
| 	return errors.Errorf("TODO SetIdentityProvider")
 | |
| }
 | |
| 
 | |
| // SendRTCP sends a user provided RTCP packet to the connected peer
 | |
| // If no peer is connected the packet is discarded
 | |
| func (pc *RTCPeerConnection) SendRTCP(pkt rtcp.Packet) error {
 | |
| 	raw, err := pkt.Marshal()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	pc.networkManager.SendRTCP(raw)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Close ends the RTCPeerConnection
 | |
| func (pc *RTCPeerConnection) Close() error {
 | |
| 	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #2)
 | |
| 	if pc.isClosed {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	err := pc.networkManager.Close()
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #3)
 | |
| 	pc.isClosed = true
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #4)
 | |
| 	pc.SignalingState = RTCSignalingStateClosed
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #11)
 | |
| 	// pc.IceConnectionState = RTCIceConnectionStateClosed
 | |
| 	pc.IceConnectionState = ice.ConnectionStateClosed // FIXME REMOVE
 | |
| 
 | |
| 	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #12)
 | |
| 	pc.ConnectionState = RTCPeerConnectionStateClosed
 | |
| 
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| /* Everything below is private */
 | |
| func (pc *RTCPeerConnection) generateChannel(ssrc uint32, payloadType uint8) (tpair *network.TransportPair) {
 | |
| 	pc.RLock()
 | |
| 	if pc.onTrackHandler == nil {
 | |
| 		pc.RUnlock()
 | |
| 		return nil
 | |
| 	}
 | |
| 	pc.RUnlock()
 | |
| 
 | |
| 	sdpCodec, err := pc.CurrentLocalDescription.parsed.GetCodecForPayloadType(payloadType)
 | |
| 	if err != nil {
 | |
| 		fmt.Printf("No codec could be found in RemoteDescription for payloadType %d \n", payloadType)
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	codec, err := pc.mediaEngine.getCodecSDP(sdpCodec)
 | |
| 	if err != nil {
 | |
| 		fmt.Printf("Codec %s in not registered\n", sdpCodec)
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	rtpTransport := make(chan *rtp.Packet, 15)
 | |
| 	rtcpTransport := make(chan rtcp.Packet, 15)
 | |
| 
 | |
| 	track := &RTCTrack{
 | |
| 		PayloadType: payloadType,
 | |
| 		Kind:        codec.Type,
 | |
| 		ID:          "0", // TODO extract from remoteDescription
 | |
| 		Label:       "",  // TODO extract from remoteDescription
 | |
| 		Ssrc:        ssrc,
 | |
| 		Codec:       codec,
 | |
| 		Packets:     rtpTransport,
 | |
| 		RTCPPackets: rtcpTransport,
 | |
| 	}
 | |
| 
 | |
| 	// TODO: Register the receiving Track
 | |
| 
 | |
| 	pc.onTrack(track)
 | |
| 	return &network.TransportPair{RTP: rtpTransport, RTCP: rtcpTransport}
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) iceStateChange(newState ice.ConnectionState) {
 | |
| 	pc.Lock()
 | |
| 	pc.IceConnectionState = newState
 | |
| 	pc.Unlock()
 | |
| 
 | |
| 	pc.onICEConnectionStateChange(newState)
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) generateLocalCandidates() ([]string, error) {
 | |
| 	candidates, err := pc.networkManager.IceAgent.GetLocalCandidates()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	res := make([]string, 0)
 | |
| 	for _, c := range candidates {
 | |
| 		res = append(res, sdp.ICECandidateMarshal(c)...)
 | |
| 	}
 | |
| 	return res, nil
 | |
| }
 | |
| 
 | |
| func localDirection(weSend bool, peerDirection RTCRtpTransceiverDirection) RTCRtpTransceiverDirection {
 | |
| 	theySend := (peerDirection == RTCRtpTransceiverDirectionSendrecv || peerDirection == RTCRtpTransceiverDirectionSendonly)
 | |
| 	if weSend && theySend {
 | |
| 		return RTCRtpTransceiverDirectionSendrecv
 | |
| 	} else if weSend && !theySend {
 | |
| 		return RTCRtpTransceiverDirectionSendonly
 | |
| 	} else if !weSend && theySend {
 | |
| 		return RTCRtpTransceiverDirectionRecvonly
 | |
| 	}
 | |
| 
 | |
| 	return RTCRtpTransceiverDirectionInactive
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) addFingerprint(d *sdp.SessionDescription) {
 | |
| 	// TODO: Handle multiple certificates
 | |
| 	for _, fingerprint := range pc.configuration.Certificates[0].GetFingerprints() {
 | |
| 		d.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codecType RTCRtpCodecType, midValue string, peerDirection RTCRtpTransceiverDirection, candidates []string, dtlsRole sdp.ConnectionRole) bool {
 | |
| 	if codecs := pc.mediaEngine.getCodecsByKind(codecType); len(codecs) == 0 {
 | |
| 		return false
 | |
| 	}
 | |
| 	media := sdp.NewJSEPMediaDescription(codecType.String(), []string{}).
 | |
| 		WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types
 | |
| 		WithValueAttribute(sdp.AttrKeyMID, midValue).
 | |
| 		WithICECredentials(pc.networkManager.IceAgent.GetLocalUserCredentials()).
 | |
| 		WithPropertyAttribute(sdp.AttrKeyRtcpMux).  // TODO: support RTCP fallback
 | |
| 		WithPropertyAttribute(sdp.AttrKeyRtcpRsize) // TODO: Support Reduced-Size RTCP?
 | |
| 
 | |
| 	for _, codec := range pc.mediaEngine.getCodecsByKind(codecType) {
 | |
| 		media.WithCodec(codec.PayloadType, codec.Name, codec.ClockRate, codec.Channels, codec.SdpFmtpLine)
 | |
| 	}
 | |
| 
 | |
| 	weSend := false
 | |
| 	for _, transceiver := range pc.rtpTransceivers {
 | |
| 		if transceiver.Sender == nil ||
 | |
| 			transceiver.Sender.Track == nil ||
 | |
| 			transceiver.Sender.Track.Kind != codecType {
 | |
| 			continue
 | |
| 		}
 | |
| 		weSend = true
 | |
| 		track := transceiver.Sender.Track
 | |
| 		media = media.WithMediaSource(track.Ssrc, track.Label /* cname */, track.Label /* streamLabel */, track.Label)
 | |
| 	}
 | |
| 	media = media.WithPropertyAttribute(localDirection(weSend, peerDirection).String())
 | |
| 
 | |
| 	for _, c := range candidates {
 | |
| 		media.WithCandidate(c)
 | |
| 	}
 | |
| 	media.WithPropertyAttribute("end-of-candidates")
 | |
| 	d.WithMedia(media)
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) addDataMediaSection(d *sdp.SessionDescription, midValue string, candidates []string, dtlsRole sdp.ConnectionRole) {
 | |
| 	media := (&sdp.MediaDescription{
 | |
| 		MediaName: sdp.MediaName{
 | |
| 			Media:   "application",
 | |
| 			Port:    sdp.RangedPort{Value: 9},
 | |
| 			Protos:  []string{"DTLS", "SCTP"},
 | |
| 			Formats: []string{"5000"},
 | |
| 		},
 | |
| 		ConnectionInformation: &sdp.ConnectionInformation{
 | |
| 			NetworkType: "IN",
 | |
| 			AddressType: "IP4",
 | |
| 			Address: &sdp.Address{
 | |
| 				IP: net.ParseIP("0.0.0.0"),
 | |
| 			},
 | |
| 		},
 | |
| 	}).
 | |
| 		WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types
 | |
| 		WithValueAttribute(sdp.AttrKeyMID, midValue).
 | |
| 		WithPropertyAttribute(RTCRtpTransceiverDirectionSendrecv.String()).
 | |
| 		WithPropertyAttribute("sctpmap:5000 webrtc-datachannel 1024").
 | |
| 		WithICECredentials(pc.networkManager.IceAgent.GetLocalUserCredentials())
 | |
| 
 | |
| 	for _, c := range candidates {
 | |
| 		media.WithCandidate(c)
 | |
| 	}
 | |
| 	media.WithPropertyAttribute("end-of-candidates")
 | |
| 
 | |
| 	d.WithMedia(media)
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) newRTCTrack(payloadType uint8, ssrc uint32, id, label string) (*RTCTrack, error) {
 | |
| 	codec, err := pc.mediaEngine.getCodec(payloadType)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if codec.Payloader == nil {
 | |
| 		return nil, errors.New("codec payloader not set")
 | |
| 	}
 | |
| 
 | |
| 	trackInput := make(chan media.RTCSample, 15) // Is the buffering needed?
 | |
| 	rawPackets := make(chan *rtp.Packet)
 | |
| 	rtcpPackets := make(chan rtcp.Packet)
 | |
| 	if ssrc == 0 {
 | |
| 		buf := make([]byte, 4)
 | |
| 		_, err = rand.Read(buf)
 | |
| 		if err != nil {
 | |
| 			return nil, errors.New("failed to generate random value")
 | |
| 		}
 | |
| 		ssrc = binary.LittleEndian.Uint32(buf)
 | |
| 
 | |
| 		go func() {
 | |
| 			packetizer := rtp.NewPacketizer(
 | |
| 				1400,
 | |
| 				payloadType,
 | |
| 				ssrc,
 | |
| 				codec.Payloader,
 | |
| 				rtp.NewRandomSequencer(),
 | |
| 				codec.ClockRate,
 | |
| 			)
 | |
| 
 | |
| 			for {
 | |
| 				in := <-trackInput
 | |
| 				packets := packetizer.Packetize(in.Data, in.Samples)
 | |
| 				for _, p := range packets {
 | |
| 					pc.networkManager.SendRTP(p)
 | |
| 				}
 | |
| 			}
 | |
| 		}()
 | |
| 		close(rawPackets)
 | |
| 	} else {
 | |
| 		// If SSRC is not 0, then we are working with an established RTP stream
 | |
| 		// and need to accept raw RTP packets for forwarding.
 | |
| 		go func() {
 | |
| 			for {
 | |
| 				p := <-rawPackets
 | |
| 				pc.networkManager.SendRTP(p)
 | |
| 			}
 | |
| 		}()
 | |
| 		close(trackInput)
 | |
| 	}
 | |
| 
 | |
| 	t := &RTCTrack{
 | |
| 		PayloadType: payloadType,
 | |
| 		Kind:        codec.Type,
 | |
| 		ID:          id,
 | |
| 		Label:       label,
 | |
| 		Ssrc:        ssrc,
 | |
| 		Codec:       codec,
 | |
| 		RTCPPackets: rtcpPackets,
 | |
| 		Samples:     trackInput,
 | |
| 		RawRTP:      rawPackets,
 | |
| 	}
 | |
| 
 | |
| 	pc.networkManager.AddTransportPair(ssrc, nil, rtcpPackets)
 | |
| 
 | |
| 	return t, nil
 | |
| }
 | |
| 
 | |
| // NewRawRTPTrack initializes a new *RTCTrack configured to accept raw *rtp.Packet
 | |
| //
 | |
| // NB: If the source RTP stream is being broadcast to multiple tracks, each track
 | |
| // must receive its own copies of the source packets in order to avoid packet corruption.
 | |
| func (pc *RTCPeerConnection) NewRawRTPTrack(payloadType uint8, ssrc uint32, id, label string) (*RTCTrack, error) {
 | |
| 	if ssrc == 0 {
 | |
| 		return nil, errors.New("SSRC supplied to NewRawRTPTrack() must be non-zero")
 | |
| 	}
 | |
| 	return pc.newRTCTrack(payloadType, ssrc, id, label)
 | |
| }
 | |
| 
 | |
| // NewRTCSampleTrack initializes a new *RTCTrack configured to accept media.RTCSample
 | |
| func (pc *RTCPeerConnection) NewRTCSampleTrack(payloadType uint8, id, label string) (*RTCTrack, error) {
 | |
| 	return pc.newRTCTrack(payloadType, 0, id, label)
 | |
| }
 | |
| 
 | |
| // NewRTCTrack is used to create a new RTCTrack
 | |
| //
 | |
| // Deprecated: Use NewRTCSampleTrack() instead
 | |
| func (pc *RTCPeerConnection) NewRTCTrack(payloadType uint8, id, label string) (*RTCTrack, error) {
 | |
| 	return pc.NewRTCSampleTrack(payloadType, id, label)
 | |
| }
 | |
| 
 | |
| func (pc *RTCPeerConnection) newRTCRtpTransceiver(
 | |
| 	receiver *RTCRtpReceiver,
 | |
| 	sender *RTCRtpSender,
 | |
| 	direction RTCRtpTransceiverDirection,
 | |
| ) *RTCRtpTransceiver {
 | |
| 
 | |
| 	t := &RTCRtpTransceiver{
 | |
| 		Receiver:  receiver,
 | |
| 		Sender:    sender,
 | |
| 		Direction: direction,
 | |
| 	}
 | |
| 	pc.rtpTransceivers = append(pc.rtpTransceivers, t)
 | |
| 	return t
 | |
| }
 | 
