Browse Source

move servers into internal/servers (#2792)

pull/2794/head
Alessandro Ros 1 year ago committed by GitHub
parent
commit
11988249df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .github/workflows/bump_hls_js.yml
  2. 4
      README.md
  3. 99
      internal/core/api.go
  4. 72
      internal/core/auth.go
  5. 315
      internal/core/core.go
  6. 309
      internal/core/hls_manager.go
  7. 2
      internal/core/hls_server_test.go
  8. 35
      internal/core/metrics.go
  9. 16
      internal/core/metrics_test.go
  10. 363
      internal/core/path.go
  11. 166
      internal/core/path_manager.go
  12. 3
      internal/core/path_manager_test.go
  13. 5
      internal/core/path_test.go
  14. 1
      internal/core/pprof.go
  15. 7
      internal/core/publisher.go
  16. 17
      internal/core/reader.go
  17. 48
      internal/core/rtmp_listener.go
  18. 327
      internal/core/rtmp_server.go
  19. 454
      internal/core/rtsp_server.go
  20. 330
      internal/core/srt_server.go
  21. 3
      internal/core/static_source_handler.go
  22. 0
      internal/core/webrtc_server_test.go
  23. 23
      internal/defs/auth.go
  24. 137
      internal/defs/path.go
  25. 9
      internal/defs/path_manager.go
  26. 7
      internal/defs/publisher.go
  27. 7
      internal/defs/reader.go
  28. 17
      internal/defs/source.go
  29. 10
      internal/protocols/mpegts/from_stream.go
  30. 2
      internal/record/agent.go
  31. 2
      internal/record/cleaner.go
  32. 0
      internal/servers/hls/hls.min.js
  33. 97
      internal/servers/hls/http_server.go
  34. 0
      internal/servers/hls/index.html
  35. 141
      internal/servers/hls/muxer.go
  36. 290
      internal/servers/hls/server.go
  37. 198
      internal/servers/rtmp/conn.go
  38. 36
      internal/servers/rtmp/listener.go
  39. 308
      internal/servers/rtmp/server.go
  40. 123
      internal/servers/rtsp/conn.go
  41. 430
      internal/servers/rtsp/server.go
  42. 230
      internal/servers/rtsp/session.go
  43. 212
      internal/servers/srt/conn.go
  44. 28
      internal/servers/srt/listener.go
  45. 310
      internal/servers/srt/server.go
  46. 148
      internal/servers/webrtc/http_server.go
  47. 0
      internal/servers/webrtc/publish_index.html
  48. 0
      internal/servers/webrtc/read_index.html
  49. 327
      internal/servers/webrtc/server.go
  50. 199
      internal/servers/webrtc/session.go
  51. 2
      internal/staticsources/hls/source.go
  52. 16
      internal/staticsources/hls/source_test.go
  53. 2
      internal/staticsources/rpicamera/source.go
  54. 2
      internal/staticsources/rtmp/source.go
  55. 2
      internal/staticsources/rtsp/source.go
  56. 4
      internal/staticsources/srt/source.go
  57. 2
      internal/staticsources/udp/source.go
  58. 2
      internal/staticsources/webrtc/source.go

4
.github/workflows/bump_hls_js.yml

@ -20,8 +20,8 @@ jobs: @@ -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: >

4
README.md

@ -435,7 +435,7 @@ This web page can be embedded into another web page by using an iframe: @@ -435,7 +435,7 @@ This web page can be embedded into another web page by using an iframe:
<iframe src="http://mediamtx-ip:8889/mystream/publish" scrolling="no"></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: @@ -823,7 +823,7 @@ This web page can be embedded into another web page by using an iframe:
<iframe src="http://mediamtx-ip:8889/mystream" scrolling="no"></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:

99
internal/core/api.go

@ -102,35 +102,35 @@ type apiPathManager interface { @@ -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
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
type apiSRTServer interface {
APIConnsList() (*defs.APISRTConnList, error)
APIConnsGet(uuid.UUID) (*defs.APISRTConn, error)
APIConnsKick(uuid.UUID) error
}
type apiSRTServer interface {
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 {
@ -145,8 +145,8 @@ type api struct { @@ -145,8 +145,8 @@ type api struct {
rtspsServer apiRTSPServer
rtmpServer apiRTMPServer
rtmpsServer apiRTMPServer
hlsManager apiHLSManager
webRTCManager apiWebRTCManager
hlsManager apiHLSServer
webRTCServer apiWebRTCServer
srtServer apiSRTServer
parent apiParent
@ -163,8 +163,8 @@ func newAPI( @@ -163,8 +163,8 @@ func newAPI(
rtspsServer apiRTSPServer,
rtmpServer apiRTMPServer,
rtmpsServer apiRTMPServer,
hlsManager apiHLSManager,
webRTCManager apiWebRTCManager,
hlsManager apiHLSServer,
webRTCServer apiWebRTCServer,
srtServer apiSRTServer,
parent apiParent,
) (*api, error) {
@ -176,7 +176,7 @@ func newAPI( @@ -176,7 +176,7 @@ func newAPI(
rtmpServer: rtmpServer,
rtmpsServer: rtmpsServer,
hlsManager: hlsManager,
webRTCManager: webRTCManager,
webRTCServer: webRTCServer,
srtServer: srtServer,
parent: parent,
}
@ -235,7 +235,7 @@ func newAPI( @@ -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() { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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

72
internal/core/authentication.go → internal/core/auth.go

@ -15,6 +15,7 @@ import ( @@ -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 { @@ -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( @@ -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( @@ -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( @@ -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( @@ -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( @@ -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"}
}
}

315
internal/core/core.go

@ -22,6 +22,11 @@ import ( @@ -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 { @@ -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() { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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
}

309
internal/core/hls_manager.go

@ -1,309 +0,0 @@ @@ -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():
}
}

2
internal/core/hls_manager_test.go → internal/core/hls_server_test.go

@ -197,7 +197,7 @@ func TestHLSRead(t *testing.T) { @@ -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"+

35
internal/core/metrics.go

@ -36,8 +36,8 @@ type metrics struct { @@ -36,8 +36,8 @@ type metrics struct {
rtmpServer apiRTMPServer
rtmpsServer apiRTMPServer
srtServer apiSRTServer
hlsManager apiHLSManager
webRTCManager apiWebRTCManager
hlsManager apiHLSServer
webRTCServer apiWebRTCServer
}
func (m *metrics) initialize() error {
@ -72,6 +72,7 @@ func (m *metrics) close() { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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
}

16
internal/core/metrics_test.go

@ -229,26 +229,26 @@ webrtc_sessions_bytes_sent 0 @@ -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"+

363
internal/core/path.go

@ -11,7 +11,6 @@ import ( @@ -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 { @@ -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 ( @@ -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 { @@ -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 { @@ -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( @@ -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( @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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 { @@ -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() { @@ -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 @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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 { @@ -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) { @@ -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 @@ -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 @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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) { @@ -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( @@ -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():
}
}

166
internal/core/path_manager.go

@ -59,9 +59,9 @@ func getConfForPath(pathConfs map[string]*conf.Path, name string) (string, *conf @@ -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 { @@ -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( @@ -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() { @@ -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: @@ -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) { @@ -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) { @@ -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) { @@ -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():
}
}

3
internal/core/path_manager_test.go

@ -7,9 +7,12 @@ import ( @@ -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) {

5
internal/core/path_test.go

@ -19,10 +19,11 @@ import ( @@ -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() { @@ -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")

1
internal/core/pprof.go

@ -58,6 +58,7 @@ func (pp *pprof) close() { @@ -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...)
}

7
internal/core/publisher.go

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
package core
// publisher is an entity that can publish a stream.
type publisher interface {
source
close()
}

17
internal/core/reader.go

@ -1,17 +0,0 @@ @@ -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))
}

48
internal/core/rtmp_listener.go

@ -1,48 +0,0 @@ @@ -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)
}
}

327
internal/core/rtmp_server.go

@ -1,327 +0,0 @@ @@ -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")
}
}

454
internal/core/rtsp_server.go

@ -1,454 +0,0 @@ @@ -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
}

330
internal/core/srt_server.go

@ -1,330 +0,0 @@ @@ -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")
}
}

3
internal/core/static_source_handler.go

@ -153,6 +153,7 @@ func (s *staticSourceHandler) stop(reason string) { @@ -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 @@ -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

0
internal/core/webrtc_manager_test.go → internal/core/webrtc_server_test.go

23
internal/defs/auth.go

@ -0,0 +1,23 @@ @@ -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
}

137
internal/defs/path.go

@ -1,25 +1,156 @@ @@ -1,25 +1,156 @@
package defs
import (
"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{}
}

9
internal/defs/path_manager.go

@ -0,0 +1,9 @@ @@ -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
}

7
internal/defs/publisher.go

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
package defs
// Publisher is an entity that can publish a stream.
type Publisher interface {
Source
Close()
}

7
internal/defs/reader.go

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
package defs
// Reader is an entity that can read a stream.
type Reader interface {
Close()
APIReaderDescribe() APIPathSourceOrReader
}

17
internal/core/source.go → internal/defs/source.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package defs
import (
"fmt"
@ -6,18 +6,17 @@ import ( @@ -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 { @@ -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 { @@ -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 { @@ -45,5 +46,5 @@ func mediaInfo(medias []*description.Media) string {
}
return "tracks"
}(),
strings.Join(mediasDescription(medias), ", "))
strings.Join(MediasDescription(medias), ", "))
}

10
internal/core/mpegts.go → internal/protocols/mpegts/from_stream.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package mpegts
import (
"bufio"
@ -10,10 +10,9 @@ import ( @@ -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 { @@ -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( @@ -251,7 +251,7 @@ func mpegtsSetupWrite(
}
if len(tracks) == 0 {
return mpegts.ErrNoTracks
return ErrNoTracks
}
w = mcmpegts.NewWriter(bw, tracks)

2
internal/record/agent.go

@ -54,7 +54,7 @@ func (w *Agent) Initialize() { @@ -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...)
}

2
internal/record/cleaner.go

@ -71,7 +71,7 @@ func (c *Cleaner) Close() { @@ -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...)
}

0
internal/core/hls.min.js → internal/servers/hls/hls.min.js vendored

97
internal/core/hls_http_server.go → internal/servers/hls/http_server.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package hls
import (
_ "embed"
@ -12,6 +12,7 @@ import ( @@ -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 ( @@ -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 {
type httpServer struct {
address string
encryption bool
serverKey string
serverCert string
allowOrigin string
pathManager *pathManager
parent hlsHTTPServerParent
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) { @@ -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) { @@ -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) { @@ -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,

0
internal/core/hls_index.html → internal/servers/hls/index.html

141
internal/core/hls_muxer.go → internal/servers/hls/muxer.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package hls
import (
"context"
@ -28,13 +28,19 @@ import ( @@ -28,13 +28,19 @@ import (
const (
closeCheckPeriod = 1 * time.Second
closeAfterInactivity = 60 * time.Second
hlsMuxerRecreatePause = 10 * 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) { @@ -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
}
type hlsMuxerParent interface {
logger.Writer
closeMuxer(*hlsMuxer)
res chan *muxer
}
type hlsMuxer struct {
type muxer struct {
parentCtx context.Context
remoteAddr string
externalAuthenticationURL string
variant conf.HLSVariant
@ -70,90 +72,59 @@ type hlsMuxer struct { @@ -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)
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),
}
func (m *muxer) initialize() {
ctx, ctxCancel := context.WithCancel(m.parentCtx)
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() { @@ -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() { @@ -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{}) @@ -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{}) @@ -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, @@ -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, @@ -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) { @@ -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) { @@ -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,

290
internal/servers/hls/server.go

@ -0,0 +1,290 @@ @@ -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")
}
}

198
internal/core/rtmp_conn.go → internal/servers/rtmp/conn.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package rtmp
import (
"context"
@ -40,24 +40,15 @@ func pathNameAndQuery(inURL *url.URL) (string, url.Values, string) { @@ -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 { @@ -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 { @@ -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)
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(),
}
func (c *conn) initialize() {
c.ctx, c.ctxCancel = context.WithCancel(c.parentCtx)
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 @@ -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 @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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( @@ -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( @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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:

36
internal/servers/rtmp/listener.go

@ -0,0 +1,36 @@ @@ -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)
}
}

308
internal/servers/rtmp/server.go

@ -0,0 +1,308 @@ @@ -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")
}
}

123
internal/core/rtsp_conn.go → internal/servers/rtsp/conn.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package rtsp
import (
"fmt"
@ -22,20 +22,19 @@ const ( @@ -22,20 +22,19 @@ const (
rtspPauseAfterAuthError = 2 * time.Second
)
type rtspConnParent interface {
logger.Writer
getISTLS() bool
getServer() *gortsplib.Server
}
type rtspConn struct {
type conn struct {
isTLS bool
rtspAddress string
authMethods []headers.AuthMethod
readTimeout conf.StringDuration
pathManager *pathManager
runOnConnect string
runOnConnectRestart bool
runOnDisconnect string
externalCmdPool *externalcmd.Pool
pathManager defs.PathManager
rconn *gortsplib.ServerConn
parent rtspConnParent
rserver *gortsplib.Server
parent *Server
uuid uuid.UUID
created time.Time
@ -44,92 +43,70 @@ type rtspConn struct { @@ -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, @@ -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, @@ -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) { @@ -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,

430
internal/servers/rtsp/server.go

@ -0,0 +1,430 @@ @@ -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
}

230
internal/core/rtsp_session.go → internal/servers/rtsp/session.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package rtsp
import (
"encoding/hex"
@ -21,29 +21,19 @@ import ( @@ -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 { @@ -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) { @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 { @@ -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 { @@ -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(),
}
}

212
internal/core/srt_conn.go → internal/servers/srt/conn.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package srt
import (
"bufio"
@ -12,7 +12,7 @@ import ( @@ -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 { @@ -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 { @@ -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 { @@ -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)
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),
}
func (c *conn) initialize() {
c.ctx, c.ctxCancel = context.WithCancel(c.parentCtx)
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 @@ -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 @@ -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 { @@ -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) { @@ -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 @@ -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 @@ -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 { @@ -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 { @@ -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 @@ -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 @@ -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) { @@ -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 { @@ -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 { @@ -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 { @@ -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:

28
internal/core/srt_listener.go → internal/servers/srt/listener.go

@ -1,35 +1,23 @@ @@ -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() { @@ -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 {

310
internal/servers/srt/server.go

@ -0,0 +1,310 @@ @@ -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")
}
}

148
internal/core/webrtc_http_server.go → internal/servers/webrtc/http_server.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package webrtc
import (
_ "embed"
@ -12,7 +12,6 @@ import ( @@ -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 ( @@ -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 { @@ -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 {
type httpServer struct {
address string
encryption bool
serverKey string
serverCert string
allowOrigin string
pathManager *pathManager
parent webRTCHTTPServerParent
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 @@ -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 @@ -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 @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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
}

0
internal/core/webrtc_publish_index.html → internal/servers/webrtc/publish_index.html

0
internal/core/webrtc_read_index.html → internal/servers/webrtc/read_index.html

327
internal/core/webrtc_manager.go → internal/servers/webrtc/server.go

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
package core
// Package webrtc contains a WebRTC server.
package webrtc
import (
"context"
@ -93,36 +94,36 @@ func randomTurnUser() (string, error) { @@ -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 { @@ -140,7 +141,7 @@ type webRTCNewSessionReq struct {
}
type webRTCAddSessionCandidatesRes struct {
sx *webRTCSession
sx *session
err error
}
@ -159,11 +160,12 @@ type webRTCDeleteSessionReq struct { @@ -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 { @@ -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{})
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.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{})
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 { @@ -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: @@ -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: @@ -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 { @@ -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) { @@ -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 @@ -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( @@ -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")
}
}

199
internal/core/webrtc_session.go → internal/servers/webrtc/session.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package core
package webrtc
import (
"context"
@ -33,7 +33,7 @@ import ( @@ -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( @@ -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 { @@ -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 { @@ -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)
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.Log(logger.Info, "created by %s", req.remoteAddr)
wg.Add(1)
go s.run()
func (s *session) initialize() {
ctx, ctxCancel := context.WithCancel(s.parentCtx)
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)
return s
s.Log(logger.Info, "created by %s", s.req.remoteAddr)
s.wg.Add(1)
go s.run()
}
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() { @@ -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 { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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 { @@ -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( @@ -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()

2
internal/staticsources/hls/source.go

@ -24,7 +24,7 @@ type Source struct { @@ -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...)
}

16
internal/staticsources/hls/source_test.go

@ -32,17 +32,17 @@ var track2 = &mpegts.Track{ @@ -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) { @@ -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 @@ -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) { @@ -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) { @@ -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()

2
internal/staticsources/rpicamera/source.go

@ -57,7 +57,7 @@ type Source struct { @@ -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...)
}

2
internal/staticsources/rtmp/source.go

@ -28,7 +28,7 @@ type Source struct { @@ -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...)
}

2
internal/staticsources/rtsp/source.go

@ -68,7 +68,7 @@ type Source struct { @@ -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...)
}

4
internal/staticsources/srt/source.go

@ -6,7 +6,7 @@ import ( @@ -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 { @@ -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...)
}

2
internal/staticsources/udp/source.go

@ -49,7 +49,7 @@ type Source struct { @@ -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...)
}

2
internal/staticsources/webrtc/source.go

@ -23,7 +23,7 @@ type Source struct { @@ -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...)
}

Loading…
Cancel
Save