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