diff --git a/.github/workflows/bump_hls_js.yml b/.github/workflows/bump_hls_js.yml
index aed1c5f5..cd78035b 100644
--- a/.github/workflows/bump_hls_js.yml
+++ b/.github/workflows/bump_hls_js.yml
@@ -20,8 +20,8 @@ jobs:
&& ((git checkout deps/hlsjs && git rebase ${GITHUB_REF_NAME}) || git checkout -b deps/hlsjs)
- run: >
- curl -o internal/core/hls.min.js https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js
- && echo VERSION=$(cat internal/core/hls.min.js | grep -o '"version",get:function(){return".*"}' | sed 's/"version",get:function(){return"\(.*\)"}/\1/') >> $GITHUB_ENV
+ curl -o internal/servers/hls/hls.min.js https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js
+ && echo VERSION=$(cat internal/servers/hls/hls.min.js | grep -o '"version",get:function(){return".*"}' | sed 's/"version",get:function(){return"\(.*\)"}/\1/') >> $GITHUB_ENV
- id: check_repo
run: >
diff --git a/README.md b/README.md
index bc1f47b4..a7e9c2ba 100644
--- a/README.md
+++ b/README.md
@@ -435,7 +435,7 @@ This web page can be embedded into another web page by using an iframe:
```
-For more advanced setups, you can create and serve a custom web page by starting from the [source code of the publish page](internal/core/webrtc_publish_index.html).
+For more advanced setups, you can create and serve a custom web page by starting from the [source code of the publish page](internal/core/servers/webrtc/publish_index.html).
### By device
@@ -823,7 +823,7 @@ This web page can be embedded into another web page by using an iframe:
```
-For more advanced setups, you can create and serve a custom web page by starting from the [source code of the read page](internal/core/webrtc_read_index.html).
+For more advanced setups, you can create and serve a custom web page by starting from the [source code of the read page](internal/core/servers/webrtc/read_index.html).
Web browsers can also read a stream with the [HLS protocol](#hls). Latency is higher but there are less problems related to connectivity between server and clients, furthermore the server load can be balanced by using a common HTTP CDN (like CloudFront or Cloudflare), and this allows to handle readers in the order of millions. Visit the web page:
diff --git a/internal/core/api.go b/internal/core/api.go
index 51e7a1c7..ddbc2dac 100644
--- a/internal/core/api.go
+++ b/internal/core/api.go
@@ -102,35 +102,35 @@ type apiPathManager interface {
apiPathsGet(string) (*defs.APIPath, error)
}
-type apiHLSManager interface {
- apiMuxersList() (*defs.APIHLSMuxerList, error)
- apiMuxersGet(string) (*defs.APIHLSMuxer, error)
+type apiHLSServer interface {
+ APIMuxersList() (*defs.APIHLSMuxerList, error)
+ APIMuxersGet(string) (*defs.APIHLSMuxer, error)
}
type apiRTSPServer interface {
- apiConnsList() (*defs.APIRTSPConnsList, error)
- apiConnsGet(uuid.UUID) (*defs.APIRTSPConn, error)
- apiSessionsList() (*defs.APIRTSPSessionList, error)
- apiSessionsGet(uuid.UUID) (*defs.APIRTSPSession, error)
- apiSessionsKick(uuid.UUID) error
+ APIConnsList() (*defs.APIRTSPConnsList, error)
+ APIConnsGet(uuid.UUID) (*defs.APIRTSPConn, error)
+ APISessionsList() (*defs.APIRTSPSessionList, error)
+ APISessionsGet(uuid.UUID) (*defs.APIRTSPSession, error)
+ APISessionsKick(uuid.UUID) error
}
type apiRTMPServer interface {
- apiConnsList() (*defs.APIRTMPConnList, error)
- apiConnsGet(uuid.UUID) (*defs.APIRTMPConn, error)
- apiConnsKick(uuid.UUID) error
-}
-
-type apiWebRTCManager interface {
- apiSessionsList() (*defs.APIWebRTCSessionList, error)
- apiSessionsGet(uuid.UUID) (*defs.APIWebRTCSession, error)
- apiSessionsKick(uuid.UUID) error
+ APIConnsList() (*defs.APIRTMPConnList, error)
+ APIConnsGet(uuid.UUID) (*defs.APIRTMPConn, error)
+ APIConnsKick(uuid.UUID) error
}
type apiSRTServer interface {
- apiConnsList() (*defs.APISRTConnList, error)
- apiConnsGet(uuid.UUID) (*defs.APISRTConn, error)
- apiConnsKick(uuid.UUID) error
+ APIConnsList() (*defs.APISRTConnList, error)
+ APIConnsGet(uuid.UUID) (*defs.APISRTConn, error)
+ APIConnsKick(uuid.UUID) error
+}
+
+type apiWebRTCServer interface {
+ APISessionsList() (*defs.APIWebRTCSessionList, error)
+ APISessionsGet(uuid.UUID) (*defs.APIWebRTCSession, error)
+ APISessionsKick(uuid.UUID) error
}
type apiParent interface {
@@ -139,16 +139,16 @@ type apiParent interface {
}
type api struct {
- conf *conf.Conf
- pathManager apiPathManager
- rtspServer apiRTSPServer
- rtspsServer apiRTSPServer
- rtmpServer apiRTMPServer
- rtmpsServer apiRTMPServer
- hlsManager apiHLSManager
- webRTCManager apiWebRTCManager
- srtServer apiSRTServer
- parent apiParent
+ conf *conf.Conf
+ pathManager apiPathManager
+ rtspServer apiRTSPServer
+ rtspsServer apiRTSPServer
+ rtmpServer apiRTMPServer
+ rtmpsServer apiRTMPServer
+ hlsManager apiHLSServer
+ webRTCServer apiWebRTCServer
+ srtServer apiSRTServer
+ parent apiParent
httpServer *httpserv.WrappedServer
mutex sync.Mutex
@@ -163,22 +163,22 @@ func newAPI(
rtspsServer apiRTSPServer,
rtmpServer apiRTMPServer,
rtmpsServer apiRTMPServer,
- hlsManager apiHLSManager,
- webRTCManager apiWebRTCManager,
+ hlsManager apiHLSServer,
+ webRTCServer apiWebRTCServer,
srtServer apiSRTServer,
parent apiParent,
) (*api, error) {
a := &api{
- conf: conf,
- pathManager: pathManager,
- rtspServer: rtspServer,
- rtspsServer: rtspsServer,
- rtmpServer: rtmpServer,
- rtmpsServer: rtmpsServer,
- hlsManager: hlsManager,
- webRTCManager: webRTCManager,
- srtServer: srtServer,
- parent: parent,
+ conf: conf,
+ pathManager: pathManager,
+ rtspServer: rtspServer,
+ rtspsServer: rtspsServer,
+ rtmpServer: rtmpServer,
+ rtmpsServer: rtmpsServer,
+ hlsManager: hlsManager,
+ webRTCServer: webRTCServer,
+ srtServer: srtServer,
+ parent: parent,
}
router := gin.New()
@@ -235,7 +235,7 @@ func newAPI(
group.POST("/v3/rtmpsconns/kick/:id", a.onRTMPSConnsKick)
}
- if !interfaceIsEmpty(a.webRTCManager) {
+ if !interfaceIsEmpty(a.webRTCServer) {
group.GET("/v3/webrtcsessions/list", a.onWebRTCSessionsList)
group.GET("/v3/webrtcsessions/get/:id", a.onWebRTCSessionsGet)
group.POST("/v3/webrtcsessions/kick/:id", a.onWebRTCSessionsKick)
@@ -273,6 +273,7 @@ func (a *api) close() {
a.httpServer.Close()
}
+// Log implements logger.Writer.
func (a *api) Log(level logger.Level, format string, args ...interface{}) {
a.parent.Log(level, "[API] "+format, args...)
}
@@ -581,7 +582,7 @@ func (a *api) onPathsGet(ctx *gin.Context) {
}
func (a *api) onRTSPConnsList(ctx *gin.Context) {
- data, err := a.rtspServer.apiConnsList()
+ data, err := a.rtspServer.APIConnsList()
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -605,7 +606,7 @@ func (a *api) onRTSPConnsGet(ctx *gin.Context) {
return
}
- data, err := a.rtspServer.apiConnsGet(uuid)
+ data, err := a.rtspServer.APIConnsGet(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -615,7 +616,7 @@ func (a *api) onRTSPConnsGet(ctx *gin.Context) {
}
func (a *api) onRTSPSessionsList(ctx *gin.Context) {
- data, err := a.rtspServer.apiSessionsList()
+ data, err := a.rtspServer.APISessionsList()
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -639,7 +640,7 @@ func (a *api) onRTSPSessionsGet(ctx *gin.Context) {
return
}
- data, err := a.rtspServer.apiSessionsGet(uuid)
+ data, err := a.rtspServer.APISessionsGet(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -655,7 +656,7 @@ func (a *api) onRTSPSessionsKick(ctx *gin.Context) {
return
}
- err = a.rtspServer.apiSessionsKick(uuid)
+ err = a.rtspServer.APISessionsKick(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -665,7 +666,7 @@ func (a *api) onRTSPSessionsKick(ctx *gin.Context) {
}
func (a *api) onRTSPSConnsList(ctx *gin.Context) {
- data, err := a.rtspsServer.apiConnsList()
+ data, err := a.rtspsServer.APIConnsList()
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -689,7 +690,7 @@ func (a *api) onRTSPSConnsGet(ctx *gin.Context) {
return
}
- data, err := a.rtspsServer.apiConnsGet(uuid)
+ data, err := a.rtspsServer.APIConnsGet(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -699,7 +700,7 @@ func (a *api) onRTSPSConnsGet(ctx *gin.Context) {
}
func (a *api) onRTSPSSessionsList(ctx *gin.Context) {
- data, err := a.rtspsServer.apiSessionsList()
+ data, err := a.rtspsServer.APISessionsList()
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -723,7 +724,7 @@ func (a *api) onRTSPSSessionsGet(ctx *gin.Context) {
return
}
- data, err := a.rtspsServer.apiSessionsGet(uuid)
+ data, err := a.rtspsServer.APISessionsGet(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -739,7 +740,7 @@ func (a *api) onRTSPSSessionsKick(ctx *gin.Context) {
return
}
- err = a.rtspsServer.apiSessionsKick(uuid)
+ err = a.rtspsServer.APISessionsKick(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -749,7 +750,7 @@ func (a *api) onRTSPSSessionsKick(ctx *gin.Context) {
}
func (a *api) onRTMPConnsList(ctx *gin.Context) {
- data, err := a.rtmpServer.apiConnsList()
+ data, err := a.rtmpServer.APIConnsList()
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -773,7 +774,7 @@ func (a *api) onRTMPConnsGet(ctx *gin.Context) {
return
}
- data, err := a.rtmpServer.apiConnsGet(uuid)
+ data, err := a.rtmpServer.APIConnsGet(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -789,7 +790,7 @@ func (a *api) onRTMPConnsKick(ctx *gin.Context) {
return
}
- err = a.rtmpServer.apiConnsKick(uuid)
+ err = a.rtmpServer.APIConnsKick(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -799,7 +800,7 @@ func (a *api) onRTMPConnsKick(ctx *gin.Context) {
}
func (a *api) onRTMPSConnsList(ctx *gin.Context) {
- data, err := a.rtmpsServer.apiConnsList()
+ data, err := a.rtmpsServer.APIConnsList()
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -823,7 +824,7 @@ func (a *api) onRTMPSConnsGet(ctx *gin.Context) {
return
}
- data, err := a.rtmpsServer.apiConnsGet(uuid)
+ data, err := a.rtmpsServer.APIConnsGet(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -839,7 +840,7 @@ func (a *api) onRTMPSConnsKick(ctx *gin.Context) {
return
}
- err = a.rtmpsServer.apiConnsKick(uuid)
+ err = a.rtmpsServer.APIConnsKick(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -849,7 +850,7 @@ func (a *api) onRTMPSConnsKick(ctx *gin.Context) {
}
func (a *api) onHLSMuxersList(ctx *gin.Context) {
- data, err := a.hlsManager.apiMuxersList()
+ data, err := a.hlsManager.APIMuxersList()
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -873,7 +874,7 @@ func (a *api) onHLSMuxersGet(ctx *gin.Context) {
return
}
- data, err := a.hlsManager.apiMuxersGet(name)
+ data, err := a.hlsManager.APIMuxersGet(name)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -883,7 +884,7 @@ func (a *api) onHLSMuxersGet(ctx *gin.Context) {
}
func (a *api) onWebRTCSessionsList(ctx *gin.Context) {
- data, err := a.webRTCManager.apiSessionsList()
+ data, err := a.webRTCServer.APISessionsList()
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -907,7 +908,7 @@ func (a *api) onWebRTCSessionsGet(ctx *gin.Context) {
return
}
- data, err := a.webRTCManager.apiSessionsGet(uuid)
+ data, err := a.webRTCServer.APISessionsGet(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -923,7 +924,7 @@ func (a *api) onWebRTCSessionsKick(ctx *gin.Context) {
return
}
- err = a.webRTCManager.apiSessionsKick(uuid)
+ err = a.webRTCServer.APISessionsKick(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -933,7 +934,7 @@ func (a *api) onWebRTCSessionsKick(ctx *gin.Context) {
}
func (a *api) onSRTConnsList(ctx *gin.Context) {
- data, err := a.srtServer.apiConnsList()
+ data, err := a.srtServer.APIConnsList()
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -957,7 +958,7 @@ func (a *api) onSRTConnsGet(ctx *gin.Context) {
return
}
- data, err := a.srtServer.apiConnsGet(uuid)
+ data, err := a.srtServer.APIConnsGet(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
@@ -973,7 +974,7 @@ func (a *api) onSRTConnsKick(ctx *gin.Context) {
return
}
- err = a.srtServer.apiConnsKick(uuid)
+ err = a.srtServer.APIConnsKick(uuid)
if err != nil {
a.writeError(ctx, http.StatusInternalServerError, err)
return
diff --git a/internal/core/authentication.go b/internal/core/auth.go
similarity index 60%
rename from internal/core/authentication.go
rename to internal/core/auth.go
index aed391d0..b643c37b 100644
--- a/internal/core/authentication.go
+++ b/internal/core/auth.go
@@ -15,6 +15,7 @@ import (
"github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/conf"
+ "github.com/bluenviron/mediamtx/internal/defs"
)
func sha256Base64(in string) string {
@@ -31,28 +32,9 @@ func checkCredential(right string, guess string) bool {
return right == guess
}
-type errAuthentication struct {
- message string
-}
-
-// Error implements the error interface.
-func (e *errAuthentication) Error() string {
- return "authentication failed: " + e.message
-}
-
-type authProtocol string
-
-const (
- authProtocolRTSP authProtocol = "rtsp"
- authProtocolRTMP authProtocol = "rtmp"
- authProtocolHLS authProtocol = "hls"
- authProtocolWebRTC authProtocol = "webrtc"
- authProtocolSRT authProtocol = "srt"
-)
-
func doExternalAuthentication(
ur string,
- accessRequest pathAccessRequest,
+ accessRequest defs.PathAccessRequest,
) error {
enc, _ := json.Marshal(struct {
IP string `json:"ip"`
@@ -64,19 +46,19 @@ func doExternalAuthentication(
Action string `json:"action"`
Query string `json:"query"`
}{
- IP: accessRequest.ip.String(),
- User: accessRequest.user,
- Password: accessRequest.pass,
- Path: accessRequest.name,
- Protocol: string(accessRequest.proto),
- ID: accessRequest.id,
+ IP: accessRequest.IP.String(),
+ User: accessRequest.User,
+ Password: accessRequest.Pass,
+ Path: accessRequest.Name,
+ Protocol: string(accessRequest.Proto),
+ ID: accessRequest.ID,
Action: func() string {
- if accessRequest.publish {
+ if accessRequest.Publish {
return "publish"
}
return "read"
}(),
- Query: accessRequest.query,
+ Query: accessRequest.Query,
})
res, err := http.Post(ur, "application/json", bytes.NewReader(enc))
if err != nil {
@@ -98,14 +80,14 @@ func doAuthentication(
externalAuthenticationURL string,
rtspAuthMethods conf.AuthMethods,
pathConf *conf.Path,
- accessRequest pathAccessRequest,
+ accessRequest defs.PathAccessRequest,
) error {
var rtspAuth headers.Authorization
- if accessRequest.rtspRequest != nil {
- err := rtspAuth.Unmarshal(accessRequest.rtspRequest.Header["Authorization"])
+ if accessRequest.RTSPRequest != nil {
+ err := rtspAuth.Unmarshal(accessRequest.RTSPRequest.Header["Authorization"])
if err == nil && rtspAuth.Method == headers.AuthBasic {
- accessRequest.user = rtspAuth.BasicUser
- accessRequest.pass = rtspAuth.BasicPass
+ accessRequest.User = rtspAuth.BasicUser
+ accessRequest.Pass = rtspAuth.BasicPass
}
}
@@ -115,7 +97,7 @@ func doAuthentication(
accessRequest,
)
if err != nil {
- return &errAuthentication{message: fmt.Sprintf("external authentication failed: %s", err)}
+ return &defs.ErrAuthentication{Message: fmt.Sprintf("external authentication failed: %s", err)}
}
}
@@ -123,7 +105,7 @@ func doAuthentication(
var pathUser string
var pathPass string
- if accessRequest.publish {
+ if accessRequest.Publish {
pathIPs = pathConf.PublishIPs
pathUser = string(pathConf.PublishUser)
pathPass = string(pathConf.PublishPass)
@@ -134,27 +116,27 @@ func doAuthentication(
}
if pathIPs != nil {
- if !ipEqualOrInRange(accessRequest.ip, pathIPs) {
- return &errAuthentication{message: fmt.Sprintf("IP %s not allowed", accessRequest.ip)}
+ if !ipEqualOrInRange(accessRequest.IP, pathIPs) {
+ return &defs.ErrAuthentication{Message: fmt.Sprintf("IP %s not allowed", accessRequest.IP)}
}
}
if pathUser != "" {
- if accessRequest.rtspRequest != nil && rtspAuth.Method == headers.AuthDigest {
+ if accessRequest.RTSPRequest != nil && rtspAuth.Method == headers.AuthDigest {
err := auth.Validate(
- accessRequest.rtspRequest,
+ accessRequest.RTSPRequest,
pathUser,
pathPass,
- accessRequest.rtspBaseURL,
+ accessRequest.RTSPBaseURL,
rtspAuthMethods,
"IPCAM",
- accessRequest.rtspNonce)
+ accessRequest.RTSPNonce)
if err != nil {
- return &errAuthentication{message: err.Error()}
+ return &defs.ErrAuthentication{Message: err.Error()}
}
- } else if !checkCredential(pathUser, accessRequest.user) ||
- !checkCredential(pathPass, accessRequest.pass) {
- return &errAuthentication{message: "invalid credentials"}
+ } else if !checkCredential(pathUser, accessRequest.User) ||
+ !checkCredential(pathPass, accessRequest.Pass) {
+ return &defs.ErrAuthentication{Message: "invalid credentials"}
}
}
diff --git a/internal/core/core.go b/internal/core/core.go
index 5eb87347..69bf1d6f 100644
--- a/internal/core/core.go
+++ b/internal/core/core.go
@@ -22,6 +22,11 @@ import (
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/record"
"github.com/bluenviron/mediamtx/internal/rlimit"
+ "github.com/bluenviron/mediamtx/internal/servers/hls"
+ "github.com/bluenviron/mediamtx/internal/servers/rtmp"
+ "github.com/bluenviron/mediamtx/internal/servers/rtsp"
+ "github.com/bluenviron/mediamtx/internal/servers/srt"
+ "github.com/bluenviron/mediamtx/internal/servers/webrtc"
)
var version = "v0.0.0"
@@ -83,13 +88,13 @@ type Core struct {
pprof *pprof
recordCleaner *record.Cleaner
pathManager *pathManager
- rtspServer *rtspServer
- rtspsServer *rtspServer
- rtmpServer *rtmpServer
- rtmpsServer *rtmpServer
- hlsManager *hlsManager
- webRTCManager *webRTCManager
- srtServer *srtServer
+ rtspServer *rtsp.Server
+ rtspsServer *rtsp.Server
+ rtmpServer *rtmp.Server
+ rtmpsServer *rtmp.Server
+ hlsServer *hls.Server
+ webRTCServer *webrtc.Server
+ srtServer *srt.Server
api *api
confWatcher *confwatcher.ConfWatcher
@@ -168,7 +173,7 @@ func (p *Core) Wait() {
<-p.done
}
-// Log is the main logging function.
+// Log implements logger.Writer.
func (p *Core) Log(level logger.Level, format string, args ...interface{}) {
p.logger.Log(level, format, args...)
}
@@ -329,31 +334,32 @@ func (p *Core) createResources(initial bool) error {
_, useUDP := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDP)]
_, useMulticast := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDPMulticast)]
- p.rtspServer, err = newRTSPServer(
- p.conf.RTSPAddress,
- p.conf.AuthMethods,
- p.conf.ReadTimeout,
- p.conf.WriteTimeout,
- p.conf.WriteQueueSize,
- useUDP,
- useMulticast,
- p.conf.RTPAddress,
- p.conf.RTCPAddress,
- p.conf.MulticastIPRange,
- p.conf.MulticastRTPPort,
- p.conf.MulticastRTCPPort,
- false,
- "",
- "",
- p.conf.RTSPAddress,
- p.conf.Protocols,
- p.conf.RunOnConnect,
- p.conf.RunOnConnectRestart,
- p.conf.RunOnDisconnect,
- p.externalCmdPool,
- p.pathManager,
- p,
- )
+ p.rtspServer = &rtsp.Server{
+ Address: p.conf.RTSPAddress,
+ AuthMethods: p.conf.AuthMethods,
+ ReadTimeout: p.conf.ReadTimeout,
+ WriteTimeout: p.conf.WriteTimeout,
+ WriteQueueSize: p.conf.WriteQueueSize,
+ UseUDP: useUDP,
+ UseMulticast: useMulticast,
+ RTPAddress: p.conf.RTPAddress,
+ RTCPAddress: p.conf.RTCPAddress,
+ MulticastIPRange: p.conf.MulticastIPRange,
+ MulticastRTPPort: p.conf.MulticastRTPPort,
+ MulticastRTCPPort: p.conf.MulticastRTCPPort,
+ IsTLS: false,
+ ServerCert: "",
+ ServerKey: "",
+ RTSPAddress: p.conf.RTSPAddress,
+ Protocols: p.conf.Protocols,
+ RunOnConnect: p.conf.RunOnConnect,
+ RunOnConnectRestart: p.conf.RunOnConnectRestart,
+ RunOnDisconnect: p.conf.RunOnDisconnect,
+ ExternalCmdPool: p.externalCmdPool,
+ PathManager: p.pathManager,
+ Parent: p,
+ }
+ err := p.rtspServer.Initialize()
if err != nil {
return err
}
@@ -367,31 +373,32 @@ func (p *Core) createResources(initial bool) error {
(p.conf.Encryption == conf.EncryptionStrict ||
p.conf.Encryption == conf.EncryptionOptional) &&
p.rtspsServer == nil {
- p.rtspsServer, err = newRTSPServer(
- p.conf.RTSPSAddress,
- p.conf.AuthMethods,
- p.conf.ReadTimeout,
- p.conf.WriteTimeout,
- p.conf.WriteQueueSize,
- false,
- false,
- "",
- "",
- "",
- 0,
- 0,
- true,
- p.conf.ServerCert,
- p.conf.ServerKey,
- p.conf.RTSPAddress,
- p.conf.Protocols,
- p.conf.RunOnConnect,
- p.conf.RunOnConnectRestart,
- p.conf.RunOnDisconnect,
- p.externalCmdPool,
- p.pathManager,
- p,
- )
+ p.rtspsServer = &rtsp.Server{
+ Address: p.conf.RTSPSAddress,
+ AuthMethods: p.conf.AuthMethods,
+ ReadTimeout: p.conf.ReadTimeout,
+ WriteTimeout: p.conf.WriteTimeout,
+ WriteQueueSize: p.conf.WriteQueueSize,
+ UseUDP: false,
+ UseMulticast: false,
+ RTPAddress: "",
+ RTCPAddress: "",
+ MulticastIPRange: "",
+ MulticastRTPPort: 0,
+ MulticastRTCPPort: 0,
+ IsTLS: true,
+ ServerCert: p.conf.ServerCert,
+ ServerKey: p.conf.ServerKey,
+ RTSPAddress: p.conf.RTSPAddress,
+ Protocols: p.conf.Protocols,
+ RunOnConnect: p.conf.RunOnConnect,
+ RunOnConnectRestart: p.conf.RunOnConnectRestart,
+ RunOnDisconnect: p.conf.RunOnDisconnect,
+ ExternalCmdPool: p.externalCmdPool,
+ PathManager: p.pathManager,
+ Parent: p,
+ }
+ err := p.rtspsServer.Initialize()
if err != nil {
return err
}
@@ -405,22 +412,23 @@ func (p *Core) createResources(initial bool) error {
(p.conf.RTMPEncryption == conf.EncryptionNo ||
p.conf.RTMPEncryption == conf.EncryptionOptional) &&
p.rtmpServer == nil {
- p.rtmpServer, err = newRTMPServer(
- p.conf.RTMPAddress,
- p.conf.ReadTimeout,
- p.conf.WriteTimeout,
- p.conf.WriteQueueSize,
- false,
- "",
- "",
- p.conf.RTSPAddress,
- p.conf.RunOnConnect,
- p.conf.RunOnConnectRestart,
- p.conf.RunOnDisconnect,
- p.externalCmdPool,
- p.pathManager,
- p,
- )
+ p.rtmpServer = &rtmp.Server{
+ Address: p.conf.RTMPAddress,
+ ReadTimeout: p.conf.ReadTimeout,
+ WriteTimeout: p.conf.WriteTimeout,
+ WriteQueueSize: p.conf.WriteQueueSize,
+ IsTLS: false,
+ ServerCert: "",
+ ServerKey: "",
+ RTSPAddress: p.conf.RTSPAddress,
+ RunOnConnect: p.conf.RunOnConnect,
+ RunOnConnectRestart: p.conf.RunOnConnectRestart,
+ RunOnDisconnect: p.conf.RunOnDisconnect,
+ ExternalCmdPool: p.externalCmdPool,
+ PathManager: p.pathManager,
+ Parent: p,
+ }
+ err := p.rtmpServer.Initialize()
if err != nil {
return err
}
@@ -434,22 +442,23 @@ func (p *Core) createResources(initial bool) error {
(p.conf.RTMPEncryption == conf.EncryptionStrict ||
p.conf.RTMPEncryption == conf.EncryptionOptional) &&
p.rtmpsServer == nil {
- p.rtmpsServer, err = newRTMPServer(
- p.conf.RTMPSAddress,
- p.conf.ReadTimeout,
- p.conf.WriteTimeout,
- p.conf.WriteQueueSize,
- true,
- p.conf.RTMPServerCert,
- p.conf.RTMPServerKey,
- p.conf.RTSPAddress,
- p.conf.RunOnConnect,
- p.conf.RunOnConnectRestart,
- p.conf.RunOnDisconnect,
- p.externalCmdPool,
- p.pathManager,
- p,
- )
+ p.rtmpsServer = &rtmp.Server{
+ Address: p.conf.RTMPSAddress,
+ ReadTimeout: p.conf.ReadTimeout,
+ WriteTimeout: p.conf.WriteTimeout,
+ WriteQueueSize: p.conf.WriteQueueSize,
+ IsTLS: true,
+ ServerCert: p.conf.RTMPServerCert,
+ ServerKey: p.conf.RTMPServerKey,
+ RTSPAddress: p.conf.RTSPAddress,
+ RunOnConnect: p.conf.RunOnConnect,
+ RunOnConnectRestart: p.conf.RunOnConnectRestart,
+ RunOnDisconnect: p.conf.RunOnDisconnect,
+ ExternalCmdPool: p.externalCmdPool,
+ PathManager: p.pathManager,
+ Parent: p,
+ }
+ err := p.rtmpsServer.Initialize()
if err != nil {
return err
}
@@ -460,41 +469,42 @@ func (p *Core) createResources(initial bool) error {
}
if p.conf.HLS &&
- p.hlsManager == nil {
- p.hlsManager, err = newHLSManager(
- p.conf.HLSAddress,
- p.conf.HLSEncryption,
- p.conf.HLSServerKey,
- p.conf.HLSServerCert,
- p.conf.ExternalAuthenticationURL,
- p.conf.HLSAlwaysRemux,
- p.conf.HLSVariant,
- p.conf.HLSSegmentCount,
- p.conf.HLSSegmentDuration,
- p.conf.HLSPartDuration,
- p.conf.HLSSegmentMaxSize,
- p.conf.HLSAllowOrigin,
- p.conf.HLSTrustedProxies,
- p.conf.HLSDirectory,
- p.conf.ReadTimeout,
- p.conf.WriteQueueSize,
- p.pathManager,
- p,
- )
+ p.hlsServer == nil {
+ p.hlsServer = &hls.Server{
+ Address: p.conf.HLSAddress,
+ Encryption: p.conf.HLSEncryption,
+ ServerKey: p.conf.HLSServerKey,
+ ServerCert: p.conf.HLSServerCert,
+ ExternalAuthenticationURL: p.conf.ExternalAuthenticationURL,
+ AlwaysRemux: p.conf.HLSAlwaysRemux,
+ Variant: p.conf.HLSVariant,
+ SegmentCount: p.conf.HLSSegmentCount,
+ SegmentDuration: p.conf.HLSSegmentDuration,
+ PartDuration: p.conf.HLSPartDuration,
+ SegmentMaxSize: p.conf.HLSSegmentMaxSize,
+ AllowOrigin: p.conf.HLSAllowOrigin,
+ TrustedProxies: p.conf.HLSTrustedProxies,
+ Directory: p.conf.HLSDirectory,
+ ReadTimeout: p.conf.ReadTimeout,
+ WriteQueueSize: p.conf.WriteQueueSize,
+ PathManager: p.pathManager,
+ Parent: p,
+ }
+ err := p.hlsServer.Initialize()
if err != nil {
return err
}
- p.pathManager.setHLSManager(p.hlsManager)
+ p.pathManager.setHLSServer(p.hlsServer)
if p.metrics != nil {
- p.metrics.setHLSManager(p.hlsManager)
+ p.metrics.setHLSServer(p.hlsServer)
}
}
if p.conf.WebRTC &&
- p.webRTCManager == nil {
- p.webRTCManager = &webRTCManager{
+ p.webRTCServer == nil {
+ p.webRTCServer = &webrtc.Server{
Address: p.conf.WebRTCAddress,
Encryption: p.conf.WebRTCEncryption,
ServerKey: p.conf.WebRTCServerKey,
@@ -513,33 +523,34 @@ func (p *Core) createResources(initial bool) error {
PathManager: p.pathManager,
Parent: p,
}
- err = p.webRTCManager.initialize()
+ err = p.webRTCServer.Initialize()
if err != nil {
- p.webRTCManager = nil
+ p.webRTCServer = nil
return err
}
if p.metrics != nil {
- p.metrics.setWebRTCManager(p.webRTCManager)
+ p.metrics.setWebRTCServer(p.webRTCServer)
}
}
if p.conf.SRT &&
p.srtServer == nil {
- p.srtServer, err = newSRTServer(
- p.conf.SRTAddress,
- p.conf.RTSPAddress,
- p.conf.ReadTimeout,
- p.conf.WriteTimeout,
- p.conf.WriteQueueSize,
- p.conf.UDPMaxPayloadSize,
- p.conf.RunOnConnect,
- p.conf.RunOnConnectRestart,
- p.conf.RunOnDisconnect,
- p.externalCmdPool,
- p.pathManager,
- p,
- )
+ p.srtServer = &srt.Server{
+ Address: p.conf.SRTAddress,
+ RTSPAddress: p.conf.RTSPAddress,
+ ReadTimeout: p.conf.ReadTimeout,
+ WriteTimeout: p.conf.WriteTimeout,
+ WriteQueueSize: p.conf.WriteQueueSize,
+ UDPMaxPayloadSize: p.conf.UDPMaxPayloadSize,
+ RunOnConnect: p.conf.RunOnConnect,
+ RunOnConnectRestart: p.conf.RunOnConnectRestart,
+ RunOnDisconnect: p.conf.RunOnDisconnect,
+ ExternalCmdPool: p.externalCmdPool,
+ PathManager: p.pathManager,
+ Parent: p,
+ }
+ err = p.srtServer.Initialize()
if err != nil {
return err
}
@@ -560,8 +571,8 @@ func (p *Core) createResources(initial bool) error {
p.rtspsServer,
p.rtmpServer,
p.rtmpsServer,
- p.hlsManager,
- p.webRTCManager,
+ p.hlsServer,
+ p.webRTCServer,
p.srtServer,
p,
)
@@ -690,7 +701,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
closePathManager ||
closeLogger
- closeHLSManager := newConf == nil ||
+ closeHLSServer := newConf == nil ||
newConf.HLS != p.conf.HLS ||
newConf.HLSAddress != p.conf.HLSAddress ||
newConf.HLSEncryption != p.conf.HLSEncryption ||
@@ -712,7 +723,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
closeMetrics ||
closeLogger
- closeWebRTCManager := newConf == nil ||
+ closeWebRTCServer := newConf == nil ||
newConf.WebRTC != p.conf.WebRTC ||
newConf.WebRTCAddress != p.conf.WebRTCAddress ||
newConf.WebRTCEncryption != p.conf.WebRTCEncryption ||
@@ -754,8 +765,8 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
closeRTSPServer ||
closeRTSPSServer ||
closeRTMPServer ||
- closeHLSManager ||
- closeWebRTCManager ||
+ closeHLSServer ||
+ closeWebRTCServer ||
closeSRTServer ||
closeLogger
@@ -778,28 +789,28 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
p.metrics.setSRTServer(nil)
}
- p.srtServer.close()
+ p.srtServer.Close()
p.srtServer = nil
}
- if closeWebRTCManager && p.webRTCManager != nil {
+ if closeWebRTCServer && p.webRTCServer != nil {
if p.metrics != nil {
- p.metrics.setWebRTCManager(nil)
+ p.metrics.setWebRTCServer(nil)
}
- p.webRTCManager.close()
- p.webRTCManager = nil
+ p.webRTCServer.Close()
+ p.webRTCServer = nil
}
- if closeHLSManager && p.hlsManager != nil {
+ if closeHLSServer && p.hlsServer != nil {
if p.metrics != nil {
- p.metrics.setHLSManager(nil)
+ p.metrics.setHLSServer(nil)
}
- p.pathManager.setHLSManager(nil)
+ p.pathManager.setHLSServer(nil)
- p.hlsManager.close()
- p.hlsManager = nil
+ p.hlsServer.Close()
+ p.hlsServer = nil
}
if closeRTMPSServer && p.rtmpsServer != nil {
@@ -807,7 +818,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
p.metrics.setRTMPSServer(nil)
}
- p.rtmpsServer.close()
+ p.rtmpsServer.Close()
p.rtmpsServer = nil
}
@@ -816,7 +827,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
p.metrics.setRTMPServer(nil)
}
- p.rtmpServer.close()
+ p.rtmpServer.Close()
p.rtmpServer = nil
}
@@ -825,7 +836,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
p.metrics.setRTSPSServer(nil)
}
- p.rtspsServer.close()
+ p.rtspsServer.Close()
p.rtspsServer = nil
}
@@ -834,7 +845,7 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
p.metrics.setRTSPServer(nil)
}
- p.rtspServer.close()
+ p.rtspServer.Close()
p.rtspServer = nil
}
diff --git a/internal/core/hls_manager.go b/internal/core/hls_manager.go
deleted file mode 100644
index 7e6915f9..00000000
--- a/internal/core/hls_manager.go
+++ /dev/null
@@ -1,309 +0,0 @@
-package core
-
-import (
- "context"
- "fmt"
- "sort"
- "sync"
-
- "github.com/bluenviron/mediamtx/internal/conf"
- "github.com/bluenviron/mediamtx/internal/defs"
- "github.com/bluenviron/mediamtx/internal/logger"
-)
-
-type hlsManagerAPIMuxersListRes struct {
- data *defs.APIHLSMuxerList
- err error
-}
-
-type hlsManagerAPIMuxersListReq struct {
- res chan hlsManagerAPIMuxersListRes
-}
-
-type hlsManagerAPIMuxersGetRes struct {
- data *defs.APIHLSMuxer
- err error
-}
-
-type hlsManagerAPIMuxersGetReq struct {
- name string
- res chan hlsManagerAPIMuxersGetRes
-}
-
-type hlsManagerParent interface {
- logger.Writer
-}
-
-type hlsManager struct {
- externalAuthenticationURL string
- alwaysRemux bool
- variant conf.HLSVariant
- segmentCount int
- segmentDuration conf.StringDuration
- partDuration conf.StringDuration
- segmentMaxSize conf.StringSize
- directory string
- writeQueueSize int
- pathManager *pathManager
- parent hlsManagerParent
-
- ctx context.Context
- ctxCancel func()
- wg sync.WaitGroup
- httpServer *hlsHTTPServer
- muxers map[string]*hlsMuxer
-
- // in
- chPathReady chan *path
- chPathNotReady chan *path
- chHandleRequest chan hlsMuxerHandleRequestReq
- chCloseMuxer chan *hlsMuxer
- chAPIMuxerList chan hlsManagerAPIMuxersListReq
- chAPIMuxerGet chan hlsManagerAPIMuxersGetReq
-}
-
-func newHLSManager(
- address string,
- encryption bool,
- serverKey string,
- serverCert string,
- externalAuthenticationURL string,
- alwaysRemux bool,
- variant conf.HLSVariant,
- segmentCount int,
- segmentDuration conf.StringDuration,
- partDuration conf.StringDuration,
- segmentMaxSize conf.StringSize,
- allowOrigin string,
- trustedProxies conf.IPsOrCIDRs,
- directory string,
- readTimeout conf.StringDuration,
- writeQueueSize int,
- pathManager *pathManager,
- parent hlsManagerParent,
-) (*hlsManager, error) {
- ctx, ctxCancel := context.WithCancel(context.Background())
-
- m := &hlsManager{
- externalAuthenticationURL: externalAuthenticationURL,
- alwaysRemux: alwaysRemux,
- variant: variant,
- segmentCount: segmentCount,
- segmentDuration: segmentDuration,
- partDuration: partDuration,
- segmentMaxSize: segmentMaxSize,
- directory: directory,
- writeQueueSize: writeQueueSize,
- pathManager: pathManager,
- parent: parent,
- ctx: ctx,
- ctxCancel: ctxCancel,
- muxers: make(map[string]*hlsMuxer),
- chPathReady: make(chan *path),
- chPathNotReady: make(chan *path),
- chHandleRequest: make(chan hlsMuxerHandleRequestReq),
- chCloseMuxer: make(chan *hlsMuxer),
- chAPIMuxerList: make(chan hlsManagerAPIMuxersListReq),
- chAPIMuxerGet: make(chan hlsManagerAPIMuxersGetReq),
- }
-
- var err error
- m.httpServer, err = newHLSHTTPServer(
- address,
- encryption,
- serverKey,
- serverCert,
- allowOrigin,
- trustedProxies,
- readTimeout,
- m.pathManager,
- m,
- )
- if err != nil {
- ctxCancel()
- return nil, err
- }
-
- m.Log(logger.Info, "listener opened on "+address)
-
- m.wg.Add(1)
- go m.run()
-
- return m, nil
-}
-
-// Log is the main logging function.
-func (m *hlsManager) Log(level logger.Level, format string, args ...interface{}) {
- m.parent.Log(level, "[HLS] "+format, args...)
-}
-
-func (m *hlsManager) close() {
- m.Log(logger.Info, "listener is closing")
- m.ctxCancel()
- m.wg.Wait()
-}
-
-func (m *hlsManager) run() {
- defer m.wg.Done()
-
-outer:
- for {
- select {
- case pa := <-m.chPathReady:
- if m.alwaysRemux && !pa.conf.SourceOnDemand {
- if _, ok := m.muxers[pa.name]; !ok {
- m.createMuxer(pa.name, "")
- }
- }
-
- case pa := <-m.chPathNotReady:
- c, ok := m.muxers[pa.name]
- if ok && c.remoteAddr == "" { // created with "always remux"
- c.close()
- delete(m.muxers, pa.name)
- }
-
- case req := <-m.chHandleRequest:
- r, ok := m.muxers[req.path]
- switch {
- case ok:
- r.processRequest(&req)
-
- default:
- r := m.createMuxer(req.path, req.ctx.ClientIP())
- r.processRequest(&req)
- }
-
- case c := <-m.chCloseMuxer:
- if c2, ok := m.muxers[c.PathName()]; !ok || c2 != c {
- continue
- }
- delete(m.muxers, c.PathName())
-
- case req := <-m.chAPIMuxerList:
- data := &defs.APIHLSMuxerList{
- Items: []*defs.APIHLSMuxer{},
- }
-
- for _, muxer := range m.muxers {
- data.Items = append(data.Items, muxer.apiItem())
- }
-
- sort.Slice(data.Items, func(i, j int) bool {
- return data.Items[i].Created.Before(data.Items[j].Created)
- })
-
- req.res <- hlsManagerAPIMuxersListRes{
- data: data,
- }
-
- case req := <-m.chAPIMuxerGet:
- muxer, ok := m.muxers[req.name]
- if !ok {
- req.res <- hlsManagerAPIMuxersGetRes{err: fmt.Errorf("muxer not found")}
- continue
- }
-
- req.res <- hlsManagerAPIMuxersGetRes{data: muxer.apiItem()}
-
- case <-m.ctx.Done():
- break outer
- }
- }
-
- m.ctxCancel()
-
- m.httpServer.close()
-}
-
-func (m *hlsManager) createMuxer(pathName string, remoteAddr string) *hlsMuxer {
- r := newHLSMuxer(
- m.ctx,
- remoteAddr,
- m.externalAuthenticationURL,
- m.variant,
- m.segmentCount,
- m.segmentDuration,
- m.partDuration,
- m.segmentMaxSize,
- m.directory,
- m.writeQueueSize,
- &m.wg,
- pathName,
- m.pathManager,
- m)
- m.muxers[pathName] = r
- return r
-}
-
-// closeMuxer is called by hlsMuxer.
-func (m *hlsManager) closeMuxer(c *hlsMuxer) {
- select {
- case m.chCloseMuxer <- c:
- case <-m.ctx.Done():
- }
-}
-
-// pathReady is called by pathManager.
-func (m *hlsManager) pathReady(pa *path) {
- select {
- case m.chPathReady <- pa:
- case <-m.ctx.Done():
- }
-}
-
-// pathNotReady is called by pathManager.
-func (m *hlsManager) pathNotReady(pa *path) {
- select {
- case m.chPathNotReady <- pa:
- case <-m.ctx.Done():
- }
-}
-
-// apiMuxersList is called by api.
-func (m *hlsManager) apiMuxersList() (*defs.APIHLSMuxerList, error) {
- req := hlsManagerAPIMuxersListReq{
- res: make(chan hlsManagerAPIMuxersListRes),
- }
-
- select {
- case m.chAPIMuxerList <- req:
- res := <-req.res
- return res.data, res.err
-
- case <-m.ctx.Done():
- return nil, fmt.Errorf("terminated")
- }
-}
-
-// apiMuxersGet is called by api.
-func (m *hlsManager) apiMuxersGet(name string) (*defs.APIHLSMuxer, error) {
- req := hlsManagerAPIMuxersGetReq{
- name: name,
- res: make(chan hlsManagerAPIMuxersGetRes),
- }
-
- select {
- case m.chAPIMuxerGet <- req:
- res := <-req.res
- return res.data, res.err
-
- case <-m.ctx.Done():
- return nil, fmt.Errorf("terminated")
- }
-}
-
-func (m *hlsManager) handleRequest(req hlsMuxerHandleRequestReq) {
- req.res = make(chan *hlsMuxer)
-
- select {
- case m.chHandleRequest <- req:
- muxer := <-req.res
- if muxer != nil {
- req.ctx.Request.URL.Path = req.file
- muxer.handleRequest(req.ctx)
- }
-
- case <-m.ctx.Done():
- }
-}
diff --git a/internal/core/hls_manager_test.go b/internal/core/hls_server_test.go
similarity index 99%
rename from internal/core/hls_manager_test.go
rename to internal/core/hls_server_test.go
index 3631e5c3..e8ea9fc0 100644
--- a/internal/core/hls_manager_test.go
+++ b/internal/core/hls_server_test.go
@@ -197,7 +197,7 @@ func TestHLSRead(t *testing.T) {
"#EXT-X-GAP\n"+
"#EXTINF:1\\.00000,\n"+
"gap.mp4\n"+
- "#EXT-X-PROGRAM-DATE-TIME:.+?Z\n"+
+ "#EXT-X-PROGRAM-DATE-TIME:.+?\n"+
"#EXT-X-PART:DURATION=1\\.00000,URI=\".*?_part0.mp4\",INDEPENDENT=YES\n"+
"#EXTINF:1\\.00000,\n"+
".*?_seg7.mp4\n"+
diff --git a/internal/core/metrics.go b/internal/core/metrics.go
index 72419121..9068ecb8 100644
--- a/internal/core/metrics.go
+++ b/internal/core/metrics.go
@@ -28,16 +28,16 @@ type metrics struct {
ReadTimeout conf.StringDuration
Parent metricsParent
- httpServer *httpserv.WrappedServer
- mutex sync.Mutex
- pathManager apiPathManager
- rtspServer apiRTSPServer
- rtspsServer apiRTSPServer
- rtmpServer apiRTMPServer
- rtmpsServer apiRTMPServer
- srtServer apiSRTServer
- hlsManager apiHLSManager
- webRTCManager apiWebRTCManager
+ httpServer *httpserv.WrappedServer
+ mutex sync.Mutex
+ pathManager apiPathManager
+ rtspServer apiRTSPServer
+ rtspsServer apiRTSPServer
+ rtmpServer apiRTMPServer
+ rtmpsServer apiRTMPServer
+ srtServer apiSRTServer
+ hlsManager apiHLSServer
+ webRTCServer apiWebRTCServer
}
func (m *metrics) initialize() error {
@@ -72,6 +72,7 @@ func (m *metrics) close() {
m.httpServer.Close()
}
+// Log implements logger.Writer.
func (m *metrics) Log(level logger.Level, format string, args ...interface{}) {
m.Parent.Log(level, "[metrics] "+format, args...)
}
@@ -99,7 +100,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
}
if !interfaceIsEmpty(m.hlsManager) {
- data, err := m.hlsManager.apiMuxersList()
+ data, err := m.hlsManager.APIMuxersList()
if err == nil && len(data.Items) != 0 {
for _, i := range data.Items {
tags := "{name=\"" + i.Path + "\"}"
@@ -114,7 +115,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
if !interfaceIsEmpty(m.rtspServer) { //nolint:dupl
func() {
- data, err := m.rtspServer.apiConnsList()
+ data, err := m.rtspServer.APIConnsList()
if err == nil && len(data.Items) != 0 {
for _, i := range data.Items {
tags := "{id=\"" + i.ID.String() + "\"}"
@@ -130,7 +131,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
}()
func() {
- data, err := m.rtspServer.apiSessionsList()
+ data, err := m.rtspServer.APISessionsList()
if err == nil && len(data.Items) != 0 {
for _, i := range data.Items {
tags := "{id=\"" + i.ID.String() + "\",state=\"" + string(i.State) + "\"}"
@@ -148,7 +149,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
if !interfaceIsEmpty(m.rtspsServer) { //nolint:dupl
func() {
- data, err := m.rtspsServer.apiConnsList()
+ data, err := m.rtspsServer.APIConnsList()
if err == nil && len(data.Items) != 0 {
for _, i := range data.Items {
tags := "{id=\"" + i.ID.String() + "\"}"
@@ -164,7 +165,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
}()
func() {
- data, err := m.rtspsServer.apiSessionsList()
+ data, err := m.rtspsServer.APISessionsList()
if err == nil && len(data.Items) != 0 {
for _, i := range data.Items {
tags := "{id=\"" + i.ID.String() + "\",state=\"" + string(i.State) + "\"}"
@@ -181,7 +182,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
}
if !interfaceIsEmpty(m.rtmpServer) {
- data, err := m.rtmpServer.apiConnsList()
+ data, err := m.rtmpServer.APIConnsList()
if err == nil && len(data.Items) != 0 {
for _, i := range data.Items {
tags := "{id=\"" + i.ID.String() + "\",state=\"" + string(i.State) + "\"}"
@@ -197,7 +198,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
}
if !interfaceIsEmpty(m.rtmpsServer) {
- data, err := m.rtmpsServer.apiConnsList()
+ data, err := m.rtmpsServer.APIConnsList()
if err == nil && len(data.Items) != 0 {
for _, i := range data.Items {
tags := "{id=\"" + i.ID.String() + "\",state=\"" + string(i.State) + "\"}"
@@ -213,7 +214,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
}
if !interfaceIsEmpty(m.srtServer) {
- data, err := m.srtServer.apiConnsList()
+ data, err := m.srtServer.APIConnsList()
if err == nil && len(data.Items) != 0 {
for _, i := range data.Items {
tags := "{id=\"" + i.ID.String() + "\",state=\"" + string(i.State) + "\"}"
@@ -228,8 +229,8 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
}
}
- if !interfaceIsEmpty(m.webRTCManager) {
- data, err := m.webRTCManager.apiSessionsList()
+ if !interfaceIsEmpty(m.webRTCServer) {
+ data, err := m.webRTCServer.APISessionsList()
if err == nil && len(data.Items) != 0 {
for _, i := range data.Items {
tags := "{id=\"" + i.ID.String() + "\",state=\"" + string(i.State) + "\"}"
@@ -255,8 +256,8 @@ func (m *metrics) setPathManager(s apiPathManager) {
m.pathManager = s
}
-// setHLSManager is called by core.
-func (m *metrics) setHLSManager(s apiHLSManager) {
+// setHLSServer is called by core.
+func (m *metrics) setHLSServer(s apiHLSServer) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.hlsManager = s
@@ -297,9 +298,9 @@ func (m *metrics) setSRTServer(s apiSRTServer) {
m.srtServer = s
}
-// setWebRTCManager is called by core.
-func (m *metrics) setWebRTCManager(s apiWebRTCManager) {
+// setWebRTCServer is called by core.
+func (m *metrics) setWebRTCServer(s apiWebRTCServer) {
m.mutex.Lock()
defer m.mutex.Unlock()
- m.webRTCManager = s
+ m.webRTCServer = s
}
diff --git a/internal/core/metrics_test.go b/internal/core/metrics_test.go
index 65359afa..4c5bc08d 100644
--- a/internal/core/metrics_test.go
+++ b/internal/core/metrics_test.go
@@ -229,26 +229,26 @@ webrtc_sessions_bytes_sent 0
require.Regexp(t,
`^paths\{name=".*?",state="ready"\} 1`+"\n"+
`paths_bytes_received\{name=".*?",state="ready"\} [0-9]+`+"\n"+
- `paths_bytes_sent\{name=".*?",state="ready"\} 0`+"\n"+
+ `paths_bytes_sent\{name=".*?",state="ready"\} [0-9]+`+"\n"+
`paths\{name=".*?",state="ready"\} 1`+"\n"+
`paths_bytes_received\{name=".*?",state="ready"\} [0-9]+`+"\n"+
- `paths_bytes_sent\{name=".*?",state="ready"\} 0`+"\n"+
+ `paths_bytes_sent\{name=".*?",state="ready"\} [0-9]+`+"\n"+
`paths\{name=".*?",state="ready"\} 1`+"\n"+
`paths_bytes_received\{name=".*?",state="ready"\} [0-9]+`+"\n"+
- `paths_bytes_sent\{name=".*?",state="ready"\} 0`+"\n"+
+ `paths_bytes_sent\{name=".*?",state="ready"\} [0-9]+`+"\n"+
`paths\{name=".*?",state="ready"\} 1`+"\n"+
`paths_bytes_received\{name=".*?",state="ready"\} [0-9]+`+"\n"+
- `paths_bytes_sent\{name=".*?",state="ready"\} 0`+"\n"+
+ `paths_bytes_sent\{name=".*?",state="ready"\} [0-9]+`+"\n"+
`paths\{name=".*?",state="ready"\} 1`+"\n"+
`paths_bytes_received\{name=".*?",state="ready"\} [0-9]+`+"\n"+
- `paths_bytes_sent\{name=".*?",state="ready"\} 0`+"\n"+
+ `paths_bytes_sent\{name=".*?",state="ready"\} [0-9]+`+"\n"+
`paths\{name=".*?",state="ready"\} 1`+"\n"+
`paths_bytes_received\{name=".*?",state="ready"\} [0-9]+`+"\n"+
- `paths_bytes_sent\{name=".*?",state="ready"\} 0`+"\n"+
+ `paths_bytes_sent\{name=".*?",state="ready"\} [0-9]+`+"\n"+
`hls_muxers\{name=".*?"\} 1`+"\n"+
- `hls_muxers_bytes_sent\{name=".*?"\} [0-9]+`+"\n"+
+ `hls_muxers_bytes_sent\{name=".*?"\} 0`+"\n"+
`hls_muxers\{name=".*?"\} 1`+"\n"+
- `hls_muxers_bytes_sent\{name=".*?"\} [0-9]+`+"\n"+
+ `hls_muxers_bytes_sent\{name=".*?"\} 0`+"\n"+
`hls_muxers\{name=".*?"\} 1`+"\n"+
`hls_muxers_bytes_sent\{name=".*?"\} 0`+"\n"+
`hls_muxers\{name=".*?"\} 1`+"\n"+
diff --git a/internal/core/path.go b/internal/core/path.go
index 50deab66..81848343 100644
--- a/internal/core/path.go
+++ b/internal/core/path.go
@@ -11,7 +11,6 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/description"
- "github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
@@ -28,15 +27,6 @@ func newEmptyTimer() *time.Timer {
return t
}
-type errPathNoOnePublishing struct {
- pathName string
-}
-
-// Error implements the error interface.
-func (e errPathNoOnePublishing) Error() string {
- return fmt.Sprintf("no one is publishing to path '%s'", e.pathName)
-}
-
type pathParent interface {
logger.Writer
pathReady(*path)
@@ -53,95 +43,6 @@ const (
pathOnDemandStateClosing
)
-type pathAccessRequest struct {
- name string
- query string
- publish bool
- skipAuth bool
-
- // only if skipAuth = false
- ip net.IP
- user string
- pass string
- proto authProtocol
- id *uuid.UUID
- rtspRequest *base.Request
- rtspBaseURL *base.URL
- rtspNonce string
-}
-
-type pathRemoveReaderReq struct {
- author reader
- res chan struct{}
-}
-
-type pathRemovePublisherReq struct {
- author publisher
- res chan struct{}
-}
-
-type pathGetConfForPathRes struct {
- conf *conf.Path
- err error
-}
-
-type pathGetConfForPathReq struct {
- accessRequest pathAccessRequest
- res chan pathGetConfForPathRes
-}
-
-type pathDescribeRes struct {
- path *path
- stream *stream.Stream
- redirect string
- err error
-}
-
-type pathDescribeReq struct {
- accessRequest pathAccessRequest
- res chan pathDescribeRes
-}
-
-type pathAddReaderRes struct {
- path *path
- stream *stream.Stream
- err error
-}
-
-type pathAddReaderReq struct {
- author reader
- accessRequest pathAccessRequest
- res chan pathAddReaderRes
-}
-
-type pathAddPublisherRes struct {
- path *path
- err error
-}
-
-type pathAddPublisherReq struct {
- author publisher
- accessRequest pathAccessRequest
- res chan pathAddPublisherRes
-}
-
-type pathStartPublisherRes struct {
- stream *stream.Stream
- err error
-}
-
-type pathStartPublisherReq struct {
- author publisher
- desc *description.Session
- generateRTPPackets bool
- res chan pathStartPublisherRes
-}
-
-type pathStopPublisherReq struct {
- author publisher
- res chan struct{}
-}
-
type pathAPIPathsListRes struct {
data *defs.APIPathList
paths map[string]*path
@@ -179,16 +80,16 @@ type path struct {
ctx context.Context
ctxCancel func()
confMutex sync.RWMutex
- source source
+ source defs.Source
publisherQuery string
stream *stream.Stream
recordAgent *record.Agent
readyTime time.Time
onUnDemandHook func(string)
onNotReadyHook func()
- readers map[reader]struct{}
- describeRequestsOnHold []pathDescribeReq
- readerAddRequestsOnHold []pathAddReaderReq
+ readers map[defs.Reader]struct{}
+ describeRequestsOnHold []defs.PathDescribeReq
+ readerAddRequestsOnHold []defs.PathAddReaderReq
onDemandStaticSourceState pathOnDemandState
onDemandStaticSourceReadyTimer *time.Timer
onDemandStaticSourceCloseTimer *time.Timer
@@ -200,13 +101,13 @@ type path struct {
chReloadConf chan *conf.Path
chStaticSourceSetReady chan defs.PathSourceStaticSetReadyReq
chStaticSourceSetNotReady chan defs.PathSourceStaticSetNotReadyReq
- chDescribe chan pathDescribeReq
- chAddPublisher chan pathAddPublisherReq
- chRemovePublisher chan pathRemovePublisherReq
- chStartPublisher chan pathStartPublisherReq
- chStopPublisher chan pathStopPublisherReq
- chAddReader chan pathAddReaderReq
- chRemoveReader chan pathRemoveReaderReq
+ chDescribe chan defs.PathDescribeReq
+ chAddPublisher chan defs.PathAddPublisherReq
+ chRemovePublisher chan defs.PathRemovePublisherReq
+ chStartPublisher chan defs.PathStartPublisherReq
+ chStopPublisher chan defs.PathStopPublisherReq
+ chAddReader chan defs.PathAddReaderReq
+ chRemoveReader chan defs.PathRemoveReaderReq
chAPIPathsGet chan pathAPIPathsGetReq
// out
@@ -245,7 +146,7 @@ func newPath(
parent: parent,
ctx: ctx,
ctxCancel: ctxCancel,
- readers: make(map[reader]struct{}),
+ readers: make(map[defs.Reader]struct{}),
onDemandStaticSourceReadyTimer: newEmptyTimer(),
onDemandStaticSourceCloseTimer: newEmptyTimer(),
onDemandPublisherReadyTimer: newEmptyTimer(),
@@ -253,13 +154,13 @@ func newPath(
chReloadConf: make(chan *conf.Path),
chStaticSourceSetReady: make(chan defs.PathSourceStaticSetReadyReq),
chStaticSourceSetNotReady: make(chan defs.PathSourceStaticSetNotReadyReq),
- chDescribe: make(chan pathDescribeReq),
- chAddPublisher: make(chan pathAddPublisherReq),
- chRemovePublisher: make(chan pathRemovePublisherReq),
- chStartPublisher: make(chan pathStartPublisherReq),
- chStopPublisher: make(chan pathStopPublisherReq),
- chAddReader: make(chan pathAddReaderReq),
- chRemoveReader: make(chan pathRemoveReaderReq),
+ chDescribe: make(chan defs.PathDescribeReq),
+ chAddPublisher: make(chan defs.PathAddPublisherReq),
+ chRemovePublisher: make(chan defs.PathRemovePublisherReq),
+ chStartPublisher: make(chan defs.PathStartPublisherReq),
+ chStopPublisher: make(chan defs.PathStopPublisherReq),
+ chAddReader: make(chan defs.PathAddReaderReq),
+ chRemoveReader: make(chan defs.PathRemoveReaderReq),
chAPIPathsGet: make(chan pathAPIPathsGetReq),
done: make(chan struct{}),
}
@@ -280,11 +181,15 @@ func (pa *path) wait() {
<-pa.done
}
-// Log is the main logging function.
+// Log implements logger.Writer.
func (pa *path) Log(level logger.Level, format string, args ...interface{}) {
pa.parent.Log(level, "[path "+pa.name+"] "+format, args...)
}
+func (pa *path) Name() string {
+ return pa.name
+}
+
func (pa *path) run() {
defer close(pa.done)
defer pa.wg.Done()
@@ -308,7 +213,7 @@ func (pa *path) run() {
Logger: pa,
ExternalCmdPool: pa.externalCmdPool,
Conf: pa.conf,
- ExternalCmdEnv: pa.externalCmdEnv(),
+ ExternalCmdEnv: pa.ExternalCmdEnv(),
})
err := pa.runInner()
@@ -326,11 +231,11 @@ func (pa *path) run() {
onUnInitHook()
for _, req := range pa.describeRequestsOnHold {
- req.res <- pathDescribeRes{err: fmt.Errorf("terminated")}
+ req.Res <- defs.PathDescribeRes{Err: fmt.Errorf("terminated")}
}
for _, req := range pa.readerAddRequestsOnHold {
- req.res <- pathAddReaderRes{err: fmt.Errorf("terminated")}
+ req.Res <- defs.PathAddReaderRes{Err: fmt.Errorf("terminated")}
}
if pa.stream != nil {
@@ -342,8 +247,8 @@ func (pa *path) run() {
if !pa.conf.SourceOnDemand || pa.onDemandStaticSourceState != pathOnDemandStateInitial {
source.close("path is closing")
}
- } else if source, ok := pa.source.(publisher); ok {
- source.close()
+ } else if source, ok := pa.source.(defs.Publisher); ok {
+ source.Close()
}
}
@@ -442,12 +347,12 @@ func (pa *path) runInner() error {
func (pa *path) doOnDemandStaticSourceReadyTimer() {
for _, req := range pa.describeRequestsOnHold {
- req.res <- pathDescribeRes{err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
+ req.Res <- defs.PathDescribeRes{Err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
}
pa.describeRequestsOnHold = nil
for _, req := range pa.readerAddRequestsOnHold {
- req.res <- pathAddReaderRes{err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
+ req.Res <- defs.PathAddReaderRes{Err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
}
pa.readerAddRequestsOnHold = nil
@@ -461,12 +366,12 @@ func (pa *path) doOnDemandStaticSourceCloseTimer() {
func (pa *path) doOnDemandPublisherReadyTimer() {
for _, req := range pa.describeRequestsOnHold {
- req.res <- pathDescribeRes{err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
+ req.Res <- defs.PathDescribeRes{Err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
}
pa.describeRequestsOnHold = nil
for _, req := range pa.readerAddRequestsOnHold {
- req.res <- pathAddReaderRes{err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
+ req.Res <- defs.PathAddReaderRes{Err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
}
pa.readerAddRequestsOnHold = nil
@@ -526,17 +431,17 @@ func (pa *path) doSourceStaticSetNotReady(req defs.PathSourceStaticSetNotReadyRe
}
}
-func (pa *path) doDescribe(req pathDescribeReq) {
+func (pa *path) doDescribe(req defs.PathDescribeReq) {
if _, ok := pa.source.(*sourceRedirect); ok {
- req.res <- pathDescribeRes{
- redirect: pa.conf.SourceRedirect,
+ req.Res <- defs.PathDescribeRes{
+ Redirect: pa.conf.SourceRedirect,
}
return
}
if pa.stream != nil {
- req.res <- pathDescribeRes{
- stream: pa.stream,
+ req.Res <- defs.PathDescribeRes{
+ Stream: pa.stream,
}
return
}
@@ -551,7 +456,7 @@ func (pa *path) doDescribe(req pathDescribeReq) {
if pa.conf.HasOnDemandPublisher() {
if pa.onDemandPublisherState == pathOnDemandStateInitial {
- pa.onDemandPublisherStart(req.accessRequest.query)
+ pa.onDemandPublisherStart(req.AccessRequest.Query)
}
pa.describeRequestsOnHold = append(pa.describeRequestsOnHold, req)
return
@@ -561,69 +466,69 @@ func (pa *path) doDescribe(req pathDescribeReq) {
fallbackURL := func() string {
if strings.HasPrefix(pa.conf.Fallback, "/") {
ur := base.URL{
- Scheme: req.accessRequest.rtspRequest.URL.Scheme,
- User: req.accessRequest.rtspRequest.URL.User,
- Host: req.accessRequest.rtspRequest.URL.Host,
+ Scheme: req.AccessRequest.RTSPRequest.URL.Scheme,
+ User: req.AccessRequest.RTSPRequest.URL.User,
+ Host: req.AccessRequest.RTSPRequest.URL.Host,
Path: pa.conf.Fallback,
}
return ur.String()
}
return pa.conf.Fallback
}()
- req.res <- pathDescribeRes{redirect: fallbackURL}
+ req.Res <- defs.PathDescribeRes{Redirect: fallbackURL}
return
}
- req.res <- pathDescribeRes{err: errPathNoOnePublishing{pathName: pa.name}}
+ req.Res <- defs.PathDescribeRes{Err: defs.ErrPathNoOnePublishing{PathName: pa.name}}
}
-func (pa *path) doRemovePublisher(req pathRemovePublisherReq) {
- if pa.source == req.author {
+func (pa *path) doRemovePublisher(req defs.PathRemovePublisherReq) {
+ if pa.source == req.Author {
pa.executeRemovePublisher()
}
- close(req.res)
+ close(req.Res)
}
-func (pa *path) doAddPublisher(req pathAddPublisherReq) {
+func (pa *path) doAddPublisher(req defs.PathAddPublisherReq) {
if pa.conf.Source != "publisher" {
- req.res <- pathAddPublisherRes{
- err: fmt.Errorf("can't publish to path '%s' since 'source' is not 'publisher'", pa.name),
+ req.Res <- defs.PathAddPublisherRes{
+ Err: fmt.Errorf("can't publish to path '%s' since 'source' is not 'publisher'", pa.name),
}
return
}
if pa.source != nil {
if !pa.conf.OverridePublisher {
- req.res <- pathAddPublisherRes{err: fmt.Errorf("someone is already publishing to path '%s'", pa.name)}
+ req.Res <- defs.PathAddPublisherRes{Err: fmt.Errorf("someone is already publishing to path '%s'", pa.name)}
return
}
pa.Log(logger.Info, "closing existing publisher")
- pa.source.(publisher).close()
+ pa.source.(defs.Publisher).Close()
pa.executeRemovePublisher()
}
- pa.source = req.author
- pa.publisherQuery = req.accessRequest.query
+ pa.source = req.Author
+ pa.publisherQuery = req.AccessRequest.Query
- req.res <- pathAddPublisherRes{path: pa}
+ req.Res <- defs.PathAddPublisherRes{Path: pa}
}
-func (pa *path) doStartPublisher(req pathStartPublisherReq) {
- if pa.source != req.author {
- req.res <- pathStartPublisherRes{err: fmt.Errorf("publisher is not assigned to this path anymore")}
+func (pa *path) doStartPublisher(req defs.PathStartPublisherReq) {
+ if pa.source != req.Author {
+ req.Res <- defs.PathStartPublisherRes{Err: fmt.Errorf("publisher is not assigned to this path anymore")}
return
}
- err := pa.setReady(req.desc, req.generateRTPPackets)
+ err := pa.setReady(req.Desc, req.GenerateRTPPackets)
if err != nil {
- req.res <- pathStartPublisherRes{err: err}
+ req.Res <- defs.PathStartPublisherRes{Err: err}
return
}
- req.author.Log(logger.Info, "is publishing to path '%s', %s",
+ req.Author.Log(logger.Info, "is publishing to path '%s', %s",
pa.name,
- mediaInfo(req.desc.Medias))
+ defs.MediasInfo(req.Desc.Medias))
if pa.conf.HasOnDemandPublisher() && pa.onDemandPublisherState != pathOnDemandStateInitial {
pa.onDemandPublisherReadyTimer.Stop()
@@ -633,17 +538,17 @@ func (pa *path) doStartPublisher(req pathStartPublisherReq) {
pa.consumeOnHoldRequests()
- req.res <- pathStartPublisherRes{stream: pa.stream}
+ req.Res <- defs.PathStartPublisherRes{Stream: pa.stream}
}
-func (pa *path) doStopPublisher(req pathStopPublisherReq) {
- if req.author == pa.source && pa.stream != nil {
+func (pa *path) doStopPublisher(req defs.PathStopPublisherReq) {
+ if req.Author == pa.source && pa.stream != nil {
pa.setNotReady()
}
- close(req.res)
+ close(req.Res)
}
-func (pa *path) doAddReader(req pathAddReaderReq) {
+func (pa *path) doAddReader(req defs.PathAddReaderReq) {
if pa.stream != nil {
pa.addReaderPost(req)
return
@@ -659,20 +564,20 @@ func (pa *path) doAddReader(req pathAddReaderReq) {
if pa.conf.HasOnDemandPublisher() {
if pa.onDemandPublisherState == pathOnDemandStateInitial {
- pa.onDemandPublisherStart(req.accessRequest.query)
+ pa.onDemandPublisherStart(req.AccessRequest.Query)
}
pa.readerAddRequestsOnHold = append(pa.readerAddRequestsOnHold, req)
return
}
- req.res <- pathAddReaderRes{err: errPathNoOnePublishing{pathName: pa.name}}
+ req.Res <- defs.PathAddReaderRes{Err: defs.ErrPathNoOnePublishing{PathName: pa.name}}
}
-func (pa *path) doRemoveReader(req pathRemoveReaderReq) {
- if _, ok := pa.readers[req.author]; ok {
- pa.executeRemoveReader(req.author)
+func (pa *path) doRemoveReader(req defs.PathRemoveReaderReq) {
+ if _, ok := pa.readers[req.Author]; ok {
+ pa.executeRemoveReader(req.Author)
}
- close(req.res)
+ close(req.Res)
if len(pa.readers) == 0 {
if pa.conf.HasOnDemandStaticSource() {
@@ -711,7 +616,7 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) {
if pa.stream == nil {
return []string{}
}
- return mediasDescription(pa.stream.Desc().Medias)
+ return defs.MediasDescription(pa.stream.Desc().Medias)
}(),
BytesReceived: func() uint64 {
if pa.stream == nil {
@@ -728,7 +633,7 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) {
Readers: func() []defs.APIPathSourceOrReader {
ret := []defs.APIPathSourceOrReader{}
for r := range pa.readers {
- ret = append(ret, r.apiReaderDescribe())
+ ret = append(ret, r.APIReaderDescribe())
}
return ret
}(),
@@ -736,21 +641,13 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) {
}
}
-func (pa *path) safeConf() *conf.Path {
+func (pa *path) SafeConf() *conf.Path {
pa.confMutex.RLock()
defer pa.confMutex.RUnlock()
return pa.conf
}
-func (pa *path) shouldClose() bool {
- return pa.conf.Regexp != nil &&
- pa.source == nil &&
- len(pa.readers) == 0 &&
- len(pa.describeRequestsOnHold) == 0 &&
- len(pa.readerAddRequestsOnHold) == 0
-}
-
-func (pa *path) externalCmdEnv() externalcmd.Environment {
+func (pa *path) ExternalCmdEnv() externalcmd.Environment {
_, port, _ := net.SplitHostPort(pa.rtspAddress)
env := externalcmd.Environment{
"MTX_PATH": pa.name,
@@ -767,6 +664,14 @@ func (pa *path) externalCmdEnv() externalcmd.Environment {
return env
}
+func (pa *path) shouldClose() bool {
+ return pa.conf.Regexp != nil &&
+ pa.source == nil &&
+ len(pa.readers) == 0 &&
+ len(pa.describeRequestsOnHold) == 0 &&
+ len(pa.readerAddRequestsOnHold) == 0
+}
+
func (pa *path) onDemandStaticSourceStart() {
pa.source.(*staticSourceHandler).start(true)
@@ -799,7 +704,7 @@ func (pa *path) onDemandPublisherStart(query string) {
Logger: pa,
ExternalCmdPool: pa.externalCmdPool,
Conf: pa.conf,
- ExternalCmdEnv: pa.externalCmdEnv(),
+ ExternalCmdEnv: pa.ExternalCmdEnv(),
Query: query,
})
@@ -850,7 +755,7 @@ func (pa *path) setReady(desc *description.Session, allocateEncoder bool) error
Logger: pa,
ExternalCmdPool: pa.externalCmdPool,
Conf: pa.conf,
- ExternalCmdEnv: pa.externalCmdEnv(),
+ ExternalCmdEnv: pa.ExternalCmdEnv(),
Desc: pa.source.APISourceDescribe(),
Query: pa.publisherQuery,
})
@@ -862,8 +767,8 @@ func (pa *path) setReady(desc *description.Session, allocateEncoder bool) error
func (pa *path) consumeOnHoldRequests() {
for _, req := range pa.describeRequestsOnHold {
- req.res <- pathDescribeRes{
- stream: pa.stream,
+ req.Res <- defs.PathDescribeRes{
+ Stream: pa.stream,
}
}
pa.describeRequestsOnHold = nil
@@ -879,7 +784,7 @@ func (pa *path) setNotReady() {
for r := range pa.readers {
pa.executeRemoveReader(r)
- r.close()
+ r.Close()
}
pa.onNotReadyHook()
@@ -904,10 +809,10 @@ func (pa *path) startRecording() {
SegmentDuration: time.Duration(pa.conf.RecordSegmentDuration),
PathName: pa.name,
Stream: pa.stream,
- OnSegmentCreate: func(path string) {
+ OnSegmentCreate: func(segmentPath string) {
if pa.conf.RunOnRecordSegmentCreate != "" {
- env := pa.externalCmdEnv()
- env["MTX_SEGMENT_PATH"] = path
+ env := pa.ExternalCmdEnv()
+ env["MTX_SEGMENT_PATH"] = segmentPath
pa.Log(logger.Info, "runOnRecordSegmentCreate command launched")
externalcmd.NewCmd(
@@ -918,10 +823,10 @@ func (pa *path) startRecording() {
nil)
}
},
- OnSegmentComplete: func(path string) {
+ OnSegmentComplete: func(segmentPath string) {
if pa.conf.RunOnRecordSegmentComplete != "" {
- env := pa.externalCmdEnv()
- env["MTX_SEGMENT_PATH"] = path
+ env := pa.ExternalCmdEnv()
+ env["MTX_SEGMENT_PATH"] = segmentPath
pa.Log(logger.Info, "runOnRecordSegmentComplete command launched")
externalcmd.NewCmd(
@@ -937,7 +842,7 @@ func (pa *path) startRecording() {
pa.recordAgent.Initialize()
}
-func (pa *path) executeRemoveReader(r reader) {
+func (pa *path) executeRemoveReader(r defs.Reader) {
delete(pa.readers, r)
}
@@ -949,23 +854,21 @@ func (pa *path) executeRemovePublisher() {
pa.source = nil
}
-func (pa *path) addReaderPost(req pathAddReaderReq) {
- if _, ok := pa.readers[req.author]; ok {
- req.res <- pathAddReaderRes{
- path: pa,
- stream: pa.stream,
+func (pa *path) addReaderPost(req defs.PathAddReaderReq) {
+ if _, ok := pa.readers[req.Author]; ok {
+ req.Res <- defs.PathAddReaderRes{
+ Path: pa,
+ Stream: pa.stream,
}
return
}
if pa.conf.MaxReaders != 0 && len(pa.readers) >= pa.conf.MaxReaders {
- req.res <- pathAddReaderRes{
- err: fmt.Errorf("maximum reader count reached"),
- }
+ req.Res <- defs.PathAddReaderRes{Err: fmt.Errorf("maximum reader count reached")}
return
}
- pa.readers[req.author] = struct{}{}
+ pa.readers[req.Author] = struct{}{}
if pa.conf.HasOnDemandStaticSource() {
if pa.onDemandStaticSourceState == pathOnDemandStateClosing {
@@ -981,9 +884,9 @@ func (pa *path) addReaderPost(req pathAddReaderReq) {
}
}
- req.res <- pathAddReaderRes{
- path: pa,
- stream: pa.stream,
+ req.Res <- defs.PathAddReaderRes{
+ Path: pa,
+ Stream: pa.stream,
}
}
@@ -1032,72 +935,72 @@ func (pa *path) staticSourceHandlerSetNotReady(
}
// describe is called by a reader or publisher through pathManager.
-func (pa *path) describe(req pathDescribeReq) pathDescribeRes {
+func (pa *path) describe(req defs.PathDescribeReq) defs.PathDescribeRes {
select {
case pa.chDescribe <- req:
- return <-req.res
+ return <-req.Res
case <-pa.ctx.Done():
- return pathDescribeRes{err: fmt.Errorf("terminated")}
+ return defs.PathDescribeRes{Err: fmt.Errorf("terminated")}
}
}
// addPublisher is called by a publisher through pathManager.
-func (pa *path) addPublisher(req pathAddPublisherReq) pathAddPublisherRes {
+func (pa *path) addPublisher(req defs.PathAddPublisherReq) defs.PathAddPublisherRes {
select {
case pa.chAddPublisher <- req:
- return <-req.res
+ return <-req.Res
case <-pa.ctx.Done():
- return pathAddPublisherRes{err: fmt.Errorf("terminated")}
+ return defs.PathAddPublisherRes{Err: fmt.Errorf("terminated")}
}
}
-// removePublisher is called by a publisher.
-func (pa *path) removePublisher(req pathRemovePublisherReq) {
- req.res = make(chan struct{})
+// RemovePublisher is called by a publisher.
+func (pa *path) RemovePublisher(req defs.PathRemovePublisherReq) {
+ req.Res = make(chan struct{})
select {
case pa.chRemovePublisher <- req:
- <-req.res
+ <-req.Res
case <-pa.ctx.Done():
}
}
-// startPublisher is called by a publisher.
-func (pa *path) startPublisher(req pathStartPublisherReq) pathStartPublisherRes {
- req.res = make(chan pathStartPublisherRes)
+// StartPublisher is called by a publisher.
+func (pa *path) StartPublisher(req defs.PathStartPublisherReq) defs.PathStartPublisherRes {
+ req.Res = make(chan defs.PathStartPublisherRes)
select {
case pa.chStartPublisher <- req:
- return <-req.res
+ return <-req.Res
case <-pa.ctx.Done():
- return pathStartPublisherRes{err: fmt.Errorf("terminated")}
+ return defs.PathStartPublisherRes{Err: fmt.Errorf("terminated")}
}
}
-// stopPublisher is called by a publisher.
-func (pa *path) stopPublisher(req pathStopPublisherReq) {
- req.res = make(chan struct{})
+// StopPublisher is called by a publisher.
+func (pa *path) StopPublisher(req defs.PathStopPublisherReq) {
+ req.Res = make(chan struct{})
select {
case pa.chStopPublisher <- req:
- <-req.res
+ <-req.Res
case <-pa.ctx.Done():
}
}
// addReader is called by a reader through pathManager.
-func (pa *path) addReader(req pathAddReaderReq) pathAddReaderRes {
+func (pa *path) addReader(req defs.PathAddReaderReq) defs.PathAddReaderRes {
select {
case pa.chAddReader <- req:
- return <-req.res
+ return <-req.Res
case <-pa.ctx.Done():
- return pathAddReaderRes{err: fmt.Errorf("terminated")}
+ return defs.PathAddReaderRes{Err: fmt.Errorf("terminated")}
}
}
-// removeReader is called by a reader.
-func (pa *path) removeReader(req pathRemoveReaderReq) {
- req.res = make(chan struct{})
+// RemoveReader is called by a reader.
+func (pa *path) RemoveReader(req defs.PathRemoveReaderReq) {
+ req.Res = make(chan struct{})
select {
case pa.chRemoveReader <- req:
- <-req.res
+ <-req.Res
case <-pa.ctx.Done():
}
}
diff --git a/internal/core/path_manager.go b/internal/core/path_manager.go
index f63d4e50..3f43aea3 100644
--- a/internal/core/path_manager.go
+++ b/internal/core/path_manager.go
@@ -59,9 +59,9 @@ func getConfForPath(pathConfs map[string]*conf.Path, name string) (string, *conf
return "", nil, nil, fmt.Errorf("path '%s' is not configured", name)
}
-type pathManagerHLSManager interface {
- pathReady(*path)
- pathNotReady(*path)
+type pathManagerHLSServer interface {
+ PathReady(defs.Path)
+ PathNotReady(defs.Path)
}
type pathManagerParent interface {
@@ -83,20 +83,20 @@ type pathManager struct {
ctx context.Context
ctxCancel func()
wg sync.WaitGroup
- hlsManager pathManagerHLSManager
+ hlsManager pathManagerHLSServer
paths map[string]*path
pathsByConf map[string]map[*path]struct{}
// in
chReloadConf chan map[string]*conf.Path
- chSetHLSManager chan pathManagerHLSManager
+ chSetHLSServer chan pathManagerHLSServer
chClosePath chan *path
chPathReady chan *path
chPathNotReady chan *path
- chGetConfForPath chan pathGetConfForPathReq
- chDescribe chan pathDescribeReq
- chAddReader chan pathAddReaderReq
- chAddPublisher chan pathAddPublisherReq
+ chGetConfForPath chan defs.PathGetConfForPathReq
+ chDescribe chan defs.PathDescribeReq
+ chAddReader chan defs.PathAddReaderReq
+ chAddPublisher chan defs.PathAddPublisherReq
chAPIPathsList chan pathAPIPathsListReq
chAPIPathsGet chan pathAPIPathsGetReq
}
@@ -131,14 +131,14 @@ func newPathManager(
paths: make(map[string]*path),
pathsByConf: make(map[string]map[*path]struct{}),
chReloadConf: make(chan map[string]*conf.Path),
- chSetHLSManager: make(chan pathManagerHLSManager),
+ chSetHLSServer: make(chan pathManagerHLSServer),
chClosePath: make(chan *path),
chPathReady: make(chan *path),
chPathNotReady: make(chan *path),
- chGetConfForPath: make(chan pathGetConfForPathReq),
- chDescribe: make(chan pathDescribeReq),
- chAddReader: make(chan pathAddReaderReq),
- chAddPublisher: make(chan pathAddPublisherReq),
+ chGetConfForPath: make(chan defs.PathGetConfForPathReq),
+ chDescribe: make(chan defs.PathDescribeReq),
+ chAddReader: make(chan defs.PathAddReaderReq),
+ chAddPublisher: make(chan defs.PathAddPublisherReq),
chAPIPathsList: make(chan pathAPIPathsListReq),
chAPIPathsGet: make(chan pathAPIPathsGetReq),
}
@@ -163,7 +163,7 @@ func (pm *pathManager) close() {
pm.wg.Wait()
}
-// Log is the main logging function.
+// Log implements logger.Writer.
func (pm *pathManager) Log(level logger.Level, format string, args ...interface{}) {
pm.parent.Log(level, format, args...)
}
@@ -177,8 +177,8 @@ outer:
case newPaths := <-pm.chReloadConf:
pm.doReloadConf(newPaths)
- case m := <-pm.chSetHLSManager:
- pm.doSetHLSManager(m)
+ case m := <-pm.chSetHLSServer:
+ pm.doSetHLSServer(m)
case pa := <-pm.chClosePath:
pm.doClosePath(pa)
@@ -252,7 +252,7 @@ func (pm *pathManager) doReloadConf(newPaths map[string]*conf.Path) {
}
}
-func (pm *pathManager) doSetHLSManager(m pathManagerHLSManager) {
+func (pm *pathManager) doSetHLSServer(m pathManagerHLSServer) {
pm.hlsManager = m
}
@@ -265,101 +265,101 @@ func (pm *pathManager) doClosePath(pa *path) {
func (pm *pathManager) doPathReady(pa *path) {
if pm.hlsManager != nil {
- pm.hlsManager.pathReady(pa)
+ pm.hlsManager.PathReady(pa)
}
}
func (pm *pathManager) doPathNotReady(pa *path) {
if pm.hlsManager != nil {
- pm.hlsManager.pathNotReady(pa)
+ pm.hlsManager.PathNotReady(pa)
}
}
-func (pm *pathManager) doGetConfForPath(req pathGetConfForPathReq) {
- _, pathConf, _, err := getConfForPath(pm.pathConfs, req.accessRequest.name)
+func (pm *pathManager) doGetConfForPath(req defs.PathGetConfForPathReq) {
+ _, pathConf, _, err := getConfForPath(pm.pathConfs, req.AccessRequest.Name)
if err != nil {
- req.res <- pathGetConfForPathRes{err: err}
+ req.Res <- defs.PathGetConfForPathRes{Err: err}
return
}
err = doAuthentication(pm.externalAuthenticationURL, pm.authMethods,
- pathConf, req.accessRequest)
+ pathConf, req.AccessRequest)
if err != nil {
- req.res <- pathGetConfForPathRes{err: err}
+ req.Res <- defs.PathGetConfForPathRes{Err: err}
return
}
- req.res <- pathGetConfForPathRes{conf: pathConf}
+ req.Res <- defs.PathGetConfForPathRes{Conf: pathConf}
}
-func (pm *pathManager) doDescribe(req pathDescribeReq) {
- pathConfName, pathConf, pathMatches, err := getConfForPath(pm.pathConfs, req.accessRequest.name)
+func (pm *pathManager) doDescribe(req defs.PathDescribeReq) {
+ pathConfName, pathConf, pathMatches, err := getConfForPath(pm.pathConfs, req.AccessRequest.Name)
if err != nil {
- req.res <- pathDescribeRes{err: err}
+ req.Res <- defs.PathDescribeRes{Err: err}
return
}
err = doAuthentication(pm.externalAuthenticationURL, pm.authMethods,
- pathConf, req.accessRequest)
+ pathConf, req.AccessRequest)
if err != nil {
- req.res <- pathDescribeRes{err: err}
+ req.Res <- defs.PathDescribeRes{Err: err}
return
}
// create path if it doesn't exist
- if _, ok := pm.paths[req.accessRequest.name]; !ok {
- pm.createPath(pathConfName, pathConf, req.accessRequest.name, pathMatches)
+ if _, ok := pm.paths[req.AccessRequest.Name]; !ok {
+ pm.createPath(pathConfName, pathConf, req.AccessRequest.Name, pathMatches)
}
- req.res <- pathDescribeRes{path: pm.paths[req.accessRequest.name]}
+ req.Res <- defs.PathDescribeRes{Path: pm.paths[req.AccessRequest.Name]}
}
-func (pm *pathManager) doAddReader(req pathAddReaderReq) {
- pathConfName, pathConf, pathMatches, err := getConfForPath(pm.pathConfs, req.accessRequest.name)
+func (pm *pathManager) doAddReader(req defs.PathAddReaderReq) {
+ pathConfName, pathConf, pathMatches, err := getConfForPath(pm.pathConfs, req.AccessRequest.Name)
if err != nil {
- req.res <- pathAddReaderRes{err: err}
+ req.Res <- defs.PathAddReaderRes{Err: err}
return
}
- if !req.accessRequest.skipAuth {
+ if !req.AccessRequest.SkipAuth {
err = doAuthentication(pm.externalAuthenticationURL, pm.authMethods,
- pathConf, req.accessRequest)
+ pathConf, req.AccessRequest)
if err != nil {
- req.res <- pathAddReaderRes{err: err}
+ req.Res <- defs.PathAddReaderRes{Err: err}
return
}
}
// create path if it doesn't exist
- if _, ok := pm.paths[req.accessRequest.name]; !ok {
- pm.createPath(pathConfName, pathConf, req.accessRequest.name, pathMatches)
+ if _, ok := pm.paths[req.AccessRequest.Name]; !ok {
+ pm.createPath(pathConfName, pathConf, req.AccessRequest.Name, pathMatches)
}
- req.res <- pathAddReaderRes{path: pm.paths[req.accessRequest.name]}
+ req.Res <- defs.PathAddReaderRes{Path: pm.paths[req.AccessRequest.Name]}
}
-func (pm *pathManager) doAddPublisher(req pathAddPublisherReq) {
- pathConfName, pathConf, pathMatches, err := getConfForPath(pm.pathConfs, req.accessRequest.name)
+func (pm *pathManager) doAddPublisher(req defs.PathAddPublisherReq) {
+ pathConfName, pathConf, pathMatches, err := getConfForPath(pm.pathConfs, req.AccessRequest.Name)
if err != nil {
- req.res <- pathAddPublisherRes{err: err}
+ req.Res <- defs.PathAddPublisherRes{Err: err}
return
}
- if !req.accessRequest.skipAuth {
+ if !req.AccessRequest.SkipAuth {
err = doAuthentication(pm.externalAuthenticationURL, pm.authMethods,
- pathConf, req.accessRequest)
+ pathConf, req.AccessRequest)
if err != nil {
- req.res <- pathAddPublisherRes{err: err}
+ req.Res <- defs.PathAddPublisherRes{Err: err}
return
}
}
// create path if it doesn't exist
- if _, ok := pm.paths[req.accessRequest.name]; !ok {
- pm.createPath(pathConfName, pathConf, req.accessRequest.name, pathMatches)
+ if _, ok := pm.paths[req.AccessRequest.Name]; !ok {
+ pm.createPath(pathConfName, pathConf, req.AccessRequest.Name, pathMatches)
}
- req.res <- pathAddPublisherRes{path: pm.paths[req.accessRequest.name]}
+ req.Res <- defs.PathAddPublisherRes{Path: pm.paths[req.AccessRequest.Name]}
}
func (pm *pathManager) doAPIPathsList(req pathAPIPathsListReq) {
@@ -454,79 +454,79 @@ func (pm *pathManager) closePath(pa *path) {
}
}
-// getConfForPath is called by a reader or publisher.
-func (pm *pathManager) getConfForPath(req pathGetConfForPathReq) pathGetConfForPathRes {
- req.res = make(chan pathGetConfForPathRes)
+// GetConfForPath is called by a reader or publisher.
+func (pm *pathManager) GetConfForPath(req defs.PathGetConfForPathReq) defs.PathGetConfForPathRes {
+ req.Res = make(chan defs.PathGetConfForPathRes)
select {
case pm.chGetConfForPath <- req:
- return <-req.res
+ return <-req.Res
case <-pm.ctx.Done():
- return pathGetConfForPathRes{err: fmt.Errorf("terminated")}
+ return defs.PathGetConfForPathRes{Err: fmt.Errorf("terminated")}
}
}
-// describe is called by a reader or publisher.
-func (pm *pathManager) describe(req pathDescribeReq) pathDescribeRes {
- req.res = make(chan pathDescribeRes)
+// Describe is called by a reader or publisher.
+func (pm *pathManager) Describe(req defs.PathDescribeReq) defs.PathDescribeRes {
+ req.Res = make(chan defs.PathDescribeRes)
select {
case pm.chDescribe <- req:
- res1 := <-req.res
- if res1.err != nil {
+ res1 := <-req.Res
+ if res1.Err != nil {
return res1
}
- res2 := res1.path.describe(req)
- if res2.err != nil {
+ res2 := res1.Path.(*path).describe(req)
+ if res2.Err != nil {
return res2
}
- res2.path = res1.path
+ res2.Path = res1.Path
return res2
case <-pm.ctx.Done():
- return pathDescribeRes{err: fmt.Errorf("terminated")}
+ return defs.PathDescribeRes{Err: fmt.Errorf("terminated")}
}
}
-// addPublisher is called by a publisher.
-func (pm *pathManager) addPublisher(req pathAddPublisherReq) pathAddPublisherRes {
- req.res = make(chan pathAddPublisherRes)
+// AddPublisher is called by a publisher.
+func (pm *pathManager) AddPublisher(req defs.PathAddPublisherReq) defs.PathAddPublisherRes {
+ req.Res = make(chan defs.PathAddPublisherRes)
select {
case pm.chAddPublisher <- req:
- res := <-req.res
- if res.err != nil {
+ res := <-req.Res
+ if res.Err != nil {
return res
}
- return res.path.addPublisher(req)
+ return res.Path.(*path).addPublisher(req)
case <-pm.ctx.Done():
- return pathAddPublisherRes{err: fmt.Errorf("terminated")}
+ return defs.PathAddPublisherRes{Err: fmt.Errorf("terminated")}
}
}
-// addReader is called by a reader.
-func (pm *pathManager) addReader(req pathAddReaderReq) pathAddReaderRes {
- req.res = make(chan pathAddReaderRes)
+// AddReader is called by a reader.
+func (pm *pathManager) AddReader(req defs.PathAddReaderReq) defs.PathAddReaderRes {
+ req.Res = make(chan defs.PathAddReaderRes)
select {
case pm.chAddReader <- req:
- res := <-req.res
- if res.err != nil {
+ res := <-req.Res
+ if res.Err != nil {
return res
}
- return res.path.addReader(req)
+ return res.Path.(*path).addReader(req)
case <-pm.ctx.Done():
- return pathAddReaderRes{err: fmt.Errorf("terminated")}
+ return defs.PathAddReaderRes{Err: fmt.Errorf("terminated")}
}
}
-// setHLSManager is called by hlsManager.
-func (pm *pathManager) setHLSManager(s pathManagerHLSManager) {
+// setHLSServer is called by hlsManager.
+func (pm *pathManager) setHLSServer(s pathManagerHLSServer) {
select {
- case pm.chSetHLSManager <- s:
+ case pm.chSetHLSServer <- s:
case <-pm.ctx.Done():
}
}
diff --git a/internal/core/path_manager_test.go b/internal/core/path_manager_test.go
index dcb062f3..79c33380 100644
--- a/internal/core/path_manager_test.go
+++ b/internal/core/path_manager_test.go
@@ -7,9 +7,12 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
+ "github.com/bluenviron/mediamtx/internal/defs"
"github.com/stretchr/testify/require"
)
+var _ defs.PathManager = &pathManager{}
+
func TestPathAutoDeletion(t *testing.T) {
for _, ca := range []string{"describe", "setup"} {
t.Run(ca, func(t *testing.T) {
diff --git a/internal/core/path_test.go b/internal/core/path_test.go
index f19ae397..25850d50 100644
--- a/internal/core/path_test.go
+++ b/internal/core/path_test.go
@@ -19,10 +19,11 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
- "github.com/datarhei/gosrt"
+ srt "github.com/datarhei/gosrt"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
+ "github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp"
"github.com/bluenviron/mediamtx/internal/protocols/webrtc"
)
@@ -81,6 +82,8 @@ func main() {
}
`
+var _ defs.Path = &path{}
+
func TestPathRunOnDemand(t *testing.T) {
onDemandFile := filepath.Join(os.TempDir(), "ondemand")
onUnDemandFile := filepath.Join(os.TempDir(), "onundemand")
diff --git a/internal/core/pprof.go b/internal/core/pprof.go
index e71d9397..b92396c0 100644
--- a/internal/core/pprof.go
+++ b/internal/core/pprof.go
@@ -58,6 +58,7 @@ func (pp *pprof) close() {
pp.httpServer.Close()
}
+// Log implements logger.Writer.
func (pp *pprof) Log(level logger.Level, format string, args ...interface{}) {
pp.parent.Log(level, "[pprof] "+format, args...)
}
diff --git a/internal/core/publisher.go b/internal/core/publisher.go
deleted file mode 100644
index c76f8d0f..00000000
--- a/internal/core/publisher.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package core
-
-// publisher is an entity that can publish a stream.
-type publisher interface {
- source
- close()
-}
diff --git a/internal/core/reader.go b/internal/core/reader.go
deleted file mode 100644
index dcac29eb..00000000
--- a/internal/core/reader.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package core
-
-import (
- "github.com/bluenviron/mediamtx/internal/asyncwriter"
- "github.com/bluenviron/mediamtx/internal/defs"
- "github.com/bluenviron/mediamtx/internal/stream"
-)
-
-// reader is an entity that can read a stream.
-type reader interface {
- close()
- apiReaderDescribe() defs.APIPathSourceOrReader
-}
-
-func readerMediaInfo(r *asyncwriter.Writer, stream *stream.Stream) string {
- return mediaInfo(stream.MediasForReader(r))
-}
diff --git a/internal/core/rtmp_listener.go b/internal/core/rtmp_listener.go
deleted file mode 100644
index 7c058a4b..00000000
--- a/internal/core/rtmp_listener.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package core
-
-import (
- "net"
- "sync"
-)
-
-type rtmpListener struct {
- ln net.Listener
- wg *sync.WaitGroup
- parent *rtmpServer
-}
-
-func newRTMPListener(
- ln net.Listener,
- wg *sync.WaitGroup,
- parent *rtmpServer,
-) *rtmpListener {
- l := &rtmpListener{
- ln: ln,
- wg: wg,
- parent: parent,
- }
-
- l.wg.Add(1)
- go l.run()
-
- return l
-}
-
-func (l *rtmpListener) run() {
- defer l.wg.Done()
-
- err := l.runInner()
-
- l.parent.acceptError(err)
-}
-
-func (l *rtmpListener) runInner() error {
- for {
- conn, err := l.ln.Accept()
- if err != nil {
- return err
- }
-
- l.parent.newConn(conn)
- }
-}
diff --git a/internal/core/rtmp_server.go b/internal/core/rtmp_server.go
deleted file mode 100644
index 5733d2c4..00000000
--- a/internal/core/rtmp_server.go
+++ /dev/null
@@ -1,327 +0,0 @@
-package core
-
-import (
- "context"
- "crypto/tls"
- "fmt"
- "net"
- "sort"
- "sync"
-
- "github.com/google/uuid"
-
- "github.com/bluenviron/mediamtx/internal/conf"
- "github.com/bluenviron/mediamtx/internal/defs"
- "github.com/bluenviron/mediamtx/internal/externalcmd"
- "github.com/bluenviron/mediamtx/internal/logger"
- "github.com/bluenviron/mediamtx/internal/restrictnetwork"
-)
-
-type rtmpServerAPIConnsListRes struct {
- data *defs.APIRTMPConnList
- err error
-}
-
-type rtmpServerAPIConnsListReq struct {
- res chan rtmpServerAPIConnsListRes
-}
-
-type rtmpServerAPIConnsGetRes struct {
- data *defs.APIRTMPConn
- err error
-}
-
-type rtmpServerAPIConnsGetReq struct {
- uuid uuid.UUID
- res chan rtmpServerAPIConnsGetRes
-}
-
-type rtmpServerAPIConnsKickRes struct {
- err error
-}
-
-type rtmpServerAPIConnsKickReq struct {
- uuid uuid.UUID
- res chan rtmpServerAPIConnsKickRes
-}
-
-type rtmpServerParent interface {
- logger.Writer
-}
-
-type rtmpServer struct {
- readTimeout conf.StringDuration
- writeTimeout conf.StringDuration
- writeQueueSize int
- isTLS bool
- rtspAddress string
- runOnConnect string
- runOnConnectRestart bool
- runOnDisconnect string
- externalCmdPool *externalcmd.Pool
- pathManager *pathManager
- parent rtmpServerParent
-
- ctx context.Context
- ctxCancel func()
- wg sync.WaitGroup
- ln net.Listener
- conns map[*rtmpConn]struct{}
-
- // in
- chNewConn chan net.Conn
- chAcceptErr chan error
- chCloseConn chan *rtmpConn
- chAPIConnsList chan rtmpServerAPIConnsListReq
- chAPIConnsGet chan rtmpServerAPIConnsGetReq
- chAPIConnsKick chan rtmpServerAPIConnsKickReq
-}
-
-func newRTMPServer(
- address string,
- readTimeout conf.StringDuration,
- writeTimeout conf.StringDuration,
- writeQueueSize int,
- isTLS bool,
- serverCert string,
- serverKey string,
- rtspAddress string,
- runOnConnect string,
- runOnConnectRestart bool,
- runOnDisconnect string,
- externalCmdPool *externalcmd.Pool,
- pathManager *pathManager,
- parent rtmpServerParent,
-) (*rtmpServer, error) {
- ln, err := func() (net.Listener, error) {
- if !isTLS {
- return net.Listen(restrictnetwork.Restrict("tcp", address))
- }
-
- cert, err := tls.LoadX509KeyPair(serverCert, serverKey)
- if err != nil {
- return nil, err
- }
-
- network, address := restrictnetwork.Restrict("tcp", address)
- return tls.Listen(network, address, &tls.Config{Certificates: []tls.Certificate{cert}})
- }()
- if err != nil {
- return nil, err
- }
-
- ctx, ctxCancel := context.WithCancel(context.Background())
-
- s := &rtmpServer{
- readTimeout: readTimeout,
- writeTimeout: writeTimeout,
- writeQueueSize: writeQueueSize,
- rtspAddress: rtspAddress,
- runOnConnect: runOnConnect,
- runOnConnectRestart: runOnConnectRestart,
- runOnDisconnect: runOnDisconnect,
- isTLS: isTLS,
- externalCmdPool: externalCmdPool,
- pathManager: pathManager,
- parent: parent,
- ctx: ctx,
- ctxCancel: ctxCancel,
- ln: ln,
- conns: make(map[*rtmpConn]struct{}),
- chNewConn: make(chan net.Conn),
- chAcceptErr: make(chan error),
- chCloseConn: make(chan *rtmpConn),
- chAPIConnsList: make(chan rtmpServerAPIConnsListReq),
- chAPIConnsGet: make(chan rtmpServerAPIConnsGetReq),
- chAPIConnsKick: make(chan rtmpServerAPIConnsKickReq),
- }
-
- s.Log(logger.Info, "listener opened on %s", address)
-
- newRTMPListener(
- s.ln,
- &s.wg,
- s,
- )
-
- s.wg.Add(1)
- go s.run()
-
- return s, nil
-}
-
-func (s *rtmpServer) Log(level logger.Level, format string, args ...interface{}) {
- label := func() string {
- if s.isTLS {
- return "RTMPS"
- }
- return "RTMP"
- }()
- s.parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...)
-}
-
-func (s *rtmpServer) close() {
- s.Log(logger.Info, "listener is closing")
- s.ctxCancel()
- s.wg.Wait()
-}
-
-func (s *rtmpServer) run() {
- defer s.wg.Done()
-
-outer:
- for {
- select {
- case err := <-s.chAcceptErr:
- s.Log(logger.Error, "%s", err)
- break outer
-
- case nconn := <-s.chNewConn:
- c := newRTMPConn(
- s.ctx,
- s.isTLS,
- s.rtspAddress,
- s.readTimeout,
- s.writeTimeout,
- s.writeQueueSize,
- s.runOnConnect,
- s.runOnConnectRestart,
- s.runOnDisconnect,
- &s.wg,
- nconn,
- s.externalCmdPool,
- s.pathManager,
- s)
- s.conns[c] = struct{}{}
-
- case c := <-s.chCloseConn:
- delete(s.conns, c)
-
- case req := <-s.chAPIConnsList:
- data := &defs.APIRTMPConnList{
- Items: []*defs.APIRTMPConn{},
- }
-
- for c := range s.conns {
- data.Items = append(data.Items, c.apiItem())
- }
-
- sort.Slice(data.Items, func(i, j int) bool {
- return data.Items[i].Created.Before(data.Items[j].Created)
- })
-
- req.res <- rtmpServerAPIConnsListRes{data: data}
-
- case req := <-s.chAPIConnsGet:
- c := s.findConnByUUID(req.uuid)
- if c == nil {
- req.res <- rtmpServerAPIConnsGetRes{err: fmt.Errorf("connection not found")}
- continue
- }
-
- req.res <- rtmpServerAPIConnsGetRes{data: c.apiItem()}
-
- case req := <-s.chAPIConnsKick:
- c := s.findConnByUUID(req.uuid)
- if c == nil {
- req.res <- rtmpServerAPIConnsKickRes{err: fmt.Errorf("connection not found")}
- continue
- }
-
- delete(s.conns, c)
- c.close()
- req.res <- rtmpServerAPIConnsKickRes{}
-
- case <-s.ctx.Done():
- break outer
- }
- }
-
- s.ctxCancel()
-
- s.ln.Close()
-}
-
-func (s *rtmpServer) findConnByUUID(uuid uuid.UUID) *rtmpConn {
- for c := range s.conns {
- if c.uuid == uuid {
- return c
- }
- }
- return nil
-}
-
-// newConn is called by rtmpListener.
-func (s *rtmpServer) newConn(conn net.Conn) {
- select {
- case s.chNewConn <- conn:
- case <-s.ctx.Done():
- conn.Close()
- }
-}
-
-// acceptError is called by rtmpListener.
-func (s *rtmpServer) acceptError(err error) {
- select {
- case s.chAcceptErr <- err:
- case <-s.ctx.Done():
- }
-}
-
-// closeConn is called by rtmpConn.
-func (s *rtmpServer) closeConn(c *rtmpConn) {
- select {
- case s.chCloseConn <- c:
- case <-s.ctx.Done():
- }
-}
-
-// apiConnsList is called by api.
-func (s *rtmpServer) apiConnsList() (*defs.APIRTMPConnList, error) {
- req := rtmpServerAPIConnsListReq{
- res: make(chan rtmpServerAPIConnsListRes),
- }
-
- select {
- case s.chAPIConnsList <- req:
- res := <-req.res
- return res.data, res.err
-
- case <-s.ctx.Done():
- return nil, fmt.Errorf("terminated")
- }
-}
-
-// apiConnsGet is called by api.
-func (s *rtmpServer) apiConnsGet(uuid uuid.UUID) (*defs.APIRTMPConn, error) {
- req := rtmpServerAPIConnsGetReq{
- uuid: uuid,
- res: make(chan rtmpServerAPIConnsGetRes),
- }
-
- select {
- case s.chAPIConnsGet <- req:
- res := <-req.res
- return res.data, res.err
-
- case <-s.ctx.Done():
- return nil, fmt.Errorf("terminated")
- }
-}
-
-// apiConnsKick is called by api.
-func (s *rtmpServer) apiConnsKick(uuid uuid.UUID) error {
- req := rtmpServerAPIConnsKickReq{
- uuid: uuid,
- res: make(chan rtmpServerAPIConnsKickRes),
- }
-
- select {
- case s.chAPIConnsKick <- req:
- res := <-req.res
- return res.err
-
- case <-s.ctx.Done():
- return fmt.Errorf("terminated")
- }
-}
diff --git a/internal/core/rtsp_server.go b/internal/core/rtsp_server.go
deleted file mode 100644
index 96ba9444..00000000
--- a/internal/core/rtsp_server.go
+++ /dev/null
@@ -1,454 +0,0 @@
-package core
-
-import (
- "context"
- "crypto/tls"
- "fmt"
- "sort"
- "strings"
- "sync"
- "time"
-
- "github.com/bluenviron/gortsplib/v4"
- "github.com/bluenviron/gortsplib/v4/pkg/base"
- "github.com/bluenviron/gortsplib/v4/pkg/headers"
- "github.com/bluenviron/gortsplib/v4/pkg/liberrors"
- "github.com/google/uuid"
-
- "github.com/bluenviron/mediamtx/internal/conf"
- "github.com/bluenviron/mediamtx/internal/defs"
- "github.com/bluenviron/mediamtx/internal/externalcmd"
- "github.com/bluenviron/mediamtx/internal/logger"
-)
-
-type rtspServerParent interface {
- logger.Writer
-}
-
-func printAddresses(srv *gortsplib.Server) string {
- var ret []string
-
- ret = append(ret, fmt.Sprintf("%s (TCP)", srv.RTSPAddress))
-
- if srv.UDPRTPAddress != "" {
- ret = append(ret, fmt.Sprintf("%s (UDP/RTP)", srv.UDPRTPAddress))
- }
-
- if srv.UDPRTCPAddress != "" {
- ret = append(ret, fmt.Sprintf("%s (UDP/RTCP)", srv.UDPRTCPAddress))
- }
-
- return strings.Join(ret, ", ")
-}
-
-type rtspServer struct {
- authMethods []headers.AuthMethod
- readTimeout conf.StringDuration
- isTLS bool
- rtspAddress string
- protocols map[conf.Protocol]struct{}
- runOnConnect string
- runOnConnectRestart bool
- runOnDisconnect string
- externalCmdPool *externalcmd.Pool
- pathManager *pathManager
- parent rtspServerParent
-
- ctx context.Context
- ctxCancel func()
- wg sync.WaitGroup
- srv *gortsplib.Server
- mutex sync.RWMutex
- conns map[*gortsplib.ServerConn]*rtspConn
- sessions map[*gortsplib.ServerSession]*rtspSession
-}
-
-func newRTSPServer(
- address string,
- authMethods []headers.AuthMethod,
- readTimeout conf.StringDuration,
- writeTimeout conf.StringDuration,
- writeQueueSize int,
- useUDP bool,
- useMulticast bool,
- rtpAddress string,
- rtcpAddress string,
- multicastIPRange string,
- multicastRTPPort int,
- multicastRTCPPort int,
- isTLS bool,
- serverCert string,
- serverKey string,
- rtspAddress string,
- protocols map[conf.Protocol]struct{},
- runOnConnect string,
- runOnConnectRestart bool,
- runOnDisconnect string,
- externalCmdPool *externalcmd.Pool,
- pathManager *pathManager,
- parent rtspServerParent,
-) (*rtspServer, error) {
- ctx, ctxCancel := context.WithCancel(context.Background())
-
- s := &rtspServer{
- authMethods: authMethods,
- readTimeout: readTimeout,
- isTLS: isTLS,
- rtspAddress: rtspAddress,
- protocols: protocols,
- runOnConnect: runOnConnect,
- runOnConnectRestart: runOnConnectRestart,
- runOnDisconnect: runOnDisconnect,
- externalCmdPool: externalCmdPool,
- pathManager: pathManager,
- parent: parent,
- ctx: ctx,
- ctxCancel: ctxCancel,
- conns: make(map[*gortsplib.ServerConn]*rtspConn),
- sessions: make(map[*gortsplib.ServerSession]*rtspSession),
- }
-
- s.srv = &gortsplib.Server{
- Handler: s,
- ReadTimeout: time.Duration(readTimeout),
- WriteTimeout: time.Duration(writeTimeout),
- WriteQueueSize: writeQueueSize,
- RTSPAddress: address,
- }
-
- if useUDP {
- s.srv.UDPRTPAddress = rtpAddress
- s.srv.UDPRTCPAddress = rtcpAddress
- }
-
- if useMulticast {
- s.srv.MulticastIPRange = multicastIPRange
- s.srv.MulticastRTPPort = multicastRTPPort
- s.srv.MulticastRTCPPort = multicastRTCPPort
- }
-
- if isTLS {
- cert, err := tls.LoadX509KeyPair(serverCert, serverKey)
- if err != nil {
- return nil, err
- }
-
- s.srv.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
- }
-
- err := s.srv.Start()
- if err != nil {
- return nil, err
- }
-
- s.Log(logger.Info, "listener opened on %s", printAddresses(s.srv))
-
- s.wg.Add(1)
- go s.run()
-
- return s, nil
-}
-
-func (s *rtspServer) Log(level logger.Level, format string, args ...interface{}) {
- label := func() string {
- if s.isTLS {
- return "RTSPS"
- }
- return "RTSP"
- }()
- s.parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...)
-}
-
-func (s *rtspServer) getISTLS() bool {
- return s.isTLS
-}
-
-func (s *rtspServer) getServer() *gortsplib.Server {
- return s.srv
-}
-
-func (s *rtspServer) close() {
- s.Log(logger.Info, "listener is closing")
- s.ctxCancel()
- s.wg.Wait()
-}
-
-func (s *rtspServer) run() {
- defer s.wg.Done()
-
- serverErr := make(chan error)
- go func() {
- serverErr <- s.srv.Wait()
- }()
-
-outer:
- select {
- case err := <-serverErr:
- s.Log(logger.Error, "%s", err)
- break outer
-
- case <-s.ctx.Done():
- s.srv.Close()
- <-serverErr
- break outer
- }
-
- s.ctxCancel()
-}
-
-// OnConnOpen implements gortsplib.ServerHandlerOnConnOpen.
-func (s *rtspServer) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
- c := newRTSPConn(
- s.isTLS,
- s.rtspAddress,
- s.authMethods,
- s.readTimeout,
- s.runOnConnect,
- s.runOnConnectRestart,
- s.runOnDisconnect,
- s.externalCmdPool,
- s.pathManager,
- ctx.Conn,
- s)
- s.mutex.Lock()
- s.conns[ctx.Conn] = c
- s.mutex.Unlock()
-
- ctx.Conn.SetUserData(c)
-}
-
-// OnConnClose implements gortsplib.ServerHandlerOnConnClose.
-func (s *rtspServer) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
- s.mutex.Lock()
- c := s.conns[ctx.Conn]
- delete(s.conns, ctx.Conn)
- s.mutex.Unlock()
- c.onClose(ctx.Error)
-}
-
-// OnRequest implements gortsplib.ServerHandlerOnRequest.
-func (s *rtspServer) OnRequest(sc *gortsplib.ServerConn, req *base.Request) {
- c := sc.UserData().(*rtspConn)
- c.onRequest(req)
-}
-
-// OnResponse implements gortsplib.ServerHandlerOnResponse.
-func (s *rtspServer) OnResponse(sc *gortsplib.ServerConn, res *base.Response) {
- c := sc.UserData().(*rtspConn)
- c.OnResponse(res)
-}
-
-// OnSessionOpen implements gortsplib.ServerHandlerOnSessionOpen.
-func (s *rtspServer) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
- se := newRTSPSession(
- s.isTLS,
- s.protocols,
- ctx.Session,
- ctx.Conn,
- s.externalCmdPool,
- s.pathManager,
- s)
- s.mutex.Lock()
- s.sessions[ctx.Session] = se
- s.mutex.Unlock()
- ctx.Session.SetUserData(se)
-}
-
-// OnSessionClose implements gortsplib.ServerHandlerOnSessionClose.
-func (s *rtspServer) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
- s.mutex.Lock()
- se := s.sessions[ctx.Session]
- delete(s.sessions, ctx.Session)
- s.mutex.Unlock()
-
- if se != nil {
- se.onClose(ctx.Error)
- }
-}
-
-// OnDescribe implements gortsplib.ServerHandlerOnDescribe.
-func (s *rtspServer) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
-) (*base.Response, *gortsplib.ServerStream, error) {
- c := ctx.Conn.UserData().(*rtspConn)
- return c.onDescribe(ctx)
-}
-
-// OnAnnounce implements gortsplib.ServerHandlerOnAnnounce.
-func (s *rtspServer) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
- c := ctx.Conn.UserData().(*rtspConn)
- se := ctx.Session.UserData().(*rtspSession)
- return se.onAnnounce(c, ctx)
-}
-
-// OnSetup implements gortsplib.ServerHandlerOnSetup.
-func (s *rtspServer) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
- c := ctx.Conn.UserData().(*rtspConn)
- se := ctx.Session.UserData().(*rtspSession)
- return se.onSetup(c, ctx)
-}
-
-// OnPlay implements gortsplib.ServerHandlerOnPlay.
-func (s *rtspServer) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
- se := ctx.Session.UserData().(*rtspSession)
- return se.onPlay(ctx)
-}
-
-// OnRecord implements gortsplib.ServerHandlerOnRecord.
-func (s *rtspServer) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
- se := ctx.Session.UserData().(*rtspSession)
- return se.onRecord(ctx)
-}
-
-// OnPause implements gortsplib.ServerHandlerOnPause.
-func (s *rtspServer) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
- se := ctx.Session.UserData().(*rtspSession)
- return se.onPause(ctx)
-}
-
-// OnPacketLost implements gortsplib.ServerHandlerOnDecodeError.
-func (s *rtspServer) OnPacketLost(ctx *gortsplib.ServerHandlerOnPacketLostCtx) {
- se := ctx.Session.UserData().(*rtspSession)
- se.onPacketLost(ctx)
-}
-
-// OnDecodeError implements gortsplib.ServerHandlerOnDecodeError.
-func (s *rtspServer) OnDecodeError(ctx *gortsplib.ServerHandlerOnDecodeErrorCtx) {
- se := ctx.Session.UserData().(*rtspSession)
- se.onDecodeError(ctx)
-}
-
-// OnDecodeError implements gortsplib.ServerHandlerOnStreamWriteError.
-func (s *rtspServer) OnStreamWriteError(ctx *gortsplib.ServerHandlerOnStreamWriteErrorCtx) {
- se := ctx.Session.UserData().(*rtspSession)
- se.onStreamWriteError(ctx)
-}
-
-func (s *rtspServer) findConnByUUID(uuid uuid.UUID) *rtspConn {
- for _, c := range s.conns {
- if c.uuid == uuid {
- return c
- }
- }
- return nil
-}
-
-func (s *rtspServer) findSessionByUUID(uuid uuid.UUID) (*gortsplib.ServerSession, *rtspSession) {
- for key, sx := range s.sessions {
- if sx.uuid == uuid {
- return key, sx
- }
- }
- return nil, nil
-}
-
-// apiConnsList is called by api and metrics.
-func (s *rtspServer) apiConnsList() (*defs.APIRTSPConnsList, error) {
- select {
- case <-s.ctx.Done():
- return nil, fmt.Errorf("terminated")
- default:
- }
-
- s.mutex.RLock()
- defer s.mutex.RUnlock()
-
- data := &defs.APIRTSPConnsList{
- Items: []*defs.APIRTSPConn{},
- }
-
- for _, c := range s.conns {
- data.Items = append(data.Items, c.apiItem())
- }
-
- sort.Slice(data.Items, func(i, j int) bool {
- return data.Items[i].Created.Before(data.Items[j].Created)
- })
-
- return data, nil
-}
-
-// apiConnsGet is called by api.
-func (s *rtspServer) apiConnsGet(uuid uuid.UUID) (*defs.APIRTSPConn, error) {
- select {
- case <-s.ctx.Done():
- return nil, fmt.Errorf("terminated")
- default:
- }
-
- s.mutex.RLock()
- defer s.mutex.RUnlock()
-
- conn := s.findConnByUUID(uuid)
- if conn == nil {
- return nil, fmt.Errorf("connection not found")
- }
-
- return conn.apiItem(), nil
-}
-
-// apiSessionsList is called by api and metrics.
-func (s *rtspServer) apiSessionsList() (*defs.APIRTSPSessionList, error) {
- select {
- case <-s.ctx.Done():
- return nil, fmt.Errorf("terminated")
- default:
- }
-
- s.mutex.RLock()
- defer s.mutex.RUnlock()
-
- data := &defs.APIRTSPSessionList{
- Items: []*defs.APIRTSPSession{},
- }
-
- for _, s := range s.sessions {
- data.Items = append(data.Items, s.apiItem())
- }
-
- sort.Slice(data.Items, func(i, j int) bool {
- return data.Items[i].Created.Before(data.Items[j].Created)
- })
-
- return data, nil
-}
-
-// apiSessionsGet is called by api.
-func (s *rtspServer) apiSessionsGet(uuid uuid.UUID) (*defs.APIRTSPSession, error) {
- select {
- case <-s.ctx.Done():
- return nil, fmt.Errorf("terminated")
- default:
- }
-
- s.mutex.RLock()
- defer s.mutex.RUnlock()
-
- _, sx := s.findSessionByUUID(uuid)
- if sx == nil {
- return nil, fmt.Errorf("session not found")
- }
-
- return sx.apiItem(), nil
-}
-
-// apiSessionsKick is called by api.
-func (s *rtspServer) apiSessionsKick(uuid uuid.UUID) error {
- select {
- case <-s.ctx.Done():
- return fmt.Errorf("terminated")
- default:
- }
-
- s.mutex.RLock()
- defer s.mutex.RUnlock()
-
- key, sx := s.findSessionByUUID(uuid)
- if sx == nil {
- return fmt.Errorf("session not found")
- }
-
- sx.close()
- delete(s.sessions, key)
- sx.onClose(liberrors.ErrServerTerminated{})
- return nil
-}
diff --git a/internal/core/srt_server.go b/internal/core/srt_server.go
deleted file mode 100644
index 96fa0721..00000000
--- a/internal/core/srt_server.go
+++ /dev/null
@@ -1,330 +0,0 @@
-package core
-
-import (
- "context"
- "fmt"
- "sort"
- "sync"
- "time"
-
- srt "github.com/datarhei/gosrt"
- "github.com/google/uuid"
-
- "github.com/bluenviron/mediamtx/internal/conf"
- "github.com/bluenviron/mediamtx/internal/defs"
- "github.com/bluenviron/mediamtx/internal/externalcmd"
- "github.com/bluenviron/mediamtx/internal/logger"
-)
-
-func srtMaxPayloadSize(u int) int {
- return ((u - 16) / 188) * 188 // 16 = SRT header, 188 = MPEG-TS packet
-}
-
-type srtNewConnReq struct {
- connReq srt.ConnRequest
- res chan *srtConn
-}
-
-type srtServerAPIConnsListRes struct {
- data *defs.APISRTConnList
- err error
-}
-
-type srtServerAPIConnsListReq struct {
- res chan srtServerAPIConnsListRes
-}
-
-type srtServerAPIConnsGetRes struct {
- data *defs.APISRTConn
- err error
-}
-
-type srtServerAPIConnsGetReq struct {
- uuid uuid.UUID
- res chan srtServerAPIConnsGetRes
-}
-
-type srtServerAPIConnsKickRes struct {
- err error
-}
-
-type srtServerAPIConnsKickReq struct {
- uuid uuid.UUID
- res chan srtServerAPIConnsKickRes
-}
-
-type srtServerParent interface {
- logger.Writer
-}
-
-type srtServer struct {
- rtspAddress string
- readTimeout conf.StringDuration
- writeTimeout conf.StringDuration
- writeQueueSize int
- udpMaxPayloadSize int
- runOnConnect string
- runOnConnectRestart bool
- runOnDisconnect string
- externalCmdPool *externalcmd.Pool
- pathManager *pathManager
- parent srtServerParent
-
- ctx context.Context
- ctxCancel func()
- wg sync.WaitGroup
- ln srt.Listener
- conns map[*srtConn]struct{}
-
- // in
- chNewConnRequest chan srtNewConnReq
- chAcceptErr chan error
- chCloseConn chan *srtConn
- chAPIConnsList chan srtServerAPIConnsListReq
- chAPIConnsGet chan srtServerAPIConnsGetReq
- chAPIConnsKick chan srtServerAPIConnsKickReq
-}
-
-func newSRTServer(
- address string,
- rtspAddress string,
- readTimeout conf.StringDuration,
- writeTimeout conf.StringDuration,
- writeQueueSize int,
- udpMaxPayloadSize int,
- runOnConnect string,
- runOnConnectRestart bool,
- runOnDisconnect string,
- externalCmdPool *externalcmd.Pool,
- pathManager *pathManager,
- parent srtServerParent,
-) (*srtServer, error) {
- conf := srt.DefaultConfig()
- conf.ConnectionTimeout = time.Duration(readTimeout)
- conf.PayloadSize = uint32(srtMaxPayloadSize(udpMaxPayloadSize))
-
- ln, err := srt.Listen("srt", address, conf)
- if err != nil {
- return nil, err
- }
-
- ctx, ctxCancel := context.WithCancel(context.Background())
-
- s := &srtServer{
- rtspAddress: rtspAddress,
- readTimeout: readTimeout,
- writeTimeout: writeTimeout,
- writeQueueSize: writeQueueSize,
- udpMaxPayloadSize: udpMaxPayloadSize,
- runOnConnect: runOnConnect,
- runOnConnectRestart: runOnConnectRestart,
- runOnDisconnect: runOnDisconnect,
- externalCmdPool: externalCmdPool,
- pathManager: pathManager,
- parent: parent,
- ctx: ctx,
- ctxCancel: ctxCancel,
- ln: ln,
- conns: make(map[*srtConn]struct{}),
- chNewConnRequest: make(chan srtNewConnReq),
- chAcceptErr: make(chan error),
- chCloseConn: make(chan *srtConn),
- chAPIConnsList: make(chan srtServerAPIConnsListReq),
- chAPIConnsGet: make(chan srtServerAPIConnsGetReq),
- chAPIConnsKick: make(chan srtServerAPIConnsKickReq),
- }
-
- s.Log(logger.Info, "listener opened on "+address+" (UDP)")
-
- newSRTListener(
- s.ln,
- &s.wg,
- s,
- )
-
- s.wg.Add(1)
- go s.run()
-
- return s, nil
-}
-
-// Log is the main logging function.
-func (s *srtServer) Log(level logger.Level, format string, args ...interface{}) {
- s.parent.Log(level, "[SRT] "+format, args...)
-}
-
-func (s *srtServer) close() {
- s.Log(logger.Info, "listener is closing")
- s.ctxCancel()
- s.wg.Wait()
-}
-
-func (s *srtServer) run() {
- defer s.wg.Done()
-
-outer:
- for {
- select {
- case err := <-s.chAcceptErr:
- s.Log(logger.Error, "%s", err)
- break outer
-
- case req := <-s.chNewConnRequest:
- c := newSRTConn(
- s.ctx,
- s.rtspAddress,
- s.readTimeout,
- s.writeTimeout,
- s.writeQueueSize,
- s.udpMaxPayloadSize,
- req.connReq,
- s.runOnConnect,
- s.runOnConnectRestart,
- s.runOnDisconnect,
- &s.wg,
- s.externalCmdPool,
- s.pathManager,
- s)
- s.conns[c] = struct{}{}
- req.res <- c
-
- case c := <-s.chCloseConn:
- delete(s.conns, c)
-
- case req := <-s.chAPIConnsList:
- data := &defs.APISRTConnList{
- Items: []*defs.APISRTConn{},
- }
-
- for c := range s.conns {
- data.Items = append(data.Items, c.apiItem())
- }
-
- sort.Slice(data.Items, func(i, j int) bool {
- return data.Items[i].Created.Before(data.Items[j].Created)
- })
-
- req.res <- srtServerAPIConnsListRes{data: data}
-
- case req := <-s.chAPIConnsGet:
- c := s.findConnByUUID(req.uuid)
- if c == nil {
- req.res <- srtServerAPIConnsGetRes{err: fmt.Errorf("connection not found")}
- continue
- }
-
- req.res <- srtServerAPIConnsGetRes{data: c.apiItem()}
-
- case req := <-s.chAPIConnsKick:
- c := s.findConnByUUID(req.uuid)
- if c == nil {
- req.res <- srtServerAPIConnsKickRes{err: fmt.Errorf("connection not found")}
- continue
- }
-
- delete(s.conns, c)
- c.close()
- req.res <- srtServerAPIConnsKickRes{}
-
- case <-s.ctx.Done():
- break outer
- }
- }
-
- s.ctxCancel()
-
- s.ln.Close()
-}
-
-func (s *srtServer) findConnByUUID(uuid uuid.UUID) *srtConn {
- for sx := range s.conns {
- if sx.uuid == uuid {
- return sx
- }
- }
- return nil
-}
-
-// newConnRequest is called by srtListener.
-func (s *srtServer) newConnRequest(connReq srt.ConnRequest) *srtConn {
- req := srtNewConnReq{
- connReq: connReq,
- res: make(chan *srtConn),
- }
-
- select {
- case s.chNewConnRequest <- req:
- c := <-req.res
-
- return c.new(req)
-
- case <-s.ctx.Done():
- return nil
- }
-}
-
-// acceptError is called by srtListener.
-func (s *srtServer) acceptError(err error) {
- select {
- case s.chAcceptErr <- err:
- case <-s.ctx.Done():
- }
-}
-
-// closeConn is called by srtConn.
-func (s *srtServer) closeConn(c *srtConn) {
- select {
- case s.chCloseConn <- c:
- case <-s.ctx.Done():
- }
-}
-
-// apiConnsList is called by api.
-func (s *srtServer) apiConnsList() (*defs.APISRTConnList, error) {
- req := srtServerAPIConnsListReq{
- res: make(chan srtServerAPIConnsListRes),
- }
-
- select {
- case s.chAPIConnsList <- req:
- res := <-req.res
- return res.data, res.err
-
- case <-s.ctx.Done():
- return nil, fmt.Errorf("terminated")
- }
-}
-
-// apiConnsGet is called by api.
-func (s *srtServer) apiConnsGet(uuid uuid.UUID) (*defs.APISRTConn, error) {
- req := srtServerAPIConnsGetReq{
- uuid: uuid,
- res: make(chan srtServerAPIConnsGetRes),
- }
-
- select {
- case s.chAPIConnsGet <- req:
- res := <-req.res
- return res.data, res.err
-
- case <-s.ctx.Done():
- return nil, fmt.Errorf("terminated")
- }
-}
-
-// apiConnsKick is called by api.
-func (s *srtServer) apiConnsKick(uuid uuid.UUID) error {
- req := srtServerAPIConnsKickReq{
- uuid: uuid,
- res: make(chan srtServerAPIConnsKickRes),
- }
-
- select {
- case s.chAPIConnsKick <- req:
- res := <-req.res
- return res.err
-
- case <-s.ctx.Done():
- return fmt.Errorf("terminated")
- }
-}
diff --git a/internal/core/static_source_handler.go b/internal/core/static_source_handler.go
index 34184cd8..1a329419 100644
--- a/internal/core/static_source_handler.go
+++ b/internal/core/static_source_handler.go
@@ -153,6 +153,7 @@ func (s *staticSourceHandler) stop(reason string) {
<-s.done
}
+// Log implements logger.Writer.
func (s *staticSourceHandler) Log(level logger.Level, format string, args ...interface{}) {
s.parent.Log(level, format, args...)
}
@@ -242,7 +243,7 @@ func (s *staticSourceHandler) SetReady(req defs.PathSourceStaticSetReadyReq) def
res := <-req.Res
if res.Err == nil {
- s.instance.Log(logger.Info, "ready: %s", mediaInfo(req.Desc.Medias))
+ s.instance.Log(logger.Info, "ready: %s", defs.MediasInfo(req.Desc.Medias))
}
return res
diff --git a/internal/core/webrtc_manager_test.go b/internal/core/webrtc_server_test.go
similarity index 100%
rename from internal/core/webrtc_manager_test.go
rename to internal/core/webrtc_server_test.go
diff --git a/internal/defs/auth.go b/internal/defs/auth.go
new file mode 100644
index 00000000..98266f1c
--- /dev/null
+++ b/internal/defs/auth.go
@@ -0,0 +1,23 @@
+package defs
+
+// AuthProtocol is a authentication protocol.
+type AuthProtocol string
+
+// authentication protocols.
+const (
+ AuthProtocolRTSP AuthProtocol = "rtsp"
+ AuthProtocolRTMP AuthProtocol = "rtmp"
+ AuthProtocolHLS AuthProtocol = "hls"
+ AuthProtocolWebRTC AuthProtocol = "webrtc"
+ AuthProtocolSRT AuthProtocol = "srt"
+)
+
+// ErrAuthentication is a authentication error.
+type ErrAuthentication struct {
+ Message string
+}
+
+// Error implements the error interface.
+func (e *ErrAuthentication) Error() string {
+ return "authentication failed: " + e.Message
+}
diff --git a/internal/defs/path.go b/internal/defs/path.go
index 8877ea4c..fe061005 100644
--- a/internal/defs/path.go
+++ b/internal/defs/path.go
@@ -1,25 +1,156 @@
package defs
import (
- "github.com/bluenviron/gortsplib/v4/pkg/description"
+ "fmt"
+ "net"
+ "github.com/bluenviron/gortsplib/v4/pkg/base"
+ "github.com/bluenviron/gortsplib/v4/pkg/description"
+ "github.com/google/uuid"
+
+ "github.com/bluenviron/mediamtx/internal/conf"
+ "github.com/bluenviron/mediamtx/internal/externalcmd"
"github.com/bluenviron/mediamtx/internal/stream"
)
-// PathSourceStaticSetReadyRes is a set ready response to a static source.
+// ErrPathNoOnePublishing is returned when no one is publishing.
+type ErrPathNoOnePublishing struct {
+ PathName string
+}
+
+// Error implements the error interface.
+func (e ErrPathNoOnePublishing) Error() string {
+ return fmt.Sprintf("no one is publishing to path '%s'", e.PathName)
+}
+
+// Path is a path.
+type Path interface {
+ Name() string
+ SafeConf() *conf.Path
+ ExternalCmdEnv() externalcmd.Environment
+ StartPublisher(req PathStartPublisherReq) PathStartPublisherRes
+ StopPublisher(req PathStopPublisherReq)
+ RemovePublisher(req PathRemovePublisherReq)
+ RemoveReader(req PathRemoveReaderReq)
+}
+
+// PathAccessRequest is an access request.
+type PathAccessRequest struct {
+ Name string
+ Query string
+ Publish bool
+ SkipAuth bool
+
+ // only if skipAuth = false
+ IP net.IP
+ User string
+ Pass string
+ Proto AuthProtocol
+ ID *uuid.UUID
+ RTSPRequest *base.Request
+ RTSPBaseURL *base.URL
+ RTSPNonce string
+}
+
+// PathGetConfForPathRes contains the response of GetConfForPath().
+type PathGetConfForPathRes struct {
+ Conf *conf.Path
+ Err error
+}
+
+// PathGetConfForPathReq contains arguments of GetConfForPath().
+type PathGetConfForPathReq struct {
+ AccessRequest PathAccessRequest
+ Res chan PathGetConfForPathRes
+}
+
+// PathDescribeRes contains the response of Describe().
+type PathDescribeRes struct {
+ Path Path
+ Stream *stream.Stream
+ Redirect string
+ Err error
+}
+
+// PathDescribeReq contains arguments of Describe().
+type PathDescribeReq struct {
+ AccessRequest PathAccessRequest
+ Res chan PathDescribeRes
+}
+
+// PathAddPublisherRes contains the response of AddPublisher().
+type PathAddPublisherRes struct {
+ Path Path
+ Err error
+}
+
+// PathAddPublisherReq contains arguments of AddPublisher().
+type PathAddPublisherReq struct {
+ Author Publisher
+ AccessRequest PathAccessRequest
+ Res chan PathAddPublisherRes
+}
+
+// PathRemovePublisherReq contains arguments of RemovePublisher().
+type PathRemovePublisherReq struct {
+ Author Publisher
+ Res chan struct{}
+}
+
+// PathStartPublisherRes contains the response of StartPublisher().
+type PathStartPublisherRes struct {
+ Stream *stream.Stream
+ Err error
+}
+
+// PathStartPublisherReq contains arguments of StartPublisher().
+type PathStartPublisherReq struct {
+ Author Publisher
+ Desc *description.Session
+ GenerateRTPPackets bool
+ Res chan PathStartPublisherRes
+}
+
+// PathStopPublisherReq contains arguments of StopPublisher().
+type PathStopPublisherReq struct {
+ Author Publisher
+ Res chan struct{}
+}
+
+// PathAddReaderRes contains the response of AddReader().
+type PathAddReaderRes struct {
+ Path Path
+ Stream *stream.Stream
+ Err error
+}
+
+// PathAddReaderReq contains arguments of AddReader().
+type PathAddReaderReq struct {
+ Author Reader
+ AccessRequest PathAccessRequest
+ Res chan PathAddReaderRes
+}
+
+// PathRemoveReaderReq contains arguments of RemoveReader().
+type PathRemoveReaderReq struct {
+ Author Reader
+ Res chan struct{}
+}
+
+// PathSourceStaticSetReadyRes contains the response of SetReadu().
type PathSourceStaticSetReadyRes struct {
Stream *stream.Stream
Err error
}
-// PathSourceStaticSetReadyReq is a set ready request from a static source.
+// PathSourceStaticSetReadyReq contains arguments of SetReady().
type PathSourceStaticSetReadyReq struct {
Desc *description.Session
GenerateRTPPackets bool
Res chan PathSourceStaticSetReadyRes
}
-// PathSourceStaticSetNotReadyReq is a set not ready request from a static source.
+// PathSourceStaticSetNotReadyReq contains arguments of SetNotReady().
type PathSourceStaticSetNotReadyReq struct {
Res chan struct{}
}
diff --git a/internal/defs/path_manager.go b/internal/defs/path_manager.go
new file mode 100644
index 00000000..b6e5c715
--- /dev/null
+++ b/internal/defs/path_manager.go
@@ -0,0 +1,9 @@
+package defs
+
+// PathManager is a path manager.
+type PathManager interface {
+ GetConfForPath(req PathGetConfForPathReq) PathGetConfForPathRes
+ Describe(req PathDescribeReq) PathDescribeRes
+ AddPublisher(req PathAddPublisherReq) PathAddPublisherRes
+ AddReader(req PathAddReaderReq) PathAddReaderRes
+}
diff --git a/internal/defs/publisher.go b/internal/defs/publisher.go
new file mode 100644
index 00000000..72ffc74d
--- /dev/null
+++ b/internal/defs/publisher.go
@@ -0,0 +1,7 @@
+package defs
+
+// Publisher is an entity that can publish a stream.
+type Publisher interface {
+ Source
+ Close()
+}
diff --git a/internal/defs/reader.go b/internal/defs/reader.go
new file mode 100644
index 00000000..c6f9f640
--- /dev/null
+++ b/internal/defs/reader.go
@@ -0,0 +1,7 @@
+package defs
+
+// Reader is an entity that can read a stream.
+type Reader interface {
+ Close()
+ APIReaderDescribe() APIPathSourceOrReader
+}
diff --git a/internal/core/source.go b/internal/defs/source.go
similarity index 63%
rename from internal/core/source.go
rename to internal/defs/source.go
index ca0a58f2..0a0bcab0 100644
--- a/internal/core/source.go
+++ b/internal/defs/source.go
@@ -1,4 +1,4 @@
-package core
+package defs
import (
"fmt"
@@ -6,18 +6,17 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/description"
- "github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/logger"
)
-// source is an entity that can provide a stream.
+// Source is an entity that can provide a stream.
// it can be:
// - publisher
// - staticSourceHandler
// - redirectSource
-type source interface {
+type Source interface {
logger.Writer
- APISourceDescribe() defs.APIPathSourceOrReader
+ APISourceDescribe() APIPathSourceOrReader
}
func mediaDescription(media *description.Media) string {
@@ -28,7 +27,8 @@ func mediaDescription(media *description.Media) string {
return strings.Join(ret, "/")
}
-func mediasDescription(medias []*description.Media) []string {
+// MediasDescription returns the description of medias.
+func MediasDescription(medias []*description.Media) []string {
ret := make([]string, len(medias))
for i, media := range medias {
ret[i] = mediaDescription(media)
@@ -36,7 +36,8 @@ func mediasDescription(medias []*description.Media) []string {
return ret
}
-func mediaInfo(medias []*description.Media) string {
+// MediasInfo returns the description of medias.
+func MediasInfo(medias []*description.Media) string {
return fmt.Sprintf("%d %s (%s)",
len(medias),
func() string {
@@ -45,5 +46,5 @@ func mediaInfo(medias []*description.Media) string {
}
return "tracks"
}(),
- strings.Join(mediasDescription(medias), ", "))
+ strings.Join(MediasDescription(medias), ", "))
}
diff --git a/internal/core/mpegts.go b/internal/protocols/mpegts/from_stream.go
similarity index 97%
rename from internal/core/mpegts.go
rename to internal/protocols/mpegts/from_stream.go
index ec533245..b1a91344 100644
--- a/internal/core/mpegts.go
+++ b/internal/protocols/mpegts/from_stream.go
@@ -1,4 +1,4 @@
-package core
+package mpegts
import (
"bufio"
@@ -10,10 +10,9 @@ import (
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
mcmpegts "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
- "github.com/datarhei/gosrt"
+ srt "github.com/datarhei/gosrt"
"github.com/bluenviron/mediamtx/internal/asyncwriter"
- "github.com/bluenviron/mediamtx/internal/protocols/mpegts"
"github.com/bluenviron/mediamtx/internal/stream"
"github.com/bluenviron/mediamtx/internal/unit"
)
@@ -22,7 +21,8 @@ func durationGoToMPEGTS(v time.Duration) int64 {
return int64(v.Seconds() * 90000)
}
-func mpegtsSetupWrite(
+// FromStream links a server stream to a MPEG-TS writer.
+func FromStream(
stream *stream.Stream,
writer *asyncwriter.Writer,
bw *bufio.Writer,
@@ -251,7 +251,7 @@ func mpegtsSetupWrite(
}
if len(tracks) == 0 {
- return mpegts.ErrNoTracks
+ return ErrNoTracks
}
w = mcmpegts.NewWriter(bw, tracks)
diff --git a/internal/record/agent.go b/internal/record/agent.go
index e4503a37..7ecde7db 100644
--- a/internal/record/agent.go
+++ b/internal/record/agent.go
@@ -54,7 +54,7 @@ func (w *Agent) Initialize() {
go w.run()
}
-// Log is the main logging function.
+// Log implements logger.Writer.
func (w *Agent) Log(level logger.Level, format string, args ...interface{}) {
w.Parent.Log(level, "[record] "+format, args...)
}
diff --git a/internal/record/cleaner.go b/internal/record/cleaner.go
index 2d606459..afcc18b4 100644
--- a/internal/record/cleaner.go
+++ b/internal/record/cleaner.go
@@ -71,7 +71,7 @@ func (c *Cleaner) Close() {
<-c.done
}
-// Log is the main logging function.
+// Log implements logger.Writer.
func (c *Cleaner) Log(level logger.Level, format string, args ...interface{}) {
c.Parent.Log(level, "[record cleaner]"+format, args...)
}
diff --git a/internal/core/hls.min.js b/internal/servers/hls/hls.min.js
similarity index 100%
rename from internal/core/hls.min.js
rename to internal/servers/hls/hls.min.js
diff --git a/internal/core/hls_http_server.go b/internal/servers/hls/http_server.go
similarity index 65%
rename from internal/core/hls_http_server.go
rename to internal/servers/hls/http_server.go
index cd8a3b8a..bfba4c45 100644
--- a/internal/core/hls_http_server.go
+++ b/internal/servers/hls/http_server.go
@@ -1,4 +1,4 @@
-package core
+package hls
import (
_ "embed"
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/bluenviron/mediamtx/internal/conf"
+ "github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/protocols/httpserv"
"github.com/bluenviron/mediamtx/internal/restrictnetwork"
@@ -21,84 +22,70 @@ const (
hlsPauseAfterAuthError = 2 * time.Second
)
-//go:embed hls_index.html
+//go:embed index.html
var hlsIndex []byte
//go:embed hls.min.js
var hlsMinJS []byte
-type hlsHTTPServerParent interface {
- logger.Writer
- handleRequest(req hlsMuxerHandleRequestReq)
-}
-
-type hlsHTTPServer struct {
- allowOrigin string
- pathManager *pathManager
- parent hlsHTTPServerParent
+type httpServer struct {
+ address string
+ encryption bool
+ serverKey string
+ serverCert string
+ allowOrigin string
+ trustedProxies conf.IPsOrCIDRs
+ readTimeout conf.StringDuration
+ pathManager defs.PathManager
+ parent *Server
inner *httpserv.WrappedServer
}
-func newHLSHTTPServer( //nolint:dupl
- address string,
- encryption bool,
- serverKey string,
- serverCert string,
- allowOrigin string,
- trustedProxies conf.IPsOrCIDRs,
- readTimeout conf.StringDuration,
- pathManager *pathManager,
- parent hlsHTTPServerParent,
-) (*hlsHTTPServer, error) {
- if encryption {
- if serverCert == "" {
- return nil, fmt.Errorf("server cert is missing")
+func (s *httpServer) initialize() error {
+ if s.encryption {
+ if s.serverCert == "" {
+ return fmt.Errorf("server cert is missing")
}
} else {
- serverKey = ""
- serverCert = ""
- }
-
- s := &hlsHTTPServer{
- allowOrigin: allowOrigin,
- pathManager: pathManager,
- parent: parent,
+ s.serverKey = ""
+ s.serverCert = ""
}
router := gin.New()
- router.SetTrustedProxies(trustedProxies.ToTrustedProxies()) //nolint:errcheck
+ router.SetTrustedProxies(s.trustedProxies.ToTrustedProxies()) //nolint:errcheck
router.NoRoute(s.onRequest)
- network, address := restrictnetwork.Restrict("tcp", address)
+ network, address := restrictnetwork.Restrict("tcp", s.address)
var err error
s.inner, err = httpserv.NewWrappedServer(
network,
address,
- time.Duration(readTimeout),
- serverCert,
- serverKey,
+ time.Duration(s.readTimeout),
+ s.serverCert,
+ s.serverKey,
router,
s,
)
if err != nil {
- return nil, err
+ return err
}
- return s, nil
+ return nil
}
-func (s *hlsHTTPServer) Log(level logger.Level, format string, args ...interface{}) {
+// Log implements logger.Writer.
+func (s *httpServer) Log(level logger.Level, format string, args ...interface{}) {
s.parent.Log(level, format, args...)
}
-func (s *hlsHTTPServer) close() {
+func (s *httpServer) close() {
s.inner.Close()
}
-func (s *hlsHTTPServer) onRequest(ctx *gin.Context) {
+func (s *httpServer) onRequest(ctx *gin.Context) {
ctx.Writer.Header().Set("Access-Control-Allow-Origin", s.allowOrigin)
ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
@@ -159,19 +146,19 @@ func (s *hlsHTTPServer) onRequest(ctx *gin.Context) {
user, pass, hasCredentials := ctx.Request.BasicAuth()
- res := s.pathManager.getConfForPath(pathGetConfForPathReq{
- accessRequest: pathAccessRequest{
- name: dir,
- query: ctx.Request.URL.RawQuery,
- publish: false,
- ip: net.ParseIP(ctx.ClientIP()),
- user: user,
- pass: pass,
- proto: authProtocolHLS,
+ res := s.pathManager.GetConfForPath(defs.PathGetConfForPathReq{
+ AccessRequest: defs.PathAccessRequest{
+ Name: dir,
+ Query: ctx.Request.URL.RawQuery,
+ Publish: false,
+ IP: net.ParseIP(ctx.ClientIP()),
+ User: user,
+ Pass: pass,
+ Proto: defs.AuthProtocolHLS,
},
})
- if res.err != nil {
- if terr, ok := res.err.(*errAuthentication); ok {
+ if res.Err != nil {
+ if terr, ok := res.Err.(*defs.ErrAuthentication); ok {
if !hasCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
ctx.Writer.WriteHeader(http.StatusUnauthorized)
@@ -182,7 +169,7 @@ func (s *hlsHTTPServer) onRequest(ctx *gin.Context) {
_, port, _ := net.SplitHostPort(ctx.Request.RemoteAddr)
remoteAddr := net.JoinHostPort(ip, port)
- s.Log(logger.Info, "connection %v failed to authenticate: %v", remoteAddr, terr.message)
+ s.Log(logger.Info, "connection %v failed to authenticate: %v", remoteAddr, terr.Message)
// wait some seconds to stop brute force attacks
<-time.After(hlsPauseAfterAuthError)
@@ -203,7 +190,7 @@ func (s *hlsHTTPServer) onRequest(ctx *gin.Context) {
ctx.Writer.Write(hlsIndex)
default:
- s.parent.handleRequest(hlsMuxerHandleRequestReq{
+ s.parent.handleRequest(muxerHandleRequestReq{
path: dir,
file: fname,
ctx: ctx,
diff --git a/internal/core/hls_index.html b/internal/servers/hls/index.html
similarity index 100%
rename from internal/core/hls_index.html
rename to internal/servers/hls/index.html
diff --git a/internal/core/hls_muxer.go b/internal/servers/hls/muxer.go
similarity index 74%
rename from internal/core/hls_muxer.go
rename to internal/servers/hls/muxer.go
index f3a854ed..75f2620e 100644
--- a/internal/core/hls_muxer.go
+++ b/internal/servers/hls/muxer.go
@@ -1,4 +1,4 @@
-package core
+package hls
import (
"context"
@@ -26,15 +26,21 @@ import (
)
const (
- closeCheckPeriod = 1 * time.Second
- closeAfterInactivity = 60 * time.Second
- hlsMuxerRecreatePause = 10 * time.Second
+ closeCheckPeriod = 1 * time.Second
+ closeAfterInactivity = 60 * time.Second
+ muxerRecreatePause = 10 * time.Second
)
func int64Ptr(v int64) *int64 {
return &v
}
+func newEmptyTimer() *time.Timer {
+ t := time.NewTimer(0)
+ <-t.C
+ return t
+}
+
type responseWriterWithCounter struct {
http.ResponseWriter
bytesSent *uint64
@@ -46,19 +52,15 @@ func (w *responseWriterWithCounter) Write(p []byte) (int, error) {
return n, err
}
-type hlsMuxerHandleRequestReq struct {
+type muxerHandleRequestReq struct {
path string
file string
ctx *gin.Context
- res chan *hlsMuxer
+ res chan *muxer
}
-type hlsMuxerParent interface {
- logger.Writer
- closeMuxer(*hlsMuxer)
-}
-
-type hlsMuxer struct {
+type muxer struct {
+ parentCtx context.Context
remoteAddr string
externalAuthenticationURL string
variant conf.HLSVariant
@@ -70,90 +72,59 @@ type hlsMuxer struct {
writeQueueSize int
wg *sync.WaitGroup
pathName string
- pathManager *pathManager
- parent hlsMuxerParent
+ pathManager defs.PathManager
+ parent *Server
ctx context.Context
ctxCancel func()
created time.Time
- path *path
+ path defs.Path
writer *asyncwriter.Writer
lastRequestTime *int64
muxer *gohlslib.Muxer
- requests []*hlsMuxerHandleRequestReq
+ requests []*muxerHandleRequestReq
bytesSent *uint64
// in
- chRequest chan *hlsMuxerHandleRequestReq
+ chRequest chan *muxerHandleRequestReq
}
-func newHLSMuxer(
- parentCtx context.Context,
- remoteAddr string,
- externalAuthenticationURL string,
- variant conf.HLSVariant,
- segmentCount int,
- segmentDuration conf.StringDuration,
- partDuration conf.StringDuration,
- segmentMaxSize conf.StringSize,
- directory string,
- writeQueueSize int,
- wg *sync.WaitGroup,
- pathName string,
- pathManager *pathManager,
- parent hlsMuxerParent,
-) *hlsMuxer {
- ctx, ctxCancel := context.WithCancel(parentCtx)
+func (m *muxer) initialize() {
+ ctx, ctxCancel := context.WithCancel(m.parentCtx)
- m := &hlsMuxer{
- remoteAddr: remoteAddr,
- externalAuthenticationURL: externalAuthenticationURL,
- variant: variant,
- segmentCount: segmentCount,
- segmentDuration: segmentDuration,
- partDuration: partDuration,
- segmentMaxSize: segmentMaxSize,
- directory: directory,
- writeQueueSize: writeQueueSize,
- wg: wg,
- pathName: pathName,
- pathManager: pathManager,
- parent: parent,
- ctx: ctx,
- ctxCancel: ctxCancel,
- created: time.Now(),
- lastRequestTime: int64Ptr(time.Now().UnixNano()),
- bytesSent: new(uint64),
- chRequest: make(chan *hlsMuxerHandleRequestReq),
- }
+ m.ctx = ctx
+ m.ctxCancel = ctxCancel
+ m.created = time.Now()
+ m.lastRequestTime = int64Ptr(time.Now().UnixNano())
+ m.bytesSent = new(uint64)
+ m.chRequest = make(chan *muxerHandleRequestReq)
m.Log(logger.Info, "created %s", func() string {
- if remoteAddr == "" {
+ if m.remoteAddr == "" {
return "automatically"
}
- return "(requested by " + remoteAddr + ")"
+ return "(requested by " + m.remoteAddr + ")"
}())
m.wg.Add(1)
go m.run()
-
- return m
}
-func (m *hlsMuxer) close() {
+func (m *muxer) Close() {
m.ctxCancel()
}
-func (m *hlsMuxer) Log(level logger.Level, format string, args ...interface{}) {
+// Log implements logger.Writer.
+func (m *muxer) Log(level logger.Level, format string, args ...interface{}) {
m.parent.Log(level, "[muxer %s] "+format, append([]interface{}{m.pathName}, args...)...)
}
// PathName returns the path name.
-func (m *hlsMuxer) PathName() string {
+func (m *muxer) PathName() string {
return m.pathName
}
-func (m *hlsMuxer) run() {
+func (m *muxer) run() {
defer m.wg.Done()
err := func() error {
@@ -213,7 +184,7 @@ func (m *hlsMuxer) run() {
m.clearQueuedRequests()
isReady = false
isRecreating = true
- recreateTimer = time.NewTimer(hlsMuxerRecreatePause)
+ recreateTimer = time.NewTimer(muxerRecreatePause)
} else {
return err
}
@@ -234,41 +205,41 @@ func (m *hlsMuxer) run() {
m.Log(logger.Info, "destroyed: %v", err)
}
-func (m *hlsMuxer) clearQueuedRequests() {
+func (m *muxer) clearQueuedRequests() {
for _, req := range m.requests {
req.res <- nil
}
m.requests = nil
}
-func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{}) error {
- res := m.pathManager.addReader(pathAddReaderReq{
- author: m,
- accessRequest: pathAccessRequest{
- name: m.pathName,
- skipAuth: true,
+func (m *muxer) runInner(innerCtx context.Context, innerReady chan struct{}) error {
+ res := m.pathManager.AddReader(defs.PathAddReaderReq{
+ Author: m,
+ AccessRequest: defs.PathAccessRequest{
+ Name: m.pathName,
+ SkipAuth: true,
},
})
- if res.err != nil {
- return res.err
+ if res.Err != nil {
+ return res.Err
}
- m.path = res.path
+ m.path = res.Path
- defer m.path.removeReader(pathRemoveReaderReq{author: m})
+ defer m.path.RemoveReader(defs.PathRemoveReaderReq{Author: m})
m.writer = asyncwriter.New(m.writeQueueSize, m)
- defer res.stream.RemoveReader(m.writer)
+ defer res.Stream.RemoveReader(m.writer)
var medias []*description.Media
- videoMedia, videoTrack := m.createVideoTrack(res.stream)
+ videoMedia, videoTrack := m.createVideoTrack(res.Stream)
if videoMedia != nil {
medias = append(medias, videoMedia)
}
- audioMedia, audioTrack := m.createAudioTrack(res.stream)
+ audioMedia, audioTrack := m.createAudioTrack(res.Stream)
if audioMedia != nil {
medias = append(medias, audioMedia)
}
@@ -305,7 +276,7 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{})
innerReady <- struct{}{}
m.Log(logger.Info, "is converting into HLS, %s",
- mediaInfo(medias))
+ defs.MediasInfo(medias))
m.writer.Start()
@@ -333,7 +304,7 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{})
}
}
-func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) {
+func (m *muxer) createVideoTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) {
var videoFormatAV1 *format.AV1
videoMedia := stream.Desc().FindFormat(&videoFormatAV1)
@@ -444,7 +415,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*description.Media,
return nil, nil
}
-func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) {
+func (m *muxer) createAudioTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) {
var audioFormatOpus *format.Opus
audioMedia := stream.Desc().FindFormat(&audioFormatOpus)
@@ -507,7 +478,7 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*description.Media,
return nil, nil
}
-func (m *hlsMuxer) handleRequest(ctx *gin.Context) {
+func (m *muxer) handleRequest(ctx *gin.Context) {
atomic.StoreInt64(m.lastRequestTime, time.Now().UnixNano())
w := &responseWriterWithCounter{
@@ -519,7 +490,7 @@ func (m *hlsMuxer) handleRequest(ctx *gin.Context) {
}
// processRequest is called by hlsserver.Server (forwarded from ServeHTTP).
-func (m *hlsMuxer) processRequest(req *hlsMuxerHandleRequestReq) {
+func (m *muxer) processRequest(req *muxerHandleRequestReq) {
select {
case m.chRequest <- req:
case <-m.ctx.Done():
@@ -527,15 +498,15 @@ func (m *hlsMuxer) processRequest(req *hlsMuxerHandleRequestReq) {
}
}
-// apiReaderDescribe implements reader.
-func (m *hlsMuxer) apiReaderDescribe() defs.APIPathSourceOrReader {
+// APIReaderDescribe implements reader.
+func (m *muxer) APIReaderDescribe() defs.APIPathSourceOrReader {
return defs.APIPathSourceOrReader{
Type: "hlsMuxer",
ID: "",
}
}
-func (m *hlsMuxer) apiItem() *defs.APIHLSMuxer {
+func (m *muxer) apiItem() *defs.APIHLSMuxer {
return &defs.APIHLSMuxer{
Path: m.pathName,
Created: m.created,
diff --git a/internal/servers/hls/server.go b/internal/servers/hls/server.go
new file mode 100644
index 00000000..d76e309e
--- /dev/null
+++ b/internal/servers/hls/server.go
@@ -0,0 +1,290 @@
+// Package hls contains a HLS server.
+package hls
+
+import (
+ "context"
+ "fmt"
+ "sort"
+ "sync"
+
+ "github.com/bluenviron/mediamtx/internal/conf"
+ "github.com/bluenviron/mediamtx/internal/defs"
+ "github.com/bluenviron/mediamtx/internal/logger"
+)
+
+type serverAPIMuxersListRes struct {
+ data *defs.APIHLSMuxerList
+ err error
+}
+
+type serverAPIMuxersListReq struct {
+ res chan serverAPIMuxersListRes
+}
+
+type serverAPIMuxersGetRes struct {
+ data *defs.APIHLSMuxer
+ err error
+}
+
+type serverAPIMuxersGetReq struct {
+ name string
+ res chan serverAPIMuxersGetRes
+}
+
+type serverParent interface {
+ logger.Writer
+}
+
+// Server is a HLS server.
+type Server struct {
+ Address string
+ Encryption bool
+ ServerKey string
+ ServerCert string
+ ExternalAuthenticationURL string
+ AlwaysRemux bool
+ Variant conf.HLSVariant
+ SegmentCount int
+ SegmentDuration conf.StringDuration
+ PartDuration conf.StringDuration
+ SegmentMaxSize conf.StringSize
+ AllowOrigin string
+ TrustedProxies conf.IPsOrCIDRs
+ Directory string
+ ReadTimeout conf.StringDuration
+ WriteQueueSize int
+ PathManager defs.PathManager
+ Parent serverParent
+
+ ctx context.Context
+ ctxCancel func()
+ wg sync.WaitGroup
+ httpServer *httpServer
+ muxers map[string]*muxer
+
+ // in
+ chPathReady chan defs.Path
+ chPathNotReady chan defs.Path
+ chHandleRequest chan muxerHandleRequestReq
+ chCloseMuxer chan *muxer
+ chAPIMuxerList chan serverAPIMuxersListReq
+ chAPIMuxerGet chan serverAPIMuxersGetReq
+}
+
+// Initialize initializes the server.
+func (s *Server) Initialize() error {
+ ctx, ctxCancel := context.WithCancel(context.Background())
+
+ s.ctx = ctx
+ s.ctxCancel = ctxCancel
+ s.muxers = make(map[string]*muxer)
+ s.chPathReady = make(chan defs.Path)
+ s.chPathNotReady = make(chan defs.Path)
+ s.chHandleRequest = make(chan muxerHandleRequestReq)
+ s.chCloseMuxer = make(chan *muxer)
+ s.chAPIMuxerList = make(chan serverAPIMuxersListReq)
+ s.chAPIMuxerGet = make(chan serverAPIMuxersGetReq)
+
+ s.httpServer = &httpServer{
+ address: s.Address,
+ encryption: s.Encryption,
+ serverKey: s.ServerKey,
+ serverCert: s.ServerCert,
+ allowOrigin: s.AllowOrigin,
+ trustedProxies: s.TrustedProxies,
+ readTimeout: s.ReadTimeout,
+ pathManager: s.PathManager,
+ parent: s,
+ }
+ err := s.httpServer.initialize()
+ if err != nil {
+ ctxCancel()
+ return err
+ }
+
+ s.Log(logger.Info, "listener opened on "+s.Address)
+
+ s.wg.Add(1)
+ go s.run()
+
+ return nil
+}
+
+// Log implements logger.Writer.
+func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+ s.Parent.Log(level, "[HLS] "+format, args...)
+}
+
+// Close closes the server.
+func (s *Server) Close() {
+ s.Log(logger.Info, "listener is closing")
+ s.ctxCancel()
+ s.wg.Wait()
+}
+
+func (s *Server) run() {
+ defer s.wg.Done()
+
+outer:
+ for {
+ select {
+ case pa := <-s.chPathReady:
+ if s.AlwaysRemux && !pa.SafeConf().SourceOnDemand {
+ if _, ok := s.muxers[pa.Name()]; !ok {
+ s.createMuxer(pa.Name(), "")
+ }
+ }
+
+ case pa := <-s.chPathNotReady:
+ c, ok := s.muxers[pa.Name()]
+ if ok && c.remoteAddr == "" { // created with "always remux"
+ c.Close()
+ delete(s.muxers, pa.Name())
+ }
+
+ case req := <-s.chHandleRequest:
+ r, ok := s.muxers[req.path]
+ switch {
+ case ok:
+ r.processRequest(&req)
+
+ default:
+ r := s.createMuxer(req.path, req.ctx.ClientIP())
+ r.processRequest(&req)
+ }
+
+ case c := <-s.chCloseMuxer:
+ if c2, ok := s.muxers[c.PathName()]; !ok || c2 != c {
+ continue
+ }
+ delete(s.muxers, c.PathName())
+
+ case req := <-s.chAPIMuxerList:
+ data := &defs.APIHLSMuxerList{
+ Items: []*defs.APIHLSMuxer{},
+ }
+
+ for _, muxer := range s.muxers {
+ data.Items = append(data.Items, muxer.apiItem())
+ }
+
+ sort.Slice(data.Items, func(i, j int) bool {
+ return data.Items[i].Created.Before(data.Items[j].Created)
+ })
+
+ req.res <- serverAPIMuxersListRes{
+ data: data,
+ }
+
+ case req := <-s.chAPIMuxerGet:
+ muxer, ok := s.muxers[req.name]
+ if !ok {
+ req.res <- serverAPIMuxersGetRes{err: fmt.Errorf("muxer not found")}
+ continue
+ }
+
+ req.res <- serverAPIMuxersGetRes{data: muxer.apiItem()}
+
+ case <-s.ctx.Done():
+ break outer
+ }
+ }
+
+ s.ctxCancel()
+
+ s.httpServer.close()
+}
+
+func (s *Server) createMuxer(pathName string, remoteAddr string) *muxer {
+ r := &muxer{
+ parentCtx: s.ctx,
+ remoteAddr: remoteAddr,
+ externalAuthenticationURL: s.ExternalAuthenticationURL,
+ variant: s.Variant,
+ segmentCount: s.SegmentCount,
+ segmentDuration: s.SegmentDuration,
+ partDuration: s.PartDuration,
+ segmentMaxSize: s.SegmentMaxSize,
+ directory: s.Directory,
+ writeQueueSize: s.WriteQueueSize,
+ wg: &s.wg,
+ pathName: pathName,
+ pathManager: s.PathManager,
+ parent: s,
+ }
+ r.initialize()
+ s.muxers[pathName] = r
+ return r
+}
+
+// closeMuxer is called by muxer.
+func (s *Server) closeMuxer(c *muxer) {
+ select {
+ case s.chCloseMuxer <- c:
+ case <-s.ctx.Done():
+ }
+}
+
+func (s *Server) handleRequest(req muxerHandleRequestReq) {
+ req.res = make(chan *muxer)
+
+ select {
+ case s.chHandleRequest <- req:
+ muxer := <-req.res
+ if muxer != nil {
+ req.ctx.Request.URL.Path = req.file
+ muxer.handleRequest(req.ctx)
+ }
+
+ case <-s.ctx.Done():
+ }
+}
+
+// PathReady is called by pathManager.
+func (s *Server) PathReady(pa defs.Path) {
+ select {
+ case s.chPathReady <- pa:
+ case <-s.ctx.Done():
+ }
+}
+
+// PathNotReady is called by pathManager.
+func (s *Server) PathNotReady(pa defs.Path) {
+ select {
+ case s.chPathNotReady <- pa:
+ case <-s.ctx.Done():
+ }
+}
+
+// APIMuxersList is called by api.
+func (s *Server) APIMuxersList() (*defs.APIHLSMuxerList, error) {
+ req := serverAPIMuxersListReq{
+ res: make(chan serverAPIMuxersListRes),
+ }
+
+ select {
+ case s.chAPIMuxerList <- req:
+ res := <-req.res
+ return res.data, res.err
+
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ }
+}
+
+// APIMuxersGet is called by api.
+func (s *Server) APIMuxersGet(name string) (*defs.APIHLSMuxer, error) {
+ req := serverAPIMuxersGetReq{
+ name: name,
+ res: make(chan serverAPIMuxersGetRes),
+ }
+
+ select {
+ case s.chAPIMuxerGet <- req:
+ res := <-req.res
+ return res.data, res.err
+
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ }
+}
diff --git a/internal/core/rtmp_conn.go b/internal/servers/rtmp/conn.go
similarity index 74%
rename from internal/core/rtmp_conn.go
rename to internal/servers/rtmp/conn.go
index 60e6f347..a5eedfb4 100644
--- a/internal/core/rtmp_conn.go
+++ b/internal/servers/rtmp/conn.go
@@ -1,4 +1,4 @@
-package core
+package rtmp
import (
"context"
@@ -40,24 +40,15 @@ func pathNameAndQuery(inURL *url.URL) (string, url.Values, string) {
return pathName, ur.Query(), ur.RawQuery
}
-type rtmpConnState int
+type connState int
const (
- rtmpConnStateRead rtmpConnState = iota + 1
- rtmpConnStatePublish
+ connStateRead connState = iota + 1
+ connStatePublish
)
-type rtmpConnPathManager interface {
- addReader(req pathAddReaderReq) pathAddReaderRes
- addPublisher(req pathAddPublisherReq) pathAddPublisherRes
-}
-
-type rtmpConnParent interface {
- logger.Writer
- closeConn(*rtmpConn)
-}
-
-type rtmpConn struct {
+type conn struct {
+ parentCtx context.Context
isTLS bool
rtspAddress string
readTimeout conf.StringDuration
@@ -69,8 +60,8 @@ type rtmpConn struct {
wg *sync.WaitGroup
nconn net.Conn
externalCmdPool *externalcmd.Pool
- pathManager rtmpConnPathManager
- parent rtmpConnParent
+ pathManager defs.PathManager
+ parent *Server
ctx context.Context
ctxCancel func()
@@ -78,73 +69,40 @@ type rtmpConn struct {
created time.Time
mutex sync.RWMutex
rconn *rtmp.Conn
- state rtmpConnState
+ state connState
pathName string
}
-func newRTMPConn(
- parentCtx context.Context,
- isTLS bool,
- rtspAddress string,
- readTimeout conf.StringDuration,
- writeTimeout conf.StringDuration,
- writeQueueSize int,
- runOnConnect string,
- runOnConnectRestart bool,
- runOnDisconnect string,
- wg *sync.WaitGroup,
- nconn net.Conn,
- externalCmdPool *externalcmd.Pool,
- pathManager rtmpConnPathManager,
- parent rtmpConnParent,
-) *rtmpConn {
- ctx, ctxCancel := context.WithCancel(parentCtx)
+func (c *conn) initialize() {
+ c.ctx, c.ctxCancel = context.WithCancel(c.parentCtx)
- c := &rtmpConn{
- isTLS: isTLS,
- rtspAddress: rtspAddress,
- readTimeout: readTimeout,
- writeTimeout: writeTimeout,
- writeQueueSize: writeQueueSize,
- runOnConnect: runOnConnect,
- runOnConnectRestart: runOnConnectRestart,
- runOnDisconnect: runOnDisconnect,
- wg: wg,
- nconn: nconn,
- externalCmdPool: externalCmdPool,
- pathManager: pathManager,
- parent: parent,
- ctx: ctx,
- ctxCancel: ctxCancel,
- uuid: uuid.New(),
- created: time.Now(),
- }
+ c.uuid = uuid.New()
+ c.created = time.Now()
c.Log(logger.Info, "opened")
c.wg.Add(1)
go c.run()
-
- return c
}
-func (c *rtmpConn) close() {
+func (c *conn) Close() {
c.ctxCancel()
}
-func (c *rtmpConn) remoteAddr() net.Addr {
+func (c *conn) remoteAddr() net.Addr {
return c.nconn.RemoteAddr()
}
-func (c *rtmpConn) Log(level logger.Level, format string, args ...interface{}) {
+// Log implements logger.Writer.
+func (c *conn) Log(level logger.Level, format string, args ...interface{}) {
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.nconn.RemoteAddr()}, args...)...)
}
-func (c *rtmpConn) ip() net.IP {
+func (c *conn) ip() net.IP {
return c.nconn.RemoteAddr().(*net.TCPAddr).IP
}
-func (c *rtmpConn) run() { //nolint:dupl
+func (c *conn) run() { //nolint:dupl
defer c.wg.Done()
onDisconnectHook := hooks.OnConnect(hooks.OnConnectParams{
@@ -154,7 +112,7 @@ func (c *rtmpConn) run() { //nolint:dupl
RunOnConnectRestart: c.runOnConnectRestart,
RunOnDisconnect: c.runOnDisconnect,
RTSPAddress: c.rtspAddress,
- Desc: c.apiReaderDescribe(),
+ Desc: c.APIReaderDescribe(),
})
defer onDisconnectHook()
@@ -167,7 +125,7 @@ func (c *rtmpConn) run() { //nolint:dupl
c.Log(logger.Info, "closed: %v", err)
}
-func (c *rtmpConn) runInner() error {
+func (c *conn) runInner() error {
readerErr := make(chan error)
go func() {
readerErr <- c.runReader()
@@ -185,7 +143,7 @@ func (c *rtmpConn) runInner() error {
}
}
-func (c *rtmpConn) runReader() error {
+func (c *conn) runReader() error {
c.nconn.SetReadDeadline(time.Now().Add(time.Duration(c.readTimeout)))
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
conn, u, publish, err := rtmp.NewServerConn(c.nconn)
@@ -203,52 +161,52 @@ func (c *rtmpConn) runReader() error {
return c.runPublish(conn, u)
}
-func (c *rtmpConn) runRead(conn *rtmp.Conn, u *url.URL) error {
+func (c *conn) runRead(conn *rtmp.Conn, u *url.URL) error {
pathName, query, rawQuery := pathNameAndQuery(u)
- res := c.pathManager.addReader(pathAddReaderReq{
- author: c,
- accessRequest: pathAccessRequest{
- name: pathName,
- query: rawQuery,
- ip: c.ip(),
- user: query.Get("user"),
- pass: query.Get("pass"),
- proto: authProtocolRTMP,
- id: &c.uuid,
+ res := c.pathManager.AddReader(defs.PathAddReaderReq{
+ Author: c,
+ AccessRequest: defs.PathAccessRequest{
+ Name: pathName,
+ Query: rawQuery,
+ IP: c.ip(),
+ User: query.Get("user"),
+ Pass: query.Get("pass"),
+ Proto: defs.AuthProtocolRTMP,
+ ID: &c.uuid,
},
})
- if res.err != nil {
- if terr, ok := res.err.(*errAuthentication); ok {
+ if res.Err != nil {
+ if terr, ok := res.Err.(*defs.ErrAuthentication); ok {
// wait some seconds to stop brute force attacks
<-time.After(rtmpPauseAfterAuthError)
return terr
}
- return res.err
+ return res.Err
}
- defer res.path.removeReader(pathRemoveReaderReq{author: c})
+ defer res.Path.RemoveReader(defs.PathRemoveReaderReq{Author: c})
c.mutex.Lock()
- c.state = rtmpConnStateRead
+ c.state = connStateRead
c.pathName = pathName
c.mutex.Unlock()
writer := asyncwriter.New(c.writeQueueSize, c)
- defer res.stream.RemoveReader(writer)
+ defer res.Stream.RemoveReader(writer)
var w *rtmp.Writer
videoFormat := c.setupVideo(
&w,
- res.stream,
+ res.Stream,
writer)
audioFormat := c.setupAudio(
&w,
- res.stream,
+ res.Stream,
writer)
if videoFormat == nil && audioFormat == nil {
@@ -257,13 +215,13 @@ func (c *rtmpConn) runRead(conn *rtmp.Conn, u *url.URL) error {
}
c.Log(logger.Info, "is reading from path '%s', %s",
- res.path.name, readerMediaInfo(writer, res.stream))
+ res.Path.Name, defs.MediasInfo(res.Stream.MediasForReader(writer)))
onUnreadHook := hooks.OnRead(hooks.OnReadParams{
Logger: c,
ExternalCmdPool: c.externalCmdPool,
- Conf: res.path.safeConf(),
- ExternalCmdEnv: res.path.externalCmdEnv(),
+ Conf: res.Path.SafeConf(),
+ ExternalCmdEnv: res.Path.ExternalCmdEnv(),
Reader: c.APISourceDescribe(),
Query: rawQuery,
})
@@ -290,7 +248,7 @@ func (c *rtmpConn) runRead(conn *rtmp.Conn, u *url.URL) error {
}
}
-func (c *rtmpConn) setupVideo(
+func (c *conn) setupVideo(
w **rtmp.Writer,
stream *stream.Stream,
writer *asyncwriter.Writer,
@@ -359,7 +317,7 @@ func (c *rtmpConn) setupVideo(
return nil
}
-func (c *rtmpConn) setupAudio(
+func (c *conn) setupAudio(
w **rtmp.Writer,
stream *stream.Stream,
writer *asyncwriter.Writer,
@@ -432,36 +390,36 @@ func (c *rtmpConn) setupAudio(
return nil
}
-func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error {
+func (c *conn) runPublish(conn *rtmp.Conn, u *url.URL) error {
pathName, query, rawQuery := pathNameAndQuery(u)
- res := c.pathManager.addPublisher(pathAddPublisherReq{
- author: c,
- accessRequest: pathAccessRequest{
- name: pathName,
- query: rawQuery,
- publish: true,
- ip: c.ip(),
- user: query.Get("user"),
- pass: query.Get("pass"),
- proto: authProtocolRTMP,
- id: &c.uuid,
+ res := c.pathManager.AddPublisher(defs.PathAddPublisherReq{
+ Author: c,
+ AccessRequest: defs.PathAccessRequest{
+ Name: pathName,
+ Query: rawQuery,
+ Publish: true,
+ IP: c.ip(),
+ User: query.Get("user"),
+ Pass: query.Get("pass"),
+ Proto: defs.AuthProtocolRTMP,
+ ID: &c.uuid,
},
})
- if res.err != nil {
- if terr, ok := res.err.(*errAuthentication); ok {
+ if res.Err != nil {
+ if terr, ok := res.Err.(*defs.ErrAuthentication); ok {
// wait some seconds to stop brute force attacks
<-time.After(rtmpPauseAfterAuthError)
return terr
}
- return res.err
+ return res.Err
}
- defer res.path.removePublisher(pathRemovePublisherReq{author: c})
+ defer res.Path.RemovePublisher(defs.PathRemovePublisherReq{Author: c})
c.mutex.Lock()
- c.state = rtmpConnStatePublish
+ c.state = connStatePublish
c.pathName = pathName
c.mutex.Unlock()
@@ -566,16 +524,16 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error {
}
}
- rres := res.path.startPublisher(pathStartPublisherReq{
- author: c,
- desc: &description.Session{Medias: medias},
- generateRTPPackets: true,
+ rres := res.Path.StartPublisher(defs.PathStartPublisherReq{
+ Author: c,
+ Desc: &description.Session{Medias: medias},
+ GenerateRTPPackets: true,
})
- if rres.err != nil {
- return rres.err
+ if rres.Err != nil {
+ return rres.Err
}
- stream = rres.stream
+ stream = rres.Stream
// disable write deadline to allow outgoing acknowledges
c.nconn.SetWriteDeadline(time.Time{})
@@ -589,8 +547,8 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error {
}
}
-// apiReaderDescribe implements reader.
-func (c *rtmpConn) apiReaderDescribe() defs.APIPathSourceOrReader {
+// APIReaderDescribe implements reader.
+func (c *conn) APIReaderDescribe() defs.APIPathSourceOrReader {
return defs.APIPathSourceOrReader{
Type: func() string {
if c.isTLS {
@@ -603,11 +561,11 @@ func (c *rtmpConn) apiReaderDescribe() defs.APIPathSourceOrReader {
}
// APISourceDescribe implements source.
-func (c *rtmpConn) APISourceDescribe() defs.APIPathSourceOrReader {
- return c.apiReaderDescribe()
+func (c *conn) APISourceDescribe() defs.APIPathSourceOrReader {
+ return c.APIReaderDescribe()
}
-func (c *rtmpConn) apiItem() *defs.APIRTMPConn {
+func (c *conn) apiItem() *defs.APIRTMPConn {
c.mutex.RLock()
defer c.mutex.RUnlock()
@@ -625,10 +583,10 @@ func (c *rtmpConn) apiItem() *defs.APIRTMPConn {
RemoteAddr: c.remoteAddr().String(),
State: func() defs.APIRTMPConnState {
switch c.state {
- case rtmpConnStateRead:
+ case connStateRead:
return defs.APIRTMPConnStateRead
- case rtmpConnStatePublish:
+ case connStatePublish:
return defs.APIRTMPConnStatePublish
default:
diff --git a/internal/servers/rtmp/listener.go b/internal/servers/rtmp/listener.go
new file mode 100644
index 00000000..5bc49988
--- /dev/null
+++ b/internal/servers/rtmp/listener.go
@@ -0,0 +1,36 @@
+package rtmp
+
+import (
+ "net"
+ "sync"
+)
+
+type listener struct {
+ ln net.Listener
+ wg *sync.WaitGroup
+ parent *Server
+}
+
+func (l *listener) initialize() {
+ l.wg.Add(1)
+ go l.run()
+}
+
+func (l *listener) run() {
+ defer l.wg.Done()
+
+ err := l.runInner()
+
+ l.parent.acceptError(err)
+}
+
+func (l *listener) runInner() error {
+ for {
+ conn, err := l.ln.Accept()
+ if err != nil {
+ return err
+ }
+
+ l.parent.newConn(conn)
+ }
+}
diff --git a/internal/servers/rtmp/server.go b/internal/servers/rtmp/server.go
new file mode 100644
index 00000000..dce8f3cc
--- /dev/null
+++ b/internal/servers/rtmp/server.go
@@ -0,0 +1,308 @@
+// Package rtmp contains a RTMP server.
+package rtmp
+
+import (
+ "context"
+ "crypto/tls"
+ "fmt"
+ "net"
+ "sort"
+ "sync"
+
+ "github.com/google/uuid"
+
+ "github.com/bluenviron/mediamtx/internal/conf"
+ "github.com/bluenviron/mediamtx/internal/defs"
+ "github.com/bluenviron/mediamtx/internal/externalcmd"
+ "github.com/bluenviron/mediamtx/internal/logger"
+ "github.com/bluenviron/mediamtx/internal/restrictnetwork"
+)
+
+type serverAPIConnsListRes struct {
+ data *defs.APIRTMPConnList
+ err error
+}
+
+type serverAPIConnsListReq struct {
+ res chan serverAPIConnsListRes
+}
+
+type serverAPIConnsGetRes struct {
+ data *defs.APIRTMPConn
+ err error
+}
+
+type serverAPIConnsGetReq struct {
+ uuid uuid.UUID
+ res chan serverAPIConnsGetRes
+}
+
+type serverAPIConnsKickRes struct {
+ err error
+}
+
+type serverAPIConnsKickReq struct {
+ uuid uuid.UUID
+ res chan serverAPIConnsKickRes
+}
+
+type serverParent interface {
+ logger.Writer
+}
+
+// Server is a RTMP server.
+type Server struct {
+ Address string
+ ReadTimeout conf.StringDuration
+ WriteTimeout conf.StringDuration
+ WriteQueueSize int
+ IsTLS bool
+ ServerCert string
+ ServerKey string
+ RTSPAddress string
+ RunOnConnect string
+ RunOnConnectRestart bool
+ RunOnDisconnect string
+ ExternalCmdPool *externalcmd.Pool
+ PathManager defs.PathManager
+ Parent serverParent
+
+ ctx context.Context
+ ctxCancel func()
+ wg sync.WaitGroup
+ ln net.Listener
+ conns map[*conn]struct{}
+
+ // in
+ chNewConn chan net.Conn
+ chAcceptErr chan error
+ chCloseConn chan *conn
+ chAPIConnsList chan serverAPIConnsListReq
+ chAPIConnsGet chan serverAPIConnsGetReq
+ chAPIConnsKick chan serverAPIConnsKickReq
+}
+
+// Initialize initializes the server.
+func (s *Server) Initialize() error {
+ ln, err := func() (net.Listener, error) {
+ if !s.IsTLS {
+ return net.Listen(restrictnetwork.Restrict("tcp", s.Address))
+ }
+
+ cert, err := tls.LoadX509KeyPair(s.ServerCert, s.ServerKey)
+ if err != nil {
+ return nil, err
+ }
+
+ network, address := restrictnetwork.Restrict("tcp", s.Address)
+ return tls.Listen(network, address, &tls.Config{Certificates: []tls.Certificate{cert}})
+ }()
+ if err != nil {
+ return err
+ }
+
+ s.ctx, s.ctxCancel = context.WithCancel(context.Background())
+
+ s.ln = ln
+ s.conns = make(map[*conn]struct{})
+ s.chNewConn = make(chan net.Conn)
+ s.chAcceptErr = make(chan error)
+ s.chCloseConn = make(chan *conn)
+ s.chAPIConnsList = make(chan serverAPIConnsListReq)
+ s.chAPIConnsGet = make(chan serverAPIConnsGetReq)
+ s.chAPIConnsKick = make(chan serverAPIConnsKickReq)
+
+ s.Log(logger.Info, "listener opened on %s", s.Address)
+
+ l := &listener{
+ ln: s.ln,
+ wg: &s.wg,
+ parent: s,
+ }
+ l.initialize()
+
+ s.wg.Add(1)
+ go s.run()
+
+ return nil
+}
+
+// Log implements logger.Writer.
+func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+ label := func() string {
+ if s.IsTLS {
+ return "RTMPS"
+ }
+ return "RTMP"
+ }()
+ s.Parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...)
+}
+
+// Close closes the server.
+func (s *Server) Close() {
+ s.Log(logger.Info, "listener is closing")
+ s.ctxCancel()
+ s.wg.Wait()
+}
+
+func (s *Server) run() {
+ defer s.wg.Done()
+
+outer:
+ for {
+ select {
+ case err := <-s.chAcceptErr:
+ s.Log(logger.Error, "%s", err)
+ break outer
+
+ case nconn := <-s.chNewConn:
+ c := &conn{
+ parentCtx: s.ctx,
+ isTLS: s.IsTLS,
+ rtspAddress: s.RTSPAddress,
+ readTimeout: s.ReadTimeout,
+ writeTimeout: s.WriteTimeout,
+ writeQueueSize: s.WriteQueueSize,
+ runOnConnect: s.RunOnConnect,
+ runOnConnectRestart: s.RunOnConnectRestart,
+ runOnDisconnect: s.RunOnDisconnect,
+ wg: &s.wg,
+ nconn: nconn,
+ externalCmdPool: s.ExternalCmdPool,
+ pathManager: s.PathManager,
+ parent: s,
+ }
+ c.initialize()
+ s.conns[c] = struct{}{}
+
+ case c := <-s.chCloseConn:
+ delete(s.conns, c)
+
+ case req := <-s.chAPIConnsList:
+ data := &defs.APIRTMPConnList{
+ Items: []*defs.APIRTMPConn{},
+ }
+
+ for c := range s.conns {
+ data.Items = append(data.Items, c.apiItem())
+ }
+
+ sort.Slice(data.Items, func(i, j int) bool {
+ return data.Items[i].Created.Before(data.Items[j].Created)
+ })
+
+ req.res <- serverAPIConnsListRes{data: data}
+
+ case req := <-s.chAPIConnsGet:
+ c := s.findConnByUUID(req.uuid)
+ if c == nil {
+ req.res <- serverAPIConnsGetRes{err: fmt.Errorf("connection not found")}
+ continue
+ }
+
+ req.res <- serverAPIConnsGetRes{data: c.apiItem()}
+
+ case req := <-s.chAPIConnsKick:
+ c := s.findConnByUUID(req.uuid)
+ if c == nil {
+ req.res <- serverAPIConnsKickRes{err: fmt.Errorf("connection not found")}
+ continue
+ }
+
+ delete(s.conns, c)
+ c.Close()
+ req.res <- serverAPIConnsKickRes{}
+
+ case <-s.ctx.Done():
+ break outer
+ }
+ }
+
+ s.ctxCancel()
+
+ s.ln.Close()
+}
+
+func (s *Server) findConnByUUID(uuid uuid.UUID) *conn {
+ for c := range s.conns {
+ if c.uuid == uuid {
+ return c
+ }
+ }
+ return nil
+}
+
+// newConn is called by rtmpListener.
+func (s *Server) newConn(conn net.Conn) {
+ select {
+ case s.chNewConn <- conn:
+ case <-s.ctx.Done():
+ conn.Close()
+ }
+}
+
+// acceptError is called by rtmpListener.
+func (s *Server) acceptError(err error) {
+ select {
+ case s.chAcceptErr <- err:
+ case <-s.ctx.Done():
+ }
+}
+
+// closeConn is called by conn.
+func (s *Server) closeConn(c *conn) {
+ select {
+ case s.chCloseConn <- c:
+ case <-s.ctx.Done():
+ }
+}
+
+// APIConnsList is called by api.
+func (s *Server) APIConnsList() (*defs.APIRTMPConnList, error) {
+ req := serverAPIConnsListReq{
+ res: make(chan serverAPIConnsListRes),
+ }
+
+ select {
+ case s.chAPIConnsList <- req:
+ res := <-req.res
+ return res.data, res.err
+
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ }
+}
+
+// APIConnsGet is called by api.
+func (s *Server) APIConnsGet(uuid uuid.UUID) (*defs.APIRTMPConn, error) {
+ req := serverAPIConnsGetReq{
+ uuid: uuid,
+ res: make(chan serverAPIConnsGetRes),
+ }
+
+ select {
+ case s.chAPIConnsGet <- req:
+ res := <-req.res
+ return res.data, res.err
+
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ }
+}
+
+// APIConnsKick is called by api.
+func (s *Server) APIConnsKick(uuid uuid.UUID) error {
+ req := serverAPIConnsKickReq{
+ uuid: uuid,
+ res: make(chan serverAPIConnsKickRes),
+ }
+
+ select {
+ case s.chAPIConnsKick <- req:
+ res := <-req.res
+ return res.err
+
+ case <-s.ctx.Done():
+ return fmt.Errorf("terminated")
+ }
+}
diff --git a/internal/core/rtsp_conn.go b/internal/servers/rtsp/conn.go
similarity index 58%
rename from internal/core/rtsp_conn.go
rename to internal/servers/rtsp/conn.go
index 5ace139f..db28fdb4 100644
--- a/internal/core/rtsp_conn.go
+++ b/internal/servers/rtsp/conn.go
@@ -1,4 +1,4 @@
-package core
+package rtsp
import (
"fmt"
@@ -22,20 +22,19 @@ const (
rtspPauseAfterAuthError = 2 * time.Second
)
-type rtspConnParent interface {
- logger.Writer
- getISTLS() bool
- getServer() *gortsplib.Server
-}
-
-type rtspConn struct {
- isTLS bool
- rtspAddress string
- authMethods []headers.AuthMethod
- readTimeout conf.StringDuration
- pathManager *pathManager
- rconn *gortsplib.ServerConn
- parent rtspConnParent
+type conn struct {
+ isTLS bool
+ rtspAddress string
+ authMethods []headers.AuthMethod
+ readTimeout conf.StringDuration
+ runOnConnect string
+ runOnConnectRestart bool
+ runOnDisconnect string
+ externalCmdPool *externalcmd.Pool
+ pathManager defs.PathManager
+ rconn *gortsplib.ServerConn
+ rserver *gortsplib.Server
+ parent *Server
uuid uuid.UUID
created time.Time
@@ -44,92 +43,70 @@ type rtspConn struct {
authFailures int
}
-func newRTSPConn(
- isTLS bool,
- rtspAddress string,
- authMethods []headers.AuthMethod,
- readTimeout conf.StringDuration,
- runOnConnect string,
- runOnConnectRestart bool,
- runOnDisconnect string,
- externalCmdPool *externalcmd.Pool,
- pathManager *pathManager,
- conn *gortsplib.ServerConn,
- parent rtspConnParent,
-) *rtspConn {
- c := &rtspConn{
- isTLS: isTLS,
- rtspAddress: rtspAddress,
- authMethods: authMethods,
- readTimeout: readTimeout,
- pathManager: pathManager,
- rconn: conn,
- parent: parent,
- uuid: uuid.New(),
- created: time.Now(),
- }
+func (c *conn) initialize() {
+ c.uuid = uuid.New()
+ c.created = time.Now()
c.Log(logger.Info, "opened")
desc := defs.APIPathSourceOrReader{
Type: func() string {
- if isTLS {
+ if c.isTLS {
return "rtspsConn"
}
- return "rtspConn"
+ return "conn"
}(),
ID: c.uuid.String(),
}
c.onDisconnectHook = hooks.OnConnect(hooks.OnConnectParams{
Logger: c,
- ExternalCmdPool: externalCmdPool,
- RunOnConnect: runOnConnect,
- RunOnConnectRestart: runOnConnectRestart,
- RunOnDisconnect: runOnDisconnect,
- RTSPAddress: rtspAddress,
+ ExternalCmdPool: c.externalCmdPool,
+ RunOnConnect: c.runOnConnect,
+ RunOnConnectRestart: c.runOnConnectRestart,
+ RunOnDisconnect: c.runOnDisconnect,
+ RTSPAddress: c.rtspAddress,
Desc: desc,
})
-
- return c
}
-func (c *rtspConn) Log(level logger.Level, format string, args ...interface{}) {
+// Log implements logger.Writer.
+func (c *conn) Log(level logger.Level, format string, args ...interface{}) {
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.rconn.NetConn().RemoteAddr()}, args...)...)
}
// Conn returns the RTSP connection.
-func (c *rtspConn) Conn() *gortsplib.ServerConn {
+func (c *conn) Conn() *gortsplib.ServerConn {
return c.rconn
}
-func (c *rtspConn) remoteAddr() net.Addr {
+func (c *conn) remoteAddr() net.Addr {
return c.rconn.NetConn().RemoteAddr()
}
-func (c *rtspConn) ip() net.IP {
+func (c *conn) ip() net.IP {
return c.rconn.NetConn().RemoteAddr().(*net.TCPAddr).IP
}
// onClose is called by rtspServer.
-func (c *rtspConn) onClose(err error) {
+func (c *conn) onClose(err error) {
c.Log(logger.Info, "closed: %v", err)
c.onDisconnectHook()
}
// onRequest is called by rtspServer.
-func (c *rtspConn) onRequest(req *base.Request) {
+func (c *conn) onRequest(req *base.Request) {
c.Log(logger.Debug, "[c->s] %v", req)
}
// OnResponse is called by rtspServer.
-func (c *rtspConn) OnResponse(res *base.Response) {
+func (c *conn) OnResponse(res *base.Response) {
c.Log(logger.Debug, "[s->c] %v", res)
}
// onDescribe is called by rtspServer.
-func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
+func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
) (*base.Response, *gortsplib.ServerStream, error) {
if len(ctx.Path) == 0 || ctx.Path[0] != '/' {
return &base.Response{
@@ -148,50 +125,50 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
}
}
- res := c.pathManager.describe(pathDescribeReq{
- accessRequest: pathAccessRequest{
- name: ctx.Path,
- query: ctx.Query,
- ip: c.ip(),
- proto: authProtocolRTSP,
- id: &c.uuid,
- rtspRequest: ctx.Request,
- rtspNonce: c.authNonce,
+ res := c.pathManager.Describe(defs.PathDescribeReq{
+ AccessRequest: defs.PathAccessRequest{
+ Name: ctx.Path,
+ Query: ctx.Query,
+ IP: c.ip(),
+ Proto: defs.AuthProtocolRTSP,
+ ID: &c.uuid,
+ RTSPRequest: ctx.Request,
+ RTSPNonce: c.authNonce,
},
})
- if res.err != nil {
- switch terr := res.err.(type) {
- case *errAuthentication:
+ if res.Err != nil {
+ switch terr := res.Err.(type) {
+ case *defs.ErrAuthentication:
res, err := c.handleAuthError(terr)
return res, nil, err
- case errPathNoOnePublishing:
+ case defs.ErrPathNoOnePublishing:
return &base.Response{
StatusCode: base.StatusNotFound,
- }, nil, res.err
+ }, nil, res.Err
default:
return &base.Response{
StatusCode: base.StatusBadRequest,
- }, nil, res.err
+ }, nil, res.Err
}
}
- if res.redirect != "" {
+ if res.Redirect != "" {
return &base.Response{
StatusCode: base.StatusMovedPermanently,
Header: base.Header{
- "Location": base.HeaderValue{res.redirect},
+ "Location": base.HeaderValue{res.Redirect},
},
}, nil, nil
}
var stream *gortsplib.ServerStream
- if !c.parent.getISTLS() {
- stream = res.stream.RTSPStream(c.parent.getServer())
+ if !c.isTLS {
+ stream = res.Stream.RTSPStream(c.rserver)
} else {
- stream = res.stream.RTSPSStream(c.parent.getServer())
+ stream = res.Stream.RTSPSStream(c.rserver)
}
return &base.Response{
@@ -199,7 +176,7 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
}, stream, nil
}
-func (c *rtspConn) handleAuthError(authErr error) (*base.Response, error) {
+func (c *conn) handleAuthError(authErr error) (*base.Response, error) {
c.authFailures++
// VLC with login prompt sends 4 requests:
@@ -225,7 +202,7 @@ func (c *rtspConn) handleAuthError(authErr error) (*base.Response, error) {
}, authErr
}
-func (c *rtspConn) apiItem() *defs.APIRTSPConn {
+func (c *conn) apiItem() *defs.APIRTSPConn {
return &defs.APIRTSPConn{
ID: c.uuid,
Created: c.created,
diff --git a/internal/servers/rtsp/server.go b/internal/servers/rtsp/server.go
new file mode 100644
index 00000000..d22a8526
--- /dev/null
+++ b/internal/servers/rtsp/server.go
@@ -0,0 +1,430 @@
+// Package rtsp contains a RTSP server.
+package rtsp
+
+import (
+ "context"
+ "crypto/tls"
+ "fmt"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/bluenviron/gortsplib/v4"
+ "github.com/bluenviron/gortsplib/v4/pkg/base"
+ "github.com/bluenviron/gortsplib/v4/pkg/headers"
+ "github.com/bluenviron/gortsplib/v4/pkg/liberrors"
+ "github.com/google/uuid"
+
+ "github.com/bluenviron/mediamtx/internal/conf"
+ "github.com/bluenviron/mediamtx/internal/defs"
+ "github.com/bluenviron/mediamtx/internal/externalcmd"
+ "github.com/bluenviron/mediamtx/internal/logger"
+)
+
+func printAddresses(srv *gortsplib.Server) string {
+ var ret []string
+
+ ret = append(ret, fmt.Sprintf("%s (TCP)", srv.RTSPAddress))
+
+ if srv.UDPRTPAddress != "" {
+ ret = append(ret, fmt.Sprintf("%s (UDP/RTP)", srv.UDPRTPAddress))
+ }
+
+ if srv.UDPRTCPAddress != "" {
+ ret = append(ret, fmt.Sprintf("%s (UDP/RTCP)", srv.UDPRTCPAddress))
+ }
+
+ return strings.Join(ret, ", ")
+}
+
+type serverParent interface {
+ logger.Writer
+}
+
+// Server is a RTSP server.
+type Server struct {
+ Address string
+ AuthMethods []headers.AuthMethod
+ ReadTimeout conf.StringDuration
+ WriteTimeout conf.StringDuration
+ WriteQueueSize int
+ UseUDP bool
+ UseMulticast bool
+ RTPAddress string
+ RTCPAddress string
+ MulticastIPRange string
+ MulticastRTPPort int
+ MulticastRTCPPort int
+ IsTLS bool
+ ServerCert string
+ ServerKey string
+ RTSPAddress string
+ Protocols map[conf.Protocol]struct{}
+ RunOnConnect string
+ RunOnConnectRestart bool
+ RunOnDisconnect string
+ ExternalCmdPool *externalcmd.Pool
+ PathManager defs.PathManager
+ Parent serverParent
+
+ ctx context.Context
+ ctxCancel func()
+ wg sync.WaitGroup
+ srv *gortsplib.Server
+ mutex sync.RWMutex
+ conns map[*gortsplib.ServerConn]*conn
+ sessions map[*gortsplib.ServerSession]*session
+}
+
+// Initialize initializes the server.
+func (s *Server) Initialize() error {
+ s.ctx, s.ctxCancel = context.WithCancel(context.Background())
+
+ s.conns = make(map[*gortsplib.ServerConn]*conn)
+ s.sessions = make(map[*gortsplib.ServerSession]*session)
+
+ s.srv = &gortsplib.Server{
+ Handler: s,
+ ReadTimeout: time.Duration(s.ReadTimeout),
+ WriteTimeout: time.Duration(s.WriteTimeout),
+ WriteQueueSize: s.WriteQueueSize,
+ RTSPAddress: s.Address,
+ }
+
+ if s.UseUDP {
+ s.srv.UDPRTPAddress = s.RTPAddress
+ s.srv.UDPRTCPAddress = s.RTCPAddress
+ }
+
+ if s.UseMulticast {
+ s.srv.MulticastIPRange = s.MulticastIPRange
+ s.srv.MulticastRTPPort = s.MulticastRTPPort
+ s.srv.MulticastRTCPPort = s.MulticastRTCPPort
+ }
+
+ if s.IsTLS {
+ cert, err := tls.LoadX509KeyPair(s.ServerCert, s.ServerKey)
+ if err != nil {
+ return err
+ }
+
+ s.srv.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
+ }
+
+ err := s.srv.Start()
+ if err != nil {
+ return err
+ }
+
+ s.Log(logger.Info, "listener opened on %s", printAddresses(s.srv))
+
+ s.wg.Add(1)
+ go s.run()
+
+ return nil
+}
+
+// Log implements logger.Writer.
+func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+ label := func() string {
+ if s.IsTLS {
+ return "RTSPS"
+ }
+ return "RTSP"
+ }()
+ s.Parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...)
+}
+
+// Close closes the server.
+func (s *Server) Close() {
+ s.Log(logger.Info, "listener is closing")
+ s.ctxCancel()
+ s.wg.Wait()
+}
+
+func (s *Server) run() {
+ defer s.wg.Done()
+
+ serverErr := make(chan error)
+ go func() {
+ serverErr <- s.srv.Wait()
+ }()
+
+outer:
+ select {
+ case err := <-serverErr:
+ s.Log(logger.Error, "%s", err)
+ break outer
+
+ case <-s.ctx.Done():
+ s.srv.Close()
+ <-serverErr
+ break outer
+ }
+
+ s.ctxCancel()
+}
+
+// OnConnOpen implements gortsplib.ServerHandlerOnConnOpen.
+func (s *Server) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
+ c := &conn{
+ isTLS: s.IsTLS,
+ rtspAddress: s.RTSPAddress,
+ authMethods: s.AuthMethods,
+ readTimeout: s.ReadTimeout,
+ runOnConnect: s.RunOnConnect,
+ runOnConnectRestart: s.RunOnConnectRestart,
+ runOnDisconnect: s.RunOnDisconnect,
+ externalCmdPool: s.ExternalCmdPool,
+ pathManager: s.PathManager,
+ rconn: ctx.Conn,
+ rserver: s.srv,
+ parent: s,
+ }
+ c.initialize()
+ s.mutex.Lock()
+ s.conns[ctx.Conn] = c
+ s.mutex.Unlock()
+
+ ctx.Conn.SetUserData(c)
+}
+
+// OnConnClose implements gortsplib.ServerHandlerOnConnClose.
+func (s *Server) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
+ s.mutex.Lock()
+ c := s.conns[ctx.Conn]
+ delete(s.conns, ctx.Conn)
+ s.mutex.Unlock()
+ c.onClose(ctx.Error)
+}
+
+// OnRequest implements gortsplib.ServerHandlerOnRequest.
+func (s *Server) OnRequest(sc *gortsplib.ServerConn, req *base.Request) {
+ c := sc.UserData().(*conn)
+ c.onRequest(req)
+}
+
+// OnResponse implements gortsplib.ServerHandlerOnResponse.
+func (s *Server) OnResponse(sc *gortsplib.ServerConn, res *base.Response) {
+ c := sc.UserData().(*conn)
+ c.OnResponse(res)
+}
+
+// OnSessionOpen implements gortsplib.ServerHandlerOnSessionOpen.
+func (s *Server) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
+ se := &session{
+ isTLS: s.IsTLS,
+ protocols: s.Protocols,
+ rsession: ctx.Session,
+ rconn: ctx.Conn,
+ rserver: s.srv,
+ externalCmdPool: s.ExternalCmdPool,
+ pathManager: s.PathManager,
+ parent: s,
+ }
+ se.initialize()
+ s.mutex.Lock()
+ s.sessions[ctx.Session] = se
+ s.mutex.Unlock()
+ ctx.Session.SetUserData(se)
+}
+
+// OnSessionClose implements gortsplib.ServerHandlerOnSessionClose.
+func (s *Server) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
+ s.mutex.Lock()
+ se := s.sessions[ctx.Session]
+ delete(s.sessions, ctx.Session)
+ s.mutex.Unlock()
+
+ if se != nil {
+ se.onClose(ctx.Error)
+ }
+}
+
+// OnDescribe implements gortsplib.ServerHandlerOnDescribe.
+func (s *Server) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
+) (*base.Response, *gortsplib.ServerStream, error) {
+ c := ctx.Conn.UserData().(*conn)
+ return c.onDescribe(ctx)
+}
+
+// OnAnnounce implements gortsplib.ServerHandlerOnAnnounce.
+func (s *Server) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
+ c := ctx.Conn.UserData().(*conn)
+ se := ctx.Session.UserData().(*session)
+ return se.onAnnounce(c, ctx)
+}
+
+// OnSetup implements gortsplib.ServerHandlerOnSetup.
+func (s *Server) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
+ c := ctx.Conn.UserData().(*conn)
+ se := ctx.Session.UserData().(*session)
+ return se.onSetup(c, ctx)
+}
+
+// OnPlay implements gortsplib.ServerHandlerOnPlay.
+func (s *Server) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
+ se := ctx.Session.UserData().(*session)
+ return se.onPlay(ctx)
+}
+
+// OnRecord implements gortsplib.ServerHandlerOnRecord.
+func (s *Server) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
+ se := ctx.Session.UserData().(*session)
+ return se.onRecord(ctx)
+}
+
+// OnPause implements gortsplib.ServerHandlerOnPause.
+func (s *Server) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
+ se := ctx.Session.UserData().(*session)
+ return se.onPause(ctx)
+}
+
+// OnPacketLost implements gortsplib.ServerHandlerOnDecodeError.
+func (s *Server) OnPacketLost(ctx *gortsplib.ServerHandlerOnPacketLostCtx) {
+ se := ctx.Session.UserData().(*session)
+ se.onPacketLost(ctx)
+}
+
+// OnDecodeError implements gortsplib.ServerHandlerOnDecodeError.
+func (s *Server) OnDecodeError(ctx *gortsplib.ServerHandlerOnDecodeErrorCtx) {
+ se := ctx.Session.UserData().(*session)
+ se.onDecodeError(ctx)
+}
+
+// OnStreamWriteError implements gortsplib.ServerHandlerOnStreamWriteError.
+func (s *Server) OnStreamWriteError(ctx *gortsplib.ServerHandlerOnStreamWriteErrorCtx) {
+ se := ctx.Session.UserData().(*session)
+ se.onStreamWriteError(ctx)
+}
+
+func (s *Server) findConnByUUID(uuid uuid.UUID) *conn {
+ for _, c := range s.conns {
+ if c.uuid == uuid {
+ return c
+ }
+ }
+ return nil
+}
+
+func (s *Server) findSessionByUUID(uuid uuid.UUID) (*gortsplib.ServerSession, *session) {
+ for key, sx := range s.sessions {
+ if sx.uuid == uuid {
+ return key, sx
+ }
+ }
+ return nil, nil
+}
+
+// APIConnsList is called by api and metrics.
+func (s *Server) APIConnsList() (*defs.APIRTSPConnsList, error) {
+ select {
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ default:
+ }
+
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+
+ data := &defs.APIRTSPConnsList{
+ Items: []*defs.APIRTSPConn{},
+ }
+
+ for _, c := range s.conns {
+ data.Items = append(data.Items, c.apiItem())
+ }
+
+ sort.Slice(data.Items, func(i, j int) bool {
+ return data.Items[i].Created.Before(data.Items[j].Created)
+ })
+
+ return data, nil
+}
+
+// APIConnsGet is called by api.
+func (s *Server) APIConnsGet(uuid uuid.UUID) (*defs.APIRTSPConn, error) {
+ select {
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ default:
+ }
+
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+
+ conn := s.findConnByUUID(uuid)
+ if conn == nil {
+ return nil, fmt.Errorf("connection not found")
+ }
+
+ return conn.apiItem(), nil
+}
+
+// APISessionsList is called by api and metrics.
+func (s *Server) APISessionsList() (*defs.APIRTSPSessionList, error) {
+ select {
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ default:
+ }
+
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+
+ data := &defs.APIRTSPSessionList{
+ Items: []*defs.APIRTSPSession{},
+ }
+
+ for _, s := range s.sessions {
+ data.Items = append(data.Items, s.apiItem())
+ }
+
+ sort.Slice(data.Items, func(i, j int) bool {
+ return data.Items[i].Created.Before(data.Items[j].Created)
+ })
+
+ return data, nil
+}
+
+// APISessionsGet is called by api.
+func (s *Server) APISessionsGet(uuid uuid.UUID) (*defs.APIRTSPSession, error) {
+ select {
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ default:
+ }
+
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+
+ _, sx := s.findSessionByUUID(uuid)
+ if sx == nil {
+ return nil, fmt.Errorf("session not found")
+ }
+
+ return sx.apiItem(), nil
+}
+
+// APISessionsKick is called by api.
+func (s *Server) APISessionsKick(uuid uuid.UUID) error {
+ select {
+ case <-s.ctx.Done():
+ return fmt.Errorf("terminated")
+ default:
+ }
+
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+
+ key, sx := s.findSessionByUUID(uuid)
+ if sx == nil {
+ return fmt.Errorf("session not found")
+ }
+
+ sx.Close()
+ delete(s.sessions, key)
+ sx.onClose(liberrors.ErrServerTerminated{})
+ return nil
+}
diff --git a/internal/core/rtsp_session.go b/internal/servers/rtsp/session.go
similarity index 57%
rename from internal/core/rtsp_session.go
rename to internal/servers/rtsp/session.go
index 91c77fb7..2150ec8e 100644
--- a/internal/core/rtsp_session.go
+++ b/internal/servers/rtsp/session.go
@@ -1,4 +1,4 @@
-package core
+package rtsp
import (
"encoding/hex"
@@ -21,29 +21,19 @@ import (
"github.com/bluenviron/mediamtx/internal/stream"
)
-type rtspSessionPathManager interface {
- addPublisher(req pathAddPublisherReq) pathAddPublisherRes
- addReader(req pathAddReaderReq) pathAddReaderRes
-}
-
-type rtspSessionParent interface {
- logger.Writer
- getISTLS() bool
- getServer() *gortsplib.Server
-}
-
-type rtspSession struct {
+type session struct {
isTLS bool
protocols map[conf.Protocol]struct{}
- session *gortsplib.ServerSession
- author *gortsplib.ServerConn
+ rsession *gortsplib.ServerSession
+ rconn *gortsplib.ServerConn
+ rserver *gortsplib.Server
externalCmdPool *externalcmd.Pool
- pathManager rtspSessionPathManager
- parent rtspSessionParent
+ pathManager defs.PathManager
+ parent *Server
uuid uuid.UUID
created time.Time
- path *path
+ path defs.Path
stream *stream.Stream
onUnreadHook func()
mutex sync.Mutex
@@ -54,61 +44,43 @@ type rtspSession struct {
writeErrLogger logger.Writer
}
-func newRTSPSession(
- isTLS bool,
- protocols map[conf.Protocol]struct{},
- session *gortsplib.ServerSession,
- sc *gortsplib.ServerConn,
- externalCmdPool *externalcmd.Pool,
- pathManager rtspSessionPathManager,
- parent rtspSessionParent,
-) *rtspSession {
- s := &rtspSession{
- isTLS: isTLS,
- protocols: protocols,
- session: session,
- author: sc,
- externalCmdPool: externalCmdPool,
- pathManager: pathManager,
- parent: parent,
- uuid: uuid.New(),
- created: time.Now(),
- }
+func (s *session) initialize() {
+ s.uuid = uuid.New()
+ s.created = time.Now()
s.decodeErrLogger = logger.NewLimitedLogger(s)
s.writeErrLogger = logger.NewLimitedLogger(s)
- s.Log(logger.Info, "created by %v", s.author.NetConn().RemoteAddr())
-
- return s
+ s.Log(logger.Info, "created by %v", s.rconn.NetConn().RemoteAddr())
}
// Close closes a Session.
-func (s *rtspSession) close() {
- s.session.Close()
+func (s *session) Close() {
+ s.rsession.Close()
}
-func (s *rtspSession) remoteAddr() net.Addr {
- return s.author.NetConn().RemoteAddr()
+func (s *session) remoteAddr() net.Addr {
+ return s.rconn.NetConn().RemoteAddr()
}
-func (s *rtspSession) Log(level logger.Level, format string, args ...interface{}) {
+// Log implements logger.Writer.
+func (s *session) Log(level logger.Level, format string, args ...interface{}) {
id := hex.EncodeToString(s.uuid[:4])
s.parent.Log(level, "[session %s] "+format, append([]interface{}{id}, args...)...)
}
// onClose is called by rtspServer.
-func (s *rtspSession) onClose(err error) {
- if s.session.State() == gortsplib.ServerSessionStatePlay {
+func (s *session) onClose(err error) {
+ if s.rsession.State() == gortsplib.ServerSessionStatePlay {
s.onUnreadHook()
}
- switch s.session.State() {
+ switch s.rsession.State() {
case gortsplib.ServerSessionStatePrePlay, gortsplib.ServerSessionStatePlay:
- s.path.removeReader(pathRemoveReaderReq{author: s})
+ s.path.RemoveReader(defs.PathRemoveReaderReq{Author: s})
case gortsplib.ServerSessionStatePreRecord, gortsplib.ServerSessionStateRecord:
- s.path.removePublisher(pathRemovePublisherReq{author: s})
+ s.path.RemovePublisher(defs.PathRemovePublisherReq{Author: s})
}
s.path = nil
@@ -118,7 +90,7 @@ func (s *rtspSession) onClose(err error) {
}
// onAnnounce is called by rtspServer.
-func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
+func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
if len(ctx.Path) == 0 || ctx.Path[0] != '/' {
return &base.Response{
StatusCode: base.StatusBadRequest,
@@ -136,34 +108,34 @@ func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnno
}
}
- res := s.pathManager.addPublisher(pathAddPublisherReq{
- author: s,
- accessRequest: pathAccessRequest{
- name: ctx.Path,
- query: ctx.Query,
- publish: true,
- ip: c.ip(),
- proto: authProtocolRTSP,
- id: &c.uuid,
- rtspRequest: ctx.Request,
- rtspBaseURL: nil,
- rtspNonce: c.authNonce,
+ res := s.pathManager.AddPublisher(defs.PathAddPublisherReq{
+ Author: s,
+ AccessRequest: defs.PathAccessRequest{
+ Name: ctx.Path,
+ Query: ctx.Query,
+ Publish: true,
+ IP: c.ip(),
+ Proto: defs.AuthProtocolRTSP,
+ ID: &c.uuid,
+ RTSPRequest: ctx.Request,
+ RTSPBaseURL: nil,
+ RTSPNonce: c.authNonce,
},
})
- if res.err != nil {
- switch terr := res.err.(type) {
- case *errAuthentication:
+ if res.Err != nil {
+ switch terr := res.Err.(type) {
+ case *defs.ErrAuthentication:
return c.handleAuthError(terr)
default:
return &base.Response{
StatusCode: base.StatusBadRequest,
- }, res.err
+ }, res.Err
}
}
- s.path = res.path
+ s.path = res.Path
s.mutex.Lock()
s.state = gortsplib.ServerSessionStatePreRecord
@@ -176,7 +148,7 @@ func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnno
}
// onSetup is called by rtspServer.
-func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCtx,
+func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx,
) (*base.Response, *gortsplib.ServerStream, error) {
if len(ctx.Path) == 0 || ctx.Path[0] != '/' {
return &base.Response{
@@ -197,7 +169,7 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
}
}
- switch s.session.State() {
+ switch s.rsession.State() {
case gortsplib.ServerSessionStateInitial, gortsplib.ServerSessionStatePrePlay: // play
baseURL := &base.URL{
Scheme: ctx.Request.URL.Scheme,
@@ -222,40 +194,40 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
}
}
- res := s.pathManager.addReader(pathAddReaderReq{
- author: s,
- accessRequest: pathAccessRequest{
- name: ctx.Path,
- query: ctx.Query,
- ip: c.ip(),
- proto: authProtocolRTSP,
- id: &c.uuid,
- rtspRequest: ctx.Request,
- rtspBaseURL: baseURL,
- rtspNonce: c.authNonce,
+ res := s.pathManager.AddReader(defs.PathAddReaderReq{
+ Author: s,
+ AccessRequest: defs.PathAccessRequest{
+ Name: ctx.Path,
+ Query: ctx.Query,
+ IP: c.ip(),
+ Proto: defs.AuthProtocolRTSP,
+ ID: &c.uuid,
+ RTSPRequest: ctx.Request,
+ RTSPBaseURL: baseURL,
+ RTSPNonce: c.authNonce,
},
})
- if res.err != nil {
- switch terr := res.err.(type) {
- case *errAuthentication:
+ if res.Err != nil {
+ switch terr := res.Err.(type) {
+ case *defs.ErrAuthentication:
res, err := c.handleAuthError(terr)
return res, nil, err
- case errPathNoOnePublishing:
+ case defs.ErrPathNoOnePublishing:
return &base.Response{
StatusCode: base.StatusNotFound,
- }, nil, res.err
+ }, nil, res.Err
default:
return &base.Response{
StatusCode: base.StatusBadRequest,
- }, nil, res.err
+ }, nil, res.Err
}
}
- s.path = res.path
- s.stream = res.stream
+ s.path = res.Path
+ s.stream = res.Stream
s.mutex.Lock()
s.state = gortsplib.ServerSessionStatePrePlay
@@ -263,10 +235,10 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
s.mutex.Unlock()
var stream *gortsplib.ServerStream
- if !s.parent.getISTLS() {
- stream = res.stream.RTSPStream(s.parent.getServer())
+ if !s.isTLS {
+ stream = res.Stream.RTSPStream(s.rserver)
} else {
- stream = res.stream.RTSPSStream(s.parent.getServer())
+ stream = res.Stream.RTSPSStream(s.rserver)
}
return &base.Response{
@@ -281,27 +253,27 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
}
// onPlay is called by rtspServer.
-func (s *rtspSession) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
+func (s *session) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
h := make(base.Header)
- if s.session.State() == gortsplib.ServerSessionStatePrePlay {
+ if s.rsession.State() == gortsplib.ServerSessionStatePrePlay {
s.Log(logger.Info, "is reading from path '%s', with %s, %s",
- s.path.name,
- s.session.SetuppedTransport(),
- mediaInfo(s.session.SetuppedMedias()))
+ s.path.Name(),
+ s.rsession.SetuppedTransport(),
+ defs.MediasInfo(s.rsession.SetuppedMedias()))
s.onUnreadHook = hooks.OnRead(hooks.OnReadParams{
Logger: s,
ExternalCmdPool: s.externalCmdPool,
- Conf: s.path.safeConf(),
- ExternalCmdEnv: s.path.externalCmdEnv(),
- Reader: s.apiReaderDescribe(),
- Query: s.session.SetuppedQuery(),
+ Conf: s.path.SafeConf(),
+ ExternalCmdEnv: s.path.ExternalCmdEnv(),
+ Reader: s.APIReaderDescribe(),
+ Query: s.rsession.SetuppedQuery(),
})
s.mutex.Lock()
s.state = gortsplib.ServerSessionStatePlay
- s.transport = s.session.SetuppedTransport()
+ s.transport = s.rsession.SetuppedTransport()
s.mutex.Unlock()
}
@@ -312,39 +284,39 @@ func (s *rtspSession) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Respons
}
// onRecord is called by rtspServer.
-func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
- res := s.path.startPublisher(pathStartPublisherReq{
- author: s,
- desc: s.session.AnnouncedDescription(),
- generateRTPPackets: false,
+func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
+ res := s.path.StartPublisher(defs.PathStartPublisherReq{
+ Author: s,
+ Desc: s.rsession.AnnouncedDescription(),
+ GenerateRTPPackets: false,
})
- if res.err != nil {
+ if res.Err != nil {
return &base.Response{
StatusCode: base.StatusBadRequest,
- }, res.err
+ }, res.Err
}
- s.stream = res.stream
+ s.stream = res.Stream
- for _, medi := range s.session.AnnouncedDescription().Medias {
+ for _, medi := range s.rsession.AnnouncedDescription().Medias {
for _, forma := range medi.Formats {
cmedi := medi
cforma := forma
- s.session.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) {
- pts, ok := s.session.PacketPTS(cmedi, pkt)
+ s.rsession.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) {
+ pts, ok := s.rsession.PacketPTS(cmedi, pkt)
if !ok {
return
}
- res.stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now(), pts)
+ res.Stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now(), pts)
})
}
}
s.mutex.Lock()
s.state = gortsplib.ServerSessionStateRecord
- s.transport = s.session.SetuppedTransport()
+ s.transport = s.rsession.SetuppedTransport()
s.mutex.Unlock()
return &base.Response{
@@ -353,8 +325,8 @@ func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Res
}
// onPause is called by rtspServer.
-func (s *rtspSession) onPause(_ *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
- switch s.session.State() {
+func (s *session) onPause(_ *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
+ switch s.rsession.State() {
case gortsplib.ServerSessionStatePlay:
s.onUnreadHook()
@@ -363,7 +335,7 @@ func (s *rtspSession) onPause(_ *gortsplib.ServerHandlerOnPauseCtx) (*base.Respo
s.mutex.Unlock()
case gortsplib.ServerSessionStateRecord:
- s.path.stopPublisher(pathStopPublisherReq{author: s})
+ s.path.StopPublisher(defs.PathStopPublisherReq{Author: s})
s.mutex.Lock()
s.state = gortsplib.ServerSessionStatePreRecord
@@ -375,8 +347,8 @@ func (s *rtspSession) onPause(_ *gortsplib.ServerHandlerOnPauseCtx) (*base.Respo
}, nil
}
-// apiReaderDescribe implements reader.
-func (s *rtspSession) apiReaderDescribe() defs.APIPathSourceOrReader {
+// APIReaderDescribe implements reader.
+func (s *session) APIReaderDescribe() defs.APIPathSourceOrReader {
return defs.APIPathSourceOrReader{
Type: func() string {
if s.isTLS {
@@ -389,26 +361,26 @@ func (s *rtspSession) apiReaderDescribe() defs.APIPathSourceOrReader {
}
// APISourceDescribe implements source.
-func (s *rtspSession) APISourceDescribe() defs.APIPathSourceOrReader {
- return s.apiReaderDescribe()
+func (s *session) APISourceDescribe() defs.APIPathSourceOrReader {
+ return s.APIReaderDescribe()
}
// onPacketLost is called by rtspServer.
-func (s *rtspSession) onPacketLost(ctx *gortsplib.ServerHandlerOnPacketLostCtx) {
+func (s *session) onPacketLost(ctx *gortsplib.ServerHandlerOnPacketLostCtx) {
s.decodeErrLogger.Log(logger.Warn, ctx.Error.Error())
}
// onDecodeError is called by rtspServer.
-func (s *rtspSession) onDecodeError(ctx *gortsplib.ServerHandlerOnDecodeErrorCtx) {
+func (s *session) onDecodeError(ctx *gortsplib.ServerHandlerOnDecodeErrorCtx) {
s.decodeErrLogger.Log(logger.Warn, ctx.Error.Error())
}
// onStreamWriteError is called by rtspServer.
-func (s *rtspSession) onStreamWriteError(ctx *gortsplib.ServerHandlerOnStreamWriteErrorCtx) {
+func (s *session) onStreamWriteError(ctx *gortsplib.ServerHandlerOnStreamWriteErrorCtx) {
s.writeErrLogger.Log(logger.Warn, ctx.Error.Error())
}
-func (s *rtspSession) apiItem() *defs.APIRTSPSession {
+func (s *session) apiItem() *defs.APIRTSPSession {
s.mutex.Lock()
defer s.mutex.Unlock()
@@ -436,7 +408,7 @@ func (s *rtspSession) apiItem() *defs.APIRTSPSession {
v := s.transport.String()
return &v
}(),
- BytesReceived: s.session.BytesReceived(),
- BytesSent: s.session.BytesSent(),
+ BytesReceived: s.rsession.BytesReceived(),
+ BytesSent: s.rsession.BytesSent(),
}
}
diff --git a/internal/core/srt_conn.go b/internal/servers/srt/conn.go
similarity index 60%
rename from internal/core/srt_conn.go
rename to internal/servers/srt/conn.go
index 9315ae1a..63baf09d 100644
--- a/internal/core/srt_conn.go
+++ b/internal/servers/srt/conn.go
@@ -1,4 +1,4 @@
-package core
+package srt
import (
"bufio"
@@ -12,7 +12,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/description"
mcmpegts "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
- "github.com/datarhei/gosrt"
+ srt "github.com/datarhei/gosrt"
"github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/asyncwriter"
@@ -42,24 +42,15 @@ func srtCheckPassphrase(connReq srt.ConnRequest, passphrase string) error {
return nil
}
-type srtConnState int
+type connState int
const (
- srtConnStateRead srtConnState = iota + 1
- srtConnStatePublish
+ connStateRead connState = iota + 1
+ connStatePublish
)
-type srtConnPathManager interface {
- addReader(req pathAddReaderReq) pathAddReaderRes
- addPublisher(req pathAddPublisherReq) pathAddPublisherRes
-}
-
-type srtConnParent interface {
- logger.Writer
- closeConn(*srtConn)
-}
-
-type srtConn struct {
+type conn struct {
+ parentCtx context.Context
rtspAddress string
readTimeout conf.StringDuration
writeTimeout conf.StringDuration
@@ -71,15 +62,15 @@ type srtConn struct {
runOnDisconnect string
wg *sync.WaitGroup
externalCmdPool *externalcmd.Pool
- pathManager srtConnPathManager
- parent srtConnParent
+ pathManager defs.PathManager
+ parent *Server
ctx context.Context
ctxCancel func()
created time.Time
uuid uuid.UUID
mutex sync.RWMutex
- state srtConnState
+ state connState
pathName string
sconn srt.Conn
@@ -87,67 +78,34 @@ type srtConn struct {
chSetConn chan srt.Conn
}
-func newSRTConn(
- parentCtx context.Context,
- rtspAddress string,
- readTimeout conf.StringDuration,
- writeTimeout conf.StringDuration,
- writeQueueSize int,
- udpMaxPayloadSize int,
- connReq srt.ConnRequest,
- runOnConnect string,
- runOnConnectRestart bool,
- runOnDisconnect string,
- wg *sync.WaitGroup,
- externalCmdPool *externalcmd.Pool,
- pathManager srtConnPathManager,
- parent srtConnParent,
-) *srtConn {
- ctx, ctxCancel := context.WithCancel(parentCtx)
+func (c *conn) initialize() {
+ c.ctx, c.ctxCancel = context.WithCancel(c.parentCtx)
- c := &srtConn{
- rtspAddress: rtspAddress,
- readTimeout: readTimeout,
- writeTimeout: writeTimeout,
- writeQueueSize: writeQueueSize,
- udpMaxPayloadSize: udpMaxPayloadSize,
- connReq: connReq,
- runOnConnect: runOnConnect,
- runOnConnectRestart: runOnConnectRestart,
- runOnDisconnect: runOnDisconnect,
- wg: wg,
- externalCmdPool: externalCmdPool,
- pathManager: pathManager,
- parent: parent,
- ctx: ctx,
- ctxCancel: ctxCancel,
- created: time.Now(),
- uuid: uuid.New(),
- chNew: make(chan srtNewConnReq),
- chSetConn: make(chan srt.Conn),
- }
+ c.created = time.Now()
+ c.uuid = uuid.New()
+ c.chNew = make(chan srtNewConnReq)
+ c.chSetConn = make(chan srt.Conn)
c.Log(logger.Info, "opened")
c.wg.Add(1)
go c.run()
-
- return c
}
-func (c *srtConn) close() {
+func (c *conn) Close() {
c.ctxCancel()
}
-func (c *srtConn) Log(level logger.Level, format string, args ...interface{}) {
+// Log implements logger.Writer.
+func (c *conn) Log(level logger.Level, format string, args ...interface{}) {
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.connReq.RemoteAddr()}, args...)...)
}
-func (c *srtConn) ip() net.IP {
+func (c *conn) ip() net.IP {
return c.connReq.RemoteAddr().(*net.UDPAddr).IP
}
-func (c *srtConn) run() { //nolint:dupl
+func (c *conn) run() { //nolint:dupl
defer c.wg.Done()
onDisconnectHook := hooks.OnConnect(hooks.OnConnectParams{
@@ -157,7 +115,7 @@ func (c *srtConn) run() { //nolint:dupl
RunOnConnectRestart: c.runOnConnectRestart,
RunOnDisconnect: c.runOnDisconnect,
RTSPAddress: c.rtspAddress,
- Desc: c.apiReaderDescribe(),
+ Desc: c.APIReaderDescribe(),
})
defer onDisconnectHook()
@@ -170,7 +128,7 @@ func (c *srtConn) run() { //nolint:dupl
c.Log(logger.Info, "closed: %v", err)
}
-func (c *srtConn) runInner() error {
+func (c *conn) runInner() error {
var req srtNewConnReq
select {
case req = <-c.chNew:
@@ -187,7 +145,7 @@ func (c *srtConn) runInner() error {
return err
}
-func (c *srtConn) runInner2(req srtNewConnReq) (bool, error) {
+func (c *conn) runInner2(req srtNewConnReq) (bool, error) {
parts := strings.Split(req.connReq.StreamId(), ":")
if (len(parts) < 2 || len(parts) > 5) || (parts[0] != "read" && parts[0] != "publish") {
return false, fmt.Errorf("invalid streamid '%s':"+
@@ -220,34 +178,34 @@ func (c *srtConn) runInner2(req srtNewConnReq) (bool, error) {
return c.runRead(req, pathName, user, pass, query)
}
-func (c *srtConn) runPublish(req srtNewConnReq, pathName string, user string, pass string, query string) (bool, error) {
- res := c.pathManager.addPublisher(pathAddPublisherReq{
- author: c,
- accessRequest: pathAccessRequest{
- name: pathName,
- ip: c.ip(),
- publish: true,
- user: user,
- pass: pass,
- proto: authProtocolSRT,
- id: &c.uuid,
- query: query,
+func (c *conn) runPublish(req srtNewConnReq, pathName string, user string, pass string, query string) (bool, error) {
+ res := c.pathManager.AddPublisher(defs.PathAddPublisherReq{
+ Author: c,
+ AccessRequest: defs.PathAccessRequest{
+ Name: pathName,
+ IP: c.ip(),
+ Publish: true,
+ User: user,
+ Pass: pass,
+ Proto: defs.AuthProtocolSRT,
+ ID: &c.uuid,
+ Query: query,
},
})
- if res.err != nil {
- if terr, ok := res.err.(*errAuthentication); ok {
+ if res.Err != nil {
+ if terr, ok := res.Err.(*defs.ErrAuthentication); ok {
// TODO: re-enable. Currently this freezes the listener.
// wait some seconds to stop brute force attacks
// <-time.After(srtPauseAfterAuthError)
return false, terr
}
- return false, res.err
+ return false, res.Err
}
- defer res.path.removePublisher(pathRemovePublisherReq{author: c})
+ defer res.Path.RemovePublisher(defs.PathRemovePublisherReq{Author: c})
- err := srtCheckPassphrase(req.connReq, res.path.conf.SRTPublishPassphrase)
+ err := srtCheckPassphrase(req.connReq, res.Path.SafeConf().SRTPublishPassphrase)
if err != nil {
return false, err
}
@@ -258,14 +216,14 @@ func (c *srtConn) runPublish(req srtNewConnReq, pathName string, user string, pa
}
c.mutex.Lock()
- c.state = srtConnStatePublish
+ c.state = connStatePublish
c.pathName = pathName
c.sconn = sconn
c.mutex.Unlock()
readerErr := make(chan error)
go func() {
- readerErr <- c.runPublishReader(sconn, res.path)
+ readerErr <- c.runPublishReader(sconn, res.Path)
}()
select {
@@ -280,7 +238,7 @@ func (c *srtConn) runPublish(req srtNewConnReq, pathName string, user string, pa
}
}
-func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
+func (c *conn) runPublishReader(sconn srt.Conn, path defs.Path) error {
sconn.SetReadDeadline(time.Now().Add(time.Duration(c.readTimeout)))
r, err := mcmpegts.NewReader(mcmpegts.NewBufferedReader(sconn))
if err != nil {
@@ -300,16 +258,16 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
return err
}
- rres := path.startPublisher(pathStartPublisherReq{
- author: c,
- desc: &description.Session{Medias: medias},
- generateRTPPackets: true,
+ rres := path.StartPublisher(defs.PathStartPublisherReq{
+ Author: c,
+ Desc: &description.Session{Medias: medias},
+ GenerateRTPPackets: true,
})
- if rres.err != nil {
- return rres.err
+ if rres.Err != nil {
+ return rres.Err
}
- stream = rres.stream
+ stream = rres.Stream
for {
err := r.Read()
@@ -319,33 +277,33 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
}
}
-func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass string, query string) (bool, error) {
- res := c.pathManager.addReader(pathAddReaderReq{
- author: c,
- accessRequest: pathAccessRequest{
- name: pathName,
- ip: c.ip(),
- user: user,
- pass: pass,
- proto: authProtocolSRT,
- id: &c.uuid,
- query: query,
+func (c *conn) runRead(req srtNewConnReq, pathName string, user string, pass string, query string) (bool, error) {
+ res := c.pathManager.AddReader(defs.PathAddReaderReq{
+ Author: c,
+ AccessRequest: defs.PathAccessRequest{
+ Name: pathName,
+ IP: c.ip(),
+ User: user,
+ Pass: pass,
+ Proto: defs.AuthProtocolSRT,
+ ID: &c.uuid,
+ Query: query,
},
})
- if res.err != nil {
- if terr, ok := res.err.(*errAuthentication); ok {
+ if res.Err != nil {
+ if terr, ok := res.Err.(*defs.ErrAuthentication); ok {
// TODO: re-enable. Currently this freezes the listener.
// wait some seconds to stop brute force attacks
// <-time.After(srtPauseAfterAuthError)
return false, terr
}
- return false, res.err
+ return false, res.Err
}
- defer res.path.removeReader(pathRemoveReaderReq{author: c})
+ defer res.Path.RemoveReader(defs.PathRemoveReaderReq{Author: c})
- err := srtCheckPassphrase(req.connReq, res.path.conf.SRTReadPassphrase)
+ err := srtCheckPassphrase(req.connReq, res.Path.SafeConf().SRTReadPassphrase)
if err != nil {
return false, err
}
@@ -357,31 +315,31 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
defer sconn.Close()
c.mutex.Lock()
- c.state = srtConnStateRead
+ c.state = connStateRead
c.pathName = pathName
c.sconn = sconn
c.mutex.Unlock()
writer := asyncwriter.New(c.writeQueueSize, c)
- defer res.stream.RemoveReader(writer)
+ defer res.Stream.RemoveReader(writer)
bw := bufio.NewWriterSize(sconn, srtMaxPayloadSize(c.udpMaxPayloadSize))
- err = mpegtsSetupWrite(res.stream, writer, bw, sconn, time.Duration(c.writeTimeout))
+ err = mpegts.FromStream(res.Stream, writer, bw, sconn, time.Duration(c.writeTimeout))
if err != nil {
return true, err
}
c.Log(logger.Info, "is reading from path '%s', %s",
- res.path.name, readerMediaInfo(writer, res.stream))
+ res.Path.Name, defs.MediasInfo(res.Stream.MediasForReader(writer)))
onUnreadHook := hooks.OnRead(hooks.OnReadParams{
Logger: c,
ExternalCmdPool: c.externalCmdPool,
- Conf: res.path.safeConf(),
- ExternalCmdEnv: res.path.externalCmdEnv(),
- Reader: c.apiReaderDescribe(),
+ Conf: res.Path.SafeConf(),
+ ExternalCmdEnv: res.Path.ExternalCmdEnv(),
+ Reader: c.APIReaderDescribe(),
Query: query,
})
defer onUnreadHook()
@@ -401,7 +359,7 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
}
}
-func (c *srtConn) exchangeRequestWithConn(req srtNewConnReq) (srt.Conn, error) {
+func (c *conn) exchangeRequestWithConn(req srtNewConnReq) (srt.Conn, error) {
req.res <- c
select {
@@ -414,7 +372,7 @@ func (c *srtConn) exchangeRequestWithConn(req srtNewConnReq) (srt.Conn, error) {
}
// new is called by srtListener through srtServer.
-func (c *srtConn) new(req srtNewConnReq) *srtConn {
+func (c *conn) new(req srtNewConnReq) *conn {
select {
case c.chNew <- req:
return <-req.res
@@ -425,15 +383,15 @@ func (c *srtConn) new(req srtNewConnReq) *srtConn {
}
// setConn is called by srtListener .
-func (c *srtConn) setConn(sconn srt.Conn) {
+func (c *conn) setConn(sconn srt.Conn) {
select {
case c.chSetConn <- sconn:
case <-c.ctx.Done():
}
}
-// apiReaderDescribe implements reader.
-func (c *srtConn) apiReaderDescribe() defs.APIPathSourceOrReader {
+// APIReaderDescribe implements reader.
+func (c *conn) APIReaderDescribe() defs.APIPathSourceOrReader {
return defs.APIPathSourceOrReader{
Type: "srtConn",
ID: c.uuid.String(),
@@ -441,11 +399,11 @@ func (c *srtConn) apiReaderDescribe() defs.APIPathSourceOrReader {
}
// APISourceDescribe implements source.
-func (c *srtConn) APISourceDescribe() defs.APIPathSourceOrReader {
- return c.apiReaderDescribe()
+func (c *conn) APISourceDescribe() defs.APIPathSourceOrReader {
+ return c.APIReaderDescribe()
}
-func (c *srtConn) apiItem() *defs.APISRTConn {
+func (c *conn) apiItem() *defs.APISRTConn {
c.mutex.RLock()
defer c.mutex.RUnlock()
@@ -465,10 +423,10 @@ func (c *srtConn) apiItem() *defs.APISRTConn {
RemoteAddr: c.connReq.RemoteAddr().String(),
State: func() defs.APISRTConnState {
switch c.state {
- case srtConnStateRead:
+ case connStateRead:
return defs.APISRTConnStateRead
- case srtConnStatePublish:
+ case connStatePublish:
return defs.APISRTConnStatePublish
default:
diff --git a/internal/core/srt_listener.go b/internal/servers/srt/listener.go
similarity index 59%
rename from internal/core/srt_listener.go
rename to internal/servers/srt/listener.go
index d0a11821..38b9e297 100644
--- a/internal/core/srt_listener.go
+++ b/internal/servers/srt/listener.go
@@ -1,35 +1,23 @@
-package core
+package srt
import (
"sync"
- "github.com/datarhei/gosrt"
+ srt "github.com/datarhei/gosrt"
)
-type srtListener struct {
+type listener struct {
ln srt.Listener
wg *sync.WaitGroup
- parent *srtServer
+ parent *Server
}
-func newSRTListener(
- ln srt.Listener,
- wg *sync.WaitGroup,
- parent *srtServer,
-) *srtListener {
- l := &srtListener{
- ln: ln,
- wg: wg,
- parent: parent,
- }
-
+func (l *listener) initialize() {
l.wg.Add(1)
go l.run()
-
- return l
}
-func (l *srtListener) run() {
+func (l *listener) run() {
defer l.wg.Done()
err := l.runInner()
@@ -37,9 +25,9 @@ func (l *srtListener) run() {
l.parent.acceptError(err)
}
-func (l *srtListener) runInner() error {
+func (l *listener) runInner() error {
for {
- var sconn *srtConn
+ var sconn *conn
conn, _, err := l.ln.Accept(func(req srt.ConnRequest) srt.ConnType {
sconn = l.parent.newConnRequest(req)
if sconn == nil {
diff --git a/internal/servers/srt/server.go b/internal/servers/srt/server.go
new file mode 100644
index 00000000..046a26b9
--- /dev/null
+++ b/internal/servers/srt/server.go
@@ -0,0 +1,310 @@
+// Package srt contains a SRT server.
+package srt
+
+import (
+ "context"
+ "fmt"
+ "sort"
+ "sync"
+ "time"
+
+ srt "github.com/datarhei/gosrt"
+ "github.com/google/uuid"
+
+ "github.com/bluenviron/mediamtx/internal/conf"
+ "github.com/bluenviron/mediamtx/internal/defs"
+ "github.com/bluenviron/mediamtx/internal/externalcmd"
+ "github.com/bluenviron/mediamtx/internal/logger"
+)
+
+func srtMaxPayloadSize(u int) int {
+ return ((u - 16) / 188) * 188 // 16 = SRT header, 188 = MPEG-TS packet
+}
+
+type srtNewConnReq struct {
+ connReq srt.ConnRequest
+ res chan *conn
+}
+
+type serverAPIConnsListRes struct {
+ data *defs.APISRTConnList
+ err error
+}
+
+type serverAPIConnsListReq struct {
+ res chan serverAPIConnsListRes
+}
+
+type serverAPIConnsGetRes struct {
+ data *defs.APISRTConn
+ err error
+}
+
+type serverAPIConnsGetReq struct {
+ uuid uuid.UUID
+ res chan serverAPIConnsGetRes
+}
+
+type serverAPIConnsKickRes struct {
+ err error
+}
+
+type serverAPIConnsKickReq struct {
+ uuid uuid.UUID
+ res chan serverAPIConnsKickRes
+}
+
+type serverParent interface {
+ logger.Writer
+}
+
+// Server is a SRT server.
+type Server struct {
+ Address string
+ RTSPAddress string
+ ReadTimeout conf.StringDuration
+ WriteTimeout conf.StringDuration
+ WriteQueueSize int
+ UDPMaxPayloadSize int
+ RunOnConnect string
+ RunOnConnectRestart bool
+ RunOnDisconnect string
+ ExternalCmdPool *externalcmd.Pool
+ PathManager defs.PathManager
+ Parent serverParent
+
+ ctx context.Context
+ ctxCancel func()
+ wg sync.WaitGroup
+ ln srt.Listener
+ conns map[*conn]struct{}
+
+ // in
+ chNewConnRequest chan srtNewConnReq
+ chAcceptErr chan error
+ chCloseConn chan *conn
+ chAPIConnsList chan serverAPIConnsListReq
+ chAPIConnsGet chan serverAPIConnsGetReq
+ chAPIConnsKick chan serverAPIConnsKickReq
+}
+
+// Initialize initializes the server.
+func (s *Server) Initialize() error {
+ conf := srt.DefaultConfig()
+ conf.ConnectionTimeout = time.Duration(s.ReadTimeout)
+ conf.PayloadSize = uint32(srtMaxPayloadSize(s.UDPMaxPayloadSize))
+
+ var err error
+ s.ln, err = srt.Listen("srt", s.Address, conf)
+ if err != nil {
+ return err
+ }
+
+ s.ctx, s.ctxCancel = context.WithCancel(context.Background())
+
+ s.conns = make(map[*conn]struct{})
+ s.chNewConnRequest = make(chan srtNewConnReq)
+ s.chAcceptErr = make(chan error)
+ s.chCloseConn = make(chan *conn)
+ s.chAPIConnsList = make(chan serverAPIConnsListReq)
+ s.chAPIConnsGet = make(chan serverAPIConnsGetReq)
+ s.chAPIConnsKick = make(chan serverAPIConnsKickReq)
+
+ s.Log(logger.Info, "listener opened on "+s.Address+" (UDP)")
+
+ l := &listener{
+ ln: s.ln,
+ wg: &s.wg,
+ parent: s,
+ }
+ l.initialize()
+
+ s.wg.Add(1)
+ go s.run()
+
+ return nil
+}
+
+// Log implements logger.Writer.
+func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+ s.Parent.Log(level, "[SRT] "+format, args...)
+}
+
+// Close closes the server.
+func (s *Server) Close() {
+ s.Log(logger.Info, "listener is closing")
+ s.ctxCancel()
+ s.wg.Wait()
+}
+
+func (s *Server) run() {
+ defer s.wg.Done()
+
+outer:
+ for {
+ select {
+ case err := <-s.chAcceptErr:
+ s.Log(logger.Error, "%s", err)
+ break outer
+
+ case req := <-s.chNewConnRequest:
+ c := &conn{
+ parentCtx: s.ctx,
+ rtspAddress: s.RTSPAddress,
+ readTimeout: s.ReadTimeout,
+ writeTimeout: s.WriteTimeout,
+ writeQueueSize: s.WriteQueueSize,
+ udpMaxPayloadSize: s.UDPMaxPayloadSize,
+ connReq: req.connReq,
+ runOnConnect: s.RunOnConnect,
+ runOnConnectRestart: s.RunOnConnectRestart,
+ runOnDisconnect: s.RunOnDisconnect,
+ wg: &s.wg,
+ externalCmdPool: s.ExternalCmdPool,
+ pathManager: s.PathManager,
+ parent: s,
+ }
+ c.initialize()
+ s.conns[c] = struct{}{}
+ req.res <- c
+
+ case c := <-s.chCloseConn:
+ delete(s.conns, c)
+
+ case req := <-s.chAPIConnsList:
+ data := &defs.APISRTConnList{
+ Items: []*defs.APISRTConn{},
+ }
+
+ for c := range s.conns {
+ data.Items = append(data.Items, c.apiItem())
+ }
+
+ sort.Slice(data.Items, func(i, j int) bool {
+ return data.Items[i].Created.Before(data.Items[j].Created)
+ })
+
+ req.res <- serverAPIConnsListRes{data: data}
+
+ case req := <-s.chAPIConnsGet:
+ c := s.findConnByUUID(req.uuid)
+ if c == nil {
+ req.res <- serverAPIConnsGetRes{err: fmt.Errorf("connection not found")}
+ continue
+ }
+
+ req.res <- serverAPIConnsGetRes{data: c.apiItem()}
+
+ case req := <-s.chAPIConnsKick:
+ c := s.findConnByUUID(req.uuid)
+ if c == nil {
+ req.res <- serverAPIConnsKickRes{err: fmt.Errorf("connection not found")}
+ continue
+ }
+
+ delete(s.conns, c)
+ c.Close()
+ req.res <- serverAPIConnsKickRes{}
+
+ case <-s.ctx.Done():
+ break outer
+ }
+ }
+
+ s.ctxCancel()
+
+ s.ln.Close()
+}
+
+func (s *Server) findConnByUUID(uuid uuid.UUID) *conn {
+ for sx := range s.conns {
+ if sx.uuid == uuid {
+ return sx
+ }
+ }
+ return nil
+}
+
+// newConnRequest is called by srtListener.
+func (s *Server) newConnRequest(connReq srt.ConnRequest) *conn {
+ req := srtNewConnReq{
+ connReq: connReq,
+ res: make(chan *conn),
+ }
+
+ select {
+ case s.chNewConnRequest <- req:
+ c := <-req.res
+
+ return c.new(req)
+
+ case <-s.ctx.Done():
+ return nil
+ }
+}
+
+// acceptError is called by srtListener.
+func (s *Server) acceptError(err error) {
+ select {
+ case s.chAcceptErr <- err:
+ case <-s.ctx.Done():
+ }
+}
+
+// closeConn is called by conn.
+func (s *Server) closeConn(c *conn) {
+ select {
+ case s.chCloseConn <- c:
+ case <-s.ctx.Done():
+ }
+}
+
+// APIConnsList is called by api.
+func (s *Server) APIConnsList() (*defs.APISRTConnList, error) {
+ req := serverAPIConnsListReq{
+ res: make(chan serverAPIConnsListRes),
+ }
+
+ select {
+ case s.chAPIConnsList <- req:
+ res := <-req.res
+ return res.data, res.err
+
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ }
+}
+
+// APIConnsGet is called by api.
+func (s *Server) APIConnsGet(uuid uuid.UUID) (*defs.APISRTConn, error) {
+ req := serverAPIConnsGetReq{
+ uuid: uuid,
+ res: make(chan serverAPIConnsGetRes),
+ }
+
+ select {
+ case s.chAPIConnsGet <- req:
+ res := <-req.res
+ return res.data, res.err
+
+ case <-s.ctx.Done():
+ return nil, fmt.Errorf("terminated")
+ }
+}
+
+// APIConnsKick is called by api.
+func (s *Server) APIConnsKick(uuid uuid.UUID) error {
+ req := serverAPIConnsKickReq{
+ uuid: uuid,
+ res: make(chan serverAPIConnsKickRes),
+ }
+
+ select {
+ case s.chAPIConnsKick <- req:
+ res := <-req.res
+ return res.err
+
+ case <-s.ctx.Done():
+ return fmt.Errorf("terminated")
+ }
+}
diff --git a/internal/core/webrtc_http_server.go b/internal/servers/webrtc/http_server.go
similarity index 65%
rename from internal/core/webrtc_http_server.go
rename to internal/servers/webrtc/http_server.go
index ea98e9c9..2b7d4e5c 100644
--- a/internal/core/webrtc_http_server.go
+++ b/internal/servers/webrtc/http_server.go
@@ -1,4 +1,4 @@
-package core
+package webrtc
import (
_ "embed"
@@ -12,7 +12,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
- pwebrtc "github.com/pion/webrtc/v3"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
@@ -22,18 +21,18 @@ import (
"github.com/bluenviron/mediamtx/internal/restrictnetwork"
)
-//go:embed webrtc_publish_index.html
-var webrtcPublishIndex []byte
+//go:embed publish_index.html
+var publishIndex []byte
-//go:embed webrtc_read_index.html
-var webrtcReadIndex []byte
+//go:embed read_index.html
+var readIndex []byte
var (
reWHIPWHEPNoID = regexp.MustCompile("^/(.+?)/(whip|whep)$")
reWHIPWHEPWithID = regexp.MustCompile("^/(.+?)/(whip|whep)/(.+?)$")
)
-func webrtcWriteError(ctx *gin.Context, statusCode int, err error) {
+func writeError(ctx *gin.Context, statusCode int, err error) {
ctx.JSON(statusCode, &defs.APIError{
Error: err.Error(),
})
@@ -50,128 +49,111 @@ func sessionLocation(publish bool, secret uuid.UUID) string {
return ret
}
-type webRTCHTTPServerParent interface {
- logger.Writer
- generateICEServers() ([]pwebrtc.ICEServer, error)
- newSession(req webRTCNewSessionReq) webRTCNewSessionRes
- addSessionCandidates(req webRTCAddSessionCandidatesReq) webRTCAddSessionCandidatesRes
- deleteSession(req webRTCDeleteSessionReq) error
-}
-
-type webRTCHTTPServer struct {
- allowOrigin string
- pathManager *pathManager
- parent webRTCHTTPServerParent
+type httpServer struct {
+ address string
+ encryption bool
+ serverKey string
+ serverCert string
+ allowOrigin string
+ trustedProxies conf.IPsOrCIDRs
+ readTimeout conf.StringDuration
+ pathManager defs.PathManager
+ parent *Server
inner *httpserv.WrappedServer
}
-func newWebRTCHTTPServer( //nolint:dupl
- address string,
- encryption bool,
- serverKey string,
- serverCert string,
- allowOrigin string,
- trustedProxies conf.IPsOrCIDRs,
- readTimeout conf.StringDuration,
- pathManager *pathManager,
- parent webRTCHTTPServerParent,
-) (*webRTCHTTPServer, error) {
- if encryption {
- if serverCert == "" {
- return nil, fmt.Errorf("server cert is missing")
+func (s *httpServer) initialize() error {
+ if s.encryption {
+ if s.serverCert == "" {
+ return fmt.Errorf("server cert is missing")
}
} else {
- serverKey = ""
- serverCert = ""
- }
-
- s := &webRTCHTTPServer{
- allowOrigin: allowOrigin,
- pathManager: pathManager,
- parent: parent,
+ s.serverKey = ""
+ s.serverCert = ""
}
router := gin.New()
- router.SetTrustedProxies(trustedProxies.ToTrustedProxies()) //nolint:errcheck
+ router.SetTrustedProxies(s.trustedProxies.ToTrustedProxies()) //nolint:errcheck
router.NoRoute(s.onRequest)
- network, address := restrictnetwork.Restrict("tcp", address)
+ network, address := restrictnetwork.Restrict("tcp", s.address)
var err error
s.inner, err = httpserv.NewWrappedServer(
network,
address,
- time.Duration(readTimeout),
- serverCert,
- serverKey,
+ time.Duration(s.readTimeout),
+ s.serverCert,
+ s.serverKey,
router,
s,
)
if err != nil {
- return nil, err
+ return err
}
- return s, nil
+ return nil
}
-func (s *webRTCHTTPServer) Log(level logger.Level, format string, args ...interface{}) {
+// Log implements logger.Writer.
+func (s *httpServer) Log(level logger.Level, format string, args ...interface{}) {
s.parent.Log(level, format, args...)
}
-func (s *webRTCHTTPServer) close() {
+func (s *httpServer) close() {
s.inner.Close()
}
-func (s *webRTCHTTPServer) checkAuthOutsideSession(ctx *gin.Context, path string, publish bool) bool {
+func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, path string, publish bool) bool {
ip := ctx.ClientIP()
_, port, _ := net.SplitHostPort(ctx.Request.RemoteAddr)
remoteAddr := net.JoinHostPort(ip, port)
user, pass, hasCredentials := ctx.Request.BasicAuth()
- res := s.pathManager.getConfForPath(pathGetConfForPathReq{
- accessRequest: pathAccessRequest{
- name: path,
- query: ctx.Request.URL.RawQuery,
- publish: publish,
- ip: net.ParseIP(ip),
- user: user,
- pass: pass,
- proto: authProtocolWebRTC,
+ res := s.pathManager.GetConfForPath(defs.PathGetConfForPathReq{
+ AccessRequest: defs.PathAccessRequest{
+ Name: path,
+ Query: ctx.Request.URL.RawQuery,
+ Publish: publish,
+ IP: net.ParseIP(ip),
+ User: user,
+ Pass: pass,
+ Proto: defs.AuthProtocolWebRTC,
},
})
- if res.err != nil {
- if terr, ok := res.err.(*errAuthentication); ok {
+ if res.Err != nil {
+ if terr, ok := res.Err.(*defs.ErrAuthentication); ok {
if !hasCredentials {
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
ctx.Writer.WriteHeader(http.StatusUnauthorized)
return false
}
- s.Log(logger.Info, "connection %v failed to authenticate: %v", remoteAddr, terr.message)
+ s.Log(logger.Info, "connection %v failed to authenticate: %v", remoteAddr, terr.Message)
// wait some seconds to stop brute force attacks
<-time.After(webrtcPauseAfterAuthError)
- webrtcWriteError(ctx, http.StatusUnauthorized, terr)
+ writeError(ctx, http.StatusUnauthorized, terr)
return false
}
- webrtcWriteError(ctx, http.StatusInternalServerError, res.err)
+ writeError(ctx, http.StatusInternalServerError, res.Err)
return false
}
return true
}
-func (s *webRTCHTTPServer) onWHIPOptions(ctx *gin.Context, path string, publish bool) {
+func (s *httpServer) onWHIPOptions(ctx *gin.Context, path string, publish bool) {
if !s.checkAuthOutsideSession(ctx, path, publish) {
return
}
servers, err := s.parent.generateICEServers()
if err != nil {
- webrtcWriteError(ctx, http.StatusInternalServerError, err)
+ writeError(ctx, http.StatusInternalServerError, err)
return
}
@@ -182,9 +164,9 @@ func (s *webRTCHTTPServer) onWHIPOptions(ctx *gin.Context, path string, publish
ctx.Writer.WriteHeader(http.StatusNoContent)
}
-func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish bool) {
+func (s *httpServer) onWHIPPost(ctx *gin.Context, path string, publish bool) {
if ctx.Request.Header.Get("Content-Type") != "application/sdp" {
- webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid Content-Type"))
+ writeError(ctx, http.StatusBadRequest, fmt.Errorf("invalid Content-Type"))
return
}
@@ -208,13 +190,13 @@ func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish boo
publish: publish,
})
if res.err != nil {
- webrtcWriteError(ctx, res.errStatusCode, res.err)
+ writeError(ctx, res.errStatusCode, res.err)
return
}
servers, err := s.parent.generateICEServers()
if err != nil {
- webrtcWriteError(ctx, http.StatusInternalServerError, err)
+ writeError(ctx, http.StatusInternalServerError, err)
return
}
@@ -229,15 +211,15 @@ func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish boo
ctx.Writer.Write(res.answer)
}
-func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
+func (s *httpServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
secret, err := uuid.Parse(rawSecret)
if err != nil {
- webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid secret"))
+ writeError(ctx, http.StatusBadRequest, fmt.Errorf("invalid secret"))
return
}
if ctx.Request.Header.Get("Content-Type") != "application/trickle-ice-sdpfrag" {
- webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid Content-Type"))
+ writeError(ctx, http.StatusBadRequest, fmt.Errorf("invalid Content-Type"))
return
}
@@ -248,7 +230,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
candidates, err := webrtc.ICEFragmentUnmarshal(byts)
if err != nil {
- webrtcWriteError(ctx, http.StatusBadRequest, err)
+ writeError(ctx, http.StatusBadRequest, err)
return
}
@@ -257,17 +239,17 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
candidates: candidates,
})
if res.err != nil {
- webrtcWriteError(ctx, http.StatusInternalServerError, res.err)
+ writeError(ctx, http.StatusInternalServerError, res.err)
return
}
ctx.Writer.WriteHeader(http.StatusNoContent)
}
-func (s *webRTCHTTPServer) onWHIPDelete(ctx *gin.Context, rawSecret string) {
+func (s *httpServer) onWHIPDelete(ctx *gin.Context, rawSecret string) {
secret, err := uuid.Parse(rawSecret)
if err != nil {
- webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid secret"))
+ writeError(ctx, http.StatusBadRequest, fmt.Errorf("invalid secret"))
return
}
@@ -275,14 +257,14 @@ func (s *webRTCHTTPServer) onWHIPDelete(ctx *gin.Context, rawSecret string) {
secret: secret,
})
if err != nil {
- webrtcWriteError(ctx, http.StatusInternalServerError, err)
+ writeError(ctx, http.StatusInternalServerError, err)
return
}
ctx.Writer.WriteHeader(http.StatusOK)
}
-func (s *webRTCHTTPServer) onPage(ctx *gin.Context, path string, publish bool) {
+func (s *httpServer) onPage(ctx *gin.Context, path string, publish bool) {
if !s.checkAuthOutsideSession(ctx, path, publish) {
return
}
@@ -292,13 +274,13 @@ func (s *webRTCHTTPServer) onPage(ctx *gin.Context, path string, publish bool) {
ctx.Writer.WriteHeader(http.StatusOK)
if publish {
- ctx.Writer.Write(webrtcPublishIndex)
+ ctx.Writer.Write(publishIndex)
} else {
- ctx.Writer.Write(webrtcReadIndex)
+ ctx.Writer.Write(readIndex)
}
}
-func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
+func (s *httpServer) onRequest(ctx *gin.Context) {
ctx.Writer.Header().Set("Access-Control-Allow-Origin", s.allowOrigin)
ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
@@ -324,7 +306,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
// RFC draft-ietf-whip-09
// The WHIP endpoints MUST return an "405 Method Not Allowed" response
// for any HTTP GET, HEAD or PUT requests
- webrtcWriteError(ctx, http.StatusMethodNotAllowed, fmt.Errorf("method not allowed"))
+ writeError(ctx, http.StatusMethodNotAllowed, fmt.Errorf("method not allowed"))
}
return
}
diff --git a/internal/core/webrtc_publish_index.html b/internal/servers/webrtc/publish_index.html
similarity index 100%
rename from internal/core/webrtc_publish_index.html
rename to internal/servers/webrtc/publish_index.html
diff --git a/internal/core/webrtc_read_index.html b/internal/servers/webrtc/read_index.html
similarity index 100%
rename from internal/core/webrtc_read_index.html
rename to internal/servers/webrtc/read_index.html
diff --git a/internal/core/webrtc_manager.go b/internal/servers/webrtc/server.go
similarity index 52%
rename from internal/core/webrtc_manager.go
rename to internal/servers/webrtc/server.go
index 5a4939c6..d261d42b 100644
--- a/internal/core/webrtc_manager.go
+++ b/internal/servers/webrtc/server.go
@@ -1,4 +1,5 @@
-package core
+// Package webrtc contains a WebRTC server.
+package webrtc
import (
"context"
@@ -93,36 +94,36 @@ func randomTurnUser() (string, error) {
return string(b), nil
}
-type webRTCManagerAPISessionsListRes struct {
+type serverAPISessionsListRes struct {
data *defs.APIWebRTCSessionList
err error
}
-type webRTCManagerAPISessionsListReq struct {
- res chan webRTCManagerAPISessionsListRes
+type serverAPISessionsListReq struct {
+ res chan serverAPISessionsListRes
}
-type webRTCManagerAPISessionsGetRes struct {
+type serverAPISessionsGetRes struct {
data *defs.APIWebRTCSession
err error
}
-type webRTCManagerAPISessionsGetReq struct {
+type serverAPISessionsGetReq struct {
uuid uuid.UUID
- res chan webRTCManagerAPISessionsGetRes
+ res chan serverAPISessionsGetRes
}
-type webRTCManagerAPISessionsKickRes struct {
+type serverAPISessionsKickRes struct {
err error
}
-type webRTCManagerAPISessionsKickReq struct {
+type serverAPISessionsKickReq struct {
uuid uuid.UUID
- res chan webRTCManagerAPISessionsKickRes
+ res chan serverAPISessionsKickRes
}
type webRTCNewSessionRes struct {
- sx *webRTCSession
+ sx *session
answer []byte
errStatusCode int
err error
@@ -140,7 +141,7 @@ type webRTCNewSessionReq struct {
}
type webRTCAddSessionCandidatesRes struct {
- sx *webRTCSession
+ sx *session
err error
}
@@ -159,11 +160,12 @@ type webRTCDeleteSessionReq struct {
res chan webRTCDeleteSessionRes
}
-type webRTCManagerParent interface {
+type serverParent interface {
logger.Writer
}
-type webRTCManager struct {
+// Server is a WebRTC server.
+type Server struct {
Address string
Encryption bool
ServerKey string
@@ -179,59 +181,60 @@ type webRTCManager struct {
AdditionalHosts []string
ICEServers []conf.WebRTCICEServer
ExternalCmdPool *externalcmd.Pool
- PathManager *pathManager
- Parent webRTCManagerParent
+ PathManager defs.PathManager
+ Parent serverParent
ctx context.Context
ctxCancel func()
- httpServer *webRTCHTTPServer
+ httpServer *httpServer
udpMuxLn net.PacketConn
tcpMuxLn net.Listener
api *pwebrtc.API
- sessions map[*webRTCSession]struct{}
- sessionsBySecret map[uuid.UUID]*webRTCSession
+ sessions map[*session]struct{}
+ sessionsBySecret map[uuid.UUID]*session
// in
chNewSession chan webRTCNewSessionReq
- chCloseSession chan *webRTCSession
+ chCloseSession chan *session
chAddSessionCandidates chan webRTCAddSessionCandidatesReq
chDeleteSession chan webRTCDeleteSessionReq
- chAPISessionsList chan webRTCManagerAPISessionsListReq
- chAPISessionsGet chan webRTCManagerAPISessionsGetReq
- chAPIConnsKick chan webRTCManagerAPISessionsKickReq
+ chAPISessionsList chan serverAPISessionsListReq
+ chAPISessionsGet chan serverAPISessionsGetReq
+ chAPIConnsKick chan serverAPISessionsKickReq
// out
done chan struct{}
}
-func (m *webRTCManager) initialize() error {
+// Initialize initializes the server.
+func (s *Server) Initialize() error {
ctx, ctxCancel := context.WithCancel(context.Background())
- m.ctx = ctx
- m.ctxCancel = ctxCancel
- m.sessions = make(map[*webRTCSession]struct{})
- m.sessionsBySecret = make(map[uuid.UUID]*webRTCSession)
- m.chNewSession = make(chan webRTCNewSessionReq)
- m.chCloseSession = make(chan *webRTCSession)
- m.chAddSessionCandidates = make(chan webRTCAddSessionCandidatesReq)
- m.chDeleteSession = make(chan webRTCDeleteSessionReq)
- m.chAPISessionsList = make(chan webRTCManagerAPISessionsListReq)
- m.chAPISessionsGet = make(chan webRTCManagerAPISessionsGetReq)
- m.chAPIConnsKick = make(chan webRTCManagerAPISessionsKickReq)
- m.done = make(chan struct{})
+ s.ctx = ctx
+ s.ctxCancel = ctxCancel
+ s.sessions = make(map[*session]struct{})
+ s.sessionsBySecret = make(map[uuid.UUID]*session)
+ s.chNewSession = make(chan webRTCNewSessionReq)
+ s.chCloseSession = make(chan *session)
+ s.chAddSessionCandidates = make(chan webRTCAddSessionCandidatesReq)
+ s.chDeleteSession = make(chan webRTCDeleteSessionReq)
+ s.chAPISessionsList = make(chan serverAPISessionsListReq)
+ s.chAPISessionsGet = make(chan serverAPISessionsGetReq)
+ s.chAPIConnsKick = make(chan serverAPISessionsKickReq)
+ s.done = make(chan struct{})
- var err error
- m.httpServer, err = newWebRTCHTTPServer(
- m.Address,
- m.Encryption,
- m.ServerKey,
- m.ServerCert,
- m.AllowOrigin,
- m.TrustedProxies,
- m.ReadTimeout,
- m.PathManager,
- m,
- )
+ s.httpServer = &httpServer{
+ address: s.Address,
+ encryption: s.Encryption,
+ serverKey: s.ServerKey,
+ serverCert: s.ServerCert,
+ allowOrigin: s.AllowOrigin,
+ trustedProxies: s.TrustedProxies,
+ readTimeout: s.ReadTimeout,
+ pathManager: s.PathManager,
+ parent: s,
+ }
+ err := s.httpServer.initialize()
if err != nil {
ctxCancel()
return err
@@ -239,95 +242,97 @@ func (m *webRTCManager) initialize() error {
apiConf := webrtc.APIConf{
LocalRandomUDP: false,
- IPsFromInterfaces: m.IPsFromInterfaces,
- IPsFromInterfacesList: m.IPsFromInterfacesList,
- AdditionalHosts: m.AdditionalHosts,
+ IPsFromInterfaces: s.IPsFromInterfaces,
+ IPsFromInterfacesList: s.IPsFromInterfacesList,
+ AdditionalHosts: s.AdditionalHosts,
}
- if m.LocalUDPAddress != "" {
- m.udpMuxLn, err = net.ListenPacket(restrictnetwork.Restrict("udp", m.LocalUDPAddress))
+ if s.LocalUDPAddress != "" {
+ s.udpMuxLn, err = net.ListenPacket(restrictnetwork.Restrict("udp", s.LocalUDPAddress))
if err != nil {
- m.httpServer.close()
+ s.httpServer.close()
ctxCancel()
return err
}
- apiConf.ICEUDPMux = pwebrtc.NewICEUDPMux(webrtcNilLogger, m.udpMuxLn)
+ apiConf.ICEUDPMux = pwebrtc.NewICEUDPMux(webrtcNilLogger, s.udpMuxLn)
}
- if m.LocalTCPAddress != "" {
- m.tcpMuxLn, err = net.Listen(restrictnetwork.Restrict("tcp", m.LocalTCPAddress))
+ if s.LocalTCPAddress != "" {
+ s.tcpMuxLn, err = net.Listen(restrictnetwork.Restrict("tcp", s.LocalTCPAddress))
if err != nil {
- m.udpMuxLn.Close()
- m.httpServer.close()
+ s.udpMuxLn.Close()
+ s.httpServer.close()
ctxCancel()
return err
}
- apiConf.ICETCPMux = pwebrtc.NewICETCPMux(webrtcNilLogger, m.tcpMuxLn, 8)
+ apiConf.ICETCPMux = pwebrtc.NewICETCPMux(webrtcNilLogger, s.tcpMuxLn, 8)
}
- m.api, err = webrtc.NewAPI(apiConf)
+ s.api, err = webrtc.NewAPI(apiConf)
if err != nil {
- m.udpMuxLn.Close()
- m.tcpMuxLn.Close()
- m.httpServer.close()
+ s.udpMuxLn.Close()
+ s.tcpMuxLn.Close()
+ s.httpServer.close()
ctxCancel()
return err
}
- str := "listener opened on " + m.Address + " (HTTP)"
- if m.udpMuxLn != nil {
- str += ", " + m.LocalUDPAddress + " (ICE/UDP)"
+ str := "listener opened on " + s.Address + " (HTTP)"
+ if s.udpMuxLn != nil {
+ str += ", " + s.LocalUDPAddress + " (ICE/UDP)"
}
- if m.tcpMuxLn != nil {
- str += ", " + m.LocalTCPAddress + " (ICE/TCP)"
+ if s.tcpMuxLn != nil {
+ str += ", " + s.LocalTCPAddress + " (ICE/TCP)"
}
- m.Log(logger.Info, str)
+ s.Log(logger.Info, str)
- go m.run()
+ go s.run()
return nil
}
-// Log is the main logging function.
-func (m *webRTCManager) Log(level logger.Level, format string, args ...interface{}) {
- m.Parent.Log(level, "[WebRTC] "+format, args...)
+// Log implements logger.Writer.
+func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
+ s.Parent.Log(level, "[WebRTC] "+format, args...)
}
-func (m *webRTCManager) close() {
- m.Log(logger.Info, "listener is closing")
- m.ctxCancel()
- <-m.done
+// Close closes the server.
+func (s *Server) Close() {
+ s.Log(logger.Info, "listener is closing")
+ s.ctxCancel()
+ <-s.done
}
-func (m *webRTCManager) run() {
- defer close(m.done)
+func (s *Server) run() {
+ defer close(s.done)
var wg sync.WaitGroup
outer:
for {
select {
- case req := <-m.chNewSession:
- sx := newWebRTCSession(
- m.ctx,
- m.WriteQueueSize,
- m.api,
- req,
- &wg,
- m.ExternalCmdPool,
- m.PathManager,
- m,
- )
- m.sessions[sx] = struct{}{}
- m.sessionsBySecret[sx.secret] = sx
+ case req := <-s.chNewSession:
+ sx := &session{
+ parentCtx: s.ctx,
+ writeQueueSize: s.WriteQueueSize,
+ api: s.api,
+ req: req,
+ wg: &wg,
+ externalCmdPool: s.ExternalCmdPool,
+ pathManager: s.PathManager,
+ parent: s,
+ }
+ sx.initialize()
+ s.sessions[sx] = struct{}{}
+ s.sessionsBySecret[sx.secret] = sx
req.res <- webRTCNewSessionRes{sx: sx}
- case sx := <-m.chCloseSession:
- delete(m.sessions, sx)
- delete(m.sessionsBySecret, sx.secret)
+ case sx := <-s.chCloseSession:
+ delete(s.sessions, sx)
+ delete(s.sessionsBySecret, sx.secret)
- case req := <-m.chAddSessionCandidates:
- sx, ok := m.sessionsBySecret[req.secret]
+ case req := <-s.chAddSessionCandidates:
+ sx, ok := s.sessionsBySecret[req.secret]
if !ok {
req.res <- webRTCAddSessionCandidatesRes{err: fmt.Errorf("session not found")}
continue
@@ -335,25 +340,25 @@ outer:
req.res <- webRTCAddSessionCandidatesRes{sx: sx}
- case req := <-m.chDeleteSession:
- sx, ok := m.sessionsBySecret[req.secret]
+ case req := <-s.chDeleteSession:
+ sx, ok := s.sessionsBySecret[req.secret]
if !ok {
req.res <- webRTCDeleteSessionRes{err: fmt.Errorf("session not found")}
continue
}
- delete(m.sessions, sx)
- delete(m.sessionsBySecret, sx.secret)
- sx.close()
+ delete(s.sessions, sx)
+ delete(s.sessionsBySecret, sx.secret)
+ sx.Close()
req.res <- webRTCDeleteSessionRes{}
- case req := <-m.chAPISessionsList:
+ case req := <-s.chAPISessionsList:
data := &defs.APIWebRTCSessionList{
Items: []*defs.APIWebRTCSession{},
}
- for sx := range m.sessions {
+ for sx := range s.sessions {
data.Items = append(data.Items, sx.apiItem())
}
@@ -361,52 +366,52 @@ outer:
return data.Items[i].Created.Before(data.Items[j].Created)
})
- req.res <- webRTCManagerAPISessionsListRes{data: data}
+ req.res <- serverAPISessionsListRes{data: data}
- case req := <-m.chAPISessionsGet:
- sx := m.findSessionByUUID(req.uuid)
+ case req := <-s.chAPISessionsGet:
+ sx := s.findSessionByUUID(req.uuid)
if sx == nil {
- req.res <- webRTCManagerAPISessionsGetRes{err: fmt.Errorf("session not found")}
+ req.res <- serverAPISessionsGetRes{err: fmt.Errorf("session not found")}
continue
}
- req.res <- webRTCManagerAPISessionsGetRes{data: sx.apiItem()}
+ req.res <- serverAPISessionsGetRes{data: sx.apiItem()}
- case req := <-m.chAPIConnsKick:
- sx := m.findSessionByUUID(req.uuid)
+ case req := <-s.chAPIConnsKick:
+ sx := s.findSessionByUUID(req.uuid)
if sx == nil {
- req.res <- webRTCManagerAPISessionsKickRes{err: fmt.Errorf("session not found")}
+ req.res <- serverAPISessionsKickRes{err: fmt.Errorf("session not found")}
continue
}
- delete(m.sessions, sx)
- delete(m.sessionsBySecret, sx.secret)
- sx.close()
+ delete(s.sessions, sx)
+ delete(s.sessionsBySecret, sx.secret)
+ sx.Close()
- req.res <- webRTCManagerAPISessionsKickRes{}
+ req.res <- serverAPISessionsKickRes{}
- case <-m.ctx.Done():
+ case <-s.ctx.Done():
break outer
}
}
- m.ctxCancel()
+ s.ctxCancel()
wg.Wait()
- m.httpServer.close()
+ s.httpServer.close()
- if m.udpMuxLn != nil {
- m.udpMuxLn.Close()
+ if s.udpMuxLn != nil {
+ s.udpMuxLn.Close()
}
- if m.tcpMuxLn != nil {
- m.tcpMuxLn.Close()
+ if s.tcpMuxLn != nil {
+ s.tcpMuxLn.Close()
}
}
-func (m *webRTCManager) findSessionByUUID(uuid uuid.UUID) *webRTCSession {
- for sx := range m.sessions {
+func (s *Server) findSessionByUUID(uuid uuid.UUID) *session {
+ for sx := range s.sessions {
if sx.uuid == uuid {
return sx
}
@@ -414,10 +419,10 @@ func (m *webRTCManager) findSessionByUUID(uuid uuid.UUID) *webRTCSession {
return nil
}
-func (m *webRTCManager) generateICEServers() ([]pwebrtc.ICEServer, error) {
- ret := make([]pwebrtc.ICEServer, len(m.ICEServers))
+func (s *Server) generateICEServers() ([]pwebrtc.ICEServer, error) {
+ ret := make([]pwebrtc.ICEServer, len(s.ICEServers))
- for i, server := range m.ICEServers {
+ for i, server := range s.ICEServers {
if server.Username == "AUTH_SECRET" {
expireDate := time.Now().Add(webrtcTurnSecretExpiration).Unix()
@@ -445,16 +450,16 @@ func (m *webRTCManager) generateICEServers() ([]pwebrtc.ICEServer, error) {
}
// newSession is called by webRTCHTTPServer.
-func (m *webRTCManager) newSession(req webRTCNewSessionReq) webRTCNewSessionRes {
+func (s *Server) newSession(req webRTCNewSessionReq) webRTCNewSessionRes {
req.res = make(chan webRTCNewSessionRes)
select {
- case m.chNewSession <- req:
+ case s.chNewSession <- req:
res := <-req.res
return res.sx.new(req)
- case <-m.ctx.Done():
+ case <-s.ctx.Done():
return webRTCNewSessionRes{
errStatusCode: http.StatusInternalServerError,
err: fmt.Errorf("terminated"),
@@ -462,21 +467,21 @@ func (m *webRTCManager) newSession(req webRTCNewSessionReq) webRTCNewSessionRes
}
}
-// closeSession is called by webRTCSession.
-func (m *webRTCManager) closeSession(sx *webRTCSession) {
+// closeSession is called by session.
+func (s *Server) closeSession(sx *session) {
select {
- case m.chCloseSession <- sx:
- case <-m.ctx.Done():
+ case s.chCloseSession <- sx:
+ case <-s.ctx.Done():
}
}
// addSessionCandidates is called by webRTCHTTPServer.
-func (m *webRTCManager) addSessionCandidates(
+func (s *Server) addSessionCandidates(
req webRTCAddSessionCandidatesReq,
) webRTCAddSessionCandidatesRes {
req.res = make(chan webRTCAddSessionCandidatesRes)
select {
- case m.chAddSessionCandidates <- req:
+ case s.chAddSessionCandidates <- req:
res1 := <-req.res
if res1.err != nil {
return res1
@@ -484,70 +489,70 @@ func (m *webRTCManager) addSessionCandidates(
return res1.sx.addCandidates(req)
- case <-m.ctx.Done():
+ case <-s.ctx.Done():
return webRTCAddSessionCandidatesRes{err: fmt.Errorf("terminated")}
}
}
// deleteSession is called by webRTCHTTPServer.
-func (m *webRTCManager) deleteSession(req webRTCDeleteSessionReq) error {
+func (s *Server) deleteSession(req webRTCDeleteSessionReq) error {
req.res = make(chan webRTCDeleteSessionRes)
select {
- case m.chDeleteSession <- req:
+ case s.chDeleteSession <- req:
res := <-req.res
return res.err
- case <-m.ctx.Done():
+ case <-s.ctx.Done():
return fmt.Errorf("terminated")
}
}
-// apiSessionsList is called by api.
-func (m *webRTCManager) apiSessionsList() (*defs.APIWebRTCSessionList, error) {
- req := webRTCManagerAPISessionsListReq{
- res: make(chan webRTCManagerAPISessionsListRes),
+// APISessionsList is called by api.
+func (s *Server) APISessionsList() (*defs.APIWebRTCSessionList, error) {
+ req := serverAPISessionsListReq{
+ res: make(chan serverAPISessionsListRes),
}
select {
- case m.chAPISessionsList <- req:
+ case s.chAPISessionsList <- req:
res := <-req.res
return res.data, res.err
- case <-m.ctx.Done():
+ case <-s.ctx.Done():
return nil, fmt.Errorf("terminated")
}
}
-// apiSessionsGet is called by api.
-func (m *webRTCManager) apiSessionsGet(uuid uuid.UUID) (*defs.APIWebRTCSession, error) {
- req := webRTCManagerAPISessionsGetReq{
+// APISessionsGet is called by api.
+func (s *Server) APISessionsGet(uuid uuid.UUID) (*defs.APIWebRTCSession, error) {
+ req := serverAPISessionsGetReq{
uuid: uuid,
- res: make(chan webRTCManagerAPISessionsGetRes),
+ res: make(chan serverAPISessionsGetRes),
}
select {
- case m.chAPISessionsGet <- req:
+ case s.chAPISessionsGet <- req:
res := <-req.res
return res.data, res.err
- case <-m.ctx.Done():
+ case <-s.ctx.Done():
return nil, fmt.Errorf("terminated")
}
}
-// apiSessionsKick is called by api.
-func (m *webRTCManager) apiSessionsKick(uuid uuid.UUID) error {
- req := webRTCManagerAPISessionsKickReq{
+// APISessionsKick is called by api.
+func (s *Server) APISessionsKick(uuid uuid.UUID) error {
+ req := serverAPISessionsKickReq{
uuid: uuid,
- res: make(chan webRTCManagerAPISessionsKickRes),
+ res: make(chan serverAPISessionsKickRes),
}
select {
- case m.chAPIConnsKick <- req:
+ case s.chAPIConnsKick <- req:
res := <-req.res
return res.err
- case <-m.ctx.Done():
+ case <-s.ctx.Done():
return fmt.Errorf("terminated")
}
}
diff --git a/internal/core/webrtc_session.go b/internal/servers/webrtc/session.go
similarity index 76%
rename from internal/core/webrtc_session.go
rename to internal/servers/webrtc/session.go
index 79003a4e..4499ea70 100644
--- a/internal/core/webrtc_session.go
+++ b/internal/servers/webrtc/session.go
@@ -1,4 +1,4 @@
-package core
+package webrtc
import (
"context"
@@ -33,7 +33,7 @@ import (
type setupStreamFunc func(*webrtc.OutgoingTrack) error
-func webrtcFindVideoTrack(
+func findVideoTrack(
stream *stream.Stream,
writer *asyncwriter.Writer,
) (format.Format, setupStreamFunc) {
@@ -202,7 +202,7 @@ func webrtcFindVideoTrack(
return nil, nil
}
-func webrtcFindAudioTrack(
+func findAudioTrack(
stream *stream.Stream,
writer *asyncwriter.Writer,
) (format.Format, setupStreamFunc) {
@@ -264,19 +264,15 @@ func whipOffer(body []byte) *pwebrtc.SessionDescription {
}
}
-type webRTCSessionPathManager interface {
- addPublisher(req pathAddPublisherReq) pathAddPublisherRes
- addReader(req pathAddReaderReq) pathAddReaderRes
-}
-
-type webRTCSession struct {
+type session struct {
+ parentCtx context.Context
writeQueueSize int
api *pwebrtc.API
req webRTCNewSessionReq
wg *sync.WaitGroup
externalCmdPool *externalcmd.Pool
- pathManager webRTCSessionPathManager
- parent *webRTCManager
+ pathManager defs.PathManager
+ parent *Server
ctx context.Context
ctxCancel func()
@@ -290,53 +286,34 @@ type webRTCSession struct {
chAddCandidates chan webRTCAddSessionCandidatesReq
}
-func newWebRTCSession(
- parentCtx context.Context,
- writeQueueSize int,
- api *pwebrtc.API,
- req webRTCNewSessionReq,
- wg *sync.WaitGroup,
- externalCmdPool *externalcmd.Pool,
- pathManager webRTCSessionPathManager,
- parent *webRTCManager,
-) *webRTCSession {
- ctx, ctxCancel := context.WithCancel(parentCtx)
+func (s *session) initialize() {
+ ctx, ctxCancel := context.WithCancel(s.parentCtx)
- s := &webRTCSession{
- writeQueueSize: writeQueueSize,
- api: api,
- req: req,
- wg: wg,
- externalCmdPool: externalCmdPool,
- pathManager: pathManager,
- parent: parent,
- ctx: ctx,
- ctxCancel: ctxCancel,
- created: time.Now(),
- uuid: uuid.New(),
- secret: uuid.New(),
- chNew: make(chan webRTCNewSessionReq),
- chAddCandidates: make(chan webRTCAddSessionCandidatesReq),
- }
+ s.ctx = ctx
+ s.ctxCancel = ctxCancel
+ s.created = time.Now()
+ s.uuid = uuid.New()
+ s.secret = uuid.New()
+ s.chNew = make(chan webRTCNewSessionReq)
+ s.chAddCandidates = make(chan webRTCAddSessionCandidatesReq)
- s.Log(logger.Info, "created by %s", req.remoteAddr)
+ s.Log(logger.Info, "created by %s", s.req.remoteAddr)
- wg.Add(1)
+ s.wg.Add(1)
go s.run()
-
- return s
}
-func (s *webRTCSession) Log(level logger.Level, format string, args ...interface{}) {
+// Log implements logger.Writer.
+func (s *session) Log(level logger.Level, format string, args ...interface{}) {
id := hex.EncodeToString(s.uuid[:4])
s.parent.Log(level, "[session %v] "+format, append([]interface{}{id}, args...)...)
}
-func (s *webRTCSession) close() {
+func (s *session) Close() {
s.ctxCancel()
}
-func (s *webRTCSession) run() {
+func (s *session) run() {
defer s.wg.Done()
err := s.runInner()
@@ -348,7 +325,7 @@ func (s *webRTCSession) run() {
s.Log(logger.Info, "closed: %v", err)
}
-func (s *webRTCSession) runInner() error {
+func (s *session) runInner() error {
select {
case <-s.chNew:
case <-s.ctx.Done():
@@ -367,41 +344,41 @@ func (s *webRTCSession) runInner() error {
return err
}
-func (s *webRTCSession) runInner2() (int, error) {
+func (s *session) runInner2() (int, error) {
if s.req.publish {
return s.runPublish()
}
return s.runRead()
}
-func (s *webRTCSession) runPublish() (int, error) {
+func (s *session) runPublish() (int, error) {
ip, _, _ := net.SplitHostPort(s.req.remoteAddr)
- res := s.pathManager.addPublisher(pathAddPublisherReq{
- author: s,
- accessRequest: pathAccessRequest{
- name: s.req.pathName,
- query: s.req.query,
- publish: true,
- ip: net.ParseIP(ip),
- user: s.req.user,
- pass: s.req.pass,
- proto: authProtocolWebRTC,
- id: &s.uuid,
+ res := s.pathManager.AddPublisher(defs.PathAddPublisherReq{
+ Author: s,
+ AccessRequest: defs.PathAccessRequest{
+ Name: s.req.pathName,
+ Query: s.req.query,
+ Publish: true,
+ IP: net.ParseIP(ip),
+ User: s.req.user,
+ Pass: s.req.pass,
+ Proto: defs.AuthProtocolWebRTC,
+ ID: &s.uuid,
},
})
- if res.err != nil {
- if _, ok := res.err.(*errAuthentication); ok {
+ if res.Err != nil {
+ if _, ok := res.Err.(*defs.ErrAuthentication); ok {
// wait some seconds to stop brute force attacks
<-time.After(webrtcPauseAfterAuthError)
- return http.StatusUnauthorized, res.err
+ return http.StatusUnauthorized, res.Err
}
- return http.StatusBadRequest, res.err
+ return http.StatusBadRequest, res.Err
}
- defer res.path.removePublisher(pathRemovePublisherReq{author: s})
+ defer res.Path.RemovePublisher(defs.PathRemovePublisherReq{Author: s})
iceServers, err := s.parent.generateICEServers()
if err != nil {
@@ -463,13 +440,13 @@ func (s *webRTCSession) runPublish() (int, error) {
medias := webrtc.TracksToMedias(tracks)
- rres := res.path.startPublisher(pathStartPublisherReq{
- author: s,
- desc: &description.Session{Medias: medias},
- generateRTPPackets: false,
+ rres := res.Path.StartPublisher(defs.PathStartPublisherReq{
+ Author: s,
+ Desc: &description.Session{Medias: medias},
+ GenerateRTPPackets: false,
})
- if rres.err != nil {
- return 0, rres.err
+ if rres.Err != nil {
+ return 0, rres.Err
}
timeDecoder := rtptime.NewGlobalDecoder()
@@ -491,7 +468,7 @@ func (s *webRTCSession) runPublish() (int, error) {
continue
}
- rres.stream.WriteRTPPacket(cmedia, cmedia.Formats[0], pkt, time.Now(), pts)
+ rres.Stream.WriteRTPPacket(cmedia, cmedia.Formats[0], pkt, time.Now(), pts)
}
}()
}
@@ -505,37 +482,37 @@ func (s *webRTCSession) runPublish() (int, error) {
}
}
-func (s *webRTCSession) runRead() (int, error) {
+func (s *session) runRead() (int, error) {
ip, _, _ := net.SplitHostPort(s.req.remoteAddr)
- res := s.pathManager.addReader(pathAddReaderReq{
- author: s,
- accessRequest: pathAccessRequest{
- name: s.req.pathName,
- query: s.req.query,
- ip: net.ParseIP(ip),
- user: s.req.user,
- pass: s.req.pass,
- proto: authProtocolWebRTC,
- id: &s.uuid,
+ res := s.pathManager.AddReader(defs.PathAddReaderReq{
+ Author: s,
+ AccessRequest: defs.PathAccessRequest{
+ Name: s.req.pathName,
+ Query: s.req.query,
+ IP: net.ParseIP(ip),
+ User: s.req.user,
+ Pass: s.req.pass,
+ Proto: defs.AuthProtocolWebRTC,
+ ID: &s.uuid,
},
})
- if res.err != nil {
- if _, ok := res.err.(*errAuthentication); ok {
+ if res.Err != nil {
+ if _, ok := res.Err.(*defs.ErrAuthentication); ok {
// wait some seconds to stop brute force attacks
<-time.After(webrtcPauseAfterAuthError)
- return http.StatusUnauthorized, res.err
+ return http.StatusUnauthorized, res.Err
}
- if strings.HasPrefix(res.err.Error(), "no one is publishing") {
- return http.StatusNotFound, res.err
+ if strings.HasPrefix(res.Err.Error(), "no one is publishing") {
+ return http.StatusNotFound, res.Err
}
- return http.StatusBadRequest, res.err
+ return http.StatusBadRequest, res.Err
}
- defer res.path.removeReader(pathRemoveReaderReq{author: s})
+ defer res.Path.RemoveReader(defs.PathRemoveReaderReq{Author: s})
iceServers, err := s.parent.generateICEServers()
if err != nil {
@@ -556,8 +533,8 @@ func (s *webRTCSession) runRead() (int, error) {
writer := asyncwriter.New(s.writeQueueSize, s)
- videoTrack, videoSetup := webrtcFindVideoTrack(res.stream, writer)
- audioTrack, audioSetup := webrtcFindAudioTrack(res.stream, writer)
+ videoTrack, videoSetup := findVideoTrack(res.Stream, writer)
+ audioTrack, audioSetup := findAudioTrack(res.Stream, writer)
if videoTrack == nil && audioTrack == nil {
return http.StatusBadRequest, fmt.Errorf(
@@ -589,7 +566,7 @@ func (s *webRTCSession) runRead() (int, error) {
s.pc = pc
s.mutex.Unlock()
- defer res.stream.RemoveReader(writer)
+ defer res.Stream.RemoveReader(writer)
n := 0
@@ -609,14 +586,14 @@ func (s *webRTCSession) runRead() (int, error) {
}
s.Log(logger.Info, "is reading from path '%s', %s",
- res.path.name, readerMediaInfo(writer, res.stream))
+ res.Path.Name(), defs.MediasInfo(res.Stream.MediasForReader(writer)))
onUnreadHook := hooks.OnRead(hooks.OnReadParams{
Logger: s,
ExternalCmdPool: s.externalCmdPool,
- Conf: res.path.safeConf(),
- ExternalCmdEnv: res.path.externalCmdEnv(),
- Reader: s.apiReaderDescribe(),
+ Conf: res.Path.SafeConf(),
+ ExternalCmdEnv: res.Path.ExternalCmdEnv(),
+ Reader: s.APIReaderDescribe(),
Query: s.req.query,
})
defer onUnreadHook()
@@ -637,14 +614,14 @@ func (s *webRTCSession) runRead() (int, error) {
}
}
-func (s *webRTCSession) writeAnswer(answer *pwebrtc.SessionDescription) {
+func (s *session) writeAnswer(answer *pwebrtc.SessionDescription) {
s.req.res <- webRTCNewSessionRes{
sx: s,
answer: []byte(answer.SDP),
}
}
-func (s *webRTCSession) readRemoteCandidates(pc *webrtc.PeerConnection) {
+func (s *session) readRemoteCandidates(pc *webrtc.PeerConnection) {
for {
select {
case req := <-s.chAddCandidates:
@@ -662,8 +639,8 @@ func (s *webRTCSession) readRemoteCandidates(pc *webrtc.PeerConnection) {
}
}
-// new is called by webRTCHTTPServer through webRTCManager.
-func (s *webRTCSession) new(req webRTCNewSessionReq) webRTCNewSessionRes {
+// new is called by webRTCHTTPServer through Server.
+func (s *session) new(req webRTCNewSessionReq) webRTCNewSessionRes {
select {
case s.chNew <- req:
return <-req.res
@@ -673,8 +650,8 @@ func (s *webRTCSession) new(req webRTCNewSessionReq) webRTCNewSessionRes {
}
}
-// addCandidates is called by webRTCHTTPServer through webRTCManager.
-func (s *webRTCSession) addCandidates(
+// addCandidates is called by webRTCHTTPServer through Server.
+func (s *session) addCandidates(
req webRTCAddSessionCandidatesReq,
) webRTCAddSessionCandidatesRes {
select {
@@ -686,20 +663,20 @@ func (s *webRTCSession) addCandidates(
}
}
-// apiReaderDescribe implements reader.
-func (s *webRTCSession) apiReaderDescribe() defs.APIPathSourceOrReader {
+// APIReaderDescribe implements reader.
+func (s *session) APIReaderDescribe() defs.APIPathSourceOrReader {
return defs.APIPathSourceOrReader{
- Type: "webRTCSession",
+ Type: "webrtcSession",
ID: s.uuid.String(),
}
}
// APISourceDescribe implements source.
-func (s *webRTCSession) APISourceDescribe() defs.APIPathSourceOrReader {
- return s.apiReaderDescribe()
+func (s *session) APISourceDescribe() defs.APIPathSourceOrReader {
+ return s.APIReaderDescribe()
}
-func (s *webRTCSession) apiItem() *defs.APIWebRTCSession {
+func (s *session) apiItem() *defs.APIWebRTCSession {
s.mutex.RLock()
defer s.mutex.RUnlock()
diff --git a/internal/staticsources/hls/source.go b/internal/staticsources/hls/source.go
index b99a76f6..cd3d8cfe 100644
--- a/internal/staticsources/hls/source.go
+++ b/internal/staticsources/hls/source.go
@@ -24,7 +24,7 @@ type Source struct {
Parent defs.StaticSourceParent
}
-// Log implements StaticSource.
+// Log implements logger.Writer.
func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
s.Parent.Log(level, "[HLS source] "+format, args...)
}
diff --git a/internal/staticsources/hls/source_test.go b/internal/staticsources/hls/source_test.go
index c4f5f4ae..38d9bb88 100644
--- a/internal/staticsources/hls/source_test.go
+++ b/internal/staticsources/hls/source_test.go
@@ -32,17 +32,17 @@ var track2 = &mpegts.Track{
},
}
-type testHLSManager struct {
+type testHLSServer struct {
s *http.Server
}
-func newTestHLSManager() (*testHLSManager, error) {
+func newTestHLSServer() (*testHLSServer, error) {
ln, err := net.Listen("tcp", "localhost:5780")
if err != nil {
return nil, err
}
- ts := &testHLSManager{}
+ ts := &testHLSServer{}
gin.SetMode(gin.ReleaseMode)
router := gin.New()
@@ -56,11 +56,11 @@ func newTestHLSManager() (*testHLSManager, error) {
return ts, nil
}
-func (ts *testHLSManager) close() {
+func (ts *testHLSServer) close() {
ts.s.Shutdown(context.Background())
}
-func (ts *testHLSManager) onPlaylist(ctx *gin.Context) {
+func (ts *testHLSServer) onPlaylist(ctx *gin.Context) {
cnt := `#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
@@ -77,7 +77,7 @@ segment2.ts
io.Copy(ctx.Writer, bytes.NewReader([]byte(cnt)))
}
-func (ts *testHLSManager) onSegment1(ctx *gin.Context) {
+func (ts *testHLSServer) onSegment1(ctx *gin.Context) {
ctx.Writer.Header().Set("Content-Type", `video/MP2T`)
w := mpegts.NewWriter(ctx.Writer, []*mpegts.Track{track1, track2})
@@ -85,7 +85,7 @@ func (ts *testHLSManager) onSegment1(ctx *gin.Context) {
w.WriteMPEG4Audio(track2, 1*90000, [][]byte{{1, 2, 3, 4}}) //nolint:errcheck
}
-func (ts *testHLSManager) onSegment2(ctx *gin.Context) {
+func (ts *testHLSServer) onSegment2(ctx *gin.Context) {
ctx.Writer.Header().Set("Content-Type", `video/MP2T`)
w := mpegts.NewWriter(ctx.Writer, []*mpegts.Track{track1, track2})
@@ -97,7 +97,7 @@ func (ts *testHLSManager) onSegment2(ctx *gin.Context) {
}
func TestSource(t *testing.T) {
- ts, err := newTestHLSManager()
+ ts, err := newTestHLSServer()
require.NoError(t, err)
defer ts.close()
diff --git a/internal/staticsources/rpicamera/source.go b/internal/staticsources/rpicamera/source.go
index 59aacf76..f44992f4 100644
--- a/internal/staticsources/rpicamera/source.go
+++ b/internal/staticsources/rpicamera/source.go
@@ -57,7 +57,7 @@ type Source struct {
Parent defs.StaticSourceParent
}
-// Log implements StaticSource.
+// Log implements logger.Writer.
func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
s.Parent.Log(level, "[RPI Camera source] "+format, args...)
}
diff --git a/internal/staticsources/rtmp/source.go b/internal/staticsources/rtmp/source.go
index 58e39d7c..70cefb52 100644
--- a/internal/staticsources/rtmp/source.go
+++ b/internal/staticsources/rtmp/source.go
@@ -28,7 +28,7 @@ type Source struct {
Parent defs.StaticSourceParent
}
-// Log implements StaticSource.
+// Log implements logger.Writer.
func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
s.Parent.Log(level, "[RTMP source] "+format, args...)
}
diff --git a/internal/staticsources/rtsp/source.go b/internal/staticsources/rtsp/source.go
index 3ad612ab..4d974755 100644
--- a/internal/staticsources/rtsp/source.go
+++ b/internal/staticsources/rtsp/source.go
@@ -68,7 +68,7 @@ type Source struct {
Parent defs.StaticSourceParent
}
-// Log implements StaticSource.
+// Log implements logger.Writer.
func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
s.Parent.Log(level, "[RTSP source] "+format, args...)
}
diff --git a/internal/staticsources/srt/source.go b/internal/staticsources/srt/source.go
index 5d6f34c0..a5adf114 100644
--- a/internal/staticsources/srt/source.go
+++ b/internal/staticsources/srt/source.go
@@ -6,7 +6,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/description"
mcmpegts "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
- "github.com/datarhei/gosrt"
+ srt "github.com/datarhei/gosrt"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
@@ -21,7 +21,7 @@ type Source struct {
Parent defs.StaticSourceParent
}
-// Log implements StaticSource.
+// Log implements logger.Writer.
func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
s.Parent.Log(level, "[SRT source] "+format, args...)
}
diff --git a/internal/staticsources/udp/source.go b/internal/staticsources/udp/source.go
index 4c7cd8c7..08ea8b95 100644
--- a/internal/staticsources/udp/source.go
+++ b/internal/staticsources/udp/source.go
@@ -49,7 +49,7 @@ type Source struct {
Parent defs.StaticSourceParent
}
-// Log implements StaticSource.
+// Log implements logger.Writer.
func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
s.Parent.Log(level, "[UDP source] "+format, args...)
}
diff --git a/internal/staticsources/webrtc/source.go b/internal/staticsources/webrtc/source.go
index 05f8c122..f022bb84 100644
--- a/internal/staticsources/webrtc/source.go
+++ b/internal/staticsources/webrtc/source.go
@@ -23,7 +23,7 @@ type Source struct {
Parent defs.StaticSourceParent
}
-// Log implements StaticSource.
+// Log implements logger.Writer.
func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
s.Parent.Log(level, "[WebRTC source] "+format, args...)
}