Add an almost complete rfc complaint RTCConfiguration

This commit is contained in:
Konstantin Itskov
2018-08-24 01:27:22 -04:00
committed by Sean DuBois
parent 26e0fb8423
commit 20191a4974
26 changed files with 738 additions and 570 deletions

View File

@@ -5,23 +5,18 @@ import (
"fmt" "fmt"
) )
// Types of InvalidStateErrors
var (
ErrConnectionClosed = errors.New("connection closed")
)
// InvalidStateError indicates the object is in an invalid state. // InvalidStateError indicates the object is in an invalid state.
type InvalidStateError struct { type InvalidStateError struct {
Err error Err error
} }
func (e *InvalidStateError) Error() string { func (e *InvalidStateError) Error() string {
return fmt.Sprintf("invalid state error: %v", e.Err) return fmt.Sprintf("webrtc: InvalidStateError: %v", e.Err)
} }
// Types of UnknownErrors // Types of InvalidStateErrors
var ( var (
ErrNoConfig = errors.New("no configuration provided") ErrConnectionClosed = errors.New("connection closed")
) )
// UnknownError indicates the operation failed for an unknown transient reason // UnknownError indicates the operation failed for an unknown transient reason
@@ -30,7 +25,21 @@ type UnknownError struct {
} }
func (e *UnknownError) Error() string { func (e *UnknownError) Error() string {
return fmt.Sprintf("unknown error: %v", e.Err) return fmt.Sprintf("webrtc: UnknownError: %v", e.Err)
}
// Types of UnknownErrors
var (
ErrNoConfig = errors.New("no configuration provided")
)
// InvalidAccessError indicates the object does not support the operation or argument.
type InvalidAccessError struct {
Err error
}
func (e *InvalidAccessError) Error() string {
return fmt.Sprintf("webrtc: InvalidAccessError: %v", e.Err)
} }
// Types of InvalidAccessErrors // Types of InvalidAccessErrors
@@ -41,34 +50,17 @@ var (
ErrExistingTrack = errors.New("track aready exists") ErrExistingTrack = errors.New("track aready exists")
) )
// InvalidAccessError indicates the object does not support the operation or argument.
type InvalidAccessError struct {
Err error
}
func (e *InvalidAccessError) Error() string {
return fmt.Sprintf("invalid access error: %v", e.Err)
}
// Types of NotSupportedErrors
var ()
// NotSupportedError indicates the operation is not supported. // NotSupportedError indicates the operation is not supported.
type NotSupportedError struct { type NotSupportedError struct {
Err error Err error
} }
func (e *NotSupportedError) Error() string { func (e *NotSupportedError) Error() string {
return fmt.Sprintf("not supported error: %v", e.Err) return fmt.Sprintf("webrtc: NotSupportedError: %v", e.Err)
} }
// Types of InvalidModificationErrors // Types of NotSupportedErrors
var ( var ()
ErrModPeerIdentity = errors.New("peer identity cannot be modified")
ErrModCertificates = errors.New("certificates cannot be modified")
ErrModRtcpMuxPolicy = errors.New("rtcp mux policy cannot be modified")
ErrModICECandidatePoolSize = errors.New("ice candidate pool size cannot be modified")
)
// InvalidModificationError indicates the object can not be modified in this way. // InvalidModificationError indicates the object can not be modified in this way.
type InvalidModificationError struct { type InvalidModificationError struct {
@@ -76,11 +68,17 @@ type InvalidModificationError struct {
} }
func (e *InvalidModificationError) Error() string { func (e *InvalidModificationError) Error() string {
return fmt.Sprintf("invalid modification error: %v", e.Err) return fmt.Sprintf("webrtc: InvalidModificationError: %v", e.Err)
} }
// Types of SyntaxErrors // Types of InvalidModificationErrors
var () var (
ErrModifyingPeerIdentity = errors.New("peerIdentity cannot be modified")
ErrModifyingCertificates = errors.New("certificates cannot be modified")
ErrModifyingBundlePolicy = errors.New("bundle policy cannot be modified")
ErrModifyingRtcpMuxPolicy = errors.New("rtcp mux policy cannot be modified")
ErrModifyingIceCandidatePoolSize = errors.New("ice candidate pool size cannot be modified")
)
// SyntaxError indicates the string did not match the expected pattern. // SyntaxError indicates the string did not match the expected pattern.
type SyntaxError struct { type SyntaxError struct {
@@ -88,13 +86,11 @@ type SyntaxError struct {
} }
func (e *SyntaxError) Error() string { func (e *SyntaxError) Error() string {
return fmt.Sprintf("syntax error: %v", e.Err) return fmt.Sprintf("webrtc: SyntaxError: %v", e.Err)
} }
// Types of TypeError // Types of SyntaxErrors
var ( var ()
ErrInvalidValue = errors.New("invalid value")
)
// TypeError indicates an issue with a supplied value // TypeError indicates an issue with a supplied value
type TypeError struct { type TypeError struct {
@@ -102,12 +98,12 @@ type TypeError struct {
} }
func (e *TypeError) Error() string { func (e *TypeError) Error() string {
return fmt.Sprintf("type error: %v", e.Err) return fmt.Sprintf("webrtc: TypeError: %v", e.Err)
} }
// Types of OperationError // Types of TypeError
var ( var (
ErrMaxDataChannels = errors.New("maximum number of datachannels reached") ErrInvalidValue = errors.New("invalid value")
) )
// OperationError indicates an issue with execution // OperationError indicates an issue with execution
@@ -116,8 +112,13 @@ type OperationError struct {
} }
func (e *OperationError) Error() string { func (e *OperationError) Error() string {
return fmt.Sprintf("operation error: %v", e.Err) return fmt.Sprintf("webrtc: OperationError: %v", e.Err)
} }
// Types of OperationError
var (
ErrMaxDataChannels = errors.New("maximum number of datachannels reached")
)
// ErrUnknownType indicates a Unknown info // ErrUnknownType indicates a Unknown info
var ErrUnknownType = errors.New("Unknown") var ErrUnknownType = errors.New("Unknown")

View File

@@ -16,7 +16,7 @@ import (
func main() { func main() {
// Prepare the configuration // Prepare the configuration
config := webrtc.RTCConfiguration{ config := webrtc.RTCConfiguration{
ICEServers: []webrtc.RTCICEServer{ IceServers: []webrtc.RTCIceServer{
{ {
URLs: []string{"stun:stun.l.google.com:19302"}, URLs: []string{"stun:stun.l.google.com:19302"},
}, },

View File

@@ -42,7 +42,7 @@ func main() {
// Create a new RTCPeerConnection // Create a new RTCPeerConnection
peerConnection, err := webrtc.New(webrtc.RTCConfiguration{ peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
ICEServers: []webrtc.RTCICEServer{ IceServers: []webrtc.RTCIceServer{
{ {
URLs: []string{"stun:stun.l.google.com:19302"}, URLs: []string{"stun:stun.l.google.com:19302"},
}, },

View File

@@ -34,7 +34,7 @@ func main() {
// Create a new RTCPeerConnection // Create a new RTCPeerConnection
peerConnection, err := webrtc.New(webrtc.RTCConfiguration{ peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
ICEServers: []webrtc.RTCICEServer{ IceServers: []webrtc.RTCIceServer{
{ {
URLs: []string{"stun:stun.l.google.com:19302"}, URLs: []string{"stun:stun.l.google.com:19302"},
}, },

View File

@@ -34,7 +34,7 @@ func main() {
// Create a new RTCPeerConnection // Create a new RTCPeerConnection
peerConnection, err := webrtc.New(webrtc.RTCConfiguration{ peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
ICEServers: []webrtc.RTCICEServer{ IceServers: []webrtc.RTCIceServer{
{ {
URLs: []string{"stun:stun.l.google.com:19302"}, URLs: []string{"stun:stun.l.google.com:19302"},
}, },

View File

@@ -15,7 +15,7 @@ import (
func buildPeerConnection() *webrtc.RTCPeerConnection { func buildPeerConnection() *webrtc.RTCPeerConnection {
peerConnection, err := webrtc.New(webrtc.RTCConfiguration{ peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
ICEServers: []webrtc.RTCICEServer{ IceServers: []webrtc.RTCIceServer{
{ {
URLs: []string{"stun:stun.l.google.com:19302"}, URLs: []string{"stun:stun.l.google.com:19302"},
}, },

View File

@@ -12,7 +12,7 @@ import (
func buildPeerConnection() *webrtc.RTCPeerConnection { func buildPeerConnection() *webrtc.RTCPeerConnection {
peerConnection, err := webrtc.New(webrtc.RTCConfiguration{ peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
ICEServers: []webrtc.RTCICEServer{ IceServers: []webrtc.RTCIceServer{
{ {
URLs: []string{"stun:stun.l.google.com:19302"}, URLs: []string{"stun:stun.l.google.com:19302"},
}, },

View File

@@ -33,7 +33,7 @@ func main() {
// Create a new RTCPeerConnection // Create a new RTCPeerConnection
peerConnection, err := webrtc.New(webrtc.RTCConfiguration{ peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
ICEServers: []webrtc.RTCICEServer{ IceServers: []webrtc.RTCIceServer{
{ {
URLs: []string{"stun:stun.l.google.com:19302"}, URLs: []string{"stun:stun.l.google.com:19302"},
}, },

View File

@@ -20,7 +20,7 @@ import (
// Manager contains all network state (DTLS, SRTP) that is shared between ports // Manager contains all network state (DTLS, SRTP) that is shared between ports
// It is also used to perform operations that involve multiple ports // It is also used to perform operations that involve multiple ports
type Manager struct { type Manager struct {
IceAgent *ice.Agent // IceAgent *ice.Agent
iceNotifier ICENotifier iceNotifier ICENotifier
isOffer bool isOffer bool
@@ -63,22 +63,22 @@ func NewManager(btg BufferTransportGenerator, dcet DataChannelEventHandler, ntf
m.sctpAssociation = sctp.NewAssocation(m.dataChannelOutboundHandler, m.dataChannelInboundHandler) m.sctpAssociation = sctp.NewAssocation(m.dataChannelOutboundHandler, m.dataChannelInboundHandler)
m.IceAgent = ice.NewAgent(m.iceOutboundHandler, m.iceNotifier) // m.IceAgent = ice.NewAgent(m.iceOutboundHandler, m.iceNotifier)
for _, i := range localInterfaces() { // for _, i := range localInterfaces() {
p, portErr := newPort(i+":0", m) // p, portErr := newPort(i+":0", m)
if portErr != nil { // if portErr != nil {
return nil, portErr // return nil, portErr
} // }
//
m.ports = append(m.ports, p) // m.ports = append(m.ports, p)
m.IceAgent.AddLocalCandidate(&ice.CandidateHost{ // m.IceAgent.AddLocalCandidate(&ice.CandidateHost{
CandidateBase: ice.CandidateBase{ // CandidateBase: ice.CandidateBase{
Protocol: ice.TransportUDP, // Protocol: ice.TransportUDP,
Address: p.listeningAddr.IP.String(), // Address: p.listeningAddr.IP.String(),
Port: p.listeningAddr.Port, // Port: p.listeningAddr.Port,
}, // },
}) // })
} // }
return m, err return m, err
} }
@@ -97,7 +97,7 @@ func (m *Manager) AddURL(url *ice.URL) error {
} }
m.ports = append(m.ports, p) m.ports = append(m.ports, p)
m.IceAgent.AddLocalCandidate(c) // m.IceAgent.AddLocalCandidate(c)
default: default:
return errors.Errorf("%s is not implemented", url.Type.String()) return errors.Errorf("%s is not implemented", url.Type.String())
} }
@@ -108,9 +108,9 @@ func (m *Manager) AddURL(url *ice.URL) error {
// Start allocates DTLS/ICE state that is dependent on if we are offering or answering // Start allocates DTLS/ICE state that is dependent on if we are offering or answering
func (m *Manager) Start(isOffer bool, remoteUfrag, remotePwd string) error { func (m *Manager) Start(isOffer bool, remoteUfrag, remotePwd string) error {
m.isOffer = isOffer m.isOffer = isOffer
if err := m.IceAgent.Start(isOffer, remoteUfrag, remotePwd); err != nil { // if err := m.IceAgent.Start(isOffer, remoteUfrag, remotePwd); err != nil {
return err // return err
} // }
m.dtlsState.Start(isOffer) m.dtlsState.Start(isOffer)
return nil return nil
} }
@@ -122,7 +122,7 @@ func (m *Manager) Close() {
err := m.sctpAssociation.Close() err := m.sctpAssociation.Close()
m.dtlsState.Close() m.dtlsState.Close()
m.IceAgent.Close() // m.IceAgent.Close()
for i := len(m.ports) - 1; i >= 0; i-- { for i := len(m.ports) - 1; i >= 0; i-- {
if portError := m.ports[i].close(); portError != nil { if portError := m.ports[i].close(); portError != nil {
@@ -147,15 +147,15 @@ func (m *Manager) SendRTP(packet *rtp.Packet) {
m.portsLock.Lock() m.portsLock.Lock()
defer m.portsLock.Unlock() defer m.portsLock.Unlock()
local, remote := m.IceAgent.SelectedPair() // local, remote := m.IceAgent.SelectedPair()
if local == nil || remote == nil { // if local == nil || remote == nil {
return // return
} // }
for _, p := range m.ports { // for _, p := range m.ports {
if p.listeningAddr.Equal(local) { // if p.listeningAddr.Equal(local) {
p.sendRTP(packet, remote) // p.sendRTP(packet, remote)
} // }
} // }
} }
// SendDataChannelMessage sends a DataChannel message to a connected peer // SendDataChannelMessage sends a DataChannel message to a connected peer
@@ -245,22 +245,22 @@ func (m *Manager) dataChannelInboundHandler(data []byte, streamIdentifier uint16
} }
func (m *Manager) dataChannelOutboundHandler(raw []byte) { func (m *Manager) dataChannelOutboundHandler(raw []byte) {
local, remote := m.IceAgent.SelectedPair() // local, remote := m.IceAgent.SelectedPair()
if remote == nil || local == nil { // if remote == nil || local == nil {
// Send data on any valid pair // // Send data on any valid pair
fmt.Println("dataChannelOutboundHandler: no valid candidates, dropping packet") // fmt.Println("dataChannelOutboundHandler: no valid candidates, dropping packet")
return // return
} // }
//
m.portsLock.Lock() // m.portsLock.Lock()
defer m.portsLock.Unlock() // defer m.portsLock.Unlock()
p, err := m.port(local) // p, err := m.port(local)
if err != nil { // if err != nil {
fmt.Println("dataChannelOutboundHandler: no valid port for candidate, dropping packet") // fmt.Println("dataChannelOutboundHandler: no valid port for candidate, dropping packet")
return // return
//
} // }
p.sendSCTP(raw, remote) // p.sendSCTP(raw, remote)
} }
func (m *Manager) port(local *stun.TransportAddr) (*port, error) { func (m *Manager) port(local *stun.TransportAddr) (*port, error) {

View File

@@ -153,7 +153,7 @@ func (p *port) networkLoop() {
} else if 19 < in.buffer[0] && in.buffer[0] < 64 { } else if 19 < in.buffer[0] && in.buffer[0] < 64 {
p.handleDTLS(in.buffer, in.srcAddr.String()) p.handleDTLS(in.buffer, in.srcAddr.String())
} else if in.buffer[0] < 2 { } else if in.buffer[0] < 2 {
p.m.IceAgent.HandleInbound(in.buffer, p.listeningAddr, in.srcAddr) // p.m.IceAgent.HandleInbound(in.buffer, p.listeningAddr, in.srcAddr)
} }
p.m.certPairLock.RLock() p.m.certPairLock.RLock()

View File

@@ -96,7 +96,7 @@ func (t *RTCRtpTransceiver) setSendingTrack(track *RTCTrack) error {
return nil return nil
} }
func (r *RTCPeerConnection) newRTCRtpTransceiver( func (pc *RTCPeerConnection) newRTCRtpTransceiver(
receiver *RTCRtpReceiver, receiver *RTCRtpReceiver,
sender *RTCRtpSender, sender *RTCRtpSender,
direction RTCRtpTransceiverDirection, direction RTCRtpTransceiverDirection,
@@ -107,7 +107,7 @@ func (r *RTCPeerConnection) newRTCRtpTransceiver(
Sender: sender, Sender: sender,
Direction: direction, Direction: direction,
} }
r.rtpTransceivers = append(r.rtpTransceivers, t) pc.rtpTransceivers = append(pc.rtpTransceivers, t)
return t return t
} }
@@ -135,8 +135,8 @@ type RTCTrack struct {
} }
// NewRTCTrack is used to create a new RTCTrack // NewRTCTrack is used to create a new RTCTrack
func (r *RTCPeerConnection) NewRTCTrack(payloadType uint8, id, label string) (*RTCTrack, error) { func (pc *RTCPeerConnection) NewRTCTrack(payloadType uint8, id, label string) (*RTCTrack, error) {
codec, err := r.mediaEngine.getCodec(payloadType) codec, err := pc.mediaEngine.getCodec(payloadType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -160,7 +160,7 @@ func (r *RTCPeerConnection) NewRTCTrack(payloadType uint8, id, label string) (*R
in := <-trackInput in := <-trackInput
packets := packetizer.Packetize(in.Data, in.Samples) packets := packetizer.Packetize(in.Data, in.Samples)
for _, p := range packets { for _, p := range packets {
r.networkManager.SendRTP(p) pc.networkManager.SendRTP(p)
} }
} }
}() }()
@@ -179,11 +179,11 @@ func (r *RTCPeerConnection) NewRTCTrack(payloadType uint8, id, label string) (*R
} }
// AddTrack adds a RTCTrack to the RTCPeerConnection // AddTrack adds a RTCTrack to the RTCPeerConnection
func (r *RTCPeerConnection) AddTrack(track *RTCTrack) (*RTCRtpSender, error) { func (pc *RTCPeerConnection) AddTrack(track *RTCTrack) (*RTCRtpSender, error) {
if r.IsClosed { if pc.IsClosed {
return nil, &InvalidStateError{Err: ErrConnectionClosed} return nil, &InvalidStateError{Err: ErrConnectionClosed}
} }
for _, transceiver := range r.rtpTransceivers { for _, transceiver := range pc.rtpTransceivers {
if transceiver.Sender.Track == nil { if transceiver.Sender.Track == nil {
continue continue
} }
@@ -192,7 +192,7 @@ func (r *RTCPeerConnection) AddTrack(track *RTCTrack) (*RTCRtpSender, error) {
} }
} }
var transceiver *RTCRtpTransceiver var transceiver *RTCRtpTransceiver
for _, t := range r.rtpTransceivers { for _, t := range pc.rtpTransceivers {
if !t.stopped && if !t.stopped &&
// t.Sender == nil && // TODO: check that the sender has never sent // t.Sender == nil && // TODO: check that the sender has never sent
t.Sender.Track == nil && t.Sender.Track == nil &&
@@ -209,7 +209,7 @@ func (r *RTCPeerConnection) AddTrack(track *RTCTrack) (*RTCRtpSender, error) {
} else { } else {
var receiver *RTCRtpReceiver var receiver *RTCRtpReceiver
sender := newRTCRtpSender(track) sender := newRTCRtpSender(track)
transceiver = r.newRTCRtpTransceiver( transceiver = pc.newRTCRtpTransceiver(
receiver, receiver,
sender, sender,
RTCRtpTransceiverDirectionSendonly, RTCRtpTransceiverDirectionSendonly,
@@ -222,27 +222,27 @@ func (r *RTCPeerConnection) AddTrack(track *RTCTrack) (*RTCRtpSender, error) {
} }
// GetSenders returns the RTCRtpSender that are currently attached to this RTCPeerConnection // GetSenders returns the RTCRtpSender that are currently attached to this RTCPeerConnection
func (r *RTCPeerConnection) GetSenders() []RTCRtpSender { func (pc *RTCPeerConnection) GetSenders() []RTCRtpSender {
result := make([]RTCRtpSender, len(r.rtpTransceivers)) result := make([]RTCRtpSender, len(pc.rtpTransceivers))
for i, tranceiver := range r.rtpTransceivers { for i, tranceiver := range pc.rtpTransceivers {
result[i] = *tranceiver.Sender result[i] = *tranceiver.Sender
} }
return result return result
} }
// GetReceivers returns the RTCRtpReceivers that are currently attached to this RTCPeerConnection // GetReceivers returns the RTCRtpReceivers that are currently attached to this RTCPeerConnection
func (r *RTCPeerConnection) GetReceivers() []RTCRtpReceiver { func (pc *RTCPeerConnection) GetReceivers() []RTCRtpReceiver {
result := make([]RTCRtpReceiver, len(r.rtpTransceivers)) result := make([]RTCRtpReceiver, len(pc.rtpTransceivers))
for i, tranceiver := range r.rtpTransceivers { for i, tranceiver := range pc.rtpTransceivers {
result[i] = *tranceiver.Receiver result[i] = *tranceiver.Receiver
} }
return result return result
} }
// GetTransceivers returns the RTCRtpTransceiver that are currently attached to this RTCPeerConnection // GetTransceivers returns the RTCRtpTransceiver that are currently attached to this RTCPeerConnection
func (r *RTCPeerConnection) GetTransceivers() []RTCRtpTransceiver { func (pc *RTCPeerConnection) GetTransceivers() []RTCRtpTransceiver {
result := make([]RTCRtpTransceiver, len(r.rtpTransceivers)) result := make([]RTCRtpTransceiver, len(pc.rtpTransceivers))
for i, tranceiver := range r.rtpTransceivers { for i, tranceiver := range pc.rtpTransceivers {
result[i] = *tranceiver result[i] = *tranceiver
} }
return result return result

23
pkg/ice/agent_test.go Normal file
View File

@@ -0,0 +1,23 @@
package ice
import (
"testing"
)
func TestTimeConsuming(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
}
func ExampleNew() {
// m := New("a", "a", "b")
// var list []string
// for elem := range m.Iter() {
// list = append(list, elem.(string))
// }
// sort.Strings(list)
// fmt.Println(list)
// Output:
// [a a b]
}

30
rtcbundlepolicy.go Normal file
View File

@@ -0,0 +1,30 @@
package webrtc
// RTCBundlePolicy affects which media tracks are negotiated if the remote endpoint is not bundle-aware,
// and what ICE candidates are gathered.
type RTCBundlePolicy int
const (
// RTCBundlePolicyBalanced indicates to gather ICE candidates for each media type in use (audio, video, and data).
RTCBundlePolicyBalanced RTCBundlePolicy = iota + 1
// RTCBundlePolicyMaxCompat indicates to gather ICE candidates for each track.
RTCBundlePolicyMaxCompat
// RTCBundlePolicyMaxBundle indicates to gather ICE candidates for only one track.
RTCBundlePolicyMaxBundle
)
func (t RTCBundlePolicy) String() string {
switch t {
case RTCBundlePolicyBalanced:
return "balanced"
case RTCBundlePolicyMaxCompat:
return "max-compat"
case RTCBundlePolicyMaxBundle:
return "max-bundle"
default:
return ErrUnknownType.Error()
}
}

29
rtccertificate.go Normal file
View File

@@ -0,0 +1,29 @@
package webrtc
import (
"crypto"
"time"
)
// RTCCertificate represents a certificate used to authenticate WebRTC communications.
type RTCCertificate struct {
privateKey crypto.PrivateKey
Expires time.Time
Hello string
}
func NewRTCCertificate(privateKey crypto.PrivateKey, expires time.Time) RTCCertificate {
return RTCCertificate{
privateKey: privateKey,
Expires: expires,
}
}
// Equals determines if two certificates are identical
func (c RTCCertificate) Equals(other RTCCertificate) bool {
return c.Expires == other.Expires
}
func (c RTCCertificate) GetFingerprints() {
}

View File

@@ -1,290 +1,16 @@
package webrtc package webrtc
import (
"fmt"
"time"
"github.com/pions/webrtc/pkg/ice"
)
// RTCICECredentialType indicates the type of credentials used to connect to an ICE server
type RTCICECredentialType int
const (
// RTCICECredentialTypePassword describes username+pasword based credentials
RTCICECredentialTypePassword RTCICECredentialType = iota + 1
// RTCICECredentialTypeOauth describes token based credentials
RTCICECredentialTypeOauth
)
func (t RTCICECredentialType) String() string {
switch t {
case RTCICECredentialTypePassword:
return "password"
case RTCICECredentialTypeOauth:
return "oauth"
default:
return ErrUnknownType.Error()
}
}
// RTCCertificate represents a certificate used to authenticate WebRTC communications.
type RTCCertificate struct {
expires time.Time
// TODO: Finish during DTLS implementation
}
// Equals determines if two certificates are identical
func (c RTCCertificate) Equals(other RTCCertificate) bool {
return c.expires == other.expires
}
// RTCICEServer describes a single ICE server, as well as required credentials
type RTCICEServer struct {
URLs []string
Username string
Credential RTCICECredential
CredentialType RTCICECredentialType
}
// RTCICECredential represents credentials used to connect to an ICE server
// Two types of credentials are supported:
// - Password (type string)
// - Password (type RTCOAuthCredential)
type RTCICECredential interface{}
// RTCOAuthCredential represents OAuth credentials used to connect to an ICE server
type RTCOAuthCredential struct {
MacKey string
AccessToken string
}
// RTCICETransportPolicy defines the ICE candidate policy [JSEP] (section 3.5.3.) used to
// surface the permitted candidates
type RTCICETransportPolicy int
const (
// RTCICETransportPolicyRelay indicates only media relay candidates such as candidates passing
// through a TURN server are used
RTCICETransportPolicyRelay RTCICETransportPolicy = iota + 1
// RTCICETransportPolicyAll indicates any type of candidate is used
RTCICETransportPolicyAll
)
func (t RTCICETransportPolicy) String() string {
switch t {
case RTCICETransportPolicyRelay:
return "relay"
case RTCICETransportPolicyAll:
return "all"
default:
return ErrUnknownType.Error()
}
}
// RTCBundlePolicy affects which media tracks are negotiated if the remote endpoint is not bundle-aware,
// and what ICE candidates are gathered.
type RTCBundlePolicy int
const (
// RTCRtcpMuxPolicyBalanced indicates to gather ICE candidates for each media type in use (audio, video, and data).
RTCRtcpMuxPolicyBalanced RTCBundlePolicy = iota + 1
// RTCRtcpMuxPolicyMaxCompat indicates to gather ICE candidates for each track.
RTCRtcpMuxPolicyMaxCompat
// RTCRtcpMuxPolicyMaxBundle indicates to gather ICE candidates for only one track.
RTCRtcpMuxPolicyMaxBundle
)
func (t RTCBundlePolicy) String() string {
switch t {
case RTCRtcpMuxPolicyBalanced:
return "balanced"
case RTCRtcpMuxPolicyMaxCompat:
return "max-compat"
case RTCRtcpMuxPolicyMaxBundle:
return "max-bundle"
default:
return ErrUnknownType.Error()
}
}
// RTCRtcpMuxPolicy affects what ICE candidates are gathered to support non-multiplexed RTCP
type RTCRtcpMuxPolicy int
const (
// RTCRtcpMuxPolicyNegotiate indicates to gather ICE candidates for both RTP and RTCP candidates.
RTCRtcpMuxPolicyNegotiate RTCRtcpMuxPolicy = iota + 1
// RTCRtcpMuxPolicyRequire indicates to gather ICE candidates only for RTP and multiplex RTCP on the RTP candidates
RTCRtcpMuxPolicyRequire
)
func (t RTCRtcpMuxPolicy) String() string {
switch t {
case RTCRtcpMuxPolicyNegotiate:
return "negotiate"
case RTCRtcpMuxPolicyRequire:
return "require"
default:
return ErrUnknownType.Error()
}
}
// RTCConfiguration contains RTCPeerConfiguration options // RTCConfiguration contains RTCPeerConfiguration options
type RTCConfiguration struct { type RTCConfiguration struct {
// ICEServers holds multiple RTCICEServer instances, each describing one server which may be used by the ICE agent; // IceServers holds multiple RTCIceServer instances, each describing one server which may be used by the ICE agent;
// these are typically STUN and/or TURN servers. If this isn't specified, the ICE agent may choose to use its own // these are typically STUN and/or TURN servers. If this isn't specified, the ICE agent may choose to use its own
// ICE servers; otherwise, the connection attempt will be made with no STUN or TURN server available, which limits // ICE servers; otherwise, the connection attempt will be made with no STUN or TURN server available, which limits
// the connection to local peers. // the connection to local peers.
ICEServers []RTCICEServer IceServers []RTCIceServer
ICETransportPolicy RTCICETransportPolicy IceTransportPolicy RTCIceTransportPolicy
BundlePolicy RTCBundlePolicy BundlePolicy RTCBundlePolicy
RtcpMuxPolicy RTCRtcpMuxPolicy RtcpMuxPolicy RTCRtcpMuxPolicy
PeerIdentity string PeerIdentity string
Certificates []RTCCertificate Certificates []RTCCertificate
ICECandidatePoolSize uint8 IceCandidatePoolSize uint8
}
// SetConfiguration updates the configuration of the RTCPeerConnection
func (r *RTCPeerConnection) SetConfiguration(config RTCConfiguration) error {
err := r.validatePeerIdentity(config)
if err != nil {
return err
}
err = r.validateCertificates(config)
if err != nil {
return err
}
err = r.validateBundlePolicy(config)
if err != nil {
return err
}
err = r.validateRtcpMuxPolicy(config)
if err != nil {
return err
}
err = r.validateICECandidatePoolSize(config)
if err != nil {
return err
}
err = r.setICEServers(config)
if err != nil {
return err
}
r.config = config
return nil
}
func (r *RTCPeerConnection) validatePeerIdentity(config RTCConfiguration) error {
current := r.config
if current.PeerIdentity != "" &&
config.PeerIdentity != "" &&
config.PeerIdentity != current.PeerIdentity {
return &InvalidModificationError{Err: ErrModPeerIdentity}
}
return nil
}
func (r *RTCPeerConnection) validateCertificates(config RTCConfiguration) error {
current := r.config
if len(current.Certificates) > 0 &&
len(config.Certificates) > 0 {
if len(config.Certificates) != len(current.Certificates) {
return &InvalidModificationError{Err: ErrModCertificates}
}
for i, cert := range config.Certificates {
if !current.Certificates[i].Equals(cert) {
return &InvalidModificationError{Err: ErrModCertificates}
}
}
}
now := time.Now()
for _, cert := range config.Certificates {
if now.After(cert.expires) {
return &InvalidAccessError{Err: ErrCertificateExpired}
}
// TODO: Check certificate 'origin'
}
return nil
}
func (r *RTCPeerConnection) validateBundlePolicy(config RTCConfiguration) error {
current := r.config
if config.BundlePolicy != current.BundlePolicy {
return &InvalidModificationError{Err: ErrModRtcpMuxPolicy}
}
return nil
}
func (r *RTCPeerConnection) validateRtcpMuxPolicy(config RTCConfiguration) error {
current := r.config
if config.RtcpMuxPolicy != current.RtcpMuxPolicy {
return &InvalidModificationError{Err: ErrModRtcpMuxPolicy}
}
return nil
}
func (r *RTCPeerConnection) validateICECandidatePoolSize(config RTCConfiguration) error {
current := r.config
if r.CurrentLocalDescription != nil &&
config.ICECandidatePoolSize != current.ICECandidatePoolSize {
return &InvalidModificationError{Err: ErrModICECandidatePoolSize}
}
return nil
}
func (r *RTCPeerConnection) setICEServers(config RTCConfiguration) error {
for _, server := range config.ICEServers {
for _, rawURL := range server.URLs {
url, err := parseICEServer(server, rawURL)
if err != nil {
return err
}
err = r.networkManager.AddURL(&url)
if err != nil {
fmt.Println(err)
}
}
}
return nil
}
func parseICEServer(server RTCICEServer, rawURL string) (ice.URL, error) {
iceurl, err := ice.NewURL(rawURL)
if err != nil {
return iceurl, &SyntaxError{Err: err}
}
if iceurl.Type == ice.ServerTypeTURN {
if server.Username == "" {
return iceurl, &InvalidAccessError{Err: ErrNoTurnCred}
}
switch t := server.Credential.(type) {
case string:
if t == "" {
return iceurl, &InvalidAccessError{Err: ErrNoTurnCred}
} else if server.CredentialType != RTCICECredentialTypePassword {
return iceurl, &InvalidAccessError{Err: ErrTurnCred}
}
case RTCOAuthCredential:
if server.CredentialType != RTCICECredentialTypeOauth {
return iceurl, &InvalidAccessError{Err: ErrTurnCred}
}
default:
return iceurl, &InvalidAccessError{Err: ErrTurnCred}
}
}
return iceurl, nil
} }

View File

@@ -1,27 +0,0 @@
package webrtc
import (
"testing"
"time"
)
func TestRTCCertificate(t *testing.T) {
t.Run("Equals", func(t *testing.T) {
now := time.Now()
testCases := []struct {
first RTCCertificate
second RTCCertificate
expected bool
}{
{RTCCertificate{expires: now}, RTCCertificate{expires: now}, true},
{RTCCertificate{expires: now}, RTCCertificate{}, false},
}
for i, testCase := range testCases {
equal := testCase.first.Equals(testCase.second)
if equal != testCase.expected {
t.Errorf("Case %d: expected %t got %t", i, testCase.expected, equal)
}
}
})
}

View File

@@ -63,8 +63,8 @@ type RTCDataChannelInit struct {
} }
// CreateDataChannel creates a new RTCDataChannel object with the given label and optitional options. // CreateDataChannel creates a new RTCDataChannel object with the given label and optitional options.
func (r *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChannelInit) (*RTCDataChannel, error) { func (pc *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChannelInit) (*RTCDataChannel, error) {
if r.IsClosed { if pc.IsClosed {
return nil, &InvalidStateError{Err: ErrConnectionClosed} return nil, &InvalidStateError{Err: ErrConnectionClosed}
} }
@@ -88,7 +88,7 @@ func (r *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChan
id = options.ID id = options.ID
} else { } else {
var err error var err error
id, err = r.generateDataChannelID(true) // TODO: base on DTLS role id, err = pc.generateDataChannelID(true) // TODO: base on DTLS role
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -98,8 +98,8 @@ func (r *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChan
return nil, &TypeError{Err: ErrInvalidValue} return nil, &TypeError{Err: ErrInvalidValue}
} }
if r.sctp.State == RTCSctpTransportStateConnected && if pc.sctp.State == RTCSctpTransportStateConnected &&
id >= r.sctp.MaxChannels { id >= pc.sctp.MaxChannels {
return nil, &OperationError{Err: ErrMaxDataChannels} return nil, &OperationError{Err: ErrMaxDataChannels}
} }
@@ -108,26 +108,26 @@ func (r *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChan
res := &RTCDataChannel{ res := &RTCDataChannel{
Label: label, Label: label,
ID: id, ID: id,
rtcPeerConnection: r, rtcPeerConnection: pc,
} }
// Remember datachannel // Remember datachannel
r.dataChannels[id] = res pc.dataChannels[id] = res
// Send opening message // Send opening message
// r.networkManager.SendOpenChannelMessage(id, label) // pc.networkManager.SendOpenChannelMessage(id, label)
return res, nil return res, nil
} }
func (r *RTCPeerConnection) generateDataChannelID(client bool) (uint16, error) { func (pc *RTCPeerConnection) generateDataChannelID(client bool) (uint16, error) {
var id uint16 var id uint16
if !client { if !client {
id++ id++
} }
for ; id < r.sctp.MaxChannels-1; id += 2 { for ; id < pc.sctp.MaxChannels-1; id += 2 {
_, ok := r.dataChannels[id] _, ok := pc.dataChannels[id]
if !ok { if !ok {
return id, nil return id, nil
} }

22
rtcicecredentialtype.go Normal file
View File

@@ -0,0 +1,22 @@
package webrtc
// RTCIceCredentialType indicates the type of credentials used to connect to an ICE server
type RTCIceCredentialType int
const (
// RTCIceCredentialTypePassword describes username+pasword based credentials
RTCIceCredentialTypePassword RTCIceCredentialType = iota + 1
// RTCIceCredentialTypeOauth describes token based credentials
RTCIceCredentialTypeOauth
)
func (t RTCIceCredentialType) String() string {
switch t {
case RTCIceCredentialTypePassword:
return "password"
case RTCIceCredentialTypeOauth:
return "oauth"
default:
return ErrUnknownType.Error()
}
}

51
rtciceserver.go Normal file
View File

@@ -0,0 +1,51 @@
package webrtc
import (
"github.com/pions/webrtc/pkg/ice"
)
// RTCIceServer describes a single STUN and TURN server that can be used by
// the IceAgent to establish a connection with a peer.
type RTCIceServer struct {
URLs []string
Username string
Credential interface{}
CredentialType RTCIceCredentialType
}
func (s RTCIceServer) parseURL(i int) (*ice.URL, error) {
return nil, nil
}
func (s RTCIceServer) validate() error {
for i := range s.URLs {
url, err := s.parseURL(i)
if err != nil {
return err // TODO Need proper error
}
if url.Type == ice.ServerTypeTURN {
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.2)
if s.Username == "" || s.Credential == nil {
return &InvalidAccessError{Err: ErrNoTurnCred}
}
switch s.CredentialType {
case RTCIceCredentialTypePassword:
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.3)
if _, ok := s.Credential.(string); !ok {
return &InvalidAccessError{Err: ErrTurnCred}
}
case RTCIceCredentialTypeOauth:
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.4)
if _, ok := s.Credential.(RTCOAuthCredential); !ok {
return &InvalidAccessError{Err: ErrTurnCred}
}
default:
return &InvalidAccessError{Err: ErrTurnCred}
}
}
}
return nil
}

25
rtcicetransportpolicy.go Normal file
View File

@@ -0,0 +1,25 @@
package webrtc
// RTCIceTransportPolicy defines the ICE candidate policy [JSEP] (section 3.5.3.) used to
// surface the permitted candidates
type RTCIceTransportPolicy int
const (
// RTCIceTransportPolicyRelay indicates only media relay candidates such as candidates passing
// through a TURN server are used
RTCIceTransportPolicyRelay RTCIceTransportPolicy = iota + 1
// RTCIceTransportPolicyAll indicates any type of candidate is used
RTCIceTransportPolicyAll
)
func (t RTCIceTransportPolicy) String() string {
switch t {
case RTCIceTransportPolicyRelay:
return "relay"
case RTCIceTransportPolicyAll:
return "all"
default:
return ErrUnknownType.Error()
}
}

7
rtcoauthcredential.go Normal file
View File

@@ -0,0 +1,7 @@
package webrtc
// RTCOAuthCredential represents OAuth credentials used to connect to an ICE server
type RTCOAuthCredential struct {
MacKey string
AccessToken string
}

View File

@@ -1,83 +1,42 @@
// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document.
package webrtc package webrtc
import ( import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt" "fmt"
"math/rand"
"sync"
"time"
"github.com/pions/webrtc/internal/network" "github.com/pions/webrtc/internal/network"
"github.com/pions/webrtc/pkg/ice" "github.com/pions/webrtc/pkg/ice"
"github.com/pions/webrtc/pkg/rtp" "github.com/pions/webrtc/pkg/rtp"
"github.com/pkg/errors" "github.com/pkg/errors"
"sync"
"time"
) )
func init() { func init() {
rand.Seed(time.Now().UTC().UnixNano()) // TODO Must address this and either revert or delete.
} // rand.Seed(time.Now().UTC().UnixNano())
// RTCPeerConnectionState indicates the state of the peer connection
type RTCPeerConnectionState int
const (
// RTCPeerConnectionStateNew indicates some of the ICE or DTLS transports are in status new
RTCPeerConnectionStateNew RTCPeerConnectionState = iota + 1
// RTCPeerConnectionStateConnecting indicates some of the ICE or DTLS transports are in status connecting or checking
RTCPeerConnectionStateConnecting
// RTCPeerConnectionStateConnected indicates all of the ICE or DTLS transports are in status connected or completed
RTCPeerConnectionStateConnected
// RTCPeerConnectionStateDisconnected indicates some of the ICE or DTLS transports are in status disconnected
RTCPeerConnectionStateDisconnected
// RTCPeerConnectionStateFailed indicates some of the ICE or DTLS transports are in status failed
RTCPeerConnectionStateFailed
// RTCPeerConnectionStateClosed indicates the peer connection is closed
RTCPeerConnectionStateClosed
)
func (t RTCPeerConnectionState) String() string {
switch t {
case RTCPeerConnectionStateNew:
return "new"
case RTCPeerConnectionStateConnecting:
return "connecting"
case RTCPeerConnectionStateConnected:
return "connected"
case RTCPeerConnectionStateDisconnected:
return "disconnected"
case RTCPeerConnectionStateFailed:
return "failed"
case RTCPeerConnectionStateClosed:
// goconst, "closed" is used in different unrelated packages
const closed = "closed"
return closed
default:
return ErrUnknownType.Error()
}
} }
// RTCPeerConnection represents a WebRTC connection between itself and a remote peer // RTCPeerConnection represents a WebRTC connection between itself and a remote peer
type RTCPeerConnection struct { type RTCPeerConnection struct {
sync.RWMutex sync.RWMutex
configuration RTCConfiguration
// ICE // ICE
OnICEConnectionStateChange func(iceConnectionState ice.ConnectionState) OnICEConnectionStateChange func(iceConnectionState ice.ConnectionState)
IceConnectionState ice.ConnectionState IceConnectionState ice.ConnectionState
config RTCConfiguration
networkManager *network.Manager networkManager *network.Manager
// Signaling // Signaling
CurrentLocalDescription *RTCSessionDescription CurrentLocalDescription *RTCSessionDescription
// pendingLocalDescription *RTCSessionDescription PendingLocalDescription *RTCSessionDescription
CurrentRemoteDescription *RTCSessionDescription CurrentRemoteDescription *RTCSessionDescription
// pendingRemoteDescription *RTCSessionDescription PendingRemoteDescription *RTCSessionDescription
idpLoginURL *string idpLoginURL *string
@@ -106,9 +65,18 @@ type RTCPeerConnection struct {
// Public // Public
// New creates a new RTCPeerConfiguration with the provided configuration // New creates a new RTCPeerConfiguration with the provided configuration
func New(config RTCConfiguration) (*RTCPeerConnection, error) { func New(configuration RTCConfiguration) (*RTCPeerConnection, error) {
r := &RTCPeerConnection{ // Some variables defined explicitly despite their implicit zero values to
config: config, // allow better readability to understand what is happening.
pc := RTCPeerConnection{
configuration: RTCConfiguration{
IceServers: []RTCIceServer{},
IceTransportPolicy: RTCIceTransportPolicyAll,
BundlePolicy: RTCBundlePolicyBalanced,
RtcpMuxPolicy: RTCRtcpMuxPolicyRequire,
Certificates: []RTCCertificate{},
IceCandidatePoolSize: 0,
},
signalingState: RTCSignalingStateStable, signalingState: RTCSignalingStateStable,
connectionState: RTCPeerConnectionStateNew, connectionState: RTCPeerConnectionStateNew,
mediaEngine: DefaultMediaEngine, mediaEngine: DefaultMediaEngine,
@@ -116,47 +84,235 @@ func New(config RTCConfiguration) (*RTCPeerConnection, error) {
dataChannels: make(map[uint16]*RTCDataChannel), dataChannels: make(map[uint16]*RTCDataChannel),
} }
var err error var err error
r.networkManager, err = network.NewManager(r.generateChannel, r.dataChannelEventHandler, r.iceStateChange)
if err != nil { if err = pc.initConfiguration(configuration); err != nil {
return nil, err
}
if err := r.SetConfiguration(config); err != nil {
return nil, err return nil, err
} }
return r, nil pc.networkManager, err = network.NewManager(pc.generateChannel, pc.dataChannelEventHandler, pc.iceStateChange)
if err != nil {
return nil, err
}
// https://www.w3.org/TR/webrtc/#constructor (step #4)
// This validation is omitted since the pions-webrtc implements rtcp-mux.
// FIXME This is actually not implemented yet but will be soon.
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 _, certificate := range configuration.Certificates {
if !certificate.Expires.IsZero() && now.After(certificate.Expires) {
return &InvalidAccessError{Err: ErrCertificateExpired}
}
}
} else {
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return &UnknownError{Err: err}
}
// Default value to expires is set as zero initialized time instant.
// Use IsZero() to check: https://golang.org/pkg/time/#Time.IsZero
pc.configuration.Certificates = []RTCCertificate{
NewRTCCertificate(pk, time.Time{}),
}
}
if configuration.BundlePolicy != 0 {
pc.configuration.BundlePolicy = configuration.BundlePolicy
}
if configuration.RtcpMuxPolicy != 0 {
pc.configuration.RtcpMuxPolicy = configuration.RtcpMuxPolicy
}
if configuration.IceCandidatePoolSize != 0 {
pc.configuration.IceCandidatePoolSize = configuration.IceCandidatePoolSize
}
if configuration.IceTransportPolicy != 0 {
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
}
// 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 &InvalidStateError{Err: ErrConnectionClosed}
}
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #3)
if configuration.PeerIdentity != "" {
if configuration.PeerIdentity != pc.configuration.PeerIdentity {
return &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 &InvalidModificationError{Err: ErrModifyingCertificates}
}
for i, certificate := range configuration.Certificates {
if !pc.configuration.Certificates[i].Equals(certificate) {
return &InvalidModificationError{Err: ErrModifyingCertificates}
}
}
pc.configuration.Certificates = configuration.Certificates
}
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #5)
if configuration.BundlePolicy != 0 {
if configuration.BundlePolicy != pc.configuration.BundlePolicy {
return &InvalidModificationError{Err: ErrModifyingBundlePolicy}
}
pc.configuration.BundlePolicy = configuration.BundlePolicy
}
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #6)
if configuration.RtcpMuxPolicy != 0 {
if configuration.RtcpMuxPolicy != pc.configuration.RtcpMuxPolicy {
return &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 &InvalidModificationError{Err: ErrModifyingIceCandidatePoolSize}
}
pc.configuration.IceCandidatePoolSize = configuration.IceCandidatePoolSize
}
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #8)
if configuration.IceTransportPolicy != 0 {
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 arguement.
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration
func (pc *RTCPeerConnection) GetConfiguration() RTCConfiguration {
return pc.configuration
}
// 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
}
// 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
} }
// SetMediaEngine allows overwriting the default media engine used by the RTCPeerConnection // SetMediaEngine allows overwriting the default media engine used by the RTCPeerConnection
// This enables RTCPeerConnection with support for different codecs // This enables RTCPeerConnection with support for different codecs
func (r *RTCPeerConnection) SetMediaEngine(m *MediaEngine) { func (pc *RTCPeerConnection) SetMediaEngine(m *MediaEngine) {
r.mediaEngine = m pc.mediaEngine = m
} }
// SetIdentityProvider is used to configure an identity provider to generate identity assertions // SetIdentityProvider is used to configure an identity provider to generate identity assertions
func (r *RTCPeerConnection) SetIdentityProvider(provider string) error { func (pc *RTCPeerConnection) SetIdentityProvider(provider string) error {
return errors.Errorf("TODO SetIdentityProvider") return errors.Errorf("TODO SetIdentityProvider")
} }
// Close ends the RTCPeerConnection // Close ends the RTCPeerConnection
func (r *RTCPeerConnection) Close() error { func (pc *RTCPeerConnection) Close() error {
r.networkManager.Close() pc.networkManager.Close()
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #2)
if pc.IsClosed {
return nil
}
// 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 = ice.ConnectionStateClosed
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #12)
pc.connectionState = RTCPeerConnectionStateClosed
return nil return nil
} }
/* Everything below is private */ /* Everything below is private */
func (r *RTCPeerConnection) generateChannel(ssrc uint32, payloadType uint8) (buffers chan<- *rtp.Packet) { func (pc *RTCPeerConnection) generateChannel(ssrc uint32, payloadType uint8) (buffers chan<- *rtp.Packet) {
if r.Ontrack == nil { if pc.Ontrack == nil {
return nil return nil
} }
sdpCodec, err := r.CurrentLocalDescription.parsed.GetCodecForPayloadType(payloadType) sdpCodec, err := pc.CurrentLocalDescription.parsed.GetCodecForPayloadType(payloadType)
if err != nil { if err != nil {
fmt.Printf("No codec could be found in RemoteDescription for payloadType %d \n", payloadType) fmt.Printf("No codec could be found in RemoteDescription for payloadType %d \n", payloadType)
return nil return nil
} }
codec, err := r.mediaEngine.getCodecSDP(sdpCodec) codec, err := pc.mediaEngine.getCodecSDP(sdpCodec)
if err != nil { if err != nil {
fmt.Printf("Codec %s in not registered\n", sdpCodec) fmt.Printf("Codec %s in not registered\n", sdpCodec)
} }
@@ -175,35 +331,35 @@ func (r *RTCPeerConnection) generateChannel(ssrc uint32, payloadType uint8) (buf
// TODO: Register the receiving Track // TODO: Register the receiving Track
go r.Ontrack(track) go pc.Ontrack(track)
return bufferTransport return bufferTransport
} }
func (r *RTCPeerConnection) iceStateChange(newState ice.ConnectionState) { func (pc *RTCPeerConnection) iceStateChange(newState ice.ConnectionState) {
r.Lock() pc.Lock()
defer r.Unlock() defer pc.Unlock()
if r.OnICEConnectionStateChange != nil && r.IceConnectionState != newState { if pc.OnICEConnectionStateChange != nil && pc.IceConnectionState != newState {
r.OnICEConnectionStateChange(newState) pc.OnICEConnectionStateChange(newState)
} }
r.IceConnectionState = newState pc.IceConnectionState = newState
} }
func (r *RTCPeerConnection) dataChannelEventHandler(e network.DataChannelEvent) { func (pc *RTCPeerConnection) dataChannelEventHandler(e network.DataChannelEvent) {
r.Lock() pc.Lock()
defer r.Unlock() defer pc.Unlock()
switch event := e.(type) { switch event := e.(type) {
case *network.DataChannelCreated: case *network.DataChannelCreated:
newDataChannel := &RTCDataChannel{ID: event.StreamIdentifier(), Label: event.Label, rtcPeerConnection: r} newDataChannel := &RTCDataChannel{ID: event.StreamIdentifier(), Label: event.Label, rtcPeerConnection: pc}
r.dataChannels[e.StreamIdentifier()] = newDataChannel pc.dataChannels[e.StreamIdentifier()] = newDataChannel
if r.Ondatachannel != nil { if pc.Ondatachannel != nil {
go r.Ondatachannel(newDataChannel) go pc.Ondatachannel(newDataChannel)
} else { } else {
fmt.Println("Ondatachannel is unset, discarding message") fmt.Println("Ondatachannel is unset, discarding message")
} }
case *network.DataChannelMessage: case *network.DataChannelMessage:
if datachannel, ok := r.dataChannels[e.StreamIdentifier()]; ok { if datachannel, ok := pc.dataChannels[e.StreamIdentifier()]; ok {
datachannel.RLock() datachannel.RLock()
defer datachannel.RUnlock() defer datachannel.RUnlock()

57
rtcpeerconnection_test.go Normal file
View File

@@ -0,0 +1,57 @@
package webrtc
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestRTCPeerConnection_initConfiguration(t *testing.T) {
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.Nil(t, err)
expected := InvalidAccessError{Err: ErrCertificateExpired}
_, actualError := New(RTCConfiguration{
Certificates: []RTCCertificate{
NewRTCCertificate(pk, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
},
})
assert.EqualError(t, actualError, expected.Error())
}
func TestRTCPeerConnection_SetConfiguration(t *testing.T) {
}
func TestRTCPeerConnection_GetConfiguration(t *testing.T) {
expected := RTCConfiguration{
IceServers: []RTCIceServer{},
IceTransportPolicy: RTCIceTransportPolicyAll,
BundlePolicy: RTCBundlePolicyBalanced,
RtcpMuxPolicy: RTCRtcpMuxPolicyRequire,
Certificates: []RTCCertificate{},
IceCandidatePoolSize: 0,
}
pc, err := New(RTCConfiguration{})
assert.Nil(t, err)
actual := pc.GetConfiguration()
assert.True(t, &expected != &actual)
assert.Equal(t, expected.IceServers, actual.IceServers)
assert.Equal(t, expected.IceTransportPolicy, actual.IceTransportPolicy)
assert.Equal(t, expected.BundlePolicy, actual.BundlePolicy)
assert.Equal(t, expected.RtcpMuxPolicy, actual.RtcpMuxPolicy)
assert.NotEqual(t, len(expected.Certificates), len(actual.Certificates))
assert.Equal(t, expected.IceCandidatePoolSize, actual.IceCandidatePoolSize)
}
func ExampleNew() {
if _, err := New(RTCConfiguration{}); err != nil {
panic(err)
}
}

45
rtcpeerconnectionstate.go Normal file
View File

@@ -0,0 +1,45 @@
package webrtc
// RTCPeerConnectionState indicates the state of the peer connection
type RTCPeerConnectionState int
const (
// RTCPeerConnectionStateNew indicates some of the ICE or DTLS transports are in status new
RTCPeerConnectionStateNew RTCPeerConnectionState = iota + 1
// RTCPeerConnectionStateConnecting indicates some of the ICE or DTLS transports are in status connecting or checking
RTCPeerConnectionStateConnecting
// RTCPeerConnectionStateConnected indicates all of the ICE or DTLS transports are in status connected or completed
RTCPeerConnectionStateConnected
// RTCPeerConnectionStateDisconnected indicates some of the ICE or DTLS transports are in status disconnected
RTCPeerConnectionStateDisconnected
// RTCPeerConnectionStateFailed indicates some of the ICE or DTLS transports are in status failed
RTCPeerConnectionStateFailed
// RTCPeerConnectionStateClosed indicates the peer connection is closed
RTCPeerConnectionStateClosed
)
func (t RTCPeerConnectionState) String() string {
switch t {
case RTCPeerConnectionStateNew:
return "new"
case RTCPeerConnectionStateConnecting:
return "connecting"
case RTCPeerConnectionStateConnected:
return "connected"
case RTCPeerConnectionStateDisconnected:
return "disconnected"
case RTCPeerConnectionStateFailed:
return "failed"
case RTCPeerConnectionStateClosed:
// goconst, "closed" is used in different unrelated packages
const closed = "closed"
return closed
default:
return ErrUnknownType.Error()
}
}

23
rtcrtcpmuxpolicy.go Normal file
View File

@@ -0,0 +1,23 @@
package webrtc
// RTCRtcpMuxPolicy affects what ICE candidates are gathered to support non-multiplexed RTCP
type RTCRtcpMuxPolicy int
const (
// RTCRtcpMuxPolicyNegotiate indicates to gather ICE candidates for both RTP and RTCP candidates.
RTCRtcpMuxPolicyNegotiate RTCRtcpMuxPolicy = iota + 1
// RTCRtcpMuxPolicyRequire indicates to gather ICE candidates only for RTP and multiplex RTCP on the RTP candidates
RTCRtcpMuxPolicyRequire
)
func (t RTCRtcpMuxPolicy) String() string {
switch t {
case RTCRtcpMuxPolicyNegotiate:
return "negotiate"
case RTCRtcpMuxPolicyRequire:
return "require"
default:
return ErrUnknownType.Error()
}
}

View File

@@ -109,8 +109,8 @@ type RTCSessionDescription struct {
} }
// SetRemoteDescription sets the SessionDescription of the remote peer // SetRemoteDescription sets the SessionDescription of the remote peer
func (r *RTCPeerConnection) SetRemoteDescription(desc RTCSessionDescription) error { func (pc *RTCPeerConnection) SetRemoteDescription(desc RTCSessionDescription) error {
if r.CurrentRemoteDescription != nil { if pc.CurrentRemoteDescription != nil {
return errors.Errorf("remoteDescription is already defined, SetRemoteDescription can only be called once") return errors.Errorf("remoteDescription is already defined, SetRemoteDescription can only be called once")
} }
@@ -121,17 +121,17 @@ func (r *RTCPeerConnection) SetRemoteDescription(desc RTCSessionDescription) err
weOffer = false weOffer = false
} }
r.CurrentRemoteDescription = &desc pc.CurrentRemoteDescription = &desc
r.CurrentRemoteDescription.parsed = &sdp.SessionDescription{} pc.CurrentRemoteDescription.parsed = &sdp.SessionDescription{}
if err := r.CurrentRemoteDescription.parsed.Unmarshal(r.CurrentRemoteDescription.Sdp); err != nil { if err := pc.CurrentRemoteDescription.parsed.Unmarshal(pc.CurrentRemoteDescription.Sdp); err != nil {
return err return err
} }
for _, m := range r.CurrentRemoteDescription.parsed.MediaDescriptions { for _, m := range pc.CurrentRemoteDescription.parsed.MediaDescriptions {
for _, a := range m.Attributes { for _, a := range m.Attributes {
if strings.HasPrefix(*a.String(), "candidate") { if strings.HasPrefix(*a.String(), "candidate") {
if c := sdp.ICECandidateUnmarshal(*a.String()); c != nil { if c := sdp.ICECandidateUnmarshal(*a.String()); c != nil {
r.networkManager.IceAgent.AddRemoteCandidate(c) // pc.networkManager.IceAgent.AddRemoteCandidate(c)
} else { } else {
fmt.Printf("Tried to parse ICE candidate, but failed %s ", a) fmt.Printf("Tried to parse ICE candidate, but failed %s ", a)
} }
@@ -142,75 +142,75 @@ func (r *RTCPeerConnection) SetRemoteDescription(desc RTCSessionDescription) err
} }
} }
} }
return r.networkManager.Start(weOffer, remoteUfrag, remotePwd) return pc.networkManager.Start(weOffer, remoteUfrag, remotePwd)
} }
func (r *RTCPeerConnection) generateLocalCandidates() []string { func (pc *RTCPeerConnection) generateLocalCandidates() []string {
r.networkManager.IceAgent.RLock() // pc.networkManager.IceAgent.RLock()
defer r.networkManager.IceAgent.RUnlock() // defer pc.networkManager.IceAgent.RUnlock()
candidates := make([]string, 0) candidates := make([]string, 0)
for _, c := range r.networkManager.IceAgent.LocalCandidates { // for _, c := range pc.networkManager.IceAgent.LocalCandidates {
candidates = append(candidates, sdp.ICECandidateMarshal(c)...) // candidates = append(candidates, sdp.ICECandidateMarshal(c)...)
} // }
return candidates return candidates
} }
// CreateOffer starts the RTCPeerConnection and generates the localDescription // CreateOffer starts the RTCPeerConnection and generates the localDescription
func (r *RTCPeerConnection) CreateOffer(options *RTCOfferOptions) (RTCSessionDescription, error) { func (pc *RTCPeerConnection) CreateOffer(options *RTCOfferOptions) (RTCSessionDescription, error) {
useIdentity := r.idpLoginURL != nil useIdentity := pc.idpLoginURL != nil
if options != nil { if options != nil {
return RTCSessionDescription{}, errors.Errorf("TODO handle options") return RTCSessionDescription{}, errors.Errorf("TODO handle options")
} else if useIdentity { } else if useIdentity {
return RTCSessionDescription{}, errors.Errorf("TODO handle identity provider") return RTCSessionDescription{}, errors.Errorf("TODO handle identity provider")
} else if r.IsClosed { } else if pc.IsClosed {
return RTCSessionDescription{}, &InvalidStateError{Err: ErrConnectionClosed} return RTCSessionDescription{}, &InvalidStateError{Err: ErrConnectionClosed}
} }
d := sdp.NewJSEPSessionDescription(r.networkManager.DTLSFingerprint(), useIdentity) d := sdp.NewJSEPSessionDescription(pc.networkManager.DTLSFingerprint(), useIdentity)
candidates := r.generateLocalCandidates() candidates := pc.generateLocalCandidates()
bundleValue := "BUNDLE" bundleValue := "BUNDLE"
if r.addRTPMediaSection(d, RTCRtpCodecTypeAudio, "audio", RTCRtpTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) { if pc.addRTPMediaSection(d, RTCRtpCodecTypeAudio, "audio", RTCRtpTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) {
bundleValue += " audio" bundleValue += " audio"
} }
if r.addRTPMediaSection(d, RTCRtpCodecTypeVideo, "video", RTCRtpTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) { if pc.addRTPMediaSection(d, RTCRtpCodecTypeVideo, "video", RTCRtpTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) {
bundleValue += " video" bundleValue += " video"
} }
r.addDataMediaSection(d, "data", candidates, sdp.ConnectionRoleActpass) pc.addDataMediaSection(d, "data", candidates, sdp.ConnectionRoleActpass)
d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue+" data") d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue+" data")
for _, m := range d.MediaDescriptions { for _, m := range d.MediaDescriptions {
m.WithPropertyAttribute("setup:actpass") m.WithPropertyAttribute("setup:actpass")
} }
r.CurrentLocalDescription = &RTCSessionDescription{ pc.CurrentLocalDescription = &RTCSessionDescription{
Type: RTCSdpTypeOffer, Type: RTCSdpTypeOffer,
Sdp: d.Marshal(), Sdp: d.Marshal(),
parsed: d, parsed: d,
} }
return *r.CurrentLocalDescription, nil return *pc.CurrentLocalDescription, nil
} }
// CreateAnswer starts the RTCPeerConnection and generates the localDescription // CreateAnswer starts the RTCPeerConnection and generates the localDescription
func (r *RTCPeerConnection) CreateAnswer(options *RTCAnswerOptions) (RTCSessionDescription, error) { func (pc *RTCPeerConnection) CreateAnswer(options *RTCAnswerOptions) (RTCSessionDescription, error) {
useIdentity := r.idpLoginURL != nil useIdentity := pc.idpLoginURL != nil
if options != nil { if options != nil {
return RTCSessionDescription{}, errors.Errorf("TODO handle options") return RTCSessionDescription{}, errors.Errorf("TODO handle options")
} else if useIdentity { } else if useIdentity {
return RTCSessionDescription{}, errors.Errorf("TODO handle identity provider") return RTCSessionDescription{}, errors.Errorf("TODO handle identity provider")
} else if r.IsClosed { } else if pc.IsClosed {
return RTCSessionDescription{}, &InvalidStateError{Err: ErrConnectionClosed} return RTCSessionDescription{}, &InvalidStateError{Err: ErrConnectionClosed}
} }
candidates := r.generateLocalCandidates() candidates := pc.generateLocalCandidates()
d := sdp.NewJSEPSessionDescription(r.networkManager.DTLSFingerprint(), useIdentity) d := sdp.NewJSEPSessionDescription(pc.networkManager.DTLSFingerprint(), useIdentity)
bundleValue := "BUNDLE" bundleValue := "BUNDLE"
for _, remoteMedia := range r.CurrentRemoteDescription.parsed.MediaDescriptions { for _, remoteMedia := range pc.CurrentRemoteDescription.parsed.MediaDescriptions {
// TODO @trivigy better SDP parser // TODO @trivigy better SDP parser
var peerDirection RTCRtpTransceiverDirection var peerDirection RTCRtpTransceiverDirection
midValue := "" midValue := ""
@@ -231,26 +231,26 @@ func (r *RTCPeerConnection) CreateAnswer(options *RTCAnswerOptions) (RTCSessionD
} }
if strings.HasPrefix(*remoteMedia.MediaName.String(), "audio") { if strings.HasPrefix(*remoteMedia.MediaName.String(), "audio") {
if r.addRTPMediaSection(d, RTCRtpCodecTypeAudio, midValue, peerDirection, candidates, sdp.ConnectionRoleActive) { if pc.addRTPMediaSection(d, RTCRtpCodecTypeAudio, midValue, peerDirection, candidates, sdp.ConnectionRoleActive) {
appendBundle() appendBundle()
} }
} else if strings.HasPrefix(*remoteMedia.MediaName.String(), "video") { } else if strings.HasPrefix(*remoteMedia.MediaName.String(), "video") {
if r.addRTPMediaSection(d, RTCRtpCodecTypeVideo, midValue, peerDirection, candidates, sdp.ConnectionRoleActive) { if pc.addRTPMediaSection(d, RTCRtpCodecTypeVideo, midValue, peerDirection, candidates, sdp.ConnectionRoleActive) {
appendBundle() appendBundle()
} }
} else if strings.HasPrefix(*remoteMedia.MediaName.String(), "application") { } else if strings.HasPrefix(*remoteMedia.MediaName.String(), "application") {
r.addDataMediaSection(d, midValue, candidates, sdp.ConnectionRoleActive) pc.addDataMediaSection(d, midValue, candidates, sdp.ConnectionRoleActive)
} }
} }
d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue) d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue)
r.CurrentLocalDescription = &RTCSessionDescription{ pc.CurrentLocalDescription = &RTCSessionDescription{
Type: RTCSdpTypeAnswer, Type: RTCSdpTypeAnswer,
Sdp: d.Marshal(), Sdp: d.Marshal(),
parsed: d, parsed: d,
} }
return *r.CurrentLocalDescription, nil return *pc.CurrentLocalDescription, nil
} }
func localDirection(weSend bool, peerDirection RTCRtpTransceiverDirection) RTCRtpTransceiverDirection { func localDirection(weSend bool, peerDirection RTCRtpTransceiverDirection) RTCRtpTransceiverDirection {
@@ -266,24 +266,24 @@ func localDirection(weSend bool, peerDirection RTCRtpTransceiverDirection) RTCRt
return RTCRtpTransceiverDirectionInactive return RTCRtpTransceiverDirectionInactive
} }
func (r *RTCPeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codecType RTCRtpCodecType, midValue string, peerDirection RTCRtpTransceiverDirection, candidates []string, dtlsRole sdp.ConnectionRole) bool { func (pc *RTCPeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codecType RTCRtpCodecType, midValue string, peerDirection RTCRtpTransceiverDirection, candidates []string, dtlsRole sdp.ConnectionRole) bool {
if codecs := r.mediaEngine.getCodecsByKind(codecType); len(codecs) == 0 { if codecs := pc.mediaEngine.getCodecsByKind(codecType); len(codecs) == 0 {
return false return false
} }
media := sdp.NewJSEPMediaDescription(codecType.String(), []string{}). media := sdp.NewJSEPMediaDescription(codecType.String(), []string{})
WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types // WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types
WithValueAttribute(sdp.AttrKeyMID, midValue). // WithValueAttribute(sdp.AttrKeyMID, midValue).
WithICECredentials(r.networkManager.IceAgent.LocalUfrag, r.networkManager.IceAgent.LocalPwd). // WithICECredentials(pc.networkManager.IceAgent.LocalUfrag, pc.networkManager.IceAgent.LocalPwd).
WithPropertyAttribute(sdp.AttrKeyRtcpMux). // TODO: support RTCP fallback // WithPropertyAttribute(sdp.AttrKeyRtcpMux). // TODO: support RTCP fallback
WithPropertyAttribute(sdp.AttrKeyRtcpRsize) // TODO: Support Reduced-Size RTCP? // WithPropertyAttribute(sdp.AttrKeyRtcpRsize) // TODO: Support Reduced-Size RTCP?
for _, codec := range r.mediaEngine.getCodecsByKind(codecType) { for _, codec := range pc.mediaEngine.getCodecsByKind(codecType) {
media.WithCodec(codec.PayloadType, codec.Name, codec.ClockRate, codec.Channels, codec.SdpFmtpLine) media.WithCodec(codec.PayloadType, codec.Name, codec.ClockRate, codec.Channels, codec.SdpFmtpLine)
} }
weSend := false weSend := false
for _, transceiver := range r.rtpTransceivers { for _, transceiver := range pc.rtpTransceivers {
if transceiver.Sender == nil || if transceiver.Sender == nil ||
transceiver.Sender.Track == nil || transceiver.Sender.Track == nil ||
transceiver.Sender.Track.Kind != codecType { transceiver.Sender.Track.Kind != codecType {
@@ -303,8 +303,8 @@ func (r *RTCPeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codecT
return true return true
} }
func (r *RTCPeerConnection) addDataMediaSection(d *sdp.SessionDescription, midValue string, candidates []string, dtlsRole sdp.ConnectionRole) { func (pc *RTCPeerConnection) addDataMediaSection(d *sdp.SessionDescription, midValue string, candidates []string, dtlsRole sdp.ConnectionRole) {
media := (&sdp.MediaDescription{ media := &sdp.MediaDescription{
MediaName: sdp.MediaName{ MediaName: sdp.MediaName{
Media: "application", Media: "application",
Port: sdp.RangedPort{Value: 9}, Port: sdp.RangedPort{Value: 9},
@@ -318,12 +318,12 @@ func (r *RTCPeerConnection) addDataMediaSection(d *sdp.SessionDescription, midVa
IP: net.ParseIP("0.0.0.0"), IP: net.ParseIP("0.0.0.0"),
}, },
}, },
}). }
WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types // WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types
WithValueAttribute(sdp.AttrKeyMID, midValue). // WithValueAttribute(sdp.AttrKeyMID, midValue).
WithPropertyAttribute(RTCRtpTransceiverDirectionSendrecv.String()). // WithPropertyAttribute(RTCRtpTransceiverDirectionSendrecv.String()).
WithPropertyAttribute("sctpmap:5000 webrtc-datachannel 1024"). // WithPropertyAttribute("sctpmap:5000 webrtc-datachannel 1024").
WithICECredentials(r.networkManager.IceAgent.LocalUfrag, r.networkManager.IceAgent.LocalPwd) // WithICECredentials(pc.networkManager.IceAgent.LocalUfrag, pc.networkManager.IceAgent.LocalPwd)
for _, c := range candidates { for _, c := range candidates {
media.WithCandidate(c) media.WithCandidate(c)