mirror of
https://github.com/pion/webrtc.git
synced 2025-10-04 14:53:05 +08:00
Add an almost complete rfc complaint RTCConfiguration
This commit is contained in:

committed by
Sean DuBois

parent
26e0fb8423
commit
20191a4974
83
errors.go
83
errors.go
@@ -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")
|
||||||
|
@@ -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"},
|
||||||
},
|
},
|
||||||
|
@@ -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"},
|
||||||
},
|
},
|
||||||
|
@@ -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"},
|
||||||
},
|
},
|
||||||
|
@@ -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"},
|
||||||
},
|
},
|
||||||
|
@@ -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"},
|
||||||
},
|
},
|
||||||
|
@@ -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"},
|
||||||
},
|
},
|
||||||
|
@@ -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"},
|
||||||
},
|
},
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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()
|
||||||
|
38
media.go
38
media.go
@@ -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
23
pkg/ice/agent_test.go
Normal 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
30
rtcbundlepolicy.go
Normal 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
29
rtccertificate.go
Normal 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() {
|
||||||
|
|
||||||
|
}
|
@@ -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
|
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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
22
rtcicecredentialtype.go
Normal 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
51
rtciceserver.go
Normal 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
25
rtcicetransportpolicy.go
Normal 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
7
rtcoauthcredential.go
Normal 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
|
||||||
|
}
|
@@ -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
57
rtcpeerconnection_test.go
Normal 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
45
rtcpeerconnectionstate.go
Normal 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
23
rtcrtcpmuxpolicy.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
106
signaling.go
106
signaling.go
@@ -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)
|
||||||
|
Reference in New Issue
Block a user