mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-10-05 07:36:57 +08:00
allow using MTX_QUERY inside source (#3486)
this allows to pass query parameters to sources, for instance: source: rtsp://my_host/my_path?$MTX_QUERY sourceOnDemand: true
This commit is contained in:
@@ -169,26 +169,19 @@ func (pa *path) run() {
|
|||||||
if pa.conf.Source == "redirect" {
|
if pa.conf.Source == "redirect" {
|
||||||
pa.source = &sourceRedirect{}
|
pa.source = &sourceRedirect{}
|
||||||
} else if pa.conf.HasStaticSource() {
|
} else if pa.conf.HasStaticSource() {
|
||||||
resolvedSource := pa.conf.Source
|
|
||||||
if len(pa.matches) > 1 {
|
|
||||||
for i, ma := range pa.matches[1:] {
|
|
||||||
resolvedSource = strings.ReplaceAll(resolvedSource, "$G"+strconv.FormatInt(int64(i+1), 10), ma)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pa.source = &staticSourceHandler{
|
pa.source = &staticSourceHandler{
|
||||||
conf: pa.conf,
|
conf: pa.conf,
|
||||||
logLevel: pa.logLevel,
|
logLevel: pa.logLevel,
|
||||||
readTimeout: pa.readTimeout,
|
readTimeout: pa.readTimeout,
|
||||||
writeTimeout: pa.writeTimeout,
|
writeTimeout: pa.writeTimeout,
|
||||||
writeQueueSize: pa.writeQueueSize,
|
writeQueueSize: pa.writeQueueSize,
|
||||||
resolvedSource: resolvedSource,
|
matches: pa.matches,
|
||||||
parent: pa,
|
parent: pa,
|
||||||
}
|
}
|
||||||
pa.source.(*staticSourceHandler).initialize()
|
pa.source.(*staticSourceHandler).initialize()
|
||||||
|
|
||||||
if !pa.conf.SourceOnDemand {
|
if !pa.conf.SourceOnDemand {
|
||||||
pa.source.(*staticSourceHandler).start(false)
|
pa.source.(*staticSourceHandler).start(false, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,7 +424,7 @@ func (pa *path) doDescribe(req defs.PathDescribeReq) {
|
|||||||
|
|
||||||
if pa.conf.HasOnDemandStaticSource() {
|
if pa.conf.HasOnDemandStaticSource() {
|
||||||
if pa.onDemandStaticSourceState == pathOnDemandStateInitial {
|
if pa.onDemandStaticSourceState == pathOnDemandStateInitial {
|
||||||
pa.onDemandStaticSourceStart()
|
pa.onDemandStaticSourceStart(req.AccessRequest.Query)
|
||||||
}
|
}
|
||||||
pa.describeRequestsOnHold = append(pa.describeRequestsOnHold, req)
|
pa.describeRequestsOnHold = append(pa.describeRequestsOnHold, req)
|
||||||
return
|
return
|
||||||
@@ -539,7 +532,7 @@ func (pa *path) doAddReader(req defs.PathAddReaderReq) {
|
|||||||
|
|
||||||
if pa.conf.HasOnDemandStaticSource() {
|
if pa.conf.HasOnDemandStaticSource() {
|
||||||
if pa.onDemandStaticSourceState == pathOnDemandStateInitial {
|
if pa.onDemandStaticSourceState == pathOnDemandStateInitial {
|
||||||
pa.onDemandStaticSourceStart()
|
pa.onDemandStaticSourceStart(req.AccessRequest.Query)
|
||||||
}
|
}
|
||||||
pa.readerAddRequestsOnHold = append(pa.readerAddRequestsOnHold, req)
|
pa.readerAddRequestsOnHold = append(pa.readerAddRequestsOnHold, req)
|
||||||
return
|
return
|
||||||
@@ -655,8 +648,8 @@ func (pa *path) shouldClose() bool {
|
|||||||
len(pa.readerAddRequestsOnHold) == 0
|
len(pa.readerAddRequestsOnHold) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) onDemandStaticSourceStart() {
|
func (pa *path) onDemandStaticSourceStart(query string) {
|
||||||
pa.source.(*staticSourceHandler).start(true)
|
pa.source.(*staticSourceHandler).start(true, query)
|
||||||
|
|
||||||
pa.onDemandStaticSourceReadyTimer.Stop()
|
pa.onDemandStaticSourceReadyTimer.Stop()
|
||||||
pa.onDemandStaticSourceReadyTimer = time.NewTimer(time.Duration(pa.conf.SourceOnDemandStartTimeout))
|
pa.onDemandStaticSourceReadyTimer = time.NewTimer(time.Duration(pa.conf.SourceOnDemandStartTimeout))
|
||||||
|
@@ -700,13 +700,14 @@ func TestPathFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathSourceRegexp(t *testing.T) {
|
func TestPathResolveSource(t *testing.T) {
|
||||||
var stream *gortsplib.ServerStream
|
var stream *gortsplib.ServerStream
|
||||||
|
|
||||||
s := gortsplib.Server{
|
s := gortsplib.Server{
|
||||||
Handler: &testServer{
|
Handler: &testServer{
|
||||||
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx,
|
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx,
|
||||||
) (*base.Response, *gortsplib.ServerStream, error) {
|
) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
|
require.Equal(t, "key=val", ctx.Query)
|
||||||
require.Equal(t, "/a", ctx.Path)
|
require.Equal(t, "/a", ctx.Path)
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -736,7 +737,7 @@ func TestPathSourceRegexp(t *testing.T) {
|
|||||||
p, ok := newInstance(
|
p, ok := newInstance(
|
||||||
"paths:\n" +
|
"paths:\n" +
|
||||||
" '~^test_(.+)$':\n" +
|
" '~^test_(.+)$':\n" +
|
||||||
" source: rtsp://127.0.0.1:8555/$G1\n" +
|
" source: rtsp://127.0.0.1:8555/$G1?$MTX_QUERY\n" +
|
||||||
" sourceOnDemand: yes\n" +
|
" sourceOnDemand: yes\n" +
|
||||||
" 'all':\n")
|
" 'all':\n")
|
||||||
require.Equal(t, true, ok)
|
require.Equal(t, true, ok)
|
||||||
@@ -744,7 +745,7 @@ func TestPathSourceRegexp(t *testing.T) {
|
|||||||
|
|
||||||
reader := gortsplib.Client{}
|
reader := gortsplib.Client{}
|
||||||
|
|
||||||
u, err := base.ParseURL("rtsp://127.0.0.1:8554/test_a")
|
u, err := base.ParseURL("rtsp://127.0.0.1:8554/test_a?key=val")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = reader.Start(u.Scheme, u.Host)
|
err = reader.Start(u.Scheme, u.Host)
|
||||||
|
@@ -3,6 +3,7 @@ package core
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -22,6 +23,18 @@ const (
|
|||||||
staticSourceHandlerRetryPause = 5 * time.Second
|
staticSourceHandlerRetryPause = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func resolveSource(s string, matches []string, query string) string {
|
||||||
|
if len(matches) > 1 {
|
||||||
|
for i, ma := range matches[1:] {
|
||||||
|
s = strings.ReplaceAll(s, "$G"+strconv.FormatInt(int64(i+1), 10), ma)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s = strings.ReplaceAll(s, "$MTX_QUERY", query)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
type staticSourceHandlerParent interface {
|
type staticSourceHandlerParent interface {
|
||||||
logger.Writer
|
logger.Writer
|
||||||
staticSourceHandlerSetReady(context.Context, defs.PathSourceStaticSetReadyReq)
|
staticSourceHandlerSetReady(context.Context, defs.PathSourceStaticSetReadyReq)
|
||||||
@@ -35,13 +48,14 @@ type staticSourceHandler struct {
|
|||||||
readTimeout conf.StringDuration
|
readTimeout conf.StringDuration
|
||||||
writeTimeout conf.StringDuration
|
writeTimeout conf.StringDuration
|
||||||
writeQueueSize int
|
writeQueueSize int
|
||||||
resolvedSource string
|
matches []string
|
||||||
parent staticSourceHandlerParent
|
parent staticSourceHandlerParent
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
ctxCancel func()
|
ctxCancel func()
|
||||||
instance defs.StaticSource
|
instance defs.StaticSource
|
||||||
running bool
|
running bool
|
||||||
|
query string
|
||||||
|
|
||||||
// in
|
// in
|
||||||
chReloadConf chan *conf.Path
|
chReloadConf chan *conf.Path
|
||||||
@@ -58,60 +72,57 @@ func (s *staticSourceHandler) initialize() {
|
|||||||
s.chInstanceSetNotReady = make(chan defs.PathSourceStaticSetNotReadyReq)
|
s.chInstanceSetNotReady = make(chan defs.PathSourceStaticSetNotReadyReq)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(s.resolvedSource, "rtsp://") ||
|
case strings.HasPrefix(s.conf.Source, "rtsp://") ||
|
||||||
strings.HasPrefix(s.resolvedSource, "rtsps://"):
|
strings.HasPrefix(s.conf.Source, "rtsps://"):
|
||||||
s.instance = &rtspsource.Source{
|
s.instance = &rtspsource.Source{
|
||||||
ResolvedSource: s.resolvedSource,
|
|
||||||
ReadTimeout: s.readTimeout,
|
ReadTimeout: s.readTimeout,
|
||||||
WriteTimeout: s.writeTimeout,
|
WriteTimeout: s.writeTimeout,
|
||||||
WriteQueueSize: s.writeQueueSize,
|
WriteQueueSize: s.writeQueueSize,
|
||||||
Parent: s,
|
Parent: s,
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.HasPrefix(s.resolvedSource, "rtmp://") ||
|
case strings.HasPrefix(s.conf.Source, "rtmp://") ||
|
||||||
strings.HasPrefix(s.resolvedSource, "rtmps://"):
|
strings.HasPrefix(s.conf.Source, "rtmps://"):
|
||||||
s.instance = &rtmpsource.Source{
|
s.instance = &rtmpsource.Source{
|
||||||
ResolvedSource: s.resolvedSource,
|
ReadTimeout: s.readTimeout,
|
||||||
ReadTimeout: s.readTimeout,
|
WriteTimeout: s.writeTimeout,
|
||||||
WriteTimeout: s.writeTimeout,
|
Parent: s,
|
||||||
Parent: s,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.HasPrefix(s.resolvedSource, "http://") ||
|
case strings.HasPrefix(s.conf.Source, "http://") ||
|
||||||
strings.HasPrefix(s.resolvedSource, "https://"):
|
strings.HasPrefix(s.conf.Source, "https://"):
|
||||||
s.instance = &hlssource.Source{
|
s.instance = &hlssource.Source{
|
||||||
ResolvedSource: s.resolvedSource,
|
ReadTimeout: s.readTimeout,
|
||||||
ReadTimeout: s.readTimeout,
|
Parent: s,
|
||||||
Parent: s,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.HasPrefix(s.resolvedSource, "udp://"):
|
case strings.HasPrefix(s.conf.Source, "udp://"):
|
||||||
s.instance = &udpsource.Source{
|
s.instance = &udpsource.Source{
|
||||||
ResolvedSource: s.resolvedSource,
|
ReadTimeout: s.readTimeout,
|
||||||
ReadTimeout: s.readTimeout,
|
Parent: s,
|
||||||
Parent: s,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.HasPrefix(s.resolvedSource, "srt://"):
|
case strings.HasPrefix(s.conf.Source, "srt://"):
|
||||||
s.instance = &srtsource.Source{
|
s.instance = &srtsource.Source{
|
||||||
ResolvedSource: s.resolvedSource,
|
ReadTimeout: s.readTimeout,
|
||||||
ReadTimeout: s.readTimeout,
|
Parent: s,
|
||||||
Parent: s,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.HasPrefix(s.resolvedSource, "whep://") ||
|
case strings.HasPrefix(s.conf.Source, "whep://") ||
|
||||||
strings.HasPrefix(s.resolvedSource, "wheps://"):
|
strings.HasPrefix(s.conf.Source, "wheps://"):
|
||||||
s.instance = &webrtcsource.Source{
|
s.instance = &webrtcsource.Source{
|
||||||
ResolvedSource: s.resolvedSource,
|
ReadTimeout: s.readTimeout,
|
||||||
ReadTimeout: s.readTimeout,
|
Parent: s,
|
||||||
Parent: s,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case s.resolvedSource == "rpiCamera":
|
case s.conf.Source == "rpiCamera":
|
||||||
s.instance = &rpicamerasource.Source{
|
s.instance = &rpicamerasource.Source{
|
||||||
LogLevel: s.logLevel,
|
LogLevel: s.logLevel,
|
||||||
Parent: s,
|
Parent: s,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("should not happen")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,12 +130,16 @@ func (s *staticSourceHandler) close(reason string) {
|
|||||||
s.stop(reason)
|
s.stop(reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *staticSourceHandler) start(onDemand bool) {
|
func (s *staticSourceHandler) start(onDemand bool, query string) {
|
||||||
if s.running {
|
if s.running {
|
||||||
panic("should not happen")
|
panic("should not happen")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.running = true
|
s.running = true
|
||||||
|
s.query = query
|
||||||
|
s.ctx, s.ctxCancel = context.WithCancel(context.Background())
|
||||||
|
s.done = make(chan struct{})
|
||||||
|
|
||||||
s.instance.Log(logger.Info, "started%s",
|
s.instance.Log(logger.Info, "started%s",
|
||||||
func() string {
|
func() string {
|
||||||
if onDemand {
|
if onDemand {
|
||||||
@@ -133,9 +148,6 @@ func (s *staticSourceHandler) start(onDemand bool) {
|
|||||||
return ""
|
return ""
|
||||||
}())
|
}())
|
||||||
|
|
||||||
s.ctx, s.ctxCancel = context.WithCancel(context.Background())
|
|
||||||
s.done = make(chan struct{})
|
|
||||||
|
|
||||||
go s.run()
|
go s.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +157,7 @@ func (s *staticSourceHandler) stop(reason string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.running = false
|
s.running = false
|
||||||
|
|
||||||
s.instance.Log(logger.Info, "stopped: %s", reason)
|
s.instance.Log(logger.Info, "stopped: %s", reason)
|
||||||
|
|
||||||
s.ctxCancel()
|
s.ctxCancel()
|
||||||
@@ -167,12 +180,15 @@ func (s *staticSourceHandler) run() {
|
|||||||
runReloadConf := make(chan *conf.Path)
|
runReloadConf := make(chan *conf.Path)
|
||||||
|
|
||||||
recreate := func() {
|
recreate := func() {
|
||||||
|
resolvedSource := resolveSource(s.conf.Source, s.matches, s.query)
|
||||||
|
|
||||||
runCtx, runCtxCancel = context.WithCancel(context.Background())
|
runCtx, runCtxCancel = context.WithCancel(context.Background())
|
||||||
go func() {
|
go func() {
|
||||||
runErr <- s.instance.Run(defs.StaticSourceRunParams{
|
runErr <- s.instance.Run(defs.StaticSourceRunParams{
|
||||||
Context: runCtx,
|
Context: runCtx,
|
||||||
Conf: s.conf,
|
ResolvedSource: resolvedSource,
|
||||||
ReloadConf: runReloadConf,
|
Conf: s.conf,
|
||||||
|
ReloadConf: runReloadConf,
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,8 @@ type StaticSourceParent interface {
|
|||||||
|
|
||||||
// StaticSourceRunParams is the set of params passed to Run().
|
// StaticSourceRunParams is the set of params passed to Run().
|
||||||
type StaticSourceRunParams struct {
|
type StaticSourceRunParams struct {
|
||||||
Context context.Context
|
Context context.Context
|
||||||
Conf *conf.Path
|
ResolvedSource string
|
||||||
ReloadConf chan *conf.Path
|
Conf *conf.Path
|
||||||
|
ReloadConf chan *conf.Path
|
||||||
}
|
}
|
||||||
|
@@ -20,9 +20,8 @@ import (
|
|||||||
|
|
||||||
// Source is a HLS static source.
|
// Source is a HLS static source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
ResolvedSource string
|
ReadTimeout conf.StringDuration
|
||||||
ReadTimeout conf.StringDuration
|
Parent defs.StaticSourceParent
|
||||||
Parent defs.StaticSourceParent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log implements logger.Writer.
|
// Log implements logger.Writer.
|
||||||
@@ -49,7 +48,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
|||||||
|
|
||||||
var c *gohlslib.Client
|
var c *gohlslib.Client
|
||||||
c = &gohlslib.Client{
|
c = &gohlslib.Client{
|
||||||
URI: s.ResolvedSource,
|
URI: params.ResolvedSource,
|
||||||
HTTPClient: &http.Client{
|
HTTPClient: &http.Client{
|
||||||
Timeout: time.Duration(s.ReadTimeout),
|
Timeout: time.Duration(s.ReadTimeout),
|
||||||
Transport: tr,
|
Transport: tr,
|
||||||
|
@@ -90,10 +90,10 @@ func TestSource(t *testing.T) {
|
|||||||
te := test.NewSourceTester(
|
te := test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "http://localhost:5780/stream.m3u8",
|
Parent: p,
|
||||||
Parent: p,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"http://localhost:5780/stream.m3u8",
|
||||||
&conf.Path{},
|
&conf.Path{},
|
||||||
)
|
)
|
||||||
defer te.Close()
|
defer te.Close()
|
||||||
|
@@ -23,10 +23,9 @@ import (
|
|||||||
|
|
||||||
// Source is a RTMP static source.
|
// Source is a RTMP static source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
ResolvedSource string
|
ReadTimeout conf.StringDuration
|
||||||
ReadTimeout conf.StringDuration
|
WriteTimeout conf.StringDuration
|
||||||
WriteTimeout conf.StringDuration
|
Parent defs.StaticSourceParent
|
||||||
Parent defs.StaticSourceParent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log implements logger.Writer.
|
// Log implements logger.Writer.
|
||||||
@@ -38,7 +37,7 @@ func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
|
|||||||
func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||||
s.Log(logger.Debug, "connecting")
|
s.Log(logger.Debug, "connecting")
|
||||||
|
|
||||||
u, err := url.Parse(s.ResolvedSource)
|
u, err := url.Parse(params.ResolvedSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -64,24 +64,24 @@ func TestSource(t *testing.T) {
|
|||||||
te = test.NewSourceTester(
|
te = test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "rtmp://localhost/teststream",
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
ReadTimeout: conf.StringDuration(10 * time.Second),
|
WriteTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteTimeout: conf.StringDuration(10 * time.Second),
|
Parent: p,
|
||||||
Parent: p,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rtmp://localhost/teststream",
|
||||||
&conf.Path{},
|
&conf.Path{},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
te = test.NewSourceTester(
|
te = test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "rtmps://localhost/teststream",
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
ReadTimeout: conf.StringDuration(10 * time.Second),
|
WriteTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteTimeout: conf.StringDuration(10 * time.Second),
|
Parent: p,
|
||||||
Parent: p,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rtmps://localhost/teststream",
|
||||||
&conf.Path{
|
&conf.Path{
|
||||||
SourceFingerprint: "33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739",
|
SourceFingerprint: "33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739",
|
||||||
},
|
},
|
||||||
|
@@ -62,7 +62,6 @@ func createRangeHeader(cnf *conf.Path) (*headers.Range, error) {
|
|||||||
|
|
||||||
// Source is a RTSP static source.
|
// Source is a RTSP static source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
ResolvedSource string
|
|
||||||
ReadTimeout conf.StringDuration
|
ReadTimeout conf.StringDuration
|
||||||
WriteTimeout conf.StringDuration
|
WriteTimeout conf.StringDuration
|
||||||
WriteQueueSize int
|
WriteQueueSize int
|
||||||
@@ -104,7 +103,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := base.ParseURL(s.ResolvedSource)
|
u, err := base.ParseURL(params.ResolvedSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -138,13 +138,13 @@ func TestSource(t *testing.T) {
|
|||||||
te = test.NewSourceTester(
|
te = test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "rtsp://testuser:testpass@localhost:8555/teststream",
|
|
||||||
ReadTimeout: conf.StringDuration(10 * time.Second),
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteTimeout: conf.StringDuration(10 * time.Second),
|
WriteTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteQueueSize: 2048,
|
WriteQueueSize: 2048,
|
||||||
Parent: p,
|
Parent: p,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rtsp://testuser:testpass@localhost:8555/teststream",
|
||||||
&conf.Path{
|
&conf.Path{
|
||||||
RTSPTransport: sp,
|
RTSPTransport: sp,
|
||||||
},
|
},
|
||||||
@@ -153,13 +153,13 @@ func TestSource(t *testing.T) {
|
|||||||
te = test.NewSourceTester(
|
te = test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "rtsps://testuser:testpass@localhost:8555/teststream",
|
|
||||||
ReadTimeout: conf.StringDuration(10 * time.Second),
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteTimeout: conf.StringDuration(10 * time.Second),
|
WriteTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteQueueSize: 2048,
|
WriteQueueSize: 2048,
|
||||||
Parent: p,
|
Parent: p,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rtsps://testuser:testpass@localhost:8555/teststream",
|
||||||
&conf.Path{
|
&conf.Path{
|
||||||
SourceFingerprint: "33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739",
|
SourceFingerprint: "33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739",
|
||||||
},
|
},
|
||||||
@@ -241,13 +241,13 @@ func TestRTSPSourceNoPassword(t *testing.T) {
|
|||||||
te := test.NewSourceTester(
|
te := test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "rtsp://testuser:@127.0.0.1:8555/teststream",
|
|
||||||
ReadTimeout: conf.StringDuration(10 * time.Second),
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteTimeout: conf.StringDuration(10 * time.Second),
|
WriteTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteQueueSize: 2048,
|
WriteQueueSize: 2048,
|
||||||
Parent: p,
|
Parent: p,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rtsp://testuser:@127.0.0.1:8555/teststream",
|
||||||
&conf.Path{
|
&conf.Path{
|
||||||
RTSPTransport: sp,
|
RTSPTransport: sp,
|
||||||
},
|
},
|
||||||
@@ -338,13 +338,13 @@ func TestRTSPSourceRange(t *testing.T) {
|
|||||||
te := test.NewSourceTester(
|
te := test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "rtsp://127.0.0.1:8555/teststream",
|
|
||||||
ReadTimeout: conf.StringDuration(10 * time.Second),
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteTimeout: conf.StringDuration(10 * time.Second),
|
WriteTimeout: conf.StringDuration(10 * time.Second),
|
||||||
WriteQueueSize: 2048,
|
WriteQueueSize: 2048,
|
||||||
Parent: p,
|
Parent: p,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rtsp://127.0.0.1:8555/teststream",
|
||||||
cnf,
|
cnf,
|
||||||
)
|
)
|
||||||
defer te.Close()
|
defer te.Close()
|
||||||
|
@@ -17,9 +17,8 @@ import (
|
|||||||
|
|
||||||
// Source is a SRT static source.
|
// Source is a SRT static source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
ResolvedSource string
|
ReadTimeout conf.StringDuration
|
||||||
ReadTimeout conf.StringDuration
|
Parent defs.StaticSourceParent
|
||||||
Parent defs.StaticSourceParent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log implements logger.Writer.
|
// Log implements logger.Writer.
|
||||||
@@ -32,7 +31,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
|||||||
s.Log(logger.Debug, "connecting")
|
s.Log(logger.Debug, "connecting")
|
||||||
|
|
||||||
conf := srt.DefaultConfig()
|
conf := srt.DefaultConfig()
|
||||||
address, err := conf.UnmarshalURL(s.ResolvedSource)
|
address, err := conf.UnmarshalURL(params.ResolvedSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -55,11 +55,11 @@ func TestSource(t *testing.T) {
|
|||||||
te := test.NewSourceTester(
|
te := test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "srt://127.0.0.1:9002?streamid=sidname&passphrase=ttest1234567",
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
ReadTimeout: conf.StringDuration(10 * time.Second),
|
Parent: p,
|
||||||
Parent: p,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"srt://127.0.0.1:9002?streamid=sidname&passphrase=ttest1234567",
|
||||||
&conf.Path{},
|
&conf.Path{},
|
||||||
)
|
)
|
||||||
defer te.Close()
|
defer te.Close()
|
||||||
|
@@ -45,9 +45,8 @@ type packetConn interface {
|
|||||||
|
|
||||||
// Source is a UDP static source.
|
// Source is a UDP static source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
ResolvedSource string
|
ReadTimeout conf.StringDuration
|
||||||
ReadTimeout conf.StringDuration
|
Parent defs.StaticSourceParent
|
||||||
Parent defs.StaticSourceParent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log implements logger.Writer.
|
// Log implements logger.Writer.
|
||||||
@@ -59,7 +58,7 @@ func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
|
|||||||
func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||||
s.Log(logger.Debug, "connecting")
|
s.Log(logger.Debug, "connecting")
|
||||||
|
|
||||||
hostPort := s.ResolvedSource[len("udp://"):]
|
hostPort := params.ResolvedSource[len("udp://"):]
|
||||||
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", hostPort)
|
addr, err := net.ResolveUDPAddr("udp", hostPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -18,11 +18,11 @@ func TestSource(t *testing.T) {
|
|||||||
te := test.NewSourceTester(
|
te := test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "udp://127.0.0.1:9001",
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
ReadTimeout: conf.StringDuration(10 * time.Second),
|
Parent: p,
|
||||||
Parent: p,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"udp://127.0.0.1:9001",
|
||||||
&conf.Path{},
|
&conf.Path{},
|
||||||
)
|
)
|
||||||
defer te.Close()
|
defer te.Close()
|
||||||
|
@@ -19,9 +19,8 @@ import (
|
|||||||
|
|
||||||
// Source is a WebRTC static source.
|
// Source is a WebRTC static source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
ResolvedSource string
|
ReadTimeout conf.StringDuration
|
||||||
ReadTimeout conf.StringDuration
|
Parent defs.StaticSourceParent
|
||||||
Parent defs.StaticSourceParent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log implements logger.Writer.
|
// Log implements logger.Writer.
|
||||||
@@ -33,7 +32,7 @@ func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
|
|||||||
func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
||||||
s.Log(logger.Debug, "connecting")
|
s.Log(logger.Debug, "connecting")
|
||||||
|
|
||||||
u, err := url.Parse(s.ResolvedSource)
|
u, err := url.Parse(params.ResolvedSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -121,11 +121,11 @@ func TestSource(t *testing.T) {
|
|||||||
te := test.NewSourceTester(
|
te := test.NewSourceTester(
|
||||||
func(p defs.StaticSourceParent) defs.StaticSource {
|
func(p defs.StaticSourceParent) defs.StaticSource {
|
||||||
return &Source{
|
return &Source{
|
||||||
ResolvedSource: "whep://localhost:9003/my/resource",
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
ReadTimeout: conf.StringDuration(10 * time.Second),
|
Parent: p,
|
||||||
Parent: p,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"whep://localhost:9003/my/resource",
|
||||||
&conf.Path{},
|
&conf.Path{},
|
||||||
)
|
)
|
||||||
defer te.Close()
|
defer te.Close()
|
||||||
|
@@ -24,7 +24,11 @@ type SourceTester struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSourceTester allocates a SourceTester.
|
// NewSourceTester allocates a SourceTester.
|
||||||
func NewSourceTester(createFunc func(defs.StaticSourceParent) defs.StaticSource, conf *conf.Path) *SourceTester {
|
func NewSourceTester(
|
||||||
|
createFunc func(defs.StaticSourceParent) defs.StaticSource,
|
||||||
|
resolvedSource string,
|
||||||
|
conf *conf.Path,
|
||||||
|
) *SourceTester {
|
||||||
ctx, ctxCancel := context.WithCancel(context.Background())
|
ctx, ctxCancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
t := &SourceTester{
|
t := &SourceTester{
|
||||||
@@ -38,8 +42,9 @@ func NewSourceTester(createFunc func(defs.StaticSourceParent) defs.StaticSource,
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
s.Run(defs.StaticSourceRunParams{ //nolint:errcheck
|
s.Run(defs.StaticSourceRunParams{ //nolint:errcheck
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Conf: conf,
|
ResolvedSource: resolvedSource,
|
||||||
|
Conf: conf,
|
||||||
})
|
})
|
||||||
close(t.done)
|
close(t.done)
|
||||||
}()
|
}()
|
||||||
|
@@ -418,8 +418,10 @@ pathDefaults:
|
|||||||
# * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS
|
# * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS
|
||||||
# * redirect -> the stream is provided by another path or server
|
# * redirect -> the stream is provided by another path or server
|
||||||
# * rpiCamera -> the stream is provided by a Raspberry Pi Camera
|
# * rpiCamera -> the stream is provided by a Raspberry Pi Camera
|
||||||
# If path name is a regular expression, $G1, G2, etc will be replaced
|
# The following variables can be used in the source string:
|
||||||
# with regular expression groups.
|
# * $MTX_QUERY: query parameters (passed by first reader)
|
||||||
|
# * $G1, $G2, ...: regular expression groups, if path name is
|
||||||
|
# a regular expression.
|
||||||
source: publisher
|
source: publisher
|
||||||
# If the source is a URL, and the source certificate is self-signed
|
# If the source is a URL, and the source certificate is self-signed
|
||||||
# or invalid, you can provide the fingerprint of the certificate in order to
|
# or invalid, you can provide the fingerprint of the certificate in order to
|
||||||
|
Reference in New Issue
Block a user