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/v3" "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) } }