Files
monibuca/plugin/webrtc/index.go
T
2025-01-02 19:57:16 +08:00

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)
}
}