From c3d9be322db90e9f3ec42662706d45c6b51273f7 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Thu, 16 Nov 2023 23:37:43 +0100 Subject: [PATCH] allow using 'fallback' with static sources (#2606) (#2706) --- apidocs/openapi.yaml | 4 +-- internal/conf/path.go | 32 ++++++++---------- internal/core/path_test.go | 55 +++++++++++++++++++++++++++++++ internal/core/rtsp_server_test.go | 44 ------------------------- mediamtx.yml | 6 ++-- 5 files changed, 74 insertions(+), 67 deletions(-) diff --git a/apidocs/openapi.yaml b/apidocs/openapi.yaml index efaeb5278cf..d15fc0976b1 100644 --- a/apidocs/openapi.yaml +++ b/apidocs/openapi.yaml @@ -210,6 +210,8 @@ components: type: integer srtReadPassphrase: type: string + fallback: + type: string # Record record: @@ -246,8 +248,6 @@ components: # Publisher source overridePublisher: type: boolean - fallback: - type: string srtPublishPassphrase: type: string diff --git a/internal/conf/path.go b/internal/conf/path.go index 82b240aeb04..a7710b59186 100644 --- a/internal/conf/path.go +++ b/internal/conf/path.go @@ -60,6 +60,7 @@ type Path struct { SourceOnDemandCloseAfter StringDuration `json:"sourceOnDemandCloseAfter"` MaxReaders int `json:"maxReaders"` SRTReadPassphrase string `json:"srtReadPassphrase"` + Fallback string `json:"fallback"` // Record Record bool `json:"record"` @@ -80,7 +81,6 @@ type Path struct { // Publisher source OverridePublisher bool `json:"overridePublisher"` DisablePublisherOverride *bool `json:"disablePublisherOverride,omitempty"` // deprecated - Fallback string `json:"fallback"` SRTPublishPassphrase string `json:"srtPublishPassphrase"` // RTSP source @@ -346,6 +346,19 @@ func (pconf *Path) check(conf *Conf, name string) error { return fmt.Errorf("invalid 'readRTPassphrase': %v", err) } } + if pconf.Fallback != "" { + if strings.HasPrefix(pconf.Fallback, "/") { + err := IsValidPathName(pconf.Fallback[1:]) + if err != nil { + return fmt.Errorf("'%s': %s", pconf.Fallback, err) + } + } else { + _, err := base.ParseURL(pconf.Fallback) + if err != nil { + return fmt.Errorf("'%s' is not a valid RTSP URL", pconf.Fallback) + } + } + } // Authentication @@ -387,23 +400,6 @@ func (pconf *Path) check(conf *Conf, name string) error { if pconf.DisablePublisherOverride != nil { pconf.OverridePublisher = !*pconf.DisablePublisherOverride } - if pconf.Fallback != "" { - if pconf.Source != "publisher" { - return fmt.Errorf("'fallback' can only be used when source is 'publisher'") - } - - if strings.HasPrefix(pconf.Fallback, "/") { - err := IsValidPathName(pconf.Fallback[1:]) - if err != nil { - return fmt.Errorf("'%s': %s", pconf.Fallback, err) - } - } else { - _, err := base.ParseURL(pconf.Fallback) - if err != nil { - return fmt.Errorf("'%s' is not a valid RTSP URL", pconf.Fallback) - } - } - } if pconf.SRTPublishPassphrase != "" { if pconf.Source != "publisher" { return fmt.Errorf("'srtPublishPassphase' can only be used when source is 'publisher'") diff --git a/internal/core/path_test.go b/internal/core/path_test.go index 1ea97049a20..f19ae39744c 100644 --- a/internal/core/path_test.go +++ b/internal/core/path_test.go @@ -515,3 +515,58 @@ func TestPathRecord(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, len(files)) } + +func TestPathFallback(t *testing.T) { + for _, ca := range []string{ + "absolute", + "relative", + "source", + } { + t.Run(ca, func(t *testing.T) { + var conf string + + switch ca { + case "absolute": + conf = "paths:\n" + + " path1:\n" + + " fallback: rtsp://localhost:8554/path2\n" + + " path2:\n" + + case "relative": + conf = "paths:\n" + + " path1:\n" + + " fallback: /path2\n" + + " path2:\n" + + case "source": + conf = "paths:\n" + + " path1:\n" + + " fallback: /path2\n" + + " source: rtsp://localhost:3333/nonexistent\n" + + " path2:\n" + } + + p1, ok := newInstance(conf) + require.Equal(t, true, ok) + defer p1.Close() + + source := gortsplib.Client{} + err := source.StartRecording("rtsp://localhost:8554/path2", + &description.Session{Medias: []*description.Media{testMediaH264}}) + require.NoError(t, err) + defer source.Close() + + u, err := base.ParseURL("rtsp://localhost:8554/path1") + require.NoError(t, err) + + dest := gortsplib.Client{} + err = dest.Start(u.Scheme, u.Host) + require.NoError(t, err) + defer dest.Close() + + desc, _, err := dest.Describe(u) + require.NoError(t, err) + require.Equal(t, 1, len(desc.Medias)) + }) + } +} diff --git a/internal/core/rtsp_server_test.go b/internal/core/rtsp_server_test.go index 7efebe79a96..152fd40dc6b 100644 --- a/internal/core/rtsp_server_test.go +++ b/internal/core/rtsp_server_test.go @@ -348,47 +348,3 @@ func TestRTSPServerPublisherOverride(t *testing.T) { }) } } - -func TestRTSPServerFallback(t *testing.T) { - for _, ca := range []string{ - "absolute", - "relative", - } { - t.Run(ca, func(t *testing.T) { - val := func() string { - if ca == "absolute" { - return "rtsp://localhost:8554/path2" - } - return "/path2" - }() - - p1, ok := newInstance("rtmp: no\n" + - "hls: no\n" + - "webrtc: no\n" + - "paths:\n" + - " path1:\n" + - " fallback: " + val + "\n" + - " path2:\n") - require.Equal(t, true, ok) - defer p1.Close() - - source := gortsplib.Client{} - err := source.StartRecording("rtsp://localhost:8554/path2", - &description.Session{Medias: []*description.Media{testMediaH264}}) - require.NoError(t, err) - defer source.Close() - - u, err := base.ParseURL("rtsp://localhost:8554/path1") - require.NoError(t, err) - - dest := gortsplib.Client{} - err = dest.Start(u.Scheme, u.Host) - require.NoError(t, err) - defer dest.Close() - - desc, _, err := dest.Describe(u) - require.NoError(t, err) - require.Equal(t, 1, len(desc.Medias)) - }) - } -} diff --git a/mediamtx.yml b/mediamtx.yml index 9a2bd66d383..98b43750c81 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -285,6 +285,9 @@ pathDefaults: maxReaders: 0 # SRT encryption passphrase require to read from this path srtReadPassphrase: + # If the stream is not available, redirect readers to this path. + # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. + fallback: ############################################### # Default path settings -> Recording @@ -335,9 +338,6 @@ pathDefaults: # Allow another client to disconnect the current publisher and publish in its place. overridePublisher: yes - # If no one is publishing, redirect readers to this path. - # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. - fallback: # SRT encryption passphrase required to publish to this path srtPublishPassphrase: