diff --git a/go.mod b/go.mod index 069f3dcf..265ebe0e 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/asticode/go-astits v1.13.0 github.com/bluenviron/gohlslib/v2 v2.2.3 github.com/bluenviron/gortmplib v0.0.0-20250916095243-72b6eaffb6a4 - github.com/bluenviron/gortsplib/v5 v5.0.0-20250917174653-288b637b2301 + github.com/bluenviron/gortsplib/v5 v5.0.0-20250917193011-6107dea9a082 github.com/bluenviron/mediacommon/v2 v2.4.2 github.com/datarhei/gosrt v0.9.0 github.com/fsnotify/fsnotify v1.9.0 diff --git a/go.sum b/go.sum index 938a0a7e..aa026d8d 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/bluenviron/gohlslib/v2 v2.2.3 h1:1R/Jnh1kNR9UB09KAX6xjS2GcdKFRLuPd9wM github.com/bluenviron/gohlslib/v2 v2.2.3/go.mod h1:z4Viks+Mdgcl7OcOVJ1fgSmuUwCCJBxYJPLN49n7Vnw= github.com/bluenviron/gortmplib v0.0.0-20250916095243-72b6eaffb6a4 h1:ZDsCiFpmOoEy2eWcJBEhV8jb0sA9MxO/BzYmyrmm4hE= github.com/bluenviron/gortmplib v0.0.0-20250916095243-72b6eaffb6a4/go.mod h1:73VbUEJLkaixSRPRI/imEkVRkBccvR1GKrd6SUOdkrQ= -github.com/bluenviron/gortsplib/v5 v5.0.0-20250917174653-288b637b2301 h1:dLEqNi4/uTN6lUM3djwWXhO0uqs9/TbRSg4AfDOJjH8= -github.com/bluenviron/gortsplib/v5 v5.0.0-20250917174653-288b637b2301/go.mod h1:zp5tDI2tkjVMgyWiy+B2tRQowqYq/GwHjdAdYFQAxrU= +github.com/bluenviron/gortsplib/v5 v5.0.0-20250917193011-6107dea9a082 h1:x/YfIKksGOKaY/PmKg7USS81gyw2tzF7HLXk5ftWlKY= +github.com/bluenviron/gortsplib/v5 v5.0.0-20250917193011-6107dea9a082/go.mod h1:zp5tDI2tkjVMgyWiy+B2tRQowqYq/GwHjdAdYFQAxrU= github.com/bluenviron/mediacommon/v2 v2.4.2 h1:rggs61nTaqPcR1+RhlIE8/nDqfF5PO57QxpxBzSFfrw= github.com/bluenviron/mediacommon/v2 v2.4.2/go.mod h1:zy1fODPuS/kBd93ftgJS1Jhvjq7LFWfAo32KP7By9AE= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= diff --git a/internal/conf/path.go b/internal/conf/path.go index 16c38607..5d3dd159 100644 --- a/internal/conf/path.go +++ b/internal/conf/path.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" "net" - gourl "net/url" + "net/url" "reflect" "regexp" "sort" @@ -372,8 +372,12 @@ func (pconf *Path) validate( } case strings.HasPrefix(pconf.Source, "rtsp://") || - strings.HasPrefix(pconf.Source, "rtsps://"): - _, err := base.ParseURL(pconf.Source) + strings.HasPrefix(pconf.Source, "rtsps://") || + strings.HasPrefix(pconf.Source, "rtsp+http://") || + strings.HasPrefix(pconf.Source, "rtsps+http://") || + strings.HasPrefix(pconf.Source, "rtsp+ws://") || + strings.HasPrefix(pconf.Source, "rtsps+ws://"): + _, err := url.Parse(pconf.Source) if err != nil { return fmt.Errorf("'%s' is not a valid URL", pconf.Source) } @@ -390,7 +394,7 @@ func (pconf *Path) validate( case strings.HasPrefix(pconf.Source, "rtmp://") || strings.HasPrefix(pconf.Source, "rtmps://"): - u, err := gourl.Parse(pconf.Source) + u, err := url.Parse(pconf.Source) if err != nil { return fmt.Errorf("'%s' is not a valid URL", pconf.Source) } @@ -406,15 +410,11 @@ func (pconf *Path) validate( case strings.HasPrefix(pconf.Source, "http://") || strings.HasPrefix(pconf.Source, "https://"): - u, err := gourl.Parse(pconf.Source) + u, err := url.Parse(pconf.Source) if err != nil { return fmt.Errorf("'%s' is not a valid URL", pconf.Source) } - if u.Scheme != "http" && u.Scheme != "https" { - return fmt.Errorf("'%s' is not a valid URL", pconf.Source) - } - if u.User != nil { pass, _ := u.User.Password() user := u.User.Username() @@ -454,14 +454,14 @@ func (pconf *Path) validate( } case strings.HasPrefix(pconf.Source, "srt://"): - _, err := gourl.Parse(pconf.Source) + _, err := url.Parse(pconf.Source) if err != nil { return fmt.Errorf("'%s' is not a valid URL", pconf.Source) } case strings.HasPrefix(pconf.Source, "whep://") || strings.HasPrefix(pconf.Source, "wheps://"): - _, err := gourl.Parse(pconf.Source) + _, err := url.Parse(pconf.Source) if err != nil { return fmt.Errorf("'%s' is not a valid URL", pconf.Source) } diff --git a/internal/staticsources/handler.go b/internal/staticsources/handler.go index e999111a..ddfe97c8 100644 --- a/internal/staticsources/handler.go +++ b/internal/staticsources/handler.go @@ -95,7 +95,11 @@ func (s *Handler) Initialize() { switch { case strings.HasPrefix(s.Conf.Source, "rtsp://") || - strings.HasPrefix(s.Conf.Source, "rtsps://"): + strings.HasPrefix(s.Conf.Source, "rtsps://") || + strings.HasPrefix(s.Conf.Source, "rtsp+http://") || + strings.HasPrefix(s.Conf.Source, "rtsps+http://") || + strings.HasPrefix(s.Conf.Source, "rtsp+ws://") || + strings.HasPrefix(s.Conf.Source, "rtsps+ws://"): s.instance = &ssrtsp.Source{ ReadTimeout: s.ReadTimeout, WriteTimeout: s.WriteTimeout, diff --git a/internal/staticsources/rtsp/source.go b/internal/staticsources/rtsp/source.go index 9c09d65d..36036f08 100644 --- a/internal/staticsources/rtsp/source.go +++ b/internal/staticsources/rtsp/source.go @@ -2,6 +2,8 @@ package rtsp import ( + "net/url" + "regexp" "time" "github.com/bluenviron/gortsplib/v5" @@ -116,14 +118,37 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { decodeErrors.Start() defer decodeErrors.Stop() - u, err := base.ParseURL(params.ResolvedSource) + u0, err := url.Parse(params.ResolvedSource) + if err != nil { + return err + } + + var scheme string + if u0.Scheme == "rtsp" || u0.Scheme == "rtsp+http" || u0.Scheme == "rtsp+ws" { + scheme = "rtsp" + } else { + scheme = "rtsps" + } + + var tunnel gortsplib.Tunnel + switch u0.Scheme { + case "rtsp+http", "rtsps+http": + tunnel = gortsplib.TunnelHTTP + case "rtsp+ws", "rtsps+ws": + tunnel = gortsplib.TunnelWebSocket + default: + tunnel = gortsplib.TunnelNone + } + + u, err := base.ParseURL(regexp.MustCompile("^.*?://").ReplaceAllString(params.ResolvedSource, "rtsp://")) if err != nil { return err } c := &gortsplib.Client{ - Scheme: u.Scheme, + Scheme: scheme, Host: u.Host, + Tunnel: tunnel, Protocol: params.Conf.RTSPTransport.Protocol, TLSConfig: tls.MakeConfig(u.Hostname(), params.Conf.SourceFingerprint), ReadTimeout: time.Duration(s.ReadTimeout), diff --git a/mediamtx.yml b/mediamtx.yml index 0793b4e1..2666b70a 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -438,6 +438,10 @@ pathDefaults: # * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client # * rtsp://existing-url -> the stream is pulled from another RTSP server / camera # * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS + # * rtsp+http://existing-url -> the stream is pulled from another RTSP server / camera, with HTTP tunneling + # * rtsps+http://existing-url -> the stream is pulled from another RTSP server / camera, with HTTPS tunneling + # * rtsp+ws://existing-url -> the stream is pulled from another RTSP server / camera, with WebSocket tunneling + # * rtsps+ws://existing-url -> the stream is pulled from another RTSP server / camera, with secure WebSocket tunneling # * rtmp://existing-url -> the stream is pulled from another RTMP server / camera # * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS # * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera