mirror of
				https://github.com/aler9/rtsp-simple-server
				synced 2025-10-31 11:06:28 +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
	 aler9
					aler9