Files
monibuca/plugin/webrtc/index.go
2025-08-29 00:51:08 +08:00

216 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package plugin_webrtc
import (
"embed"
"fmt"
"io"
"net"
"net/http"
"regexp"
"strings"
"time"
"github.com/pion/logging"
"github.com/pion/sdp/v3"
. "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
//go:embed default.yaml
defaultYaml m7s.DefaultYaml
reg_level = regexp.MustCompile("profile-level-id=(4.+f)")
_ = m7s.InstallPlugin[WebRTCPlugin](m7s.PluginMeta{
DefaultYaml: defaultYaml,
NewPuller: NewPuller,
NewPusher: NewPusher,
NewPullProxy: NewPullProxy,
})
)
type WebRTCPlugin struct {
m7s.Plugin
ICEServers []ICEServer `desc:"ice服务器配置"`
Port string `default:"tcp:9000" desc:"监听端口"`
PLI time.Duration `default:"2s" desc:"发送PLI请求间隔"` // 视频流丢包后发送PLI请求
EnableDC bool `default:"true" desc:"是否启用DataChannel"` // 在不支持编码格式的情况下是否启用DataChannel传输
MimeType []string `desc:"MimeType过滤列表为空则不过滤"` // MimeType过滤列表支持的格式如video/H264, audio/opus
s SettingEngine
}
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)}
}
// initSettingEngine 初始化SettingEngine
func (p *WebRTCPlugin) initSettingEngine() error {
// 设置LoggerFactory
p.s.LoggerFactory = p
// 配置NAT 1:1 IP映射
if p.GetCommonConf().PublicIP != "" {
ips := []string{p.GetCommonConf().PublicIP}
if p.GetCommonConf().PublicIPv6 != "" {
ips = append(ips, p.GetCommonConf().PublicIPv6)
}
p.s.SetNAT1To1IPs(ips, ICECandidateTypeHost)
}
// 配置端口
if err := p.configurePort(); err != nil {
return err
}
return nil
}
// configurePort 配置端口设置
func (p *WebRTCPlugin) configurePort() error {
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,
})
if err != nil {
p.Error("webrtc listener tcp", "error", err)
}
p.Using(tcpl)
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})
p.s.DisableSRTPReplayProtection(true)
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),
})
if err != nil {
p.Error("webrtc listener udp", "error", err)
return err
}
p.Using(udpListener)
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})
}
return nil
}
func (p *WebRTCPlugin) CreatePC(sd SessionDescription, conf Configuration) (pc *PeerConnection, err error) {
var ssd *sdp.SessionDescription
ssd, err = sd.Unmarshal()
if err != nil {
return
}
var api *API
api, err = CreateAPI(ssd, p.s)
if err != nil {
return
}
pc, err = api.NewPeerConnection(conf)
if err == nil {
err = pc.SetRemoteDescription(sd)
}
return
}
func (p *WebRTCPlugin) Start() (err error) {
if len(p.ICEServers) > 0 {
for i := range p.ICEServers {
b, _ := p.ICEServers[i].MarshalJSON()
p.ICEServers[i].UnmarshalJSON(b)
}
}
MimeTypes = p.MimeType
if err = p.initSettingEngine(); err != nil {
return err
}
_, port, _ := strings.Cut(p.GetCommonConf().HTTP.ListenAddr, ":")
if port == "80" {
p.PushAddr = append(p.PushAddr, "http://{hostName}/webrtc/push/{streamPath}")
p.PlayAddr = append(p.PlayAddr, "http://{hostName}/webrtc/play/{streamPath}")
} else if port != "" {
p.PushAddr = append(p.PushAddr, fmt.Sprintf("http://{hostName}:%s/webrtc/push/{streamPath}", port))
p.PlayAddr = append(p.PlayAddr, fmt.Sprintf("http://{hostName}:%s/webrtc/play/{streamPath}", port))
}
_, port, _ = strings.Cut(p.GetCommonConf().HTTP.ListenAddrTLS, ":")
if port == "443" {
p.PushAddr = append(p.PushAddr, "https://{hostName}/webrtc/push/{streamPath}")
p.PlayAddr = append(p.PlayAddr, "https://{hostName}/webrtc/play/{streamPath}")
} else if port != "" {
p.PushAddr = append(p.PushAddr, fmt.Sprintf("https://{hostName}:%s/webrtc/push/{streamPath}", port))
p.PlayAddr = append(p.PlayAddr, fmt.Sprintf("https://{hostName}:%s/webrtc/play/{streamPath}", 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"
case "batchv2":
name = "web/batchv2.html"
default:
name = "web/" + name
}
// Set appropriate MIME type based on file extension
if strings.HasSuffix(name, ".html") {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
} else if strings.HasSuffix(name, ".js") {
w.Header().Set("Content-Type", "application/javascript")
// } else if strings.HasSuffix(name, ".css") {
// w.Header().Set("Content-Type", "text/css")
// } else if strings.HasSuffix(name, ".json") {
// w.Header().Set("Content-Type", "application/json")
// } else if strings.HasSuffix(name, ".png") {
// w.Header().Set("Content-Type", "image/png")
// } else if strings.HasSuffix(name, ".jpg") || strings.HasSuffix(name, ".jpeg") {
// w.Header().Set("Content-Type", "image/jpeg")
// } else if strings.HasSuffix(name, ".svg") {
// w.Header().Set("Content-Type", "image/svg+xml")
}
f, err := web.Open(name)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
io.Copy(w, f)
}