Improve WebRTC candidates handling
This commit is contained in:
+54
-13
@@ -2,6 +2,7 @@ package webrtc
|
||||
|
||||
import (
|
||||
"net"
|
||||
"slices"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/webrtc/v3"
|
||||
@@ -15,7 +16,15 @@ func NewAPI() (*webrtc.API, error) {
|
||||
return NewServerAPI("", "", nil)
|
||||
}
|
||||
|
||||
func NewServerAPI(address, network string, candidateHost []string) (*webrtc.API, error) {
|
||||
type Filters struct {
|
||||
Candidates []string `yaml:"candidates"`
|
||||
Interfaces []string `yaml:"interfaces"`
|
||||
IPs []string `yaml:"ips"`
|
||||
Networks []string `yaml:"networks"`
|
||||
UDPPorts []uint16 `yaml:"udp_ports"`
|
||||
}
|
||||
|
||||
func NewServerAPI(network, address string, filters *Filters) (*webrtc.API, error) {
|
||||
// for debug logs add to env: `PION_LOG_DEBUG=all`
|
||||
m := &webrtc.MediaEngine{}
|
||||
//if err := m.RegisterDefaultCodecs(); err != nil {
|
||||
@@ -32,23 +41,55 @@ func NewServerAPI(address, network string, candidateHost []string) (*webrtc.API,
|
||||
|
||||
s := webrtc.SettingEngine{}
|
||||
|
||||
// disable listen on Hassio docker interfaces
|
||||
s.SetInterfaceFilter(func(name string) bool {
|
||||
return name != "hassio" && name != "docker0"
|
||||
})
|
||||
|
||||
// fix https://github.com/pion/webrtc/pull/2407
|
||||
s.SetDTLSInsecureSkipHelloVerify(true)
|
||||
|
||||
s.SetReceiveMTU(ReceiveMTU)
|
||||
if filters != nil && filters.Interfaces != nil {
|
||||
s.SetIncludeLoopbackCandidate(true)
|
||||
s.SetInterfaceFilter(func(name string) bool {
|
||||
return slices.Contains(filters.Interfaces, name)
|
||||
})
|
||||
} else {
|
||||
// disable listen on Hassio docker interfaces
|
||||
s.SetInterfaceFilter(func(name string) bool {
|
||||
return name != "hassio" && name != "docker0"
|
||||
})
|
||||
}
|
||||
|
||||
s.SetNAT1To1IPs(candidateHost, webrtc.ICECandidateTypeHost)
|
||||
if filters != nil && filters.IPs != nil {
|
||||
s.SetIncludeLoopbackCandidate(true)
|
||||
s.SetIPFilter(func(ip net.IP) bool {
|
||||
return slices.Contains(filters.IPs, ip.String())
|
||||
})
|
||||
}
|
||||
|
||||
// by default enable IPv4 + IPv6 modes
|
||||
s.SetNetworkTypes([]webrtc.NetworkType{
|
||||
webrtc.NetworkTypeUDP4, webrtc.NetworkTypeTCP4,
|
||||
webrtc.NetworkTypeUDP6, webrtc.NetworkTypeTCP6,
|
||||
})
|
||||
if filters != nil && filters.Networks != nil {
|
||||
var networkTypes []webrtc.NetworkType
|
||||
for _, s := range filters.Networks {
|
||||
if networkType, err := webrtc.NewNetworkType(s); err == nil {
|
||||
networkTypes = append(networkTypes, networkType)
|
||||
}
|
||||
}
|
||||
s.SetNetworkTypes(networkTypes)
|
||||
} else {
|
||||
s.SetNetworkTypes([]webrtc.NetworkType{
|
||||
webrtc.NetworkTypeUDP4, webrtc.NetworkTypeUDP6,
|
||||
webrtc.NetworkTypeTCP4, webrtc.NetworkTypeTCP6,
|
||||
})
|
||||
}
|
||||
|
||||
if filters != nil && len(filters.UDPPorts) == 2 {
|
||||
_ = s.SetEphemeralUDPPortRange(filters.UDPPorts[0], filters.UDPPorts[1])
|
||||
}
|
||||
|
||||
//if len(hosts) != 0 {
|
||||
// // support only: host, srflx
|
||||
// if candidateType, err := webrtc.NewICECandidateType(hosts[0]); err == nil {
|
||||
// s.SetNAT1To1IPs(hosts[1:], candidateType)
|
||||
// } else {
|
||||
// s.SetNAT1To1IPs(hosts, 0) // 0 = host
|
||||
// }
|
||||
//}
|
||||
|
||||
if address != "" {
|
||||
if network == "" || network == "tcp" {
|
||||
|
||||
+27
-24
@@ -273,38 +273,41 @@ func MimeType(codec *core.Codec) string {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// 4.1.2.2. Guidelines for Choosing Type and Local Preferences
|
||||
// The RECOMMENDED values are 126 for host candidates, 100
|
||||
// for server reflexive candidates, 110 for peer reflexive candidates,
|
||||
// and 0 for relayed candidates.
|
||||
|
||||
const PriorityTypeHostUDP = (1 << 24) * int(126)
|
||||
const PriorityTypeHostTCP = (1 << 24) * int(126-27)
|
||||
const PriorityLocalUDP = (1 << 8) * int(65535)
|
||||
const PriorityLocalTCPPassive = (1 << 8) * int((1<<13)*4+8191)
|
||||
const PriorityComponentRTP = 1 * int(256-ice.ComponentRTP)
|
||||
|
||||
func CandidateManualHostUDP(host, port string, offset int) string {
|
||||
foundation := crc32.ChecksumIEEE([]byte("host" + host + "udp4"))
|
||||
priority := PriorityTypeHostUDP + PriorityLocalUDP + PriorityComponentRTP + offset
|
||||
|
||||
func CandidateICE(network, host, port string, priority uint32) string {
|
||||
// 1. Foundation
|
||||
// 2. Component, always 1 because RTP
|
||||
// 3. udp or tcp
|
||||
// 3. "udp" or "tcp"
|
||||
// 4. Priority
|
||||
// 5. Host - IP4 or IP6 or domain name
|
||||
// 6. Port
|
||||
// 7. typ host
|
||||
return fmt.Sprintf("candidate:%d 1 udp %d %s %s typ host", foundation, priority, host, port)
|
||||
// 7. "typ host"
|
||||
foundation := crc32.ChecksumIEEE([]byte("host" + host + network + "4"))
|
||||
s := fmt.Sprintf("candidate:%d 1 %s %d %s %s typ host", foundation, network, priority, host, port)
|
||||
if network == "tcp" {
|
||||
return s + " tcptype passive"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func CandidateManualHostTCPPassive(host, port string, offset int) string {
|
||||
foundation := crc32.ChecksumIEEE([]byte("host" + host + "tcp4"))
|
||||
priority := PriorityTypeHostTCP + PriorityLocalTCPPassive + PriorityComponentRTP + offset
|
||||
// Priority = type << 24 + local << 8 + component
|
||||
// https://www.rfc-editor.org/rfc/rfc8445#section-5.1.2.1
|
||||
|
||||
return fmt.Sprintf(
|
||||
"candidate:%d 1 tcp %d %s %s typ host tcptype passive", foundation, priority, host, port,
|
||||
)
|
||||
const PriorityHostUDP uint32 = 0x001F_FFFF |
|
||||
126<<24 | // udp host
|
||||
7<<21 // udp
|
||||
const PriorityHostTCPPassive uint32 = 0x001F_FFFF |
|
||||
99<<24 | // tcp host
|
||||
4<<21 // tcp passive
|
||||
|
||||
// CandidateHostPriority (lower indexes has a higher priority)
|
||||
func CandidateHostPriority(network string, index int) uint32 {
|
||||
switch network {
|
||||
case "udp":
|
||||
return PriorityHostUDP - uint32(index)
|
||||
case "tcp":
|
||||
return PriorityHostTCPPassive - uint32(index)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func UnmarshalICEServers(b []byte) ([]webrtc.ICEServer, error) {
|
||||
|
||||
+36
-5
@@ -81,11 +81,42 @@ transeivers:
|
||||
return c.pc.LocalDescription().SDP, nil
|
||||
}
|
||||
|
||||
func (c *Conn) GetCompleteAnswer() (answer string, err error) {
|
||||
if _, err = c.GetAnswer(); err != nil {
|
||||
return
|
||||
// GetCompleteAnswer - get SDP answer with candidates inside
|
||||
func (c *Conn) GetCompleteAnswer(candidates []string, filter func(*webrtc.ICECandidate) bool) (string, error) {
|
||||
var done = make(chan struct{})
|
||||
|
||||
c.pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
||||
if candidate != nil {
|
||||
if filter == nil || filter(candidate) {
|
||||
candidates = append(candidates, candidate.ToJSON().Candidate)
|
||||
}
|
||||
} else {
|
||||
done <- struct{}{}
|
||||
}
|
||||
})
|
||||
|
||||
answer, err := c.GetAnswer()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
<-webrtc.GatheringCompletePromise(c.pc)
|
||||
return c.pc.LocalDescription().SDP, nil
|
||||
<-done
|
||||
|
||||
sd := &sdp.SessionDescription{}
|
||||
if err = sd.Unmarshal([]byte(answer)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
md := sd.MediaDescriptions[0]
|
||||
|
||||
for _, candidate := range candidates {
|
||||
md.WithPropertyAttribute(candidate)
|
||||
}
|
||||
|
||||
b, err := sd.Marshal()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user