diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ea8c81..d308a9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ #### Core v16.8.0 > ? +- Allow RTMP server if RTMPS server is enabled - Add optional escape character to process placeholder - Fix output address validation for tee outputs - Fix updating process config diff --git a/README.md b/README.md index 50a39225..c22593ee 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ The currently known environment variables (but not all will be respected) are: | CORE_RTMP_ENABLE | `false` | Enable RTMP server. | | CORE_RTMP_ENABLE_TLS | `false` | Enable RTMP over TLS (RTMPS). Requires `CORE_TLS_ENABLE` to be `true`. | | CORE_RTMP_ADDRESS | `:1935` | RTMP server listen address. | +| CORE_RTMP_ADDRESS_TLS | `:1936` | RTMPS server listen address. | | CORE_RTMP_APP | `/` | RTMP app for publishing. | | CORE_RTMP_TOKEN | (not set) | RTMP token for publishing and playing. The token is the value of the URL query parameter `token`. | | CORE_SRT_ENABLE | `false` | Enable SRT server. | @@ -257,6 +258,7 @@ All other values will be filled with default values and persisted on disk. The e "enable": false, "enable_tls": false, "address": ":1935", + "address_tls": ":1936", "app": "/", "token": "" }, @@ -386,6 +388,8 @@ If you set a value for `CORE_STORAGE_DISK_CACHE_MAXSIZEMBYTES`, which is larger The datarhei Core includes a simple RTMP server for publishing and playing streams. Set the environment variable `CORE_RTMP_ENABLE` to `true` to enable the RTMP server. It is listening on `CORE_RTMP_ADDRESS`. Use `CORE_RTMP_APP` to limit the app a stream can be published on, e.g. `/live` to require URLs to start with `/live`. To prevent anybody can publish streams, set `CORE_RTMP_TOKEN` to a secret only known to the publishers and subscribers. The token has to be put in the query of the stream URL, e.g. `/live/stream?token=...`. +For additionaly enabling the RTMPS server, set the config variable `rtmp.enable_tls` or environment variable `CORE_RTMP_ENABLE_TLS` to `true`. This requires `tls.enable` or `CORE_TLS_ENABLE` to be set to to `true`. Use `rtmp.address_tls` or `CORE_RTMP_ADDRESS_TLS` to set the listen address for the RTMPS server. + | Method | Path | Description | | ------ | ------------ | ------------------------------------- | | GET | /api/v3/rtmp | List all currently published streams. | diff --git a/app/api/api.go b/app/api/api.go index 8b4137cf..255c700a 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -88,6 +88,7 @@ type api struct { main log.Logger sidecar log.Logger rtmp log.Logger + rtmps log.Logger srt log.Logger } } @@ -669,11 +670,14 @@ func (a *api) start() error { } if cfg.RTMP.Enable { + a.log.logger.rtmp = a.log.logger.core.WithComponent("RTMP").WithField("address", cfg.RTMP.Address) + config := rtmp.Config{ Addr: cfg.RTMP.Address, + TLSAddr: cfg.RTMP.AddressTLS, App: cfg.RTMP.App, Token: cfg.RTMP.Token, - Logger: a.log.logger.core.WithComponent("RTMP").WithField("address", cfg.RTMP.Address), + Logger: a.log.logger.rtmp, Collector: a.sessions.Collector("rtmp"), } @@ -682,7 +686,9 @@ func (a *api) start() error { GetCertificate: autocertManager.GetCertificate, } - config.Logger = config.Logger.WithComponent("RTMPS") + config.Logger = config.Logger.WithComponent("RTMP/S") + + a.log.logger.rtmps = a.log.logger.core.WithComponent("RTMPS").WithField("address", cfg.RTMP.AddressTLS) } rtmpserver, err := rtmp.New(config) @@ -690,7 +696,6 @@ func (a *api) start() error { return fmt.Errorf("unable to create RMTP server: %w", err) } - a.log.logger.rtmp = config.Logger a.rtmpserver = rtmpserver } @@ -901,7 +906,33 @@ func (a *api) start() error { var err error - if cfg.TLS.Enable && cfg.RTMP.EnableTLS { + logger.Info().Log("Server started") + err = a.rtmpserver.ListenAndServe() + if err != nil && err != rtmp.ErrServerClosed { + err = fmt.Errorf("RTMP server: %w", err) + } else { + err = nil + } + + sendError(err) + }() + + if cfg.TLS.Enable && cfg.RTMP.EnableTLS { + wgStart.Add(1) + a.wgStop.Add(1) + + go func() { + logger := a.log.logger.rtmps + + defer func() { + logger.Info().Log("Server exited") + a.wgStop.Done() + }() + + wgStart.Done() + + var err error + logger.Info().Log("Server started") err = a.rtmpserver.ListenAndServeTLS(cfg.TLS.CertFile, cfg.TLS.KeyFile) if err != nil && err != rtmp.ErrServerClosed { @@ -909,18 +940,8 @@ func (a *api) start() error { } else { err = nil } - } else { - logger.Info().Log("Server started") - err = a.rtmpserver.ListenAndServe() - if err != nil && err != rtmp.ErrServerClosed { - err = fmt.Errorf("RTMP server: %w", err) - } else { - err = nil - } - } - - sendError(err) - }() + }() + } } if a.srtserver != nil { @@ -1106,6 +1127,10 @@ func (a *api) stop() { if a.rtmpserver != nil { a.log.logger.rtmp.Info().Log("Stopping ...") + if a.log.logger.rtmps != nil { + a.log.logger.rtmps.Info().Log("Stopping ...") + } + a.rtmpserver.Close() a.rtmpserver = nil } diff --git a/config/config.go b/config/config.go index f5b734ac..c36ad943 100644 --- a/config/config.go +++ b/config/config.go @@ -568,6 +568,14 @@ func (d *Config) Validate(resetLogs bool) { if !d.TLS.Enable { d.log("error", d.findVariable("rtmp.enable_tls"), "RTMPS server can only be enabled if TLS is enabled") } + + if len(d.RTMP.AddressTLS) == 0 { + d.log("error", d.findVariable("rtmp.address_tls"), "RTMPS server address must be set") + } + + if d.RTMP.Address == d.RTMP.AddressTLS { + d.log("error", d.findVariable("rtmp.address"), "The RTMP and RTMPS server can't listen on the same address") + } } // If CORE_MEMFS_USERNAME and CORE_MEMFS_PASSWORD are set, automatically active/deactivate Basic-Auth for memfs diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 39da7f3d..503a7779 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -4,6 +4,7 @@ package rtmp import ( "context" "crypto/tls" + "fmt" "net" "path/filepath" "strings" @@ -175,6 +176,9 @@ type Config struct { // The address the RTMP server should listen on, e.g. ":1935" Addr string + // The address the RTMPS server should listen on, e.g. ":1936" + TLSAddr string + // The app path for the streams, e.g. "/live". Optional. Defaults // to "/". App string @@ -216,7 +220,8 @@ type server struct { collector session.Collector // A joy4 RTMP server instance - server *rtmp.Server + server *rtmp.Server + tlsServer *rtmp.Server // Map of publishing channels and a lock to serialize // access to the map. @@ -231,7 +236,7 @@ func New(config Config) (Server, error) { } if config.Logger == nil { - config.Logger = log.New("RTMP") + config.Logger = log.New("") } s := &server{ @@ -247,11 +252,19 @@ func New(config Config) (Server, error) { s.server = &rtmp.Server{ Addr: config.Addr, - TLSConfig: config.TLSConfig.Clone(), HandlePlay: s.handlePlay, HandlePublish: s.handlePublish, } + if len(config.TLSAddr) != 0 { + s.tlsServer = &rtmp.Server{ + Addr: config.TLSAddr, + TLSConfig: config.TLSConfig.Clone(), + HandlePlay: s.handlePlay, + HandlePublish: s.handlePublish, + } + } + s.channels = make(map[string]*channel) rtmp.Debug = false @@ -265,13 +278,21 @@ func (s *server) ListenAndServe() error { } func (s *server) ListenAndServeTLS(certFile, keyFile string) error { - return s.server.ListenAndServeTLS(certFile, keyFile) + if s.tlsServer == nil { + return fmt.Errorf("RTMPS server is not configured") + } + + return s.tlsServer.ListenAndServeTLS(certFile, keyFile) } func (s *server) Close() { // Stop listening s.server.Close() + if s.tlsServer != nil { + s.tlsServer.Close() + } + s.lock.Lock() defer s.lock.Unlock()