mirror of
https://github.com/pion/webrtc.git
synced 2025-09-27 11:32:19 +08:00
Add ICE Trickle support
Resolves pion/ice#51 Co-authored-by: Konstantin Itskov <konstantin.itskov@kovits.com>
This commit is contained in:
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.12
|
|||||||
require (
|
require (
|
||||||
github.com/pion/datachannel v1.4.3
|
github.com/pion/datachannel v1.4.3
|
||||||
github.com/pion/dtls v1.3.5
|
github.com/pion/dtls v1.3.5
|
||||||
github.com/pion/ice v0.3.2
|
github.com/pion/ice v0.3.3
|
||||||
github.com/pion/logging v0.2.1
|
github.com/pion/logging v0.2.1
|
||||||
github.com/pion/quic v0.1.1
|
github.com/pion/quic v0.1.1
|
||||||
github.com/pion/rtcp v1.2.0
|
github.com/pion/rtcp v1.2.0
|
||||||
|
4
go.sum
4
go.sum
@@ -26,8 +26,8 @@ github.com/pion/datachannel v1.4.3 h1:tqS6YiqqAiFCxGGhvn1K7fHEzemK9Aov025dE/isGF
|
|||||||
github.com/pion/datachannel v1.4.3/go.mod h1:SpMJbuu8v+qbA94m6lWQwSdCf8JKQvgmdSHDNtcbe+w=
|
github.com/pion/datachannel v1.4.3/go.mod h1:SpMJbuu8v+qbA94m6lWQwSdCf8JKQvgmdSHDNtcbe+w=
|
||||||
github.com/pion/dtls v1.3.5 h1:mBioifvh6JSE9pD4FtJh5WoizygoqkOJNJyS5Ns+y1U=
|
github.com/pion/dtls v1.3.5 h1:mBioifvh6JSE9pD4FtJh5WoizygoqkOJNJyS5Ns+y1U=
|
||||||
github.com/pion/dtls v1.3.5/go.mod h1:CjlPLfQdsTg3G4AEXjJp8FY5bRweBlxHrgoFrN+fQsk=
|
github.com/pion/dtls v1.3.5/go.mod h1:CjlPLfQdsTg3G4AEXjJp8FY5bRweBlxHrgoFrN+fQsk=
|
||||||
github.com/pion/ice v0.3.2 h1:wBm0F9an2y+mpIlmn2sC4sHVjZnCl0K9zY23R3ijYmA=
|
github.com/pion/ice v0.3.3 h1:ysSx7pDczIJx8XyYpFI2zoqtYhFD+B1cQdtY2ol5lT4=
|
||||||
github.com/pion/ice v0.3.2/go.mod h1:T57BaxW8oBC+CuV1+ZAAVm8/UsnpQB/S/hII+Y2Nyn0=
|
github.com/pion/ice v0.3.3/go.mod h1:T57BaxW8oBC+CuV1+ZAAVm8/UsnpQB/S/hII+Y2Nyn0=
|
||||||
github.com/pion/logging v0.2.1 h1:LwASkBKZ+2ysGJ+jLv1E/9H1ge0k1nTfi1X+5zirkDk=
|
github.com/pion/logging v0.2.1 h1:LwASkBKZ+2ysGJ+jLv1E/9H1ge0k1nTfi1X+5zirkDk=
|
||||||
github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA=
|
github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA=
|
||||||
|
121
icegatherer.go
121
icegatherer.go
@@ -21,7 +21,8 @@ type ICEGatherer struct {
|
|||||||
|
|
||||||
validatedServers []*ice.URL
|
validatedServers []*ice.URL
|
||||||
|
|
||||||
agent *ice.Agent
|
agentIsTrickle bool
|
||||||
|
agent *ice.Agent
|
||||||
|
|
||||||
portMin uint16
|
portMin uint16
|
||||||
portMax uint16
|
portMax uint16
|
||||||
@@ -29,7 +30,11 @@ type ICEGatherer struct {
|
|||||||
connectionTimeout *time.Duration
|
connectionTimeout *time.Duration
|
||||||
keepaliveInterval *time.Duration
|
keepaliveInterval *time.Duration
|
||||||
loggerFactory logging.LoggerFactory
|
loggerFactory logging.LoggerFactory
|
||||||
|
log logging.LeveledLogger
|
||||||
networkTypes []NetworkType
|
networkTypes []NetworkType
|
||||||
|
|
||||||
|
onLocalCandidateHdlr func(candidate *ICECandidate)
|
||||||
|
onStateChangeHdlr func(state ICEGathererState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewICEGatherer creates a new NewICEGatherer.
|
// NewICEGatherer creates a new NewICEGatherer.
|
||||||
@@ -66,24 +71,27 @@ func NewICEGatherer(
|
|||||||
connectionTimeout: connectionTimeout,
|
connectionTimeout: connectionTimeout,
|
||||||
keepaliveInterval: keepaliveInterval,
|
keepaliveInterval: keepaliveInterval,
|
||||||
loggerFactory: loggerFactory,
|
loggerFactory: loggerFactory,
|
||||||
|
log: loggerFactory.NewLogger("ice"),
|
||||||
networkTypes: networkTypes,
|
networkTypes: networkTypes,
|
||||||
candidateTypes: candidateTypes,
|
candidateTypes: candidateTypes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// State indicates the current state of the ICE gatherer.
|
func (g *ICEGatherer) createAgent() error {
|
||||||
func (g *ICEGatherer) State() ICEGathererState {
|
|
||||||
g.lock.RLock()
|
|
||||||
defer g.lock.RUnlock()
|
|
||||||
return g.state
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather ICE candidates.
|
|
||||||
func (g *ICEGatherer) Gather() error {
|
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
defer g.lock.Unlock()
|
defer g.lock.Unlock()
|
||||||
|
agentIsTrickle := g.onLocalCandidateHdlr != nil || g.onStateChangeHdlr != nil
|
||||||
|
|
||||||
|
if g.agent != nil {
|
||||||
|
if !g.agentIsTrickle && agentIsTrickle {
|
||||||
|
return errors.New("ICEAgent created without OnCandidate or StateChange handler, but now has one set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
config := &ice.AgentConfig{
|
config := &ice.AgentConfig{
|
||||||
|
Trickle: agentIsTrickle,
|
||||||
Urls: g.validatedServers,
|
Urls: g.validatedServers,
|
||||||
PortMin: g.portMin,
|
PortMin: g.portMin,
|
||||||
PortMax: g.portMax,
|
PortMax: g.portMax,
|
||||||
@@ -108,11 +116,49 @@ func (g *ICEGatherer) Gather() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g.agent = agent
|
g.agent = agent
|
||||||
g.state = ICEGathererStateComplete
|
g.agentIsTrickle = agentIsTrickle
|
||||||
|
if agentIsTrickle {
|
||||||
|
g.state = ICEGathererStateComplete
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gather ICE candidates.
|
||||||
|
func (g *ICEGatherer) Gather() error {
|
||||||
|
if err := g.createAgent(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.lock.Lock()
|
||||||
|
onLocalCandidateHdlr := g.onLocalCandidateHdlr
|
||||||
|
isTrickle := g.agentIsTrickle
|
||||||
|
agent := g.agent
|
||||||
|
g.lock.Unlock()
|
||||||
|
|
||||||
|
if !isTrickle {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setState(ICEGathererStateGathering)
|
||||||
|
if err := agent.OnCandidate(func(candidate ice.Candidate) {
|
||||||
|
if candidate != nil {
|
||||||
|
c, err := newICECandidateFromICE(candidate)
|
||||||
|
if err != nil {
|
||||||
|
g.log.Warnf("Failed to convert ice.Candidate: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onLocalCandidateHdlr(&c)
|
||||||
|
} else {
|
||||||
|
g.setState(ICEGathererStateComplete)
|
||||||
|
onLocalCandidateHdlr(nil)
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return agent.GatherCandidates()
|
||||||
|
}
|
||||||
|
|
||||||
// Close prunes all local candidates, and closes the ports.
|
// Close prunes all local candidates, and closes the ports.
|
||||||
func (g *ICEGatherer) Close() error {
|
func (g *ICEGatherer) Close() error {
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
@@ -133,14 +179,11 @@ func (g *ICEGatherer) Close() error {
|
|||||||
|
|
||||||
// GetLocalParameters returns the ICE parameters of the ICEGatherer.
|
// GetLocalParameters returns the ICE parameters of the ICEGatherer.
|
||||||
func (g *ICEGatherer) GetLocalParameters() (ICEParameters, error) {
|
func (g *ICEGatherer) GetLocalParameters() (ICEParameters, error) {
|
||||||
g.lock.RLock()
|
if err := g.createAgent(); err != nil {
|
||||||
defer g.lock.RUnlock()
|
return ICEParameters{}, err
|
||||||
if g.agent == nil {
|
|
||||||
return ICEParameters{}, errors.New("gatherer not started")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
frag, pwd := g.agent.GetLocalUserCredentials()
|
frag, pwd := g.agent.GetLocalUserCredentials()
|
||||||
|
|
||||||
return ICEParameters{
|
return ICEParameters{
|
||||||
UsernameFragment: frag,
|
UsernameFragment: frag,
|
||||||
Password: pwd,
|
Password: pwd,
|
||||||
@@ -150,13 +193,9 @@ func (g *ICEGatherer) GetLocalParameters() (ICEParameters, error) {
|
|||||||
|
|
||||||
// GetLocalCandidates returns the sequence of valid local candidates associated with the ICEGatherer.
|
// GetLocalCandidates returns the sequence of valid local candidates associated with the ICEGatherer.
|
||||||
func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) {
|
func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) {
|
||||||
g.lock.RLock()
|
if err := g.createAgent(); err != nil {
|
||||||
defer g.lock.RUnlock()
|
return nil, err
|
||||||
|
|
||||||
if g.agent == nil {
|
|
||||||
return nil, errors.New("gatherer not started")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iceCandidates, err := g.agent.GetLocalCandidates()
|
iceCandidates, err := g.agent.GetLocalCandidates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -164,3 +203,41 @@ func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) {
|
|||||||
|
|
||||||
return newICECandidatesFromICE(iceCandidates)
|
return newICECandidatesFromICE(iceCandidates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnLocalCandidate sets an event handler which fires when a new local ICE candidate is available
|
||||||
|
func (g *ICEGatherer) OnLocalCandidate(f func(*ICECandidate)) {
|
||||||
|
g.lock.Lock()
|
||||||
|
defer g.lock.Unlock()
|
||||||
|
g.onLocalCandidateHdlr = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnStateChange fires any time the ICEGatherer changes
|
||||||
|
func (g *ICEGatherer) OnStateChange(f func(ICEGathererState)) {
|
||||||
|
g.lock.Lock()
|
||||||
|
defer g.lock.Unlock()
|
||||||
|
g.onStateChangeHdlr = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// State indicates the current state of the ICE gatherer.
|
||||||
|
func (g *ICEGatherer) State() ICEGathererState {
|
||||||
|
g.lock.RLock()
|
||||||
|
defer g.lock.RUnlock()
|
||||||
|
return g.state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *ICEGatherer) setState(s ICEGathererState) {
|
||||||
|
g.lock.Lock()
|
||||||
|
g.state = s
|
||||||
|
hdlr := g.onStateChangeHdlr
|
||||||
|
g.lock.Unlock()
|
||||||
|
|
||||||
|
if hdlr != nil {
|
||||||
|
go hdlr(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *ICEGatherer) getAgent() *ice.Agent {
|
||||||
|
g.lock.RLock()
|
||||||
|
defer g.lock.RUnlock()
|
||||||
|
return g.agent
|
||||||
|
}
|
||||||
|
@@ -260,8 +260,7 @@ func (t *ICETransport) NewEndpoint(f mux.MatchFunc) *mux.Endpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *ICETransport) ensureGatherer() error {
|
func (t *ICETransport) ensureGatherer() error {
|
||||||
if t.gatherer == nil ||
|
if t.gatherer == nil || t.gatherer.getAgent() == nil {
|
||||||
t.gatherer.agent == nil {
|
|
||||||
return errors.New("gatherer not started")
|
return errors.New("gatherer not started")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -43,7 +43,6 @@ type PeerConnection struct {
|
|||||||
currentRemoteDescription *SessionDescription
|
currentRemoteDescription *SessionDescription
|
||||||
pendingRemoteDescription *SessionDescription
|
pendingRemoteDescription *SessionDescription
|
||||||
signalingState SignalingState
|
signalingState SignalingState
|
||||||
iceGatheringState ICEGatheringState
|
|
||||||
iceConnectionState ICEConnectionState
|
iceConnectionState ICEConnectionState
|
||||||
connectionState PeerConnectionState
|
connectionState PeerConnectionState
|
||||||
|
|
||||||
@@ -69,8 +68,6 @@ type PeerConnection struct {
|
|||||||
onICEConnectionStateChangeHandler func(ICEConnectionState)
|
onICEConnectionStateChangeHandler func(ICEConnectionState)
|
||||||
onTrackHandler func(*Track, *RTPReceiver)
|
onTrackHandler func(*Track, *RTPReceiver)
|
||||||
onDataChannelHandler func(*DataChannel)
|
onDataChannelHandler func(*DataChannel)
|
||||||
onICECandidateHandler func(*ICECandidate)
|
|
||||||
onICEGatheringStateChangeHandler func()
|
|
||||||
|
|
||||||
iceGatherer *ICEGatherer
|
iceGatherer *ICEGatherer
|
||||||
iceTransport *ICETransport
|
iceTransport *ICETransport
|
||||||
@@ -111,7 +108,6 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection,
|
|||||||
lastAnswer: "",
|
lastAnswer: "",
|
||||||
signalingState: SignalingStateStable,
|
signalingState: SignalingStateStable,
|
||||||
iceConnectionState: ICEConnectionStateNew,
|
iceConnectionState: ICEConnectionStateNew,
|
||||||
iceGatheringState: ICEGatheringStateNew,
|
|
||||||
connectionState: PeerConnectionStateNew,
|
connectionState: PeerConnectionStateNew,
|
||||||
dataChannels: make(map[uint16]*DataChannel),
|
dataChannels: make(map[uint16]*DataChannel),
|
||||||
|
|
||||||
@@ -124,19 +120,12 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now we eagerly allocate and start the gatherer
|
|
||||||
gatherer, err := pc.createICEGatherer()
|
gatherer, err := pc.createICEGatherer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pc.iceGatherer = gatherer
|
pc.iceGatherer = gatherer
|
||||||
|
|
||||||
err = pc.gather()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the ice transport
|
// Create the ice transport
|
||||||
iceTransport := pc.createICETransport()
|
iceTransport := pc.createICETransport()
|
||||||
pc.iceTransport = iceTransport
|
pc.iceTransport = iceTransport
|
||||||
@@ -252,57 +241,14 @@ func (pc *PeerConnection) OnDataChannel(f func(*DataChannel)) {
|
|||||||
|
|
||||||
// OnICECandidate sets an event handler which is invoked when a new ICE
|
// OnICECandidate sets an event handler which is invoked when a new ICE
|
||||||
// candidate is found.
|
// candidate is found.
|
||||||
// BUG: trickle ICE is not supported so this event is triggered immediately when
|
|
||||||
// SetLocalDescription is called. Typically, you only need to use this method
|
|
||||||
// if you want API compatibility with the JavaScript/Wasm bindings.
|
|
||||||
func (pc *PeerConnection) OnICECandidate(f func(*ICECandidate)) {
|
func (pc *PeerConnection) OnICECandidate(f func(*ICECandidate)) {
|
||||||
pc.mu.Lock()
|
pc.iceGatherer.OnLocalCandidate(f)
|
||||||
defer pc.mu.Unlock()
|
|
||||||
pc.onICECandidateHandler = f
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnICEGatheringStateChange sets an event handler which is invoked when the
|
// OnICEGatheringStateChange sets an event handler which is invoked when the
|
||||||
// ICE candidate gathering state has changed.
|
// ICE candidate gathering state has changed.
|
||||||
// BUG: trickle ICE is not supported so this event is triggered immediately when
|
func (pc *PeerConnection) OnICEGatheringStateChange(f func(ICEGathererState)) {
|
||||||
// SetLocalDescription is called. Typically, you only need to use this method
|
pc.iceGatherer.OnStateChange(f)
|
||||||
// if you want API compatibility with the JavaScript/Wasm bindings.
|
|
||||||
func (pc *PeerConnection) OnICEGatheringStateChange(f func()) {
|
|
||||||
pc.mu.Lock()
|
|
||||||
defer pc.mu.Unlock()
|
|
||||||
pc.onICEGatheringStateChangeHandler = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// signalICECandidateGatheringComplete should be called after ICE candidate
|
|
||||||
// gathering is complete. It triggers the appropriate event handlers in order to
|
|
||||||
// emulate a trickle ICE process.
|
|
||||||
func (pc *PeerConnection) signalICECandidateGatheringComplete() error {
|
|
||||||
pc.mu.Lock()
|
|
||||||
defer pc.mu.Unlock()
|
|
||||||
|
|
||||||
// Call onICECandidateHandler for all candidates.
|
|
||||||
if pc.onICECandidateHandler != nil {
|
|
||||||
candidates, err := pc.iceGatherer.GetLocalCandidates()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i := range candidates {
|
|
||||||
go pc.onICECandidateHandler(&candidates[i])
|
|
||||||
}
|
|
||||||
// Call the handler one last time with nil. This is a signal that candidate
|
|
||||||
// gathering is complete.
|
|
||||||
go pc.onICECandidateHandler(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
pc.iceGatheringState = ICEGatheringStateComplete
|
|
||||||
|
|
||||||
// Also trigger the onICEGatheringStateChangeHandler
|
|
||||||
if pc.onICEGatheringStateChangeHandler != nil {
|
|
||||||
// Note: Gathering is already done at this point, but some clients might
|
|
||||||
// still expect the state change handler to be triggered.
|
|
||||||
go pc.onICEGatheringStateChangeHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnTrack sets an event handler which is called when remote track
|
// OnTrack sets an event handler which is called when remote track
|
||||||
@@ -464,6 +410,12 @@ func (pc *PeerConnection) CreateOffer(options *OfferOptions) (SessionDescription
|
|||||||
return SessionDescription{}, err
|
return SessionDescription{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !pc.iceGatherer.agentIsTrickle {
|
||||||
|
if err = pc.iceGatherer.Gather(); err != nil {
|
||||||
|
return SessionDescription{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
candidates, err := pc.iceGatherer.GetLocalCandidates()
|
candidates, err := pc.iceGatherer.GetLocalCandidates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SessionDescription{}, err
|
return SessionDescription{}, err
|
||||||
@@ -549,10 +501,6 @@ func (pc *PeerConnection) createICEGatherer() (*ICEGatherer, error) {
|
|||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc *PeerConnection) gather() error {
|
|
||||||
return pc.iceGatherer.Gather()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *PeerConnection) createICETransport() *ICETransport {
|
func (pc *PeerConnection) createICETransport() *ICETransport {
|
||||||
t := pc.api.NewICETransport(pc.iceGatherer)
|
t := pc.api.NewICETransport(pc.iceGatherer)
|
||||||
|
|
||||||
@@ -641,6 +589,12 @@ func (pc *PeerConnection) addAnswerMediaTransceivers(d *sdp.SessionDescription)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !pc.iceGatherer.agentIsTrickle {
|
||||||
|
if err = pc.iceGatherer.Gather(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
candidates, err := pc.iceGatherer.GetLocalCandidates()
|
candidates, err := pc.iceGatherer.GetLocalCandidates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -872,8 +826,6 @@ func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Initiate ICE candidate gathering?
|
|
||||||
|
|
||||||
desc.parsed = &sdp.SessionDescription{}
|
desc.parsed = &sdp.SessionDescription{}
|
||||||
if err := desc.parsed.Unmarshal([]byte(desc.SDP)); err != nil {
|
if err := desc.parsed.Unmarshal([]byte(desc.SDP)); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -882,14 +834,10 @@ func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the appropriate event handlers to signal that ICE candidate gathering
|
if !pc.iceGatherer.agentIsTrickle {
|
||||||
// is complete. In reality it completed a while ago, but triggering these
|
return nil
|
||||||
// events helps maintain API compatibility with the JavaScript/Wasm bindings.
|
|
||||||
if err := pc.signalICECandidateGatheringComplete(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
return pc.iceGatherer.Gather()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalDescription returns pendingLocalDescription if it is not null and
|
// LocalDescription returns pendingLocalDescription if it is not null and
|
||||||
@@ -897,8 +845,8 @@ func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) error {
|
|||||||
// determine if setLocalDescription has already been called.
|
// determine if setLocalDescription has already been called.
|
||||||
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-localdescription
|
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-localdescription
|
||||||
func (pc *PeerConnection) LocalDescription() *SessionDescription {
|
func (pc *PeerConnection) LocalDescription() *SessionDescription {
|
||||||
if pc.pendingLocalDescription != nil {
|
if localDescription := pc.PendingLocalDescription(); localDescription != nil {
|
||||||
return pc.pendingLocalDescription
|
return localDescription
|
||||||
}
|
}
|
||||||
return pc.currentLocalDescription
|
return pc.currentLocalDescription
|
||||||
}
|
}
|
||||||
@@ -913,6 +861,12 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error {
|
|||||||
return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
|
return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !pc.iceGatherer.agentIsTrickle {
|
||||||
|
if err := pc.iceGatherer.Gather(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
desc.parsed = &sdp.SessionDescription{}
|
desc.parsed = &sdp.SessionDescription{}
|
||||||
if err := desc.parsed.Unmarshal([]byte(desc.SDP)); err != nil {
|
if err := desc.parsed.Unmarshal([]byte(desc.SDP)); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1729,18 +1683,8 @@ func (pc *PeerConnection) addTransceiverSDP(d *sdp.SessionDescription, midValue
|
|||||||
}
|
}
|
||||||
|
|
||||||
media = media.WithPropertyAttribute(t.Direction.String())
|
media = media.WithPropertyAttribute(t.Direction.String())
|
||||||
for _, c := range candidates {
|
|
||||||
sdpCandidate := iceCandidateToSDP(c)
|
|
||||||
sdpCandidate.ExtensionAttributes = append(sdpCandidate.ExtensionAttributes, sdp.ICECandidateAttribute{Key: "generation", Value: "0"})
|
|
||||||
sdpCandidate.Component = 1
|
|
||||||
media.WithICECandidate(sdpCandidate)
|
|
||||||
sdpCandidate.Component = 2
|
|
||||||
media.WithICECandidate(sdpCandidate)
|
|
||||||
}
|
|
||||||
if len(candidates) != 0 {
|
|
||||||
media.WithPropertyAttribute("end-of-candidates")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
addCandidatesToMediaDescriptions(candidates, media)
|
||||||
d.WithMedia(media)
|
d.WithMedia(media)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -1768,16 +1712,7 @@ func (pc *PeerConnection) addDataMediaSection(d *sdp.SessionDescription, midValu
|
|||||||
WithPropertyAttribute("sctpmap:5000 webrtc-datachannel 1024").
|
WithPropertyAttribute("sctpmap:5000 webrtc-datachannel 1024").
|
||||||
WithICECredentials(iceParams.UsernameFragment, iceParams.Password)
|
WithICECredentials(iceParams.UsernameFragment, iceParams.Password)
|
||||||
|
|
||||||
for _, c := range candidates {
|
addCandidatesToMediaDescriptions(candidates, media)
|
||||||
sdpCandidate := iceCandidateToSDP(c)
|
|
||||||
sdpCandidate.ExtensionAttributes = append(sdpCandidate.ExtensionAttributes, sdp.ICECandidateAttribute{Key: "generation", Value: "0"})
|
|
||||||
sdpCandidate.Component = 1
|
|
||||||
media.WithICECandidate(sdpCandidate)
|
|
||||||
sdpCandidate.Component = 2
|
|
||||||
media.WithICECandidate(sdpCandidate)
|
|
||||||
}
|
|
||||||
media.WithPropertyAttribute("end-of-candidates")
|
|
||||||
|
|
||||||
d.WithMedia(media)
|
d.WithMedia(media)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1812,12 +1747,39 @@ func (pc *PeerConnection) newRTPTransceiver(
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pc *PeerConnection) populateLocalCandidates(orig *SessionDescription) *SessionDescription {
|
||||||
|
if orig == nil {
|
||||||
|
return nil
|
||||||
|
} else if pc.iceGatherer == nil {
|
||||||
|
return orig
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates, err := pc.iceGatherer.GetLocalCandidates()
|
||||||
|
if err != nil {
|
||||||
|
return orig
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed := pc.pendingLocalDescription.parsed
|
||||||
|
for _, m := range parsed.MediaDescriptions {
|
||||||
|
addCandidatesToMediaDescriptions(candidates, m)
|
||||||
|
}
|
||||||
|
sdp, err := parsed.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return orig
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SessionDescription{
|
||||||
|
SDP: string(sdp),
|
||||||
|
Type: pc.pendingLocalDescription.Type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CurrentLocalDescription represents the local description that was
|
// CurrentLocalDescription represents the local description that was
|
||||||
// successfully negotiated the last time the PeerConnection transitioned
|
// successfully negotiated the last time the PeerConnection transitioned
|
||||||
// into the stable state plus any local candidates that have been generated
|
// into the stable state plus any local candidates that have been generated
|
||||||
// by the ICEAgent since the offer or answer was created.
|
// by the ICEAgent since the offer or answer was created.
|
||||||
func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription {
|
func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription {
|
||||||
return pc.currentLocalDescription
|
return pc.populateLocalCandidates(pc.currentLocalDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingLocalDescription represents a local description that is in the
|
// PendingLocalDescription represents a local description that is in the
|
||||||
@@ -1825,7 +1787,7 @@ func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription {
|
|||||||
// generated by the ICEAgent since the offer or answer was created. If the
|
// generated by the ICEAgent since the offer or answer was created. If the
|
||||||
// PeerConnection is in the stable state, the value is null.
|
// PeerConnection is in the stable state, the value is null.
|
||||||
func (pc *PeerConnection) PendingLocalDescription() *SessionDescription {
|
func (pc *PeerConnection) PendingLocalDescription() *SessionDescription {
|
||||||
return pc.pendingLocalDescription
|
return pc.populateLocalCandidates(pc.pendingLocalDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentRemoteDescription represents the last remote description that was
|
// CurrentRemoteDescription represents the last remote description that was
|
||||||
@@ -1854,7 +1816,14 @@ func (pc *PeerConnection) SignalingState() SignalingState {
|
|||||||
// ICEGatheringState attribute returns the ICE gathering state of the
|
// ICEGatheringState attribute returns the ICE gathering state of the
|
||||||
// PeerConnection instance.
|
// PeerConnection instance.
|
||||||
func (pc *PeerConnection) ICEGatheringState() ICEGatheringState {
|
func (pc *PeerConnection) ICEGatheringState() ICEGatheringState {
|
||||||
return pc.iceGatheringState
|
switch pc.iceGatherer.State() {
|
||||||
|
case ICEGathererStateNew:
|
||||||
|
return ICEGatheringStateNew
|
||||||
|
case ICEGathererStateGathering:
|
||||||
|
return ICEGatheringStateGathering
|
||||||
|
default:
|
||||||
|
return ICEGatheringStateComplete
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectionState attribute returns the connection state of the
|
// ConnectionState attribute returns the connection state of the
|
||||||
@@ -1862,3 +1831,17 @@ func (pc *PeerConnection) ICEGatheringState() ICEGatheringState {
|
|||||||
func (pc *PeerConnection) ConnectionState() PeerConnectionState {
|
func (pc *PeerConnection) ConnectionState() PeerConnectionState {
|
||||||
return pc.connectionState
|
return pc.connectionState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addCandidatesToMediaDescriptions(candidates []ICECandidate, m *sdp.MediaDescription) {
|
||||||
|
for _, c := range candidates {
|
||||||
|
sdpCandidate := iceCandidateToSDP(c)
|
||||||
|
sdpCandidate.ExtensionAttributes = append(sdpCandidate.ExtensionAttributes, sdp.ICECandidateAttribute{Key: "generation", Value: "0"})
|
||||||
|
sdpCandidate.Component = 1
|
||||||
|
m.WithICECandidate(sdpCandidate)
|
||||||
|
sdpCandidate.Component = 2
|
||||||
|
m.WithICECandidate(sdpCandidate)
|
||||||
|
}
|
||||||
|
if len(candidates) != 0 {
|
||||||
|
m.WithPropertyAttribute("end-of-candidates")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -351,7 +351,6 @@ func TestPeerConnection_PeropertyGetters(t *testing.T) {
|
|||||||
currentRemoteDescription: &SessionDescription{},
|
currentRemoteDescription: &SessionDescription{},
|
||||||
pendingRemoteDescription: &SessionDescription{},
|
pendingRemoteDescription: &SessionDescription{},
|
||||||
signalingState: SignalingStateHaveLocalOffer,
|
signalingState: SignalingStateHaveLocalOffer,
|
||||||
iceGatheringState: ICEGatheringStateGathering,
|
|
||||||
iceConnectionState: ICEConnectionStateChecking,
|
iceConnectionState: ICEConnectionStateChecking,
|
||||||
connectionState: PeerConnectionStateConnecting,
|
connectionState: PeerConnectionStateConnecting,
|
||||||
}
|
}
|
||||||
@@ -361,7 +360,6 @@ func TestPeerConnection_PeropertyGetters(t *testing.T) {
|
|||||||
assert.Equal(t, pc.currentRemoteDescription, pc.CurrentRemoteDescription(), "should match")
|
assert.Equal(t, pc.currentRemoteDescription, pc.CurrentRemoteDescription(), "should match")
|
||||||
assert.Equal(t, pc.pendingRemoteDescription, pc.PendingRemoteDescription(), "should match")
|
assert.Equal(t, pc.pendingRemoteDescription, pc.PendingRemoteDescription(), "should match")
|
||||||
assert.Equal(t, pc.signalingState, pc.SignalingState(), "should match")
|
assert.Equal(t, pc.signalingState, pc.SignalingState(), "should match")
|
||||||
assert.Equal(t, pc.iceGatheringState, pc.ICEGatheringState(), "should match")
|
|
||||||
assert.Equal(t, pc.iceConnectionState, pc.ICEConnectionState(), "should match")
|
assert.Equal(t, pc.iceConnectionState, pc.ICEConnectionState(), "should match")
|
||||||
assert.Equal(t, pc.connectionState, pc.ConnectionState(), "should match")
|
assert.Equal(t, pc.connectionState, pc.ConnectionState(), "should match")
|
||||||
}
|
}
|
||||||
|
@@ -389,3 +389,31 @@ func TestPeerConnection_EventHandlers(t *testing.T) {
|
|||||||
t.Fatalf("timed out waiting for one or more events handlers to be called (these *were* called: %+v)", wasCalled)
|
t.Fatalf("timed out waiting for one or more events handlers to be called (these *were* called: %+v)", wasCalled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMultipleOfferAnswer(t *testing.T) {
|
||||||
|
nonTricklePeerConn, err := NewPeerConnection(Configuration{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("New PeerConnection: got error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = nonTricklePeerConn.CreateOffer(nil); err != nil {
|
||||||
|
t.Errorf("First Offer: got error: %v", err)
|
||||||
|
}
|
||||||
|
if _, err = nonTricklePeerConn.CreateOffer(nil); err != nil {
|
||||||
|
t.Errorf("Second Offer: got error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tricklePeerConn, err := NewPeerConnection(Configuration{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("New PeerConnection: got error: %v", err)
|
||||||
|
}
|
||||||
|
tricklePeerConn.OnICECandidate(func(i *ICECandidate) {
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err = tricklePeerConn.CreateOffer(nil); err != nil {
|
||||||
|
t.Errorf("First Offer: got error: %v", err)
|
||||||
|
}
|
||||||
|
if _, err = tricklePeerConn.CreateOffer(nil); err != nil {
|
||||||
|
t.Errorf("Second Offer: got error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user