mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-10-30 18:46:24 +08:00
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:
14
README.md
14
README.md
@@ -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
|
||||
|
||||
@@ -165,9 +165,9 @@ components:
|
||||
type: string
|
||||
runOnDemandCloseAfter:
|
||||
type: string
|
||||
runOnPublish:
|
||||
runOnReady:
|
||||
type: string
|
||||
runOnPublishRestart:
|
||||
runOnReadyRestart:
|
||||
type: boolean
|
||||
runOnRead:
|
||||
type: string
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user