Add new parameter 'runOnReady' (#752)

This is called when a stream is ready, whether it is published or proxied.
It replaces 'runOnPublsh'.
This commit is contained in:
aler9
2022-01-19 22:50:32 +01:00
parent 8337a90b29
commit 49449eb5ad
7 changed files with 76 additions and 43 deletions

View File

@@ -46,7 +46,7 @@ Features:
* [Encrypt the configuration](#encrypt-the-configuration)
* [Proxy mode](#proxy-mode)
* [Remuxing, re-encoding, compression](#remuxing-re-encoding-compression)
* [Save published videos to disk](#save-published-videos-to-disk)
* [Save streams to disk](#save-streams-to-disk)
* [On-demand publishing](#on-demand-publishing)
* [Start on boot with systemd](#start-on-boot-with-systemd)
* [HTTP API](#http-api)
@@ -304,20 +304,20 @@ To change the format, codec or compression of a stream, use _FFmpeg_ or _Gstream
paths:
all:
original:
runOnPublish: ffmpeg -i rtsp://localhost:$RTSP_PORT/$RTSP_PATH -c:v libx264 -preset ultrafast -b:v 500k -max_muxing_queue_size 1024 -f rtsp rtsp://localhost:$RTSP_PORT/compressed
runOnPublishRestart: yes
runOnReady: ffmpeg -i rtsp://localhost:$RTSP_PORT/$RTSP_PATH -c:v libx264 -preset ultrafast -b:v 500k -max_muxing_queue_size 1024 -f rtsp rtsp://localhost:$RTSP_PORT/compressed
runOnReadyRestart: yes
```
### Save published videos to disk
### Save streams to disk
To Save published videos to disk, put an _FFmpeg_ command inside `runOnPublish`:
To save available streams to disk, you can use the `runOnReady` parameter and _FFmpeg_:
```yml
paths:
all:
original:
runOnPublish: ffmpeg -i rtsp://localhost:$RTSP_PORT/$RTSP_PATH -c copy -f segment -strftime 1 -segment_time 60 -segment_format mp4 saved_%Y-%m-%d_%H-%M-%S.mp4
runOnPublishRestart: yes
runOnReady: ffmpeg -i rtsp://localhost:$RTSP_PORT/$RTSP_PATH -c copy -f segment -strftime 1 -segment_time 60 -segment_format mp4 saved_%Y-%m-%d_%H-%M-%S.mp4
runOnReadyRestart: yes
```
### On-demand publishing

View File

@@ -165,9 +165,9 @@ components:
type: string
runOnDemandCloseAfter:
type: string
runOnPublish:
runOnReady:
type: string
runOnPublishRestart:
runOnReadyRestart:
type: boolean
runOnRead:
type: string

View File

@@ -65,8 +65,10 @@ type PathConf struct {
RunOnDemandRestart bool `json:"runOnDemandRestart"`
RunOnDemandStartTimeout StringDuration `json:"runOnDemandStartTimeout"`
RunOnDemandCloseAfter StringDuration `json:"runOnDemandCloseAfter"`
RunOnPublish string `json:"runOnPublish"`
RunOnPublishRestart bool `json:"runOnPublishRestart"`
RunOnReady string `json:"runOnReady"`
RunOnReadyRestart bool `json:"runOnReadyRestart"`
RunOnPublish string `json:"runOnPublish"` // deprecated, replaced by runOnReady
RunOnPublishRestart bool `json:"runOnPublishRestart"` // deprecated, replaced by runOnReadyRestart
RunOnRead string `json:"runOnRead"`
RunOnReadRestart bool `json:"runOnReadRestart"`
}
@@ -237,15 +239,18 @@ func (pconf *PathConf) checkAndFillMissing(conf *Conf, name string) error {
return fmt.Errorf("a path with a regular expression does not support option 'runOnInit'; use another path")
}
if pconf.RunOnPublish != "" && pconf.Source != "publisher" {
return fmt.Errorf("'runOnPublish' is useless when source is not 'publisher', since " +
"the stream is not provided by a publisher, but by a fixed source")
}
if pconf.RunOnDemand != "" && pconf.Source != "publisher" {
return fmt.Errorf("'runOnDemand' can be used only when source is 'publisher'")
}
if pconf.RunOnPublish != "" {
pconf.RunOnReady = pconf.RunOnPublish
}
if pconf.RunOnPublishRestart {
pconf.RunOnReadyRestart = true
}
if pconf.RunOnDemandStartTimeout == 0 {
pconf.RunOnDemandStartTimeout = 10 * StringDuration(time.Second)
}

View File

@@ -125,8 +125,10 @@ func loadConfPathData(ctx *gin.Context) (interface{}, error) {
RunOnDemandRestart *bool `json:"runOnDemandRestart"`
RunOnDemandStartTimeout *conf.StringDuration `json:"runOnDemandStartTimeout"`
RunOnDemandCloseAfter *conf.StringDuration `json:"runOnDemandCloseAfter"`
RunOnPublish *string `json:"runOnPublish"`
RunOnPublishRestart *bool `json:"runOnPublishRestart"`
RunOnReady *string `json:"runOnReady"`
RunOnReadyRestart *bool `json:"runOnReadyRestart"`
RunOnPublish *string `json:"runOnPublish"` // deprecated, replaced by runOnReady
RunOnPublishRestart *bool `json:"runOnPublishRestart"` // deprecated, replaced by runOnReadyRestart
RunOnRead *string `json:"runOnRead"`
RunOnReadRestart *bool `json:"runOnReadRestart"`
}

View File

@@ -313,6 +313,36 @@ func main() {
}
}
func TestCorePathRunOnReady(t *testing.T) {
doneFile := filepath.Join(os.TempDir(), "onready_done")
defer os.Remove(doneFile)
p, ok := newInstance(fmt.Sprintf("rtmpDisable: yes\n"+
"hlsDisable: yes\n"+
"paths:\n"+
" test:\n"+
" runOnReady: touch %s\n",
doneFile))
require.Equal(t, true, ok)
defer p.close()
track, err := gortsplib.NewTrackH264(96,
&gortsplib.TrackConfigH264{SPS: []byte{0x01, 0x02, 0x03, 0x04}, PPS: []byte{0x01, 0x02, 0x03, 0x04}})
require.NoError(t, err)
c := gortsplib.Client{}
err = c.StartPublishing(
"rtsp://localhost:8554/test",
gortsplib.Tracks{track})
require.NoError(t, err)
defer c.Close()
time.Sleep(1 * time.Second)
_, err = os.Stat(doneFile)
require.NoError(t, err)
}
func TestCoreHotReloading(t *testing.T) {
confPath := filepath.Join(os.TempDir(), "rtsp-conf")

View File

@@ -235,7 +235,7 @@ type path struct {
setupPlayRequests []pathReaderSetupPlayReq
stream *stream
onDemandCmd *externalcmd.Cmd
onPublishCmd *externalcmd.Cmd
onReadyCmd *externalcmd.Cmd
onDemandReadyTimer *time.Timer
onDemandCloseTimer *time.Timer
onDemandState pathOnDemandState
@@ -622,6 +622,18 @@ func (pa *path) sourceSetReady(tracks gortsplib.Tracks) {
}
pa.parent.onPathSourceReady(pa)
if pa.conf.RunOnReady != "" {
pa.log(logger.Info, "runOnReady command started")
pa.onReadyCmd = externalcmd.NewCmd(
pa.externalCmdPool,
pa.conf.RunOnReady,
pa.conf.RunOnReadyRestart,
pa.externalCmdEnv(),
func(co int) {
pa.log(logger.Info, "runOnReady command exited with code %d", co)
})
}
}
func (pa *path) sourceSetNotReady() {
@@ -630,9 +642,9 @@ func (pa *path) sourceSetNotReady() {
r.close()
}
if pa.onPublishCmd != nil {
pa.onPublishCmd.Close()
pa.onPublishCmd = nil
if pa.onReadyCmd != nil {
pa.onReadyCmd.Close()
pa.onReadyCmd = nil
pa.log(logger.Info, "runOnReady command stopped")
}
@@ -696,11 +708,6 @@ func (pa *path) doPublisherRemove() {
} else {
pa.sourceSetNotReady()
}
} else {
for r := range pa.readers {
pa.doReaderRemove(r)
r.close()
}
}
pa.source = nil
@@ -788,18 +795,6 @@ func (pa *path) handlePublisherRecord(req pathPublisherRecordReq) {
pa.sourceSetReady(req.tracks)
if pa.conf.RunOnPublish != "" {
pa.log(logger.Info, "runOnPublish command started")
pa.onPublishCmd = externalcmd.NewCmd(
pa.externalCmdPool,
pa.conf.RunOnPublish,
pa.conf.RunOnPublishRestart,
pa.externalCmdEnv(),
func(co int) {
pa.log(logger.Info, "runOnPublish command exited with code %d", co)
})
}
req.res <- pathPublisherRecordRes{stream: pa.stream}
}

View File

@@ -234,16 +234,17 @@ paths:
# readers connected and this amount of time has passed.
runOnDemandCloseAfter: 10s
# Command to run when a client starts publishing.
# This is terminated with SIGINT when a client stops publishing.
# Command to run when the stream is ready to be read, whether it is
# published by a client or read by a server / camera.
# This is terminated with SIGINT when the stream is not ready anymore.
# The following environment variables are available:
# * RTSP_PATH: path name
# * RTSP_PORT: server port
# * 1, 2, ...: regular expression groups, if path name is
# a regular expression.
runOnPublish:
runOnReady:
# Restart the command if it exits suddenly.
runOnPublishRestart: no
runOnReadyRestart: no
# Command to run when a clients starts reading.
# This is terminated with SIGINT when a client stops reading.