Browse Source

implement redirect sources (#92)

pull/169/head
aler9 5 years ago
parent
commit
53ba724f96
  1. 7
      README.md
  2. 16
      client/client.go
  3. 14
      conf/pathconf.go
  4. 92
      main_test.go
  5. 41
      path/path.go
  6. 75
      rtsp-simple-server.yml

7
README.md

@ -5,14 +5,15 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/aler9/rtsp-simple-server)](https://goreportcard.com/report/github.com/aler9/rtsp-simple-server) [![Go Report Card](https://goreportcard.com/badge/github.com/aler9/rtsp-simple-server)](https://goreportcard.com/report/github.com/aler9/rtsp-simple-server)
[![Docker Hub](https://img.shields.io/badge/docker-aler9%2Frtsp--simple--server-blue)](https://hub.docker.com/r/aler9/rtsp-simple-server) [![Docker Hub](https://img.shields.io/badge/docker-aler9%2Frtsp--simple--server-blue)](https://hub.docker.com/r/aler9/rtsp-simple-server)
_rtsp-simple-server_ is a simple, ready-to-use and zero-dependency RTSP server and RTSP proxy, a software that allows multiple users to publish and read live video and audio streams over time. RTSP is a standard protocol that describes how to perform these operations with the help of a server, that is contacted by both readers and publishers in order to negotiate a streaming method, then relays the publisher streams to the readers. _rtsp-simple-server_ is a simple, ready-to-use and zero-dependency RTSP server and RTSP proxy, a software that allows multiple users to publish and read live video and audio streams over time. RTSP is a standard protocol that describes how to perform these operations with the help of a server, that is contacted by both readers and publishers and relays the publisher streams to the readers.
Features: Features:
* Read and publish live streams with UDP and TCP * Read and publish live streams with UDP and TCP
* Each stream can have multiple video and audio tracks, encoded with any codec (including H264, H265, VP8, VP9, MP3, AAC, Opus, PCM) * Each stream can have multiple video and audio tracks, encoded with any codec (including H264, H265, VP8, VP9, MP3, AAC, Opus, PCM)
* Publish multiple streams at once, each in a separate path, that can be read by multiple users * Serve multiple streams at once, each in a separate path, that can be read by multiple users
* Pull and serve streams from other RTSP or RTMP servers, always or on-demand (RTSP proxy) * Pull and serve streams from other RTSP or RTMP servers, always or on-demand (RTSP proxy)
* Provide separate authentication for reading and publishing * Redirect to other RTSP servers (load balancing)
* Authenticate readers and publishers separately
* Run custom commands when clients connect, disconnect, read or publish streams * Run custom commands when clients connect, disconnect, read or publish streams
* Compatible with Linux, Windows and Mac, does not require any dependency or interpreter, it's a single executable * Compatible with Linux, Windows and Mac, does not require any dependency or interpreter, it's a single executable

16
client/client.go

@ -40,6 +40,7 @@ type streamTrack struct {
type describeData struct { type describeData struct {
sdp []byte sdp []byte
redirect string
err error err error
} }
@ -889,6 +890,17 @@ func (c *Client) runWaitingDescribe() bool {
return true return true
} }
if res.redirect != "" {
c.conn.WriteResponse(&base.Response{
StatusCode: base.StatusMovedPermanently,
Header: base.Header{
"CSeq": c.describeCSeq,
"Location": base.HeaderValue{res.redirect},
},
})
return true
}
c.conn.WriteResponse(&base.Response{ c.conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
Header: base.Header{ Header: base.Header{
@ -1309,6 +1321,6 @@ func (c *Client) OnReaderFrame(trackId int, streamType base.StreamType, buf []by
} }
} }
func (c *Client) OnPathDescribeData(sdp []byte, err error) { func (c *Client) OnPathDescribeData(sdp []byte, redirect string, err error) {
c.describeData <- describeData{sdp, err} c.describeData <- describeData{sdp, redirect, err}
} }

14
conf/pathconf.go

@ -16,6 +16,7 @@ type PathConf struct {
SourceProtocol string `yaml:"sourceProtocol"` SourceProtocol string `yaml:"sourceProtocol"`
SourceProtocolParsed gortsplib.StreamProtocol `yaml:"-" json:"-"` SourceProtocolParsed gortsplib.StreamProtocol `yaml:"-" json:"-"`
SourceOnDemand bool `yaml:"sourceOnDemand"` SourceOnDemand bool `yaml:"sourceOnDemand"`
SourceRedirect string `yaml:"sourceRedirect"`
RunOnInit string `yaml:"runOnInit"` RunOnInit string `yaml:"runOnInit"`
RunOnDemand string `yaml:"runOnDemand"` RunOnDemand string `yaml:"runOnDemand"`
RunOnPublish string `yaml:"runOnPublish"` RunOnPublish string `yaml:"runOnPublish"`
@ -64,6 +65,7 @@ func (pconf *PathConf) fillAndCheck(name string) error {
if err != nil { if err != nil {
return fmt.Errorf("'%s' is not a valid url", pconf.Source) return fmt.Errorf("'%s' is not a valid url", pconf.Source)
} }
if u.User != nil { if u.User != nil {
pass, _ := u.User.Password() pass, _ := u.User.Password()
user := u.User.Username() user := u.User.Username()
@ -76,6 +78,7 @@ func (pconf *PathConf) fillAndCheck(name string) error {
if pconf.SourceProtocol == "" { if pconf.SourceProtocol == "" {
pconf.SourceProtocol = "udp" pconf.SourceProtocol = "udp"
} }
switch pconf.SourceProtocol { switch pconf.SourceProtocol {
case "udp": case "udp":
pconf.SourceProtocolParsed = gortsplib.StreamProtocolUDP pconf.SourceProtocolParsed = gortsplib.StreamProtocolUDP
@ -96,6 +99,7 @@ func (pconf *PathConf) fillAndCheck(name string) error {
if err != nil { if err != nil {
return fmt.Errorf("'%s' is not a valid url", pconf.Source) return fmt.Errorf("'%s' is not a valid url", pconf.Source)
} }
if u.User != nil { if u.User != nil {
pass, _ := u.User.Password() pass, _ := u.User.Password()
user := u.User.Username() user := u.User.Username()
@ -107,15 +111,25 @@ func (pconf *PathConf) fillAndCheck(name string) error {
} else if pconf.Source == "record" { } else if pconf.Source == "record" {
} else if pconf.Source == "redirect" {
} else { } else {
return fmt.Errorf("unsupported source: '%s'", pconf.Source) return fmt.Errorf("unsupported source: '%s'", pconf.Source)
} }
if pconf.SourceRedirect != "" {
_, err := url.Parse(pconf.SourceRedirect)
if err != nil {
return fmt.Errorf("'%s' is not a valid url", pconf.SourceRedirect)
}
}
if pconf.PublishUser != "" { if pconf.PublishUser != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.PublishUser) { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.PublishUser) {
return fmt.Errorf("publish username must be alphanumeric") return fmt.Errorf("publish username must be alphanumeric")
} }
} }
if pconf.PublishPass != "" { if pconf.PublishPass != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.PublishPass) { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.PublishPass) {
return fmt.Errorf("publish password must be alphanumeric") return fmt.Errorf("publish password must be alphanumeric")

92
main_test.go

@ -233,7 +233,7 @@ func TestPublish(t *testing.T) {
switch conf.publishSoft { switch conf.publishSoft {
case "ffmpeg": case "ffmpeg":
cnt1, err := newContainer("ffmpeg", "publish", []string{ cnt1, err := newContainer("ffmpeg", "source", []string{
"-re", "-re",
"-stream_loop", "-1", "-stream_loop", "-1",
"-i", "/emptyvideo.ts", "-i", "/emptyvideo.ts",
@ -256,7 +256,7 @@ func TestPublish(t *testing.T) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
cnt2, err := newContainer("ffmpeg", "read", []string{ cnt2, err := newContainer("ffmpeg", "dest", []string{
"-rtsp_transport", "udp", "-rtsp_transport", "udp",
"-i", "rtsp://" + ownDockerIp + ":8554/teststream", "-i", "rtsp://" + ownDockerIp + ":8554/teststream",
"-vframes", "1", "-vframes", "1",
@ -289,7 +289,7 @@ func TestRead(t *testing.T) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
cnt1, err := newContainer("ffmpeg", "publish", []string{ cnt1, err := newContainer("ffmpeg", "source", []string{
"-re", "-re",
"-stream_loop", "-1", "-stream_loop", "-1",
"-i", "/emptyvideo.ts", "-i", "/emptyvideo.ts",
@ -305,7 +305,7 @@ func TestRead(t *testing.T) {
switch conf.readSoft { switch conf.readSoft {
case "ffmpeg": case "ffmpeg":
cnt2, err := newContainer("ffmpeg", "read", []string{ cnt2, err := newContainer("ffmpeg", "dest", []string{
"-rtsp_transport", conf.readProto, "-rtsp_transport", conf.readProto,
"-i", "rtsp://" + ownDockerIp + ":8554/teststream", "-i", "rtsp://" + ownDockerIp + ":8554/teststream",
"-vframes", "1", "-vframes", "1",
@ -337,14 +337,13 @@ func TestRead(t *testing.T) {
} }
func TestTCPOnly(t *testing.T) { func TestTCPOnly(t *testing.T) {
conf := "protocols: [tcp]\n" p, err := testProgram("protocols: [tcp]\n")
p, err := testProgram(conf)
require.NoError(t, err) require.NoError(t, err)
defer p.close() defer p.close()
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
cnt1, err := newContainer("ffmpeg", "publish", []string{ cnt1, err := newContainer("ffmpeg", "source", []string{
"-re", "-re",
"-stream_loop", "-1", "-stream_loop", "-1",
"-i", "/emptyvideo.ts", "-i", "/emptyvideo.ts",
@ -356,7 +355,7 @@ func TestTCPOnly(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer cnt1.close() defer cnt1.close()
cnt2, err := newContainer("ffmpeg", "read", []string{ cnt2, err := newContainer("ffmpeg", "dest", []string{
"-rtsp_transport", "tcp", "-rtsp_transport", "tcp",
"-i", "rtsp://" + ownDockerIp + ":8554/teststream", "-i", "rtsp://" + ownDockerIp + ":8554/teststream",
"-vframes", "1", "-vframes", "1",
@ -377,7 +376,7 @@ func TestPathWithSlash(t *testing.T) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
cnt1, err := newContainer("ffmpeg", "publish", []string{ cnt1, err := newContainer("ffmpeg", "source", []string{
"-re", "-re",
"-stream_loop", "-1", "-stream_loop", "-1",
"-i", "/emptyvideo.ts", "-i", "/emptyvideo.ts",
@ -389,7 +388,7 @@ func TestPathWithSlash(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer cnt1.close() defer cnt1.close()
cnt2, err := newContainer("ffmpeg", "read", []string{ cnt2, err := newContainer("ffmpeg", "dest", []string{
"-rtsp_transport", "udp", "-rtsp_transport", "udp",
"-i", "rtsp://" + ownDockerIp + ":8554/test/stream", "-i", "rtsp://" + ownDockerIp + ":8554/test/stream",
"-vframes", "1", "-vframes", "1",
@ -410,7 +409,7 @@ func TestPathWithQuery(t *testing.T) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
cnt1, err := newContainer("ffmpeg", "publish", []string{ cnt1, err := newContainer("ffmpeg", "source", []string{
"-re", "-re",
"-stream_loop", "-1", "-stream_loop", "-1",
"-i", "/emptyvideo.ts", "-i", "/emptyvideo.ts",
@ -422,7 +421,7 @@ func TestPathWithQuery(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer cnt1.close() defer cnt1.close()
cnt2, err := newContainer("ffmpeg", "read", []string{ cnt2, err := newContainer("ffmpeg", "dest", []string{
"-rtsp_transport", "udp", "-rtsp_transport", "udp",
"-i", "rtsp://" + ownDockerIp + ":8554/test?param3=otherval", "-i", "rtsp://" + ownDockerIp + ":8554/test?param3=otherval",
"-vframes", "1", "-vframes", "1",
@ -438,12 +437,11 @@ func TestPathWithQuery(t *testing.T) {
func TestAuth(t *testing.T) { func TestAuth(t *testing.T) {
t.Run("publish", func(t *testing.T) { t.Run("publish", func(t *testing.T) {
conf := "paths:\n" + p, err := testProgram("paths:\n" +
" all:\n" + " all:\n" +
" publishUser: testuser\n" + " publishUser: testuser\n" +
" publishPass: testpass\n" + " publishPass: testpass\n" +
" publishIps: [172.17.0.0/16]\n" " publishIps: [172.17.0.0/16]\n")
p, err := testProgram(conf)
require.NoError(t, err) require.NoError(t, err)
defer p.close() defer p.close()
@ -482,12 +480,11 @@ func TestAuth(t *testing.T) {
"vlc", "vlc",
} { } {
t.Run("read_"+soft, func(t *testing.T) { t.Run("read_"+soft, func(t *testing.T) {
conf := "paths:\n" + p, err := testProgram("paths:\n" +
" all:\n" + " all:\n" +
" readUser: testuser\n" + " readUser: testuser\n" +
" readPass: testpass\n" + " readPass: testpass\n" +
" readIps: [172.17.0.0/16]\n" " readIps: [172.17.0.0/16]\n")
p, err := testProgram(conf)
require.NoError(t, err) require.NoError(t, err)
defer p.close() defer p.close()
@ -540,11 +537,10 @@ func TestSourceRtsp(t *testing.T) {
"tcp", "tcp",
} { } {
t.Run(proto, func(t *testing.T) { t.Run(proto, func(t *testing.T) {
conf := "paths:\n" + p1, err := testProgram("paths:\n" +
" all:\n" + " all:\n" +
" readUser: testuser\n" + " readUser: testuser\n" +
" readPass: testpass\n" " readPass: testpass\n")
p1, err := testProgram(conf)
require.NoError(t, err) require.NoError(t, err)
defer p1.close() defer p1.close()
@ -564,7 +560,7 @@ func TestSourceRtsp(t *testing.T) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
conf = "rtspPort: 8555\n" + p2, err := testProgram("rtspPort: 8555\n" +
"rtpPort: 8100\n" + "rtpPort: 8100\n" +
"rtcpPort: 8101\n" + "rtcpPort: 8101\n" +
"\n" + "\n" +
@ -572,8 +568,7 @@ func TestSourceRtsp(t *testing.T) {
" proxied:\n" + " proxied:\n" +
" source: rtsp://testuser:testpass@localhost:8554/teststream\n" + " source: rtsp://testuser:testpass@localhost:8554/teststream\n" +
" sourceProtocol: " + proto + "\n" + " sourceProtocol: " + proto + "\n" +
" sourceOnDemand: yes\n" " sourceOnDemand: yes\n")
p2, err := testProgram(conf)
require.NoError(t, err) require.NoError(t, err)
defer p2.close() defer p2.close()
@ -615,11 +610,10 @@ func TestSourceRtmp(t *testing.T) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
conf := "paths:\n" + p, err := testProgram("paths:\n" +
" proxied:\n" + " proxied:\n" +
" source: rtmp://" + cnt1.ip() + "/stream/test\n" + " source: rtmp://" + cnt1.ip() + "/stream/test\n" +
" sourceOnDemand: yes\n" " sourceOnDemand: yes\n")
p, err := testProgram(conf)
require.NoError(t, err) require.NoError(t, err)
defer p.close() defer p.close()
@ -639,11 +633,49 @@ func TestSourceRtmp(t *testing.T) {
require.Equal(t, 0, code) require.Equal(t, 0, code)
} }
func TestRedirect(t *testing.T) {
p1, err := testProgram("paths:\n" +
" path1:\n" +
" source: redirect\n" +
" sourceRedirect: rtsp://" + ownDockerIp + ":8554/path2\n" +
" path2:\n")
require.NoError(t, err)
defer p1.close()
time.Sleep(1 * time.Second)
cnt1, err := newContainer("ffmpeg", "source", []string{
"-re",
"-stream_loop", "-1",
"-i", "/emptyvideo.ts",
"-c", "copy",
"-f", "rtsp",
"-rtsp_transport", "udp",
"rtsp://" + ownDockerIp + ":8554/path2",
})
require.NoError(t, err)
defer cnt1.close()
time.Sleep(1 * time.Second)
cnt2, err := newContainer("ffmpeg", "dest", []string{
"-rtsp_transport", "udp",
"-i", "rtsp://" + ownDockerIp + ":8554/path1",
"-vframes", "1",
"-f", "image2",
"-y", "/dev/null",
})
require.NoError(t, err)
defer cnt2.close()
code := cnt2.wait()
require.Equal(t, 0, code)
}
func TestRunOnDemand(t *testing.T) { func TestRunOnDemand(t *testing.T) {
conf := "paths:\n" + p1, err := testProgram("paths:\n" +
" all:\n" + " all:\n" +
" runOnDemand: ffmpeg -hide_banner -loglevel error -re -i testimages/ffmpeg/emptyvideo.ts -c copy -f rtsp rtsp://localhost:8554/$RTSP_SERVER_PATH\n" " runOnDemand: ffmpeg -hide_banner -loglevel error -re -i testimages/ffmpeg/emptyvideo.ts -c copy -f rtsp rtsp://localhost:8554/$RTSP_SERVER_PATH\n")
p1, err := testProgram(conf)
require.NoError(t, err) require.NoError(t, err)
defer p1.close() defer p1.close()

41
path/path.go

@ -31,12 +31,18 @@ type Parent interface {
OnPathClientClose(*client.Client) OnPathClientClose(*client.Client)
} }
// a source is either a client.Client, a sourcertsp.Source or a sourcertmp.Source // a source can be
// * client.Client
// * sourcertsp.Source
// * sourcertmp.Source
// * sourceRedirect
type source interface { type source interface {
IsSource() IsSource()
} }
// a sourceExternal is either a sourcertsp.Source or a sourcertmp.Source // a sourceExternal can be
// * sourcertsp.Source
// * sourcertmp.Source
type sourceExternal interface { type sourceExternal interface {
IsSource() IsSource()
Close() Close()
@ -44,6 +50,10 @@ type sourceExternal interface {
SetRunning(bool) SetRunning(bool)
} }
type sourceRedirect struct{}
func (*sourceRedirect) IsSource() {}
type ClientDescribeRes struct { type ClientDescribeRes struct {
Path client.Path Path client.Path
Err error Err error
@ -205,8 +215,10 @@ func (pa *Path) run() {
pa.Log("starting source") pa.Log("starting source")
} }
pa.source = sourcertmp.New(pa.conf.Source, state, pa.source = sourcertmp.New(pa.conf.Source, state, pa.stats, pa)
pa.stats, pa)
} else if pa.conf.Source == "redirect" {
pa.source = &sourceRedirect{}
} }
if pa.conf.RunOnInit != "" { if pa.conf.RunOnInit != "" {
@ -434,7 +446,7 @@ func (pa *Path) onCheck() bool {
for c, state := range pa.clients { for c, state := range pa.clients {
if state != clientStatePreRemove && state == clientStateWaitingDescribe { if state != clientStatePreRemove && state == clientStateWaitingDescribe {
pa.clients[c] = clientStatePreRemove pa.clients[c] = clientStatePreRemove
c.OnPathDescribeData(nil, fmt.Errorf("publisher of path '%s' has timed out", pa.name)) c.OnPathDescribeData(nil, "", fmt.Errorf("publisher of path '%s' has timed out", pa.name))
} }
} }
} }
@ -477,7 +489,7 @@ func (pa *Path) onSourceSetReady() {
for c, state := range pa.clients { for c, state := range pa.clients {
if state == clientStateWaitingDescribe { if state == clientStateWaitingDescribe {
pa.clients[c] = clientStatePreRemove pa.clients[c] = clientStatePreRemove
c.OnPathDescribeData(pa.sourceSdp, nil) c.OnPathDescribeData(pa.sourceSdp, "", nil)
} }
} }
} }
@ -497,7 +509,7 @@ func (pa *Path) onSourceSetNotReady() {
func (pa *Path) onClientDescribe(c *client.Client) { func (pa *Path) onClientDescribe(c *client.Client) {
pa.lastDescribeReq = time.Now() pa.lastDescribeReq = time.Now()
// publisher not found // source not found
if pa.source == nil { if pa.source == nil {
// on demand command is available: put the client on hold // on demand command is available: put the client on hold
if pa.conf.RunOnDemand != "" { if pa.conf.RunOnDemand != "" {
@ -519,10 +531,17 @@ func (pa *Path) onClientDescribe(c *client.Client) {
pa.clients[c] = clientStatePreRemove pa.clients[c] = clientStatePreRemove
pa.clientsWg.Add(1) pa.clientsWg.Add(1)
c.OnPathDescribeData(nil, fmt.Errorf("no one is publishing to path '%s'", pa.name)) c.OnPathDescribeData(nil, "", fmt.Errorf("no one is publishing to path '%s'", pa.name))
} }
// publisher was found but is not ready: put the client on hold // source found and is redirect
} else if _, ok := pa.source.(*sourceRedirect); ok {
pa.clients[c] = clientStatePreRemove
pa.clientsWg.Add(1)
c.OnPathDescribeData(nil, pa.conf.SourceRedirect, nil)
// source was found but is not ready: put the client on hold
} else if !pa.sourceReady { } else if !pa.sourceReady {
// start source if needed // start source if needed
if source, ok := pa.source.(sourceExternal); ok { if source, ok := pa.source.(sourceExternal); ok {
@ -536,12 +555,12 @@ func (pa *Path) onClientDescribe(c *client.Client) {
pa.clients[c] = clientStateWaitingDescribe pa.clients[c] = clientStateWaitingDescribe
pa.clientsWg.Add(1) pa.clientsWg.Add(1)
// publisher was found and is ready // source was found and is ready
} else { } else {
pa.clients[c] = clientStatePreRemove pa.clients[c] = clientStatePreRemove
pa.clientsWg.Add(1) pa.clientsWg.Add(1)
c.OnPathDescribeData(pa.sourceSdp, nil) c.OnPathDescribeData(pa.sourceSdp, "", nil)
} }
} }

75
rtsp-simple-server.yml

@ -1,39 +1,41 @@
# supported stream protocols (the handshake is always performed with TCP) # supported stream protocols (the handshake is always performed with TCP).
protocols: [udp, tcp] protocols: [udp, tcp]
# port of the TCP RTSP listener # port of the TCP RTSP listener.
rtspPort: 8554 rtspPort: 8554
# port of the UDP RTP listener (used only if udp is in protocols) # port of the UDP RTP listener (used only if udp is in protocols).
rtpPort: 8000 rtpPort: 8000
# port of the UDP RTCP listener (used only if udp is in protocols) # port of the UDP RTCP listener (used only if udp is in protocols).
rtcpPort: 8001 rtcpPort: 8001
# timeout of read operations # timeout of read operations.
readTimeout: 10s readTimeout: 10s
# timeout of write operations # timeout of write operations.
writeTimeout: 5s writeTimeout: 5s
# supported authentication methods (both are insecure, use RTSP inside a VPN to enforce security) # supported authentication methods (both are insecure, use RTSP inside a VPN
# to enforce security).
authMethods: [basic, digest] authMethods: [basic, digest]
# command to run when a client connects. # command to run when a client connects.
# this is terminated with SIGINT when a client disconnects. # this is terminated with SIGINT when a client disconnects.
runOnConnect: runOnConnect:
# enable Prometheus-compatible metrics on port 9998 # enable Prometheus-compatible metrics on port 9998.
metrics: no metrics: no
# enable pprof on port 9999 to monitor performances # enable pprof on port 9999 to monitor performances.
pprof: no pprof: no
# destinations of log messages; available options are 'stdout', 'file' and 'syslog'
# destinations of log messages; available options are "stdout", "file" and "syslog".
logDestinations: [stdout] logDestinations: [stdout]
# if 'file' is in logDestinations, this is the file that will receive the logs # if "file" is in logDestinations, this is the file that will receive the logs.
logFile: rtsp-simple-server.log logFile: rtsp-simple-server.log
# these settings are path-dependent. # these settings are path-dependent.
# It's possible to use regular expressions by using a tilde as prefix. # it's possible to use regular expressions by using a tilde as prefix.
# for instance, '~^(test1|test2)$' will match both 'test1' and 'test2'. # for example, "~^(test1|test2)$" will match both "test1" and "test2".
# for instance, '~^prefix' will match all paths that start with 'prefix'. # for example, "~^prefix" will match all paths that start with "prefix".
# The settings under the path 'all' are applied to all paths that do not match # the settings under the path "all" are applied to all paths that do not match
# another entry. # another entry.
paths: paths:
all: all:
@ -41,45 +43,52 @@ paths:
# * record -> the stream is provided by a RTSP client # * record -> the stream is provided by a RTSP client
# * rtsp://existing-url -> the stream is pulled from another RTSP server # * rtsp://existing-url -> the stream is pulled from another RTSP server
# * rtmp://existing-url -> the stream is pulled from a RTMP server # * rtmp://existing-url -> the stream is pulled from a RTMP server
# * redirect -> the stream is provided by another path or server
source: record source: record
# if the source is an RTSP url, this is the protocol that will be used to pull the stream
# if the source is an RTSP url, this is the protocol that will be used to
# pull the stream.
sourceProtocol: udp sourceProtocol: udp
# if the source is an RTSP or RTMP url, it will be pulled only when at least one reader # if the source is an RTSP or RTMP url, it will be pulled only when at least
# is connected, saving bandwidth # one reader is connected, saving bandwidth.
sourceOnDemand: no sourceOnDemand: no
# if the source is "redirect", this is the RTSP url which clients will be
# redirected to.
sourceRedirect:
# command to run when this path is loaded by the program. # command to run when this path is loaded by the program.
# this can be used, for example, to publish a stream and keep it always opened. # this can be used, for example, to publish a stream and keep it always opened.
# This is terminated with SIGINT when the program closes. # this is terminated with SIGINT when the program closes.
# The path name is available in the RTSP_SERVER_PATH variable # the path name is available in the RTSP_SERVER_PATH variable.
runOnInit: runOnInit:
# command to run when this path is requested. # command to run when this path is requested.
# This can be used, for example, to publish a stream on demand. # this can be used, for example, to publish a stream on demand.
# This is terminated with SIGINT when the path is not requested anymore. # this is terminated with SIGINT when the path is not requested anymore.
# The path name is available in the RTSP_SERVER_PATH variable # the path name is available in the RTSP_SERVER_PATH variable.
runOnDemand: runOnDemand:
# command to run when a client starts publishing. # command to run when a client starts publishing.
# This is terminated with SIGINT when a client stops publishing. # this is terminated with SIGINT when a client stops publishing.
# The path name is available in the RTSP_SERVER_PATH variable # the path name is available in the RTSP_SERVER_PATH variable.
runOnPublish: runOnPublish:
# command to run when a clients starts reading. # command to run when a clients starts reading.
# This is terminated with SIGINT when a client stops reading. # this is terminated with SIGINT when a client stops reading.
# The path name is available in the RTSP_SERVER_PATH variable # the path name is available in the RTSP_SERVER_PATH variable.
runOnRead: runOnRead:
# username required to publish # username required to publish.
publishUser: publishUser:
# password required to publish # password required to publish.
publishPass: publishPass:
# IPs or networks (x.x.x.x/24) allowed to publish # ips or networks (x.x.x.x/24) allowed to publish.
publishIps: [] publishIps: []
# username required to read # username required to read.
readUser: readUser:
# password required to read # password required to read.
readPass: readPass:
# IPs or networks (x.x.x.x/24) allowed to read # ips or networks (x.x.x.x/24) allowed to read.
readIps: [] readIps: []

Loading…
Cancel
Save