mirror of
https://github.com/langhuihui/monibuca.git
synced 2026-05-08 08:31:10 +08:00
196 lines
5.9 KiB
Go
196 lines
5.9 KiB
Go
package plugin_webrtc
|
|
|
|
import (
|
|
"embed"
|
|
_ "embed"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pion/logging"
|
|
"m7s.live/v5/pkg/config"
|
|
|
|
"github.com/pion/interceptor"
|
|
. "github.com/pion/webrtc/v4"
|
|
"m7s.live/v5"
|
|
. "m7s.live/v5/pkg"
|
|
. "m7s.live/v5/plugin/webrtc/pkg"
|
|
)
|
|
|
|
var (
|
|
//go:embed web
|
|
web embed.FS
|
|
reg_level = regexp.MustCompile("profile-level-id=(4.+f)")
|
|
_ = m7s.InstallPlugin[WebRTCPlugin](NewPuller, NewPusher)
|
|
)
|
|
|
|
type WebRTCPlugin struct {
|
|
m7s.Plugin
|
|
ICEServers []ICEServer `desc:"ice服务器配置"`
|
|
Port string `default:"tcp:9000" desc:"监听端口"`
|
|
PLI time.Duration `default:"2s" desc:"发送PLI请求间隔"` // 视频流丢包后,发送PLI请求
|
|
EnableOpus bool `default:"true" desc:"是否启用opus编码"` // 是否启用opus编码
|
|
EnableVP9 bool `default:"false" desc:"是否启用vp9编码"` // 是否启用vp9编码
|
|
EnableAv1 bool `default:"false" desc:"是否启用av1编码"` // 是否启用av1编码
|
|
EnableDC bool `default:"true" desc:"是否启用DataChannel"` // 在不支持编码格式的情况下是否启用DataChannel传输
|
|
m MediaEngine
|
|
s SettingEngine
|
|
api *API
|
|
}
|
|
|
|
func (p *WebRTCPlugin) RegisterHandler() map[string]http.HandlerFunc {
|
|
return map[string]http.HandlerFunc{
|
|
"/test/{name}": p.testPage,
|
|
"/push/{streamPath...}": p.servePush,
|
|
"/play/{streamPath...}": p.servePlay,
|
|
}
|
|
}
|
|
|
|
func (p *WebRTCPlugin) NewLogger(scope string) logging.LeveledLogger {
|
|
return &LoggerTransform{Logger: p.Logger.With("scope", scope)}
|
|
}
|
|
|
|
func (p *WebRTCPlugin) OnInit() (err error) {
|
|
if len(p.ICEServers) > 0 {
|
|
for i := range p.ICEServers {
|
|
b, _ := p.ICEServers[i].MarshalJSON()
|
|
p.ICEServers[i].UnmarshalJSON(b)
|
|
}
|
|
}
|
|
p.s.LoggerFactory = p
|
|
RegisterCodecs(&p.m)
|
|
if p.EnableOpus {
|
|
p.m.RegisterCodec(RTPCodecParameters{
|
|
RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil},
|
|
PayloadType: 111,
|
|
}, RTPCodecTypeAudio)
|
|
}
|
|
if p.EnableVP9 {
|
|
p.m.RegisterCodec(RTPCodecParameters{
|
|
RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "", nil},
|
|
PayloadType: 100,
|
|
}, RTPCodecTypeVideo)
|
|
}
|
|
if p.EnableAv1 {
|
|
p.m.RegisterCodec(RTPCodecParameters{
|
|
RTPCodecCapability: RTPCodecCapability{MimeTypeAV1, 90000, 0, "profile=2;level-idx=8;tier=1", nil},
|
|
PayloadType: 45,
|
|
}, RTPCodecTypeVideo)
|
|
}
|
|
i := &interceptor.Registry{}
|
|
if p.GetCommonConf().PublicIP != "" {
|
|
ips := []string{p.GetCommonConf().PublicIP}
|
|
if p.GetCommonConf().PublicIPv6 != "" {
|
|
ips = append(ips, p.GetCommonConf().PublicIPv6)
|
|
}
|
|
p.s.SetNAT1To1IPs(ips, ICECandidateTypeHost)
|
|
}
|
|
ports, err := ParsePort2(p.Port)
|
|
if err != nil {
|
|
p.Error("webrtc port config error", "error", err, "port", p.Port)
|
|
return err
|
|
}
|
|
|
|
switch v := ports.(type) {
|
|
case TCPPort:
|
|
tcpport := int(v)
|
|
tcpl, err := net.ListenTCP("tcp", &net.TCPAddr{
|
|
IP: net.IP{0, 0, 0, 0},
|
|
Port: tcpport,
|
|
})
|
|
p.OnDispose(func() {
|
|
_ = tcpl.Close()
|
|
})
|
|
if err != nil {
|
|
p.Error("webrtc listener tcp", "error", err)
|
|
}
|
|
p.SetDescription("tcp", fmt.Sprintf("%d", tcpport))
|
|
p.Info("webrtc start listen", "port", tcpport)
|
|
p.s.SetICETCPMux(NewICETCPMux(nil, tcpl, 4096))
|
|
p.s.SetNetworkTypes([]NetworkType{NetworkTypeTCP4, NetworkTypeTCP6})
|
|
case UDPRangePort:
|
|
p.s.SetEphemeralUDPPortRange(uint16(v[0]), uint16(v[1]))
|
|
p.SetDescription("udp", fmt.Sprintf("%d-%d", v[0], v[1]))
|
|
case UDPPort:
|
|
// 创建共享WEBRTC端口 默认9000
|
|
udpListener, err := net.ListenUDP("udp", &net.UDPAddr{
|
|
IP: net.IP{0, 0, 0, 0},
|
|
Port: int(v),
|
|
})
|
|
p.OnDispose(func() {
|
|
_ = udpListener.Close()
|
|
})
|
|
if err != nil {
|
|
p.Error("webrtc listener udp", "error", err)
|
|
return err
|
|
}
|
|
p.SetDescription("udp", fmt.Sprintf("%d", v))
|
|
p.Info("webrtc start listen", "port", v)
|
|
p.s.SetICEUDPMux(NewICEUDPMux(nil, udpListener))
|
|
p.s.SetNetworkTypes([]NetworkType{NetworkTypeUDP4, NetworkTypeUDP6})
|
|
}
|
|
if err = RegisterDefaultInterceptors(&p.m, i); err != nil {
|
|
return err
|
|
}
|
|
p.api = NewAPI(WithMediaEngine(&p.m),
|
|
WithInterceptorRegistry(i), WithSettingEngine(p.s))
|
|
_, port, _ := strings.Cut(p.GetCommonConf().HTTP.ListenAddr, ":")
|
|
if port == "80" {
|
|
p.PushAddr = append(p.PushAddr, "http://{hostName}/webrtc/push")
|
|
p.PlayAddr = append(p.PlayAddr, "http://{hostName}/webrtc/play")
|
|
} else if port != "" {
|
|
p.PushAddr = append(p.PushAddr, fmt.Sprintf("http://{hostName}:%s/webrtc/push", port))
|
|
p.PlayAddr = append(p.PlayAddr, fmt.Sprintf("http://{hostName}:%s/webrtc/play", port))
|
|
}
|
|
_, port, _ = strings.Cut(p.GetCommonConf().HTTP.ListenAddrTLS, ":")
|
|
if port == "443" {
|
|
p.PushAddr = append(p.PushAddr, "https://{hostName}/webrtc/push")
|
|
p.PlayAddr = append(p.PlayAddr, "https://{hostName}/webrtc/play")
|
|
} else if port != "" {
|
|
p.PushAddr = append(p.PushAddr, fmt.Sprintf("https://{hostName}:%s/webrtc/push", port))
|
|
p.PlayAddr = append(p.PlayAddr, fmt.Sprintf("https://{hostName}:%s/webrtc/play", port))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (p *WebRTCPlugin) testPage(w http.ResponseWriter, r *http.Request) {
|
|
name := r.PathValue("name")
|
|
switch name {
|
|
case "publish", "screenshare":
|
|
name = "web/publish.html"
|
|
case "subscribe":
|
|
name = "web/subscribe.html"
|
|
case "push":
|
|
name = "web/push.html"
|
|
case "pull":
|
|
name = "web/pull.html"
|
|
}
|
|
f, err := web.Open(name)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
io.Copy(w, f)
|
|
}
|
|
|
|
func (p *WebRTCPlugin) Pull(streamPath string, conf config.Pull, pubConf *config.Publish) {
|
|
if strings.HasPrefix(conf.URL, "https://rtc.live.cloudflare.com") {
|
|
cfClient := NewCFClient(DIRECTION_PULL)
|
|
var err error
|
|
cfClient.PeerConnection, err = p.api.NewPeerConnection(Configuration{
|
|
ICEServers: p.ICEServers,
|
|
BundlePolicy: BundlePolicyMaxBundle,
|
|
})
|
|
if err != nil {
|
|
p.Error("pull", "error", err)
|
|
return
|
|
}
|
|
cfClient.GetPullJob().Init(cfClient, &p.Plugin, streamPath, conf, pubConf)
|
|
}
|
|
}
|