allow changing default path settings; bump API in order to allow so (#2455)
This commit is contained in:
@@ -642,7 +642,7 @@ paths:
|
||||
|
||||
The resulting stream will be available in path `/proxied`.
|
||||
|
||||
The server supports any number of source streams (count is just limited by hardware capability) it's enough to add additional entries to the paths section:
|
||||
The server supports any number of source streams (count is just limited by available hardware resources) it's enough to add additional entries to the paths section:
|
||||
|
||||
```yml
|
||||
paths:
|
||||
|
||||
+209
-75
@@ -15,7 +15,7 @@ security: []
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Conf:
|
||||
GlobalConf:
|
||||
type: object
|
||||
properties:
|
||||
# General
|
||||
@@ -195,12 +195,6 @@ components:
|
||||
recordDeleteAfter:
|
||||
type: string
|
||||
|
||||
# Paths
|
||||
paths:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: '#/components/schemas/PathConf'
|
||||
|
||||
PathConf:
|
||||
type: object
|
||||
properties:
|
||||
@@ -356,6 +350,16 @@ components:
|
||||
runOnRecordSegmentComplete:
|
||||
type: string
|
||||
|
||||
PathConfList:
|
||||
type: object
|
||||
properties:
|
||||
pageCount:
|
||||
type: integer
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PathConf'
|
||||
|
||||
Path:
|
||||
type: object
|
||||
properties:
|
||||
@@ -363,10 +367,8 @@ components:
|
||||
type: string
|
||||
confName:
|
||||
type: string
|
||||
conf:
|
||||
$ref: '#/components/schemas/PathConf'
|
||||
source:
|
||||
$ref: '#/components/schemas/PathSourceOrReader'
|
||||
$ref: '#/components/schemas/PathSource'
|
||||
nullable: true
|
||||
ready:
|
||||
type: boolean
|
||||
@@ -383,9 +385,9 @@ components:
|
||||
readers:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PathSourceOrReader'
|
||||
$ref: '#/components/schemas/PathReader'
|
||||
|
||||
PathsList:
|
||||
PathList:
|
||||
type: object
|
||||
properties:
|
||||
pageCount:
|
||||
@@ -395,13 +397,12 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/Path'
|
||||
|
||||
PathSourceOrReader:
|
||||
PathSource:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- hlsMuxer
|
||||
- hlsSource
|
||||
- redirect
|
||||
- rpiCameraSource
|
||||
@@ -418,6 +419,21 @@ components:
|
||||
id:
|
||||
type: string
|
||||
|
||||
PathReader:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- hlsMuxer
|
||||
- rtmpConn
|
||||
- rtspSession
|
||||
- rtspsSession
|
||||
- srtConn
|
||||
- webRTCSession
|
||||
id:
|
||||
type: string
|
||||
|
||||
HLSMuxer:
|
||||
type: object
|
||||
properties:
|
||||
@@ -431,7 +447,7 @@ components:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
HLSMuxersList:
|
||||
HLSMuxerList:
|
||||
type: object
|
||||
properties:
|
||||
pageCount:
|
||||
@@ -462,7 +478,7 @@ components:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
RTMPConnsList:
|
||||
RTMPConnList:
|
||||
type: object
|
||||
properties:
|
||||
pageCount:
|
||||
@@ -488,7 +504,7 @@ components:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
RTSPConnsList:
|
||||
RTSPConnList:
|
||||
type: object
|
||||
properties:
|
||||
pageCount:
|
||||
@@ -522,7 +538,7 @@ components:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
RTSPSessionsList:
|
||||
RTSPSessionList:
|
||||
type: object
|
||||
properties:
|
||||
pageCount:
|
||||
@@ -553,7 +569,7 @@ components:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
SRTConnsList:
|
||||
SRTConnList:
|
||||
type: object
|
||||
properties:
|
||||
pageCount:
|
||||
@@ -590,7 +606,7 @@ components:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
WebRTCSessionsList:
|
||||
WebRTCSessionList:
|
||||
type: object
|
||||
properties:
|
||||
pageCount:
|
||||
@@ -601,10 +617,10 @@ components:
|
||||
$ref: '#/components/schemas/WebRTCSession'
|
||||
|
||||
paths:
|
||||
/v2/config/get:
|
||||
/v3/config/global/get:
|
||||
get:
|
||||
operationId: configGet
|
||||
summary: returns the configuration.
|
||||
operationId: configGlobalGet
|
||||
summary: returns the global configuration.
|
||||
description: ''
|
||||
responses:
|
||||
'200':
|
||||
@@ -612,23 +628,23 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Conf'
|
||||
$ref: '#/components/schemas/GlobalConf'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/config/set:
|
||||
post:
|
||||
operationId: configSet
|
||||
summary: changes the configuration.
|
||||
description: all fields are optional. paths can't be edited with this request, use /v2/config/paths/{operation}/{name} to edit them.
|
||||
/v3/config/global/patch:
|
||||
patch:
|
||||
operationId: configGlobalSet
|
||||
summary: patches the global configuration.
|
||||
description: all fields are optional.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Conf'
|
||||
$ref: '#/components/schemas/GlobalConf'
|
||||
responses:
|
||||
'200':
|
||||
description: the request was successful.
|
||||
@@ -637,10 +653,100 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/config/paths/add/{name}:
|
||||
/v3/config/pathdefaults/get:
|
||||
get:
|
||||
operationId: configPathDefaultsGet
|
||||
summary: returns the default path configuration.
|
||||
description: ''
|
||||
responses:
|
||||
'200':
|
||||
description: the request was successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PathConf'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v3/config/pathdefaults/patch:
|
||||
patch:
|
||||
operationId: configPathDefaultsPatch
|
||||
summary: patches the default path configuration.
|
||||
description: all fields are optional.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PathConf'
|
||||
responses:
|
||||
'200':
|
||||
description: the request was successful.
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v3/config/paths/list:
|
||||
get:
|
||||
operationId: configPathsList
|
||||
summary: returns all path configurations.
|
||||
description: ''
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: page number.
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- name: itemsPerPage
|
||||
in: query
|
||||
description: items per page.
|
||||
schema:
|
||||
type: integer
|
||||
default: 100
|
||||
responses:
|
||||
'200':
|
||||
description: the request was successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PathConfList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v3/config/paths/get/{name}:
|
||||
get:
|
||||
operationId: configPathsGet
|
||||
summary: returns a path configuration.
|
||||
description: ''
|
||||
parameters:
|
||||
- name: name
|
||||
in: path
|
||||
required: true
|
||||
description: the name of the path.
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: the request was successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PathConf'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v3/config/paths/add/{name}:
|
||||
post:
|
||||
operationId: configPathsAdd
|
||||
summary: adds the configuration of a path.
|
||||
summary: adds a path configuration.
|
||||
description: all fields are optional.
|
||||
parameters:
|
||||
- name: name
|
||||
@@ -663,10 +769,10 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/config/paths/edit/{name}:
|
||||
post:
|
||||
operationId: configPathsEdit
|
||||
summary: changes the configuration of a path.
|
||||
/v3/config/paths/patch/{name}:
|
||||
patch:
|
||||
operationId: configPathsPatch
|
||||
summary: patches a path configuration.
|
||||
description: all fields are optional.
|
||||
parameters:
|
||||
- name: name
|
||||
@@ -691,10 +797,38 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/config/paths/remove/{name}:
|
||||
/v3/config/paths/replace/{name}:
|
||||
post:
|
||||
operationId: configPathsRemove
|
||||
summary: removes the configuration of a path.
|
||||
operationId: configPathsReplace
|
||||
summary: replaces all values of a path configuration.
|
||||
description: all fields are optional.
|
||||
parameters:
|
||||
- name: name
|
||||
in: path
|
||||
required: true
|
||||
description: the name of the path.
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PathConf'
|
||||
responses:
|
||||
'200':
|
||||
description: the request was successful.
|
||||
'400':
|
||||
description: invalid request.
|
||||
'404':
|
||||
description: configuration not found.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v3/config/paths/delete/{name}:
|
||||
delete:
|
||||
operationId: configPathsDelete
|
||||
summary: removes a path configuration.
|
||||
description: ''
|
||||
parameters:
|
||||
- name: name
|
||||
@@ -713,7 +847,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/hlsmuxers/list:
|
||||
/v3/hlsmuxers/list:
|
||||
get:
|
||||
operationId: hlsMuxersList
|
||||
summary: returns all HLS muxers.
|
||||
@@ -737,13 +871,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HLSMuxersList'
|
||||
$ref: '#/components/schemas/HLSMuxerList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/hlsmuxers/get/{name}:
|
||||
/v3/hlsmuxers/get/{name}:
|
||||
get:
|
||||
operationId: hlsMuxersGet
|
||||
summary: returns a HLS muxer.
|
||||
@@ -769,7 +903,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/paths/list:
|
||||
/v3/paths/list:
|
||||
get:
|
||||
operationId: pathsList
|
||||
summary: returns all paths.
|
||||
@@ -793,13 +927,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PathsList'
|
||||
$ref: '#/components/schemas/PathList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/paths/get/{name}:
|
||||
/v3/paths/get/{name}:
|
||||
get:
|
||||
operationId: pathsGet
|
||||
summary: returns a path.
|
||||
@@ -825,7 +959,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspconns/list:
|
||||
/v3/rtspconns/list:
|
||||
get:
|
||||
operationId: rtspConnsList
|
||||
summary: returns all RTSP connections.
|
||||
@@ -849,13 +983,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RTSPConnsList'
|
||||
$ref: '#/components/schemas/RTSPConnList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspconns/get/{id}:
|
||||
/v3/rtspconns/get/{id}:
|
||||
get:
|
||||
operationId: rtspConnsGet
|
||||
summary: returns a RTSP connection.
|
||||
@@ -881,7 +1015,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspsessions/list:
|
||||
/v3/rtspsessions/list:
|
||||
get:
|
||||
operationId: rtspSessionsList
|
||||
summary: returns all RTSP sessions.
|
||||
@@ -905,13 +1039,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RTSPSessionsList'
|
||||
$ref: '#/components/schemas/RTSPSessionList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspsessions/get/{id}:
|
||||
/v3/rtspsessions/get/{id}:
|
||||
get:
|
||||
operationId: rtspSessionsGet
|
||||
summary: returns a RTSP session.
|
||||
@@ -937,7 +1071,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspsessions/kick/{id}:
|
||||
/v3/rtspsessions/kick/{id}:
|
||||
post:
|
||||
operationId: rtspSessionsKick
|
||||
summary: kicks out a RTSP session from the server.
|
||||
@@ -959,7 +1093,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspsconns/list:
|
||||
/v3/rtspsconns/list:
|
||||
get:
|
||||
operationId: rtspsConnsList
|
||||
summary: returns all RTSPS connections.
|
||||
@@ -983,13 +1117,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RTSPConnsList'
|
||||
$ref: '#/components/schemas/RTSPConnList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspsconns/get/{id}:
|
||||
/v3/rtspsconns/get/{id}:
|
||||
get:
|
||||
operationId: rtspsConnsGet
|
||||
summary: returns a RTSPS connection.
|
||||
@@ -1015,7 +1149,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspssessions/list:
|
||||
/v3/rtspssessions/list:
|
||||
get:
|
||||
operationId: rtspsSessionsList
|
||||
summary: returns all RTSPS sessions.
|
||||
@@ -1039,7 +1173,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RTSPSessionsList'
|
||||
$ref: '#/components/schemas/RTSPSessionList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'404':
|
||||
@@ -1047,7 +1181,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspssessions/get/{id}:
|
||||
/v3/rtspssessions/get/{id}:
|
||||
get:
|
||||
operationId: rtspsSessionsGet
|
||||
summary: returns a RTSPS session.
|
||||
@@ -1073,7 +1207,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtspssessions/kick/{id}:
|
||||
/v3/rtspssessions/kick/{id}:
|
||||
post:
|
||||
operationId: rtspsSessionsKick
|
||||
summary: kicks out a RTSPS session from the server.
|
||||
@@ -1095,7 +1229,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtmpconns/list:
|
||||
/v3/rtmpconns/list:
|
||||
get:
|
||||
operationId: rtmpConnsList
|
||||
summary: returns all RTMP connections.
|
||||
@@ -1119,13 +1253,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RTMPConnsList'
|
||||
$ref: '#/components/schemas/RTMPConnList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtmpconns/get/{id}:
|
||||
/v3/rtmpconns/get/{id}:
|
||||
get:
|
||||
operationId: rtmpConnectionsGet
|
||||
summary: returns a RTMP connection.
|
||||
@@ -1151,7 +1285,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtmpconns/kick/{id}:
|
||||
/v3/rtmpconns/kick/{id}:
|
||||
post:
|
||||
operationId: rtmpConnsKick
|
||||
summary: kicks out a RTMP connection from the server.
|
||||
@@ -1173,7 +1307,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtmpsconns/list:
|
||||
/v3/rtmpsconns/list:
|
||||
get:
|
||||
operationId: rtmpsConnsList
|
||||
summary: returns all RTMPS connections.
|
||||
@@ -1197,13 +1331,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RTMPConnsList'
|
||||
$ref: '#/components/schemas/RTMPConnList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtmpsconns/get/{id}:
|
||||
/v3/rtmpsconns/get/{id}:
|
||||
get:
|
||||
operationId: rtmpsConnectionsGet
|
||||
summary: returns a RTMPS connection.
|
||||
@@ -1229,7 +1363,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/rtmpsconns/kick/{id}:
|
||||
/v3/rtmpsconns/kick/{id}:
|
||||
post:
|
||||
operationId: rtmpsConnsKick
|
||||
summary: kicks out a RTMPS connection from the server.
|
||||
@@ -1251,7 +1385,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/srtconns/list:
|
||||
/v3/srtconns/list:
|
||||
get:
|
||||
operationId: srtConnsList
|
||||
summary: returns all SRT connections.
|
||||
@@ -1275,13 +1409,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SRTConnsList'
|
||||
$ref: '#/components/schemas/SRTConnList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/srtconns/get/{id}:
|
||||
/v3/srtconns/get/{id}:
|
||||
get:
|
||||
operationId: srtConnsGet
|
||||
summary: returns a SRT connection.
|
||||
@@ -1307,7 +1441,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/srtconns/kick/{id}:
|
||||
/v3/srtconns/kick/{id}:
|
||||
post:
|
||||
operationId: srtConnsKick
|
||||
summary: kicks out a SRT connection from the server.
|
||||
@@ -1329,7 +1463,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/webrtcsessions/list:
|
||||
/v3/webrtcsessions/list:
|
||||
get:
|
||||
operationId: webrtcSessionsList
|
||||
summary: returns all WebRTC sessions.
|
||||
@@ -1353,13 +1487,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/WebRTCSessionsList'
|
||||
$ref: '#/components/schemas/WebRTCSessionList'
|
||||
'400':
|
||||
description: invalid request.
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/webrtcsessions/get/{id}:
|
||||
/v3/webrtcsessions/get/{id}:
|
||||
get:
|
||||
operationId: webrtcSessionsGet
|
||||
summary: returns a WebRTC session.
|
||||
@@ -1385,7 +1519,7 @@ paths:
|
||||
'500':
|
||||
description: internal server error.
|
||||
|
||||
/v2/webrtcsessions/kick/{id}:
|
||||
/v3/webrtcsessions/kick/{id}:
|
||||
post:
|
||||
operationId: webrtcSessionsKick
|
||||
summary: kicks out a WebRTC session from the server.
|
||||
|
||||
@@ -59,8 +59,8 @@ func (d *AuthMethods) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *AuthMethods) UnmarshalEnv(s string) error {
|
||||
byts, _ := json.Marshal(strings.Split(s, ","))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *AuthMethods) UnmarshalEnv(_ string, v string) error {
|
||||
byts, _ := json.Marshal(strings.Split(v, ","))
|
||||
return d.UnmarshalJSON(byts)
|
||||
}
|
||||
|
||||
+302
-202
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
)
|
||||
|
||||
func getSortedKeys(paths map[string]*PathConf) []string {
|
||||
func sortedKeys(paths map[string]*OptionalPath) []string {
|
||||
ret := make([]string, len(paths))
|
||||
i := 0
|
||||
for name := range paths {
|
||||
@@ -41,45 +42,6 @@ func firstThatExists(paths []string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func loadFromFile(fpath string, defaultConfPaths []string, conf *Conf) (string, error) {
|
||||
if fpath == "" {
|
||||
fpath = firstThatExists(defaultConfPaths)
|
||||
|
||||
// when the configuration file is not explicitly set,
|
||||
// it is optional. Load defaults.
|
||||
if fpath == "" {
|
||||
conf.UnmarshalJSON(nil) //nolint:errcheck
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
byts, err := os.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if key, ok := os.LookupEnv("RTSP_CONFKEY"); ok { // legacy format
|
||||
byts, err = decrypt.Decrypt(key, byts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if key, ok := os.LookupEnv("MTX_CONFKEY"); ok {
|
||||
byts, err = decrypt.Decrypt(key, byts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
err = yaml.Load(byts, conf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fpath, nil
|
||||
}
|
||||
|
||||
func contains(list []headers.AuthMethod, item headers.AuthMethod) bool {
|
||||
for _, i := range list {
|
||||
if i == item {
|
||||
@@ -89,6 +51,33 @@ func contains(list []headers.AuthMethod, item headers.AuthMethod) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func copyStructFields(dest interface{}, source interface{}) {
|
||||
rvsource := reflect.ValueOf(source).Elem()
|
||||
rvdest := reflect.ValueOf(dest)
|
||||
nf := rvsource.NumField()
|
||||
var zero reflect.Value
|
||||
|
||||
for i := 0; i < nf; i++ {
|
||||
fnew := rvsource.Field(i)
|
||||
f := rvdest.Elem().FieldByName(rvsource.Type().Field(i).Name)
|
||||
if f == zero {
|
||||
continue
|
||||
}
|
||||
|
||||
if fnew.Kind() == reflect.Pointer {
|
||||
if !fnew.IsNil() {
|
||||
if f.Kind() == reflect.Ptr {
|
||||
f.Set(fnew)
|
||||
} else {
|
||||
f.Set(fnew.Elem())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f.Set(fnew)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Conf is a configuration.
|
||||
type Conf struct {
|
||||
// General
|
||||
@@ -97,7 +86,7 @@ type Conf struct {
|
||||
LogFile string `json:"logFile"`
|
||||
ReadTimeout StringDuration `json:"readTimeout"`
|
||||
WriteTimeout StringDuration `json:"writeTimeout"`
|
||||
ReadBufferCount int `json:"readBufferCount"` // deprecated
|
||||
ReadBufferCount *int `json:"readBufferCount,omitempty"` // deprecated
|
||||
WriteQueueSize int `json:"writeQueueSize"`
|
||||
UDPMaxPayloadSize int `json:"udpMaxPayloadSize"`
|
||||
ExternalAuthenticationURL string `json:"externalAuthenticationURL"`
|
||||
@@ -113,7 +102,7 @@ type Conf struct {
|
||||
|
||||
// RTSP
|
||||
RTSP bool `json:"rtsp"`
|
||||
RTSPDisable bool `json:"rtspDisable"` // deprecated
|
||||
RTSPDisable *bool `json:"rtspDisable,omitempty"` // deprecated
|
||||
Protocols Protocols `json:"protocols"`
|
||||
Encryption Encryption `json:"encryption"`
|
||||
RTSPAddress string `json:"rtspAddress"`
|
||||
@@ -129,7 +118,7 @@ type Conf struct {
|
||||
|
||||
// RTMP
|
||||
RTMP bool `json:"rtmp"`
|
||||
RTMPDisable bool `json:"rtmpDisable"` // deprecated
|
||||
RTMPDisable *bool `json:"rtmpDisable,omitempty"` // deprecated
|
||||
RTMPAddress string `json:"rtmpAddress"`
|
||||
RTMPEncryption Encryption `json:"rtmpEncryption"`
|
||||
RTMPSAddress string `json:"rtmpsAddress"`
|
||||
@@ -138,7 +127,7 @@ type Conf struct {
|
||||
|
||||
// HLS
|
||||
HLS bool `json:"hls"`
|
||||
HLSDisable bool `json:"hlsDisable"` // depreacted
|
||||
HLSDisable *bool `json:"hlsDisable,omitempty"` // depreacted
|
||||
HLSAddress string `json:"hlsAddress"`
|
||||
HLSEncryption bool `json:"hlsEncryption"`
|
||||
HLSServerKey string `json:"hlsServerKey"`
|
||||
@@ -155,14 +144,14 @@ type Conf struct {
|
||||
|
||||
// WebRTC
|
||||
WebRTC bool `json:"webrtc"`
|
||||
WebRTCDisable bool `json:"webrtcDisable"` // deprecated
|
||||
WebRTCDisable *bool `json:"webrtcDisable,omitempty"` // deprecated
|
||||
WebRTCAddress string `json:"webrtcAddress"`
|
||||
WebRTCEncryption bool `json:"webrtcEncryption"`
|
||||
WebRTCServerKey string `json:"webrtcServerKey"`
|
||||
WebRTCServerCert string `json:"webrtcServerCert"`
|
||||
WebRTCAllowOrigin string `json:"webrtcAllowOrigin"`
|
||||
WebRTCTrustedProxies IPsOrCIDRs `json:"webrtcTrustedProxies"`
|
||||
WebRTCICEServers []string `json:"webrtcICEServers"` // deprecated
|
||||
WebRTCICEServers *[]string `json:"webrtcICEServers,omitempty"` // deprecated
|
||||
WebRTCICEServers2 []WebRTCICEServer `json:"webrtcICEServers2"`
|
||||
WebRTCICEInterfaces []string `json:"webrtcICEInterfaces"`
|
||||
WebRTCICEHostNAT1To1IPs []string `json:"webrtcICEHostNAT1To1IPs"`
|
||||
@@ -181,166 +170,16 @@ type Conf struct {
|
||||
RecordSegmentDuration StringDuration `json:"recordSegmentDuration"`
|
||||
RecordDeleteAfter StringDuration `json:"recordDeleteAfter"`
|
||||
|
||||
// Path defaults
|
||||
PathDefaults Path `json:"pathDefaults"`
|
||||
|
||||
// Paths
|
||||
Paths map[string]*PathConf `json:"paths"`
|
||||
OptionalPaths map[string]*OptionalPath `json:"paths"`
|
||||
Paths map[string]*Path `json:"-"`
|
||||
}
|
||||
|
||||
// Load loads a Conf.
|
||||
func Load(fpath string, defaultConfPaths []string) (*Conf, string, error) {
|
||||
conf := &Conf{}
|
||||
|
||||
fpath, err := loadFromFile(fpath, defaultConfPaths, conf)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = env.Load("RTSP", conf) // legacy prefix
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = env.Load("MTX", conf)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = conf.Check()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return conf, fpath, nil
|
||||
}
|
||||
|
||||
// Clone clones the configuration.
|
||||
func (conf Conf) Clone() *Conf {
|
||||
enc, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var dest Conf
|
||||
err = json.Unmarshal(enc, &dest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &dest
|
||||
}
|
||||
|
||||
// Check checks the configuration for errors.
|
||||
func (conf *Conf) Check() error {
|
||||
func (conf *Conf) setDefaults() {
|
||||
// General
|
||||
|
||||
if conf.ReadBufferCount != 0 {
|
||||
conf.WriteQueueSize = conf.ReadBufferCount
|
||||
}
|
||||
if (conf.WriteQueueSize & (conf.WriteQueueSize - 1)) != 0 {
|
||||
return fmt.Errorf("'writeQueueSize' must be a power of two")
|
||||
}
|
||||
if conf.UDPMaxPayloadSize > 1472 {
|
||||
return fmt.Errorf("'udpMaxPayloadSize' must be less than 1472")
|
||||
}
|
||||
if conf.ExternalAuthenticationURL != "" {
|
||||
if !strings.HasPrefix(conf.ExternalAuthenticationURL, "http://") &&
|
||||
!strings.HasPrefix(conf.ExternalAuthenticationURL, "https://") {
|
||||
return fmt.Errorf("'externalAuthenticationURL' must be a HTTP URL")
|
||||
}
|
||||
|
||||
if contains(conf.AuthMethods, headers.AuthDigest) {
|
||||
return fmt.Errorf("'externalAuthenticationURL' can't be used when 'digest' is in authMethods")
|
||||
}
|
||||
}
|
||||
|
||||
// RTSP
|
||||
|
||||
if conf.RTSPDisable {
|
||||
conf.RTSP = false
|
||||
}
|
||||
if conf.Encryption == EncryptionStrict {
|
||||
if _, ok := conf.Protocols[Protocol(gortsplib.TransportUDP)]; ok {
|
||||
return fmt.Errorf("strict encryption can't be used with the UDP transport protocol")
|
||||
}
|
||||
if _, ok := conf.Protocols[Protocol(gortsplib.TransportUDPMulticast)]; ok {
|
||||
return fmt.Errorf("strict encryption can't be used with the UDP-multicast transport protocol")
|
||||
}
|
||||
}
|
||||
|
||||
// RTMP
|
||||
|
||||
if conf.RTMPDisable {
|
||||
conf.RTMP = false
|
||||
}
|
||||
|
||||
// HLS
|
||||
|
||||
if conf.HLSDisable {
|
||||
conf.HLS = false
|
||||
}
|
||||
|
||||
// WebRTC
|
||||
|
||||
if conf.WebRTCDisable {
|
||||
conf.WebRTC = false
|
||||
}
|
||||
for _, server := range conf.WebRTCICEServers {
|
||||
parts := strings.Split(server, ":")
|
||||
if len(parts) == 5 {
|
||||
conf.WebRTCICEServers2 = append(conf.WebRTCICEServers2, WebRTCICEServer{
|
||||
URL: parts[0] + ":" + parts[3] + ":" + parts[4],
|
||||
Username: parts[1],
|
||||
Password: parts[2],
|
||||
})
|
||||
} else {
|
||||
conf.WebRTCICEServers2 = append(conf.WebRTCICEServers2, WebRTCICEServer{
|
||||
URL: server,
|
||||
})
|
||||
}
|
||||
}
|
||||
conf.WebRTCICEServers = nil
|
||||
for _, server := range conf.WebRTCICEServers2 {
|
||||
if !strings.HasPrefix(server.URL, "stun:") &&
|
||||
!strings.HasPrefix(server.URL, "turn:") &&
|
||||
!strings.HasPrefix(server.URL, "turns:") {
|
||||
return fmt.Errorf("invalid ICE server: '%s'", server.URL)
|
||||
}
|
||||
}
|
||||
|
||||
// Record
|
||||
|
||||
if conf.RecordFormat != "fmp4" {
|
||||
return fmt.Errorf("unsupported record format '%s'", conf.RecordFormat)
|
||||
}
|
||||
|
||||
// do not add automatically "all", since user may want to
|
||||
// initialize all paths through API or hot reloading.
|
||||
if conf.Paths == nil {
|
||||
conf.Paths = make(map[string]*PathConf)
|
||||
}
|
||||
|
||||
for _, name := range getSortedKeys(conf.Paths) {
|
||||
pconf := conf.Paths[name]
|
||||
if pconf == nil {
|
||||
pconf = &PathConf{}
|
||||
// load defaults
|
||||
pconf.UnmarshalJSON(nil) //nolint:errcheck
|
||||
conf.Paths[name] = pconf
|
||||
}
|
||||
|
||||
err := pconf.check(conf, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler. It is used to:
|
||||
// - force DisallowUnknownFields
|
||||
// - set default values
|
||||
func (conf *Conf) UnmarshalJSON(b []byte) error {
|
||||
// general
|
||||
conf.LogLevel = LogLevel(logger.Info)
|
||||
conf.LogDestinations = LogDestinations{logger.DestinationStdout}
|
||||
conf.LogFile = "mediamtx.log"
|
||||
@@ -410,8 +249,269 @@ func (conf *Conf) UnmarshalJSON(b []byte) error {
|
||||
conf.RecordSegmentDuration = 3600 * StringDuration(time.Second)
|
||||
conf.RecordDeleteAfter = 24 * 3600 * StringDuration(time.Second)
|
||||
|
||||
conf.PathDefaults.setDefaults()
|
||||
}
|
||||
|
||||
// Load loads a Conf.
|
||||
func Load(fpath string, defaultConfPaths []string) (*Conf, string, error) {
|
||||
conf := &Conf{}
|
||||
|
||||
fpath, err := conf.loadFromFile(fpath, defaultConfPaths)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = env.Load("RTSP", conf) // legacy prefix
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = env.Load("MTX", conf)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = conf.Check()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return conf, fpath, nil
|
||||
}
|
||||
|
||||
func (conf *Conf) loadFromFile(fpath string, defaultConfPaths []string) (string, error) {
|
||||
if fpath == "" {
|
||||
fpath = firstThatExists(defaultConfPaths)
|
||||
|
||||
// when the configuration file is not explicitly set,
|
||||
// it is optional.
|
||||
if fpath == "" {
|
||||
conf.setDefaults()
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
byts, err := os.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if key, ok := os.LookupEnv("RTSP_CONFKEY"); ok { // legacy format
|
||||
byts, err = decrypt.Decrypt(key, byts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if key, ok := os.LookupEnv("MTX_CONFKEY"); ok {
|
||||
byts, err = decrypt.Decrypt(key, byts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
err = yaml.Load(byts, conf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fpath, nil
|
||||
}
|
||||
|
||||
// Clone clones the configuration.
|
||||
func (conf Conf) Clone() *Conf {
|
||||
enc, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var dest Conf
|
||||
err = json.Unmarshal(enc, &dest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &dest
|
||||
}
|
||||
|
||||
// Check checks the configuration for errors.
|
||||
func (conf *Conf) Check() error {
|
||||
// General
|
||||
|
||||
if conf.ReadBufferCount != nil {
|
||||
conf.WriteQueueSize = *conf.ReadBufferCount
|
||||
}
|
||||
if (conf.WriteQueueSize & (conf.WriteQueueSize - 1)) != 0 {
|
||||
return fmt.Errorf("'writeQueueSize' must be a power of two")
|
||||
}
|
||||
if conf.UDPMaxPayloadSize > 1472 {
|
||||
return fmt.Errorf("'udpMaxPayloadSize' must be less than 1472")
|
||||
}
|
||||
if conf.ExternalAuthenticationURL != "" {
|
||||
if !strings.HasPrefix(conf.ExternalAuthenticationURL, "http://") &&
|
||||
!strings.HasPrefix(conf.ExternalAuthenticationURL, "https://") {
|
||||
return fmt.Errorf("'externalAuthenticationURL' must be a HTTP URL")
|
||||
}
|
||||
|
||||
if contains(conf.AuthMethods, headers.AuthDigest) {
|
||||
return fmt.Errorf("'externalAuthenticationURL' can't be used when 'digest' is in authMethods")
|
||||
}
|
||||
}
|
||||
|
||||
// RTSP
|
||||
|
||||
if conf.RTSPDisable != nil {
|
||||
conf.RTSP = !*conf.RTSPDisable
|
||||
}
|
||||
if conf.Encryption == EncryptionStrict {
|
||||
if _, ok := conf.Protocols[Protocol(gortsplib.TransportUDP)]; ok {
|
||||
return fmt.Errorf("strict encryption can't be used with the UDP transport protocol")
|
||||
}
|
||||
if _, ok := conf.Protocols[Protocol(gortsplib.TransportUDPMulticast)]; ok {
|
||||
return fmt.Errorf("strict encryption can't be used with the UDP-multicast transport protocol")
|
||||
}
|
||||
}
|
||||
|
||||
// RTMP
|
||||
|
||||
if conf.RTMPDisable != nil {
|
||||
conf.RTMP = !*conf.RTMPDisable
|
||||
}
|
||||
|
||||
// HLS
|
||||
|
||||
if conf.HLSDisable != nil {
|
||||
conf.HLS = !*conf.HLSDisable
|
||||
}
|
||||
|
||||
// WebRTC
|
||||
|
||||
if conf.WebRTCDisable != nil {
|
||||
conf.WebRTC = !*conf.WebRTCDisable
|
||||
}
|
||||
if conf.WebRTCICEServers != nil {
|
||||
for _, server := range *conf.WebRTCICEServers {
|
||||
parts := strings.Split(server, ":")
|
||||
if len(parts) == 5 {
|
||||
conf.WebRTCICEServers2 = append(conf.WebRTCICEServers2, WebRTCICEServer{
|
||||
URL: parts[0] + ":" + parts[3] + ":" + parts[4],
|
||||
Username: parts[1],
|
||||
Password: parts[2],
|
||||
})
|
||||
} else {
|
||||
conf.WebRTCICEServers2 = append(conf.WebRTCICEServers2, WebRTCICEServer{
|
||||
URL: server,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, server := range conf.WebRTCICEServers2 {
|
||||
if !strings.HasPrefix(server.URL, "stun:") &&
|
||||
!strings.HasPrefix(server.URL, "turn:") &&
|
||||
!strings.HasPrefix(server.URL, "turns:") {
|
||||
return fmt.Errorf("invalid ICE server: '%s'", server.URL)
|
||||
}
|
||||
}
|
||||
|
||||
// Record
|
||||
|
||||
if conf.RecordFormat != "fmp4" {
|
||||
return fmt.Errorf("unsupported record format '%s'", conf.RecordFormat)
|
||||
}
|
||||
|
||||
conf.Paths = make(map[string]*Path)
|
||||
|
||||
for _, name := range sortedKeys(conf.OptionalPaths) {
|
||||
optional := conf.OptionalPaths[name]
|
||||
if optional == nil {
|
||||
optional = &OptionalPath{
|
||||
Values: newOptionalPathValues(),
|
||||
}
|
||||
}
|
||||
|
||||
pconf := newPath(&conf.PathDefaults, optional)
|
||||
conf.Paths[name] = pconf
|
||||
|
||||
err := pconf.check(conf, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (conf *Conf) UnmarshalJSON(b []byte) error {
|
||||
conf.setDefaults()
|
||||
|
||||
type alias Conf
|
||||
d := json.NewDecoder(bytes.NewReader(b))
|
||||
d.DisallowUnknownFields()
|
||||
return d.Decode((*alias)(conf))
|
||||
}
|
||||
|
||||
// Global returns the global part of Conf.
|
||||
func (conf *Conf) Global() *Global {
|
||||
g := &Global{
|
||||
Values: newGlobalValues(),
|
||||
}
|
||||
copyStructFields(g.Values, conf)
|
||||
return g
|
||||
}
|
||||
|
||||
// PatchGlobal patches the global configuration.
|
||||
func (conf *Conf) PatchGlobal(optional *OptionalGlobal) {
|
||||
copyStructFields(conf, optional.Values)
|
||||
}
|
||||
|
||||
// PatchPathDefaults patches path default settings.
|
||||
func (conf *Conf) PatchPathDefaults(optional *OptionalPath) {
|
||||
copyStructFields(&conf.PathDefaults, optional.Values)
|
||||
}
|
||||
|
||||
// AddPath adds a path.
|
||||
func (conf *Conf) AddPath(name string, p *OptionalPath) error {
|
||||
if _, ok := conf.OptionalPaths[name]; ok {
|
||||
return fmt.Errorf("path already exists")
|
||||
}
|
||||
|
||||
if conf.OptionalPaths == nil {
|
||||
conf.OptionalPaths = make(map[string]*OptionalPath)
|
||||
}
|
||||
|
||||
conf.OptionalPaths[name] = p
|
||||
return nil
|
||||
}
|
||||
|
||||
// PatchPath patches a path.
|
||||
func (conf *Conf) PatchPath(name string, optional2 *OptionalPath) error {
|
||||
optional, ok := conf.OptionalPaths[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("path not found")
|
||||
}
|
||||
|
||||
copyStructFields(optional.Values, optional2.Values)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplacePath replaces a path.
|
||||
func (conf *Conf) ReplacePath(name string, optional2 *OptionalPath) error {
|
||||
_, ok := conf.OptionalPaths[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("path not found")
|
||||
}
|
||||
|
||||
conf.OptionalPaths[name] = optional2
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePath removes a path.
|
||||
func (conf *Conf) RemovePath(name string) error {
|
||||
if _, ok := conf.OptionalPaths[name]; !ok {
|
||||
return fmt.Errorf("path not found")
|
||||
}
|
||||
|
||||
delete(conf.OptionalPaths, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
+23
-59
@@ -47,7 +47,7 @@ func TestConfFromFile(t *testing.T) {
|
||||
|
||||
pa, ok := conf.Paths["cam1"]
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, &PathConf{
|
||||
require.Equal(t, &Path{
|
||||
Source: "publisher",
|
||||
SourceOnDemandStartTimeout: 10 * StringDuration(time.Second),
|
||||
SourceOnDemandCloseAfter: 10 * StringDuration(time.Second),
|
||||
@@ -107,11 +107,21 @@ func TestConfFromFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfFromFileAndEnv(t *testing.T) {
|
||||
// global parameter
|
||||
os.Setenv("RTSP_PROTOCOLS", "tcp")
|
||||
defer os.Unsetenv("RTSP_PROTOCOLS")
|
||||
|
||||
// path parameter
|
||||
os.Setenv("MTX_PATHS_CAM1_SOURCE", "rtsp://testing")
|
||||
defer os.Unsetenv("MTX_PATHS_CAM1_SOURCE")
|
||||
|
||||
os.Setenv("RTSP_PROTOCOLS", "tcp")
|
||||
defer os.Unsetenv("RTSP_PROTOCOLS")
|
||||
// deprecated global parameter
|
||||
os.Setenv("MTX_RTMPDISABLE", "yes")
|
||||
defer os.Unsetenv("MTX_RTMPDISABLE")
|
||||
|
||||
// deprecated path parameter
|
||||
os.Setenv("MTX_PATHS_CAM2_DISABLEPUBLISHEROVERRIDE", "yes")
|
||||
defer os.Unsetenv("MTX_PATHS_CAM2_DISABLEPUBLISHEROVERRIDE")
|
||||
|
||||
tmpf, err := writeTempFile([]byte("{}"))
|
||||
require.NoError(t, err)
|
||||
@@ -122,36 +132,15 @@ func TestConfFromFileAndEnv(t *testing.T) {
|
||||
require.Equal(t, tmpf, confPath)
|
||||
|
||||
require.Equal(t, Protocols{Protocol(gortsplib.TransportTCP): {}}, conf.Protocols)
|
||||
require.Equal(t, false, conf.RTMP)
|
||||
|
||||
pa, ok := conf.Paths["cam1"]
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, &PathConf{
|
||||
Source: "rtsp://testing",
|
||||
SourceOnDemandStartTimeout: 10 * StringDuration(time.Second),
|
||||
SourceOnDemandCloseAfter: 10 * StringDuration(time.Second),
|
||||
Record: true,
|
||||
OverridePublisher: true,
|
||||
RPICameraWidth: 1920,
|
||||
RPICameraHeight: 1080,
|
||||
RPICameraContrast: 1,
|
||||
RPICameraSaturation: 1,
|
||||
RPICameraSharpness: 1,
|
||||
RPICameraExposure: "normal",
|
||||
RPICameraAWB: "auto",
|
||||
RPICameraDenoise: "off",
|
||||
RPICameraMetering: "centre",
|
||||
RPICameraFPS: 30,
|
||||
RPICameraIDRPeriod: 60,
|
||||
RPICameraBitrate: 1000000,
|
||||
RPICameraProfile: "main",
|
||||
RPICameraLevel: "4.1",
|
||||
RPICameraAfMode: "auto",
|
||||
RPICameraAfRange: "normal",
|
||||
RPICameraAfSpeed: "normal",
|
||||
RPICameraTextOverlay: "%Y-%m-%d %H:%M:%S - MediaMTX",
|
||||
RunOnDemandStartTimeout: 10 * StringDuration(time.Second),
|
||||
RunOnDemandCloseAfter: 10 * StringDuration(time.Second),
|
||||
}, pa)
|
||||
require.Equal(t, "rtsp://testing", pa.Source)
|
||||
|
||||
pa, ok = conf.Paths["cam2"]
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, false, pa.OverridePublisher)
|
||||
}
|
||||
|
||||
func TestConfFromEnvOnly(t *testing.T) {
|
||||
@@ -164,33 +153,7 @@ func TestConfFromEnvOnly(t *testing.T) {
|
||||
|
||||
pa, ok := conf.Paths["cam1"]
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, &PathConf{
|
||||
Source: "rtsp://testing",
|
||||
SourceOnDemandStartTimeout: 10 * StringDuration(time.Second),
|
||||
SourceOnDemandCloseAfter: 10 * StringDuration(time.Second),
|
||||
Record: true,
|
||||
OverridePublisher: true,
|
||||
RPICameraWidth: 1920,
|
||||
RPICameraHeight: 1080,
|
||||
RPICameraContrast: 1,
|
||||
RPICameraSaturation: 1,
|
||||
RPICameraSharpness: 1,
|
||||
RPICameraExposure: "normal",
|
||||
RPICameraAWB: "auto",
|
||||
RPICameraDenoise: "off",
|
||||
RPICameraMetering: "centre",
|
||||
RPICameraFPS: 30,
|
||||
RPICameraIDRPeriod: 60,
|
||||
RPICameraBitrate: 1000000,
|
||||
RPICameraProfile: "main",
|
||||
RPICameraLevel: "4.1",
|
||||
RPICameraAfMode: "auto",
|
||||
RPICameraAfRange: "normal",
|
||||
RPICameraAfSpeed: "normal",
|
||||
RPICameraTextOverlay: "%Y-%m-%d %H:%M:%S - MediaMTX",
|
||||
RunOnDemandStartTimeout: 10 * StringDuration(time.Second),
|
||||
RunOnDemandCloseAfter: 10 * StringDuration(time.Second),
|
||||
}, pa)
|
||||
require.Equal(t, "rtsp://testing", pa.Source)
|
||||
}
|
||||
|
||||
func TestConfEncryption(t *testing.T) {
|
||||
@@ -299,7 +262,7 @@ func TestConfErrors(t *testing.T) {
|
||||
" source: rpiCamera\n" +
|
||||
" cam2:\n" +
|
||||
" source: rpiCamera\n",
|
||||
"'rpiCamera' with same camera ID 0 is used as source in two paths, 'cam1' and 'cam2'",
|
||||
"'rpiCamera' with same camera ID 0 is used as source in two paths, 'cam2' and 'cam1'",
|
||||
},
|
||||
{
|
||||
"invalid srt publish passphrase",
|
||||
@@ -332,7 +295,8 @@ func TestSampleConfFile(t *testing.T) {
|
||||
conf1, confPath1, err := Load("../../mediamtx.yml", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "../../mediamtx.yml", confPath1)
|
||||
delete(conf1.Paths, "all")
|
||||
conf1.Paths = make(map[string]*Path)
|
||||
conf1.OptionalPaths = nil
|
||||
|
||||
conf2, confPath2, err := Load("", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -36,7 +36,7 @@ func (d *Credential) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *Credential) UnmarshalEnv(s string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + s + `"`))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *Credential) UnmarshalEnv(_ string, v string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func (d *Encryption) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *Encryption) UnmarshalEnv(s string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + s + `"`))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *Encryption) UnmarshalEnv(_ string, v string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
Vendored
+58
-30
@@ -2,7 +2,6 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -10,8 +9,9 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type envUnmarshaler interface {
|
||||
UnmarshalEnv(string) error
|
||||
// Unmarshaler can be implemented to override the unmarshaling process.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalEnv(prefix string, v string) error
|
||||
}
|
||||
|
||||
func envHasAtLeastAKeyWithPrefix(env map[string]string, prefix string) bool {
|
||||
@@ -23,12 +23,19 @@ func envHasAtLeastAKeyWithPrefix(env map[string]string, prefix string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func loadEnvInternal(env map[string]string, prefix string, rv reflect.Value) error {
|
||||
rt := rv.Type()
|
||||
func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) error {
|
||||
if prv.Kind() != reflect.Pointer {
|
||||
return loadEnvInternal(env, prefix, prv.Addr())
|
||||
}
|
||||
|
||||
if i, ok := rv.Addr().Interface().(envUnmarshaler); ok {
|
||||
if i, ok := prv.Interface().(Unmarshaler); ok {
|
||||
if ev, ok := env[prefix]; ok {
|
||||
err := i.UnmarshalEnv(ev)
|
||||
err := i.UnmarshalEnv(prefix, ev)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", prefix, err)
|
||||
}
|
||||
} else if envHasAtLeastAKeyWithPrefix(env, prefix) {
|
||||
err := i.UnmarshalEnv(prefix, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", prefix, err)
|
||||
}
|
||||
@@ -36,51 +43,68 @@ func loadEnvInternal(env map[string]string, prefix string, rv reflect.Value) err
|
||||
return nil
|
||||
}
|
||||
|
||||
rt := prv.Type().Elem()
|
||||
|
||||
switch rt {
|
||||
case reflect.TypeOf(""):
|
||||
if ev, ok := env[prefix]; ok {
|
||||
rv.SetString(ev)
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
prv.Elem().SetString(ev)
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.TypeOf(int(0)):
|
||||
if ev, ok := env[prefix]; ok {
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
iv, err := strconv.ParseInt(ev, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", prefix, err)
|
||||
}
|
||||
rv.SetInt(iv)
|
||||
prv.Elem().SetInt(iv)
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.TypeOf(uint64(0)):
|
||||
if ev, ok := env[prefix]; ok {
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
iv, err := strconv.ParseUint(ev, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", prefix, err)
|
||||
}
|
||||
rv.SetUint(iv)
|
||||
prv.Elem().SetUint(iv)
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.TypeOf(float64(0)):
|
||||
if ev, ok := env[prefix]; ok {
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
iv, err := strconv.ParseFloat(ev, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", prefix, err)
|
||||
}
|
||||
rv.SetFloat(iv)
|
||||
prv.Elem().SetFloat(iv)
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.TypeOf(bool(false)):
|
||||
if ev, ok := env[prefix]; ok {
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
switch strings.ToLower(ev) {
|
||||
case "yes", "true":
|
||||
rv.SetBool(true)
|
||||
prv.Elem().SetBool(true)
|
||||
|
||||
case "no", "false":
|
||||
rv.SetBool(false)
|
||||
prv.Elem().SetBool(false)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%s: invalid value '%s'", prefix, ev)
|
||||
@@ -107,20 +131,16 @@ func loadEnvInternal(env map[string]string, prefix string, rv reflect.Value) err
|
||||
}
|
||||
|
||||
// initialize only if there's at least one key
|
||||
if rv.IsNil() {
|
||||
rv.Set(reflect.MakeMap(rt))
|
||||
if prv.Elem().IsNil() {
|
||||
prv.Elem().Set(reflect.MakeMap(rt))
|
||||
}
|
||||
|
||||
mapKeyLower := strings.ToLower(mapKey)
|
||||
nv := rv.MapIndex(reflect.ValueOf(mapKeyLower))
|
||||
nv := prv.Elem().MapIndex(reflect.ValueOf(mapKeyLower))
|
||||
zero := reflect.Value{}
|
||||
if nv == zero {
|
||||
nv = reflect.New(rt.Elem().Elem())
|
||||
if unm, ok := nv.Interface().(json.Unmarshaler); ok {
|
||||
// load defaults
|
||||
unm.UnmarshalJSON(nil) //nolint:errcheck
|
||||
}
|
||||
rv.SetMapIndex(reflect.ValueOf(mapKeyLower), nv)
|
||||
prv.Elem().SetMapIndex(reflect.ValueOf(mapKeyLower), nv)
|
||||
}
|
||||
|
||||
err := loadEnvInternal(env, prefix+"_"+mapKey, nv.Elem())
|
||||
@@ -134,13 +154,15 @@ func loadEnvInternal(env map[string]string, prefix string, rv reflect.Value) err
|
||||
flen := rt.NumField()
|
||||
for i := 0; i < flen; i++ {
|
||||
f := rt.Field(i)
|
||||
jsonTag := f.Tag.Get("json")
|
||||
|
||||
// load only public fields
|
||||
if f.Tag.Get("json") == "-" {
|
||||
if jsonTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
err := loadEnvInternal(env, prefix+"_"+strings.ToUpper(f.Name), rv.Field(i))
|
||||
err := loadEnvInternal(env, prefix+"_"+
|
||||
strings.ToUpper(strings.TrimSuffix(jsonTag, ",omitempty")), prv.Elem().Field(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -148,20 +170,26 @@ func loadEnvInternal(env map[string]string, prefix string, rv reflect.Value) err
|
||||
return nil
|
||||
|
||||
case reflect.Slice:
|
||||
if rt.Elem() == reflect.TypeOf("") {
|
||||
switch {
|
||||
case rt.Elem() == reflect.TypeOf(""):
|
||||
if ev, ok := env[prefix]; ok {
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
if ev == "" {
|
||||
rv.Set(reflect.MakeSlice(rv.Type(), 0, 0))
|
||||
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0))
|
||||
} else {
|
||||
rv.Set(reflect.ValueOf(strings.Split(ev, ",")))
|
||||
prv.Elem().Set(reflect.ValueOf(strings.Split(ev, ",")))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if rt.Elem().Kind() == reflect.Struct {
|
||||
case rt.Elem().Kind() == reflect.Struct:
|
||||
if ev, ok := env[prefix]; ok && ev == "" { // special case: empty list
|
||||
rv.Set(reflect.MakeSlice(rv.Type(), 0, 0))
|
||||
if prv.IsNil() {
|
||||
prv.Set(reflect.New(rt))
|
||||
}
|
||||
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0))
|
||||
} else {
|
||||
for i := 0; ; i++ {
|
||||
itemPrefix := prefix + "_" + strconv.FormatInt(int64(i), 10)
|
||||
@@ -175,7 +203,7 @@ func loadEnvInternal(env map[string]string, prefix string, rv reflect.Value) err
|
||||
return err
|
||||
}
|
||||
|
||||
rv.Set(reflect.Append(rv, elem.Elem()))
|
||||
prv.Elem().Set(reflect.Append(prv.Elem(), elem.Elem()))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
Vendored
+19
-19
@@ -10,12 +10,12 @@ import (
|
||||
)
|
||||
|
||||
type subStruct struct {
|
||||
MyParam int
|
||||
MyParam int `json:"myParam"`
|
||||
}
|
||||
|
||||
type mapEntry struct {
|
||||
MyValue string
|
||||
MyStruct subStruct
|
||||
MyValue string `json:"myValue"`
|
||||
MyStruct subStruct `json:"myStruct"`
|
||||
}
|
||||
|
||||
type myDuration time.Duration
|
||||
@@ -35,28 +35,28 @@ func (d *myDuration) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *myDuration) UnmarshalEnv(s string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + s + `"`))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *myDuration) UnmarshalEnv(_ string, v string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
type mySubStruct struct {
|
||||
URL string
|
||||
Username string
|
||||
Password string
|
||||
URL string `json:"url"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type testStruct struct {
|
||||
MyString string
|
||||
MyInt int
|
||||
MyFloat float64
|
||||
MyBool bool
|
||||
MyDuration myDuration
|
||||
MyMap map[string]*mapEntry
|
||||
MySlice []string
|
||||
MySliceEmpty []string
|
||||
MySliceSubStruct []mySubStruct
|
||||
MySliceSubStructEmpty []mySubStruct
|
||||
MyString string `json:"myString"`
|
||||
MyInt int `json:"myInt"`
|
||||
MyFloat float64 `json:"myFloat"`
|
||||
MyBool bool `json:"myBool"`
|
||||
MyDuration myDuration `json:"myDuration"`
|
||||
MyMap map[string]*mapEntry `json:"myMap"`
|
||||
MySlice []string `json:"mySlice"`
|
||||
MySliceEmpty []string `json:"mySliceEmpty"`
|
||||
MySliceSubStruct []mySubStruct `json:"mySliceSubStruct"`
|
||||
MySliceSubStructEmpty []mySubStruct `json:"mySliceSubStructEmpty"`
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var globalValuesType = func() reflect.Type {
|
||||
var fields []reflect.StructField
|
||||
rt := reflect.TypeOf(Conf{})
|
||||
nf := rt.NumField()
|
||||
|
||||
for i := 0; i < nf; i++ {
|
||||
f := rt.Field(i)
|
||||
j := f.Tag.Get("json")
|
||||
|
||||
if j != "-" && j != "pathDefaults" && j != "paths" {
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: f.Name,
|
||||
Type: f.Type,
|
||||
Tag: f.Tag,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.StructOf(fields)
|
||||
}()
|
||||
|
||||
func newGlobalValues() interface{} {
|
||||
return reflect.New(globalValuesType).Interface()
|
||||
}
|
||||
|
||||
// Global is the global part of Conf.
|
||||
type Global struct {
|
||||
Values interface{}
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (p *Global) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(p.Values)
|
||||
}
|
||||
@@ -55,7 +55,7 @@ func (d *HLSVariant) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *HLSVariant) UnmarshalEnv(s string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + s + `"`))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *HLSVariant) UnmarshalEnv(_ string, v string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ func (d *IPsOrCIDRs) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *IPsOrCIDRs) UnmarshalEnv(s string) error {
|
||||
byts, _ := json.Marshal(strings.Split(s, ","))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *IPsOrCIDRs) UnmarshalEnv(_ string, v string) error {
|
||||
byts, _ := json.Marshal(strings.Split(v, ","))
|
||||
return d.UnmarshalJSON(byts)
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ func (d *LogDestinations) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *LogDestinations) UnmarshalEnv(s string) error {
|
||||
byts, _ := json.Marshal(strings.Split(s, ","))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *LogDestinations) UnmarshalEnv(_ string, v string) error {
|
||||
byts, _ := json.Marshal(strings.Split(v, ","))
|
||||
return d.UnmarshalJSON(byts)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func (d *LogLevel) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *LogLevel) UnmarshalEnv(s string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + s + `"`))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *LogLevel) UnmarshalEnv(_ string, v string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var optionalGlobalValuesType = func() reflect.Type {
|
||||
var fields []reflect.StructField
|
||||
rt := reflect.TypeOf(Conf{})
|
||||
nf := rt.NumField()
|
||||
|
||||
for i := 0; i < nf; i++ {
|
||||
f := rt.Field(i)
|
||||
j := f.Tag.Get("json")
|
||||
|
||||
if j != "-" && j != "pathDefaults" && j != "paths" {
|
||||
if !strings.Contains(j, ",omitempty") {
|
||||
j += ",omitempty"
|
||||
}
|
||||
|
||||
typ := f.Type
|
||||
if typ.Kind() != reflect.Pointer {
|
||||
typ = reflect.PtrTo(typ)
|
||||
}
|
||||
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: f.Name,
|
||||
Type: typ,
|
||||
Tag: reflect.StructTag(`json:"` + j + `"`),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.StructOf(fields)
|
||||
}()
|
||||
|
||||
func newOptionalGlobalValues() interface{} {
|
||||
return reflect.New(optionalGlobalValuesType).Interface()
|
||||
}
|
||||
|
||||
// OptionalGlobal is a Conf whose values can all be optional.
|
||||
type OptionalGlobal struct {
|
||||
Values interface{}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (p *OptionalGlobal) UnmarshalJSON(b []byte) error {
|
||||
p.Values = newOptionalGlobalValues()
|
||||
d := json.NewDecoder(bytes.NewReader(b))
|
||||
d.DisallowUnknownFields()
|
||||
return d.Decode(p.Values)
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (p *OptionalGlobal) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(p.Values)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/conf/env"
|
||||
)
|
||||
|
||||
var optionalPathValuesType = func() reflect.Type {
|
||||
var fields []reflect.StructField
|
||||
rt := reflect.TypeOf(Path{})
|
||||
nf := rt.NumField()
|
||||
|
||||
for i := 0; i < nf; i++ {
|
||||
f := rt.Field(i)
|
||||
j := f.Tag.Get("json")
|
||||
|
||||
if j != "-" {
|
||||
if !strings.Contains(j, ",omitempty") {
|
||||
j += ",omitempty"
|
||||
}
|
||||
|
||||
typ := f.Type
|
||||
if typ.Kind() != reflect.Pointer {
|
||||
typ = reflect.PtrTo(typ)
|
||||
}
|
||||
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: f.Name,
|
||||
Type: typ,
|
||||
Tag: reflect.StructTag(`json:"` + j + `"`),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.StructOf(fields)
|
||||
}()
|
||||
|
||||
func newOptionalPathValues() interface{} {
|
||||
return reflect.New(optionalPathValuesType).Interface()
|
||||
}
|
||||
|
||||
// OptionalPath is a Path whose values can all be optional.
|
||||
type OptionalPath struct {
|
||||
Values interface{}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (p *OptionalPath) UnmarshalJSON(b []byte) error {
|
||||
p.Values = newOptionalPathValues()
|
||||
d := json.NewDecoder(bytes.NewReader(b))
|
||||
d.DisallowUnknownFields()
|
||||
return d.Decode(p.Values)
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (p *OptionalPath) UnmarshalEnv(prefix string, _ string) error {
|
||||
if p.Values == nil {
|
||||
p.Values = newOptionalPathValues()
|
||||
}
|
||||
return env.Load(prefix, p.Values)
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (p *OptionalPath) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(p.Values)
|
||||
}
|
||||
+73
-75
@@ -1,7 +1,6 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -48,8 +47,8 @@ func srtCheckPassphrase(passphrase string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// PathConf is a path configuration.
|
||||
type PathConf struct {
|
||||
// Path is a path configuration.
|
||||
type Path struct {
|
||||
Regexp *regexp.Regexp `json:"-"`
|
||||
|
||||
// General
|
||||
@@ -72,7 +71,7 @@ type PathConf struct {
|
||||
|
||||
// Publisher
|
||||
OverridePublisher bool `json:"overridePublisher"`
|
||||
DisablePublisherOverride bool `json:"disablePublisherOverride"` // deprecated
|
||||
DisablePublisherOverride *bool `json:"disablePublisherOverride,omitempty"` // deprecated
|
||||
Fallback string `json:"fallback"`
|
||||
SRTPublishPassphrase string `json:"srtPublishPassphrase"`
|
||||
|
||||
@@ -135,7 +134,67 @@ type PathConf struct {
|
||||
RunOnRecordSegmentComplete string `json:"runOnRecordSegmentComplete"`
|
||||
}
|
||||
|
||||
func (pconf *PathConf) check(conf *Conf, name string) error {
|
||||
func (pconf *Path) setDefaults() {
|
||||
// General
|
||||
pconf.Source = "publisher"
|
||||
pconf.SourceOnDemandStartTimeout = 10 * StringDuration(time.Second)
|
||||
pconf.SourceOnDemandCloseAfter = 10 * StringDuration(time.Second)
|
||||
pconf.Record = true
|
||||
|
||||
// Publisher
|
||||
pconf.OverridePublisher = true
|
||||
|
||||
// Raspberry Pi Camera
|
||||
pconf.RPICameraWidth = 1920
|
||||
pconf.RPICameraHeight = 1080
|
||||
pconf.RPICameraContrast = 1
|
||||
pconf.RPICameraSaturation = 1
|
||||
pconf.RPICameraSharpness = 1
|
||||
pconf.RPICameraExposure = "normal"
|
||||
pconf.RPICameraAWB = "auto"
|
||||
pconf.RPICameraDenoise = "off"
|
||||
pconf.RPICameraMetering = "centre"
|
||||
pconf.RPICameraFPS = 30
|
||||
pconf.RPICameraIDRPeriod = 60
|
||||
pconf.RPICameraBitrate = 1000000
|
||||
pconf.RPICameraProfile = "main"
|
||||
pconf.RPICameraLevel = "4.1"
|
||||
pconf.RPICameraAfMode = "auto"
|
||||
pconf.RPICameraAfRange = "normal"
|
||||
pconf.RPICameraAfSpeed = "normal"
|
||||
pconf.RPICameraTextOverlay = "%Y-%m-%d %H:%M:%S - MediaMTX"
|
||||
|
||||
// Hooks
|
||||
pconf.RunOnDemandStartTimeout = 10 * StringDuration(time.Second)
|
||||
pconf.RunOnDemandCloseAfter = 10 * StringDuration(time.Second)
|
||||
}
|
||||
|
||||
func newPath(defaults *Path, partial *OptionalPath) *Path {
|
||||
pconf := &Path{}
|
||||
copyStructFields(pconf, defaults)
|
||||
copyStructFields(pconf, partial.Values)
|
||||
return pconf
|
||||
}
|
||||
|
||||
// Clone clones the configuration.
|
||||
func (pconf Path) Clone() *Path {
|
||||
enc, err := json.Marshal(pconf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var dest Path
|
||||
err = json.Unmarshal(enc, &dest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dest.Regexp = pconf.Regexp
|
||||
|
||||
return &dest
|
||||
}
|
||||
|
||||
func (pconf *Path) check(conf *Conf, name string) error {
|
||||
switch {
|
||||
case name == "all":
|
||||
pconf.Regexp = regexp.MustCompile("^.*$")
|
||||
@@ -147,11 +206,11 @@ func (pconf *PathConf) check(conf *Conf, name string) error {
|
||||
}
|
||||
|
||||
default: // regular expression-based path
|
||||
pathRegexp, err := regexp.Compile(name[1:])
|
||||
regexp, err := regexp.Compile(name[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid regular expression: %s", name[1:])
|
||||
}
|
||||
pconf.Regexp = pathRegexp
|
||||
pconf.Regexp = regexp
|
||||
}
|
||||
|
||||
// General
|
||||
@@ -329,8 +388,8 @@ func (pconf *PathConf) check(conf *Conf, name string) error {
|
||||
|
||||
// Publisher
|
||||
|
||||
if pconf.DisablePublisherOverride {
|
||||
pconf.OverridePublisher = true
|
||||
if pconf.DisablePublisherOverride != nil {
|
||||
pconf.OverridePublisher = !*pconf.DisablePublisherOverride
|
||||
}
|
||||
if pconf.Fallback != "" {
|
||||
if pconf.Source != "publisher" {
|
||||
@@ -408,31 +467,13 @@ func (pconf *PathConf) check(conf *Conf, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal checks whether two PathConfs are equal.
|
||||
func (pconf *PathConf) Equal(other *PathConf) bool {
|
||||
// Equal checks whether two Paths are equal.
|
||||
func (pconf *Path) Equal(other *Path) bool {
|
||||
return reflect.DeepEqual(pconf, other)
|
||||
}
|
||||
|
||||
// Clone clones the configuration.
|
||||
func (pconf PathConf) Clone() *PathConf {
|
||||
enc, err := json.Marshal(pconf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var dest PathConf
|
||||
err = json.Unmarshal(enc, &dest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dest.Regexp = pconf.Regexp
|
||||
|
||||
return &dest
|
||||
}
|
||||
|
||||
// HasStaticSource checks whether the path has a static source.
|
||||
func (pconf PathConf) HasStaticSource() bool {
|
||||
func (pconf Path) HasStaticSource() bool {
|
||||
return strings.HasPrefix(pconf.Source, "rtsp://") ||
|
||||
strings.HasPrefix(pconf.Source, "rtsps://") ||
|
||||
strings.HasPrefix(pconf.Source, "rtmp://") ||
|
||||
@@ -447,54 +488,11 @@ func (pconf PathConf) HasStaticSource() bool {
|
||||
}
|
||||
|
||||
// HasOnDemandStaticSource checks whether the path has a on demand static source.
|
||||
func (pconf PathConf) HasOnDemandStaticSource() bool {
|
||||
func (pconf Path) HasOnDemandStaticSource() bool {
|
||||
return pconf.HasStaticSource() && pconf.SourceOnDemand
|
||||
}
|
||||
|
||||
// HasOnDemandPublisher checks whether the path has a on-demand publisher.
|
||||
func (pconf PathConf) HasOnDemandPublisher() bool {
|
||||
func (pconf Path) HasOnDemandPublisher() bool {
|
||||
return pconf.RunOnDemand != ""
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler. It is used to:
|
||||
// - force DisallowUnknownFields
|
||||
// - set default values
|
||||
func (pconf *PathConf) UnmarshalJSON(b []byte) error {
|
||||
// General
|
||||
pconf.Source = "publisher"
|
||||
pconf.SourceOnDemandStartTimeout = 10 * StringDuration(time.Second)
|
||||
pconf.SourceOnDemandCloseAfter = 10 * StringDuration(time.Second)
|
||||
pconf.Record = true
|
||||
|
||||
// Publisher
|
||||
pconf.OverridePublisher = true
|
||||
|
||||
// Raspberry Pi Camera
|
||||
pconf.RPICameraWidth = 1920
|
||||
pconf.RPICameraHeight = 1080
|
||||
pconf.RPICameraContrast = 1
|
||||
pconf.RPICameraSaturation = 1
|
||||
pconf.RPICameraSharpness = 1
|
||||
pconf.RPICameraExposure = "normal"
|
||||
pconf.RPICameraAWB = "auto"
|
||||
pconf.RPICameraDenoise = "off"
|
||||
pconf.RPICameraMetering = "centre"
|
||||
pconf.RPICameraFPS = 30
|
||||
pconf.RPICameraIDRPeriod = 60
|
||||
pconf.RPICameraBitrate = 1000000
|
||||
pconf.RPICameraProfile = "main"
|
||||
pconf.RPICameraLevel = "4.1"
|
||||
pconf.RPICameraAfMode = "auto"
|
||||
pconf.RPICameraAfRange = "normal"
|
||||
pconf.RPICameraAfSpeed = "normal"
|
||||
pconf.RPICameraTextOverlay = "%Y-%m-%d %H:%M:%S - MediaMTX"
|
||||
|
||||
// Hooks
|
||||
pconf.RunOnDemandStartTimeout = 10 * StringDuration(time.Second)
|
||||
pconf.RunOnDemandCloseAfter = 10 * StringDuration(time.Second)
|
||||
|
||||
type alias PathConf
|
||||
d := json.NewDecoder(bytes.NewReader(b))
|
||||
d.DisallowUnknownFields()
|
||||
return d.Decode((*alias)(pconf))
|
||||
}
|
||||
|
||||
@@ -74,8 +74,8 @@ func (d *Protocols) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *Protocols) UnmarshalEnv(s string) error {
|
||||
byts, _ := json.Marshal(strings.Split(s, ","))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *Protocols) UnmarshalEnv(_ string, v string) error {
|
||||
byts, _ := json.Marshal(strings.Split(v, ","))
|
||||
return d.UnmarshalJSON(byts)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (d *RTSPRangeType) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *RTSPRangeType) UnmarshalEnv(s string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + s + `"`))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *RTSPRangeType) UnmarshalEnv(_ string, v string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (d *SourceProtocol) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *SourceProtocol) UnmarshalEnv(s string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + s + `"`))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *SourceProtocol) UnmarshalEnv(_ string, v string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (d *StringDuration) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (d *StringDuration) UnmarshalEnv(s string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + s + `"`))
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (d *StringDuration) UnmarshalEnv(_ string, v string) error {
|
||||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (s *StringSize) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalEnv implements envUnmarshaler.
|
||||
func (s *StringSize) UnmarshalEnv(v string) error {
|
||||
// UnmarshalEnv implements env.Unmarshaler.
|
||||
func (s *StringSize) UnmarshalEnv(_ string, v string) error {
|
||||
return s.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||
}
|
||||
|
||||
+211
-154
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -24,68 +25,6 @@ func interfaceIsEmpty(i interface{}) bool {
|
||||
return reflect.ValueOf(i).Kind() != reflect.Ptr || reflect.ValueOf(i).IsNil()
|
||||
}
|
||||
|
||||
func fillStruct(dest interface{}, source interface{}) {
|
||||
rvsource := reflect.ValueOf(source).Elem()
|
||||
rvdest := reflect.ValueOf(dest)
|
||||
nf := rvsource.NumField()
|
||||
for i := 0; i < nf; i++ {
|
||||
fnew := rvsource.Field(i)
|
||||
if !fnew.IsNil() {
|
||||
f := rvdest.Elem().FieldByName(rvsource.Type().Field(i).Name)
|
||||
if f.Kind() == reflect.Ptr {
|
||||
f.Set(fnew)
|
||||
} else {
|
||||
f.Set(fnew.Elem())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateStructWithOptionalFields(model interface{}) interface{} {
|
||||
var fields []reflect.StructField
|
||||
|
||||
rt := reflect.TypeOf(model)
|
||||
nf := rt.NumField()
|
||||
for i := 0; i < nf; i++ {
|
||||
f := rt.Field(i)
|
||||
j := f.Tag.Get("json")
|
||||
|
||||
if j != "-" && j != "paths" {
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: f.Name,
|
||||
Type: reflect.PtrTo(f.Type),
|
||||
Tag: f.Tag,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.New(reflect.StructOf(fields)).Interface()
|
||||
}
|
||||
|
||||
func loadConfData(ctx *gin.Context) (interface{}, error) {
|
||||
in := generateStructWithOptionalFields(conf.Conf{})
|
||||
d := json.NewDecoder(ctx.Request.Body)
|
||||
d.DisallowUnknownFields()
|
||||
err := d.Decode(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return in, err
|
||||
}
|
||||
|
||||
func loadConfPathData(ctx *gin.Context) (interface{}, error) {
|
||||
in := generateStructWithOptionalFields(conf.PathConf{})
|
||||
d := json.NewDecoder(ctx.Request.Body)
|
||||
d.DisallowUnknownFields()
|
||||
err := d.Decode(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return in, err
|
||||
}
|
||||
|
||||
func paginate2(itemsPtr interface{}, itemsPerPage int, page int) int {
|
||||
ritems := reflect.ValueOf(itemsPtr).Elem()
|
||||
|
||||
@@ -138,6 +77,17 @@ func paginate(itemsPtr interface{}, itemsPerPageStr string, pageStr string) (int
|
||||
return paginate2(itemsPtr, itemsPerPage, page), nil
|
||||
}
|
||||
|
||||
func sortedKeys(paths map[string]*conf.OptionalPath) []string {
|
||||
ret := make([]string, len(paths))
|
||||
i := 0
|
||||
for name := range paths {
|
||||
ret[i] = name
|
||||
i++
|
||||
}
|
||||
sort.Strings(ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func paramName(ctx *gin.Context) (string, bool) {
|
||||
name := ctx.Param("name")
|
||||
|
||||
@@ -149,37 +99,37 @@ func paramName(ctx *gin.Context) (string, bool) {
|
||||
}
|
||||
|
||||
type apiPathManager interface {
|
||||
apiPathsList() (*apiPathsList, error)
|
||||
apiPathsList() (*apiPathList, error)
|
||||
apiPathsGet(string) (*apiPath, error)
|
||||
}
|
||||
|
||||
type apiHLSManager interface {
|
||||
apiMuxersList() (*apiHLSMuxersList, error)
|
||||
apiMuxersList() (*apiHLSMuxerList, error)
|
||||
apiMuxersGet(string) (*apiHLSMuxer, error)
|
||||
}
|
||||
|
||||
type apiRTSPServer interface {
|
||||
apiConnsList() (*apiRTSPConnsList, error)
|
||||
apiConnsGet(uuid.UUID) (*apiRTSPConn, error)
|
||||
apiSessionsList() (*apiRTSPSessionsList, error)
|
||||
apiSessionsList() (*apiRTSPSessionList, error)
|
||||
apiSessionsGet(uuid.UUID) (*apiRTSPSession, error)
|
||||
apiSessionsKick(uuid.UUID) error
|
||||
}
|
||||
|
||||
type apiRTMPServer interface {
|
||||
apiConnsList() (*apiRTMPConnsList, error)
|
||||
apiConnsList() (*apiRTMPConnList, error)
|
||||
apiConnsGet(uuid.UUID) (*apiRTMPConn, error)
|
||||
apiConnsKick(uuid.UUID) error
|
||||
}
|
||||
|
||||
type apiWebRTCManager interface {
|
||||
apiSessionsList() (*apiWebRTCSessionsList, error)
|
||||
apiSessionsList() (*apiWebRTCSessionList, error)
|
||||
apiSessionsGet(uuid.UUID) (*apiWebRTCSession, error)
|
||||
apiSessionsKick(uuid.UUID) error
|
||||
}
|
||||
|
||||
type apiSRTServer interface {
|
||||
apiConnsList() (*apiSRTConnsList, error)
|
||||
apiConnsList() (*apiSRTConnList, error)
|
||||
apiConnsGet(uuid.UUID) (*apiSRTConn, error)
|
||||
apiConnsKick(uuid.UUID) error
|
||||
}
|
||||
@@ -237,58 +187,65 @@ func newAPI(
|
||||
|
||||
group := router.Group("/")
|
||||
|
||||
group.GET("/v2/config/get", a.onConfigGet)
|
||||
group.POST("/v2/config/set", a.onConfigSet)
|
||||
group.POST("/v2/config/paths/add/*name", a.onConfigPathsAdd)
|
||||
group.POST("/v2/config/paths/edit/*name", a.onConfigPathsEdit)
|
||||
group.POST("/v2/config/paths/remove/*name", a.onConfigPathsDelete)
|
||||
group.GET("/v3/config/global/get", a.onConfigGlobalGet)
|
||||
group.PATCH("/v3/config/global/patch", a.onConfigGlobalPatch)
|
||||
|
||||
group.GET("/v3/config/pathdefaults/get", a.onConfigPathDefaultsGet)
|
||||
group.PATCH("/v3/config/pathdefaults/patch", a.onConfigPathDefaultsPatch)
|
||||
|
||||
group.GET("/v3/config/paths/list", a.onConfigPathsList)
|
||||
group.GET("/v3/config/paths/get/*name", a.onConfigPathsGet)
|
||||
group.POST("/v3/config/paths/add/*name", a.onConfigPathsAdd)
|
||||
group.PATCH("/v3/config/paths/patch/*name", a.onConfigPathsPatch)
|
||||
group.POST("/v3/config/paths/replace/*name", a.onConfigPathsReplace)
|
||||
group.DELETE("/v3/config/paths/delete/*name", a.onConfigPathsDelete)
|
||||
|
||||
group.GET("/v3/paths/list", a.onPathsList)
|
||||
group.GET("/v3/paths/get/*name", a.onPathsGet)
|
||||
|
||||
if !interfaceIsEmpty(a.hlsManager) {
|
||||
group.GET("/v2/hlsmuxers/list", a.onHLSMuxersList)
|
||||
group.GET("/v2/hlsmuxers/get/*name", a.onHLSMuxersGet)
|
||||
group.GET("/v3/hlsmuxers/list", a.onHLSMuxersList)
|
||||
group.GET("/v3/hlsmuxers/get/*name", a.onHLSMuxersGet)
|
||||
}
|
||||
|
||||
group.GET("/v2/paths/list", a.onPathsList)
|
||||
group.GET("/v2/paths/get/*name", a.onPathsGet)
|
||||
|
||||
if !interfaceIsEmpty(a.rtspServer) {
|
||||
group.GET("/v2/rtspconns/list", a.onRTSPConnsList)
|
||||
group.GET("/v2/rtspconns/get/:id", a.onRTSPConnsGet)
|
||||
group.GET("/v2/rtspsessions/list", a.onRTSPSessionsList)
|
||||
group.GET("/v2/rtspsessions/get/:id", a.onRTSPSessionsGet)
|
||||
group.POST("/v2/rtspsessions/kick/:id", a.onRTSPSessionsKick)
|
||||
group.GET("/v3/rtspconns/list", a.onRTSPConnsList)
|
||||
group.GET("/v3/rtspconns/get/:id", a.onRTSPConnsGet)
|
||||
group.GET("/v3/rtspsessions/list", a.onRTSPSessionsList)
|
||||
group.GET("/v3/rtspsessions/get/:id", a.onRTSPSessionsGet)
|
||||
group.POST("/v3/rtspsessions/kick/:id", a.onRTSPSessionsKick)
|
||||
}
|
||||
|
||||
if !interfaceIsEmpty(a.rtspsServer) {
|
||||
group.GET("/v2/rtspsconns/list", a.onRTSPSConnsList)
|
||||
group.GET("/v2/rtspsconns/get/:id", a.onRTSPSConnsGet)
|
||||
group.GET("/v2/rtspssessions/list", a.onRTSPSSessionsList)
|
||||
group.GET("/v2/rtspssessions/get/:id", a.onRTSPSSessionsGet)
|
||||
group.POST("/v2/rtspssessions/kick/:id", a.onRTSPSSessionsKick)
|
||||
group.GET("/v3/rtspsconns/list", a.onRTSPSConnsList)
|
||||
group.GET("/v3/rtspsconns/get/:id", a.onRTSPSConnsGet)
|
||||
group.GET("/v3/rtspssessions/list", a.onRTSPSSessionsList)
|
||||
group.GET("/v3/rtspssessions/get/:id", a.onRTSPSSessionsGet)
|
||||
group.POST("/v3/rtspssessions/kick/:id", a.onRTSPSSessionsKick)
|
||||
}
|
||||
|
||||
if !interfaceIsEmpty(a.rtmpServer) {
|
||||
group.GET("/v2/rtmpconns/list", a.onRTMPConnsList)
|
||||
group.GET("/v2/rtmpconns/get/:id", a.onRTMPConnsGet)
|
||||
group.POST("/v2/rtmpconns/kick/:id", a.onRTMPConnsKick)
|
||||
group.GET("/v3/rtmpconns/list", a.onRTMPConnsList)
|
||||
group.GET("/v3/rtmpconns/get/:id", a.onRTMPConnsGet)
|
||||
group.POST("/v3/rtmpconns/kick/:id", a.onRTMPConnsKick)
|
||||
}
|
||||
|
||||
if !interfaceIsEmpty(a.rtmpsServer) {
|
||||
group.GET("/v2/rtmpsconns/list", a.onRTMPSConnsList)
|
||||
group.GET("/v2/rtmpsconns/get/:id", a.onRTMPSConnsGet)
|
||||
group.POST("/v2/rtmpsconns/kick/:id", a.onRTMPSConnsKick)
|
||||
group.GET("/v3/rtmpsconns/list", a.onRTMPSConnsList)
|
||||
group.GET("/v3/rtmpsconns/get/:id", a.onRTMPSConnsGet)
|
||||
group.POST("/v3/rtmpsconns/kick/:id", a.onRTMPSConnsKick)
|
||||
}
|
||||
|
||||
if !interfaceIsEmpty(a.webRTCManager) {
|
||||
group.GET("/v2/webrtcsessions/list", a.onWebRTCSessionsList)
|
||||
group.GET("/v2/webrtcsessions/get/:id", a.onWebRTCSessionsGet)
|
||||
group.POST("/v2/webrtcsessions/kick/:id", a.onWebRTCSessionsKick)
|
||||
group.GET("/v3/webrtcsessions/list", a.onWebRTCSessionsList)
|
||||
group.GET("/v3/webrtcsessions/get/:id", a.onWebRTCSessionsGet)
|
||||
group.POST("/v3/webrtcsessions/kick/:id", a.onWebRTCSessionsKick)
|
||||
}
|
||||
|
||||
if !interfaceIsEmpty(a.srtServer) {
|
||||
group.GET("/v2/srtconns/list", a.onSRTConnsList)
|
||||
group.GET("/v2/srtconns/get/:id", a.onSRTConnsGet)
|
||||
group.POST("/v2/srtconns/kick/:id", a.onSRTConnsKick)
|
||||
group.GET("/v3/srtconns/list", a.onSRTConnsList)
|
||||
group.GET("/v3/srtconns/get/:id", a.onSRTConnsGet)
|
||||
group.POST("/v3/srtconns/kick/:id", a.onSRTConnsKick)
|
||||
}
|
||||
|
||||
network, address := restrictNetwork("tcp", address)
|
||||
@@ -345,16 +302,17 @@ func (a *api) writeServerErrorOrNotFound(ctx *gin.Context, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *api) onConfigGet(ctx *gin.Context) {
|
||||
func (a *api) onConfigGlobalGet(ctx *gin.Context) {
|
||||
a.mutex.Lock()
|
||||
c := a.conf
|
||||
a.mutex.Unlock()
|
||||
|
||||
ctx.JSON(http.StatusOK, c)
|
||||
ctx.JSON(http.StatusOK, c.Global())
|
||||
}
|
||||
|
||||
func (a *api) onConfigSet(ctx *gin.Context) {
|
||||
in, err := loadConfData(ctx)
|
||||
func (a *api) onConfigGlobalPatch(ctx *gin.Context) {
|
||||
var c conf.OptionalGlobal
|
||||
err := json.NewDecoder(ctx.Request.Body).Decode(&c)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
@@ -365,7 +323,7 @@ func (a *api) onConfigSet(ctx *gin.Context) {
|
||||
|
||||
newConf := a.conf.Clone()
|
||||
|
||||
fillStruct(newConf, in)
|
||||
newConf.PatchGlobal(&c)
|
||||
|
||||
err = newConf.Check()
|
||||
if err != nil {
|
||||
@@ -382,14 +340,17 @@ func (a *api) onConfigSet(ctx *gin.Context) {
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (a *api) onConfigPathsAdd(ctx *gin.Context) {
|
||||
name, ok := paramName(ctx)
|
||||
if !ok {
|
||||
a.writeUserError(ctx, fmt.Errorf("invalid name"))
|
||||
return
|
||||
}
|
||||
func (a *api) onConfigPathDefaultsGet(ctx *gin.Context) {
|
||||
a.mutex.Lock()
|
||||
c := a.conf
|
||||
a.mutex.Unlock()
|
||||
|
||||
in, err := loadConfPathData(ctx)
|
||||
ctx.JSON(http.StatusOK, c.PathDefaults)
|
||||
}
|
||||
|
||||
func (a *api) onConfigPathDefaultsPatch(ctx *gin.Context) {
|
||||
var p conf.OptionalPath
|
||||
err := json.NewDecoder(ctx.Request.Body).Decode(&p)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
@@ -400,19 +361,7 @@ func (a *api) onConfigPathsAdd(ctx *gin.Context) {
|
||||
|
||||
newConf := a.conf.Clone()
|
||||
|
||||
if _, ok := newConf.Paths[name]; ok {
|
||||
a.writeUserError(ctx, fmt.Errorf("path already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
newConfPath := &conf.PathConf{}
|
||||
|
||||
// load default values
|
||||
newConfPath.UnmarshalJSON([]byte("{}")) //nolint:errcheck
|
||||
|
||||
fillStruct(newConfPath, in)
|
||||
|
||||
newConf.Paths[name] = newConfPath
|
||||
newConf.PatchPathDefaults(&p)
|
||||
|
||||
err = newConf.Check()
|
||||
if err != nil {
|
||||
@@ -421,39 +370,79 @@ func (a *api) onConfigPathsAdd(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
a.conf = newConf
|
||||
|
||||
// since reloading the configuration can cause the shutdown of the API,
|
||||
// call it in a goroutine
|
||||
go a.parent.apiConfigSet(newConf)
|
||||
a.parent.apiConfigSet(newConf)
|
||||
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (a *api) onConfigPathsEdit(ctx *gin.Context) {
|
||||
func (a *api) onConfigPathsList(ctx *gin.Context) {
|
||||
a.mutex.Lock()
|
||||
c := a.conf
|
||||
a.mutex.Unlock()
|
||||
|
||||
data := &apiPathConfList{
|
||||
Items: make([]*conf.OptionalPath, len(c.OptionalPaths)),
|
||||
}
|
||||
|
||||
for i, key := range sortedKeys(c.OptionalPaths) {
|
||||
data.Items[i] = c.OptionalPaths[key]
|
||||
}
|
||||
|
||||
data.ItemCount = len(data.Items)
|
||||
pageCount, err := paginate(&data.Items, ctx.Query("itemsPerPage"), ctx.Query("page"))
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
data.PageCount = pageCount
|
||||
|
||||
ctx.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
func (a *api) onConfigPathsGet(ctx *gin.Context) {
|
||||
name, ok := paramName(ctx)
|
||||
if !ok {
|
||||
a.writeUserError(ctx, fmt.Errorf("invalid name"))
|
||||
return
|
||||
}
|
||||
|
||||
in, err := loadConfPathData(ctx)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
c := a.conf
|
||||
a.mutex.Unlock()
|
||||
|
||||
newConf := a.conf.Clone()
|
||||
|
||||
newConfPath, ok := newConf.Paths[name]
|
||||
p, ok := c.OptionalPaths[name]
|
||||
if !ok {
|
||||
a.writeNotFound(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
fillStruct(newConfPath, in)
|
||||
ctx.JSON(http.StatusOK, p)
|
||||
}
|
||||
|
||||
func (a *api) onConfigPathsAdd(ctx *gin.Context) { //nolint:dupl
|
||||
name, ok := paramName(ctx)
|
||||
if !ok {
|
||||
a.writeUserError(ctx, fmt.Errorf("invalid name"))
|
||||
return
|
||||
}
|
||||
|
||||
var p conf.OptionalPath
|
||||
err := json.NewDecoder(ctx.Request.Body).Decode(&p)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
newConf := a.conf.Clone()
|
||||
|
||||
err = newConf.AddPath(name, &p)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = newConf.Check()
|
||||
if err != nil {
|
||||
@@ -462,10 +451,81 @@ func (a *api) onConfigPathsEdit(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
a.conf = newConf
|
||||
a.parent.apiConfigSet(newConf)
|
||||
|
||||
// since reloading the configuration can cause the shutdown of the API,
|
||||
// call it in a goroutine
|
||||
go a.parent.apiConfigSet(newConf)
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (a *api) onConfigPathsPatch(ctx *gin.Context) { //nolint:dupl
|
||||
name, ok := paramName(ctx)
|
||||
if !ok {
|
||||
a.writeUserError(ctx, fmt.Errorf("invalid name"))
|
||||
return
|
||||
}
|
||||
|
||||
var p conf.OptionalPath
|
||||
err := json.NewDecoder(ctx.Request.Body).Decode(&p)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
newConf := a.conf.Clone()
|
||||
|
||||
err = newConf.PatchPath(name, &p)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = newConf.Check()
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
a.conf = newConf
|
||||
a.parent.apiConfigSet(newConf)
|
||||
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (a *api) onConfigPathsReplace(ctx *gin.Context) { //nolint:dupl
|
||||
name, ok := paramName(ctx)
|
||||
if !ok {
|
||||
a.writeUserError(ctx, fmt.Errorf("invalid name"))
|
||||
return
|
||||
}
|
||||
|
||||
var p conf.OptionalPath
|
||||
err := json.NewDecoder(ctx.Request.Body).Decode(&p)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
newConf := a.conf.Clone()
|
||||
|
||||
err = newConf.ReplacePath(name, &p)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = newConf.Check()
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
a.conf = newConf
|
||||
a.parent.apiConfigSet(newConf)
|
||||
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
@@ -480,25 +540,22 @@ func (a *api) onConfigPathsDelete(ctx *gin.Context) {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
if _, ok := a.conf.Paths[name]; !ok {
|
||||
a.writeNotFound(ctx)
|
||||
newConf := a.conf.Clone()
|
||||
|
||||
err := newConf.RemovePath(name)
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
newConf := a.conf.Clone()
|
||||
delete(newConf.Paths, name)
|
||||
|
||||
err := newConf.Check()
|
||||
err = newConf.Check()
|
||||
if err != nil {
|
||||
a.writeUserError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
a.conf = newConf
|
||||
|
||||
// since reloading the configuration can cause the shutdown of the API,
|
||||
// call it in a goroutine
|
||||
go a.parent.apiConfigSet(newConf)
|
||||
a.parent.apiConfigSet(newConf)
|
||||
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ import (
|
||||
"github.com/bluenviron/mediamtx/internal/conf"
|
||||
)
|
||||
|
||||
type apiPathConfList struct {
|
||||
ItemCount int `json:"itemCount"`
|
||||
PageCount int `json:"pageCount"`
|
||||
Items []*conf.OptionalPath `json:"items"`
|
||||
}
|
||||
|
||||
type apiPathSourceOrReader struct {
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
@@ -16,9 +22,7 @@ type apiPathSourceOrReader struct {
|
||||
type apiPath struct {
|
||||
Name string `json:"name"`
|
||||
ConfName string `json:"confName"`
|
||||
Conf *conf.PathConf `json:"conf"`
|
||||
Source *apiPathSourceOrReader `json:"source"`
|
||||
SourceReady bool `json:"sourceReady"` // Deprecated: renamed to Ready
|
||||
Ready bool `json:"ready"`
|
||||
ReadyTime *time.Time `json:"readyTime"`
|
||||
Tracks []string `json:"tracks"`
|
||||
@@ -26,7 +30,7 @@ type apiPath struct {
|
||||
Readers []apiPathSourceOrReader `json:"readers"`
|
||||
}
|
||||
|
||||
type apiPathsList struct {
|
||||
type apiPathList struct {
|
||||
ItemCount int `json:"itemCount"`
|
||||
PageCount int `json:"pageCount"`
|
||||
Items []*apiPath `json:"items"`
|
||||
@@ -39,7 +43,7 @@ type apiHLSMuxer struct {
|
||||
BytesSent uint64 `json:"bytesSent"`
|
||||
}
|
||||
|
||||
type apiHLSMuxersList struct {
|
||||
type apiHLSMuxerList struct {
|
||||
ItemCount int `json:"itemCount"`
|
||||
PageCount int `json:"pageCount"`
|
||||
Items []*apiHLSMuxer `json:"items"`
|
||||
@@ -77,7 +81,7 @@ type apiRTMPConn struct {
|
||||
BytesSent uint64 `json:"bytesSent"`
|
||||
}
|
||||
|
||||
type apiRTMPConnsList struct {
|
||||
type apiRTMPConnList struct {
|
||||
ItemCount int `json:"itemCount"`
|
||||
PageCount int `json:"pageCount"`
|
||||
Items []*apiRTMPConn `json:"items"`
|
||||
@@ -102,7 +106,7 @@ type apiRTSPSession struct {
|
||||
BytesSent uint64 `json:"bytesSent"`
|
||||
}
|
||||
|
||||
type apiRTSPSessionsList struct {
|
||||
type apiRTSPSessionList struct {
|
||||
ItemCount int `json:"itemCount"`
|
||||
PageCount int `json:"pageCount"`
|
||||
Items []*apiRTSPSession `json:"items"`
|
||||
@@ -126,7 +130,7 @@ type apiSRTConn struct {
|
||||
BytesSent uint64 `json:"bytesSent"`
|
||||
}
|
||||
|
||||
type apiSRTConnsList struct {
|
||||
type apiSRTConnList struct {
|
||||
ItemCount int `json:"itemCount"`
|
||||
PageCount int `json:"pageCount"`
|
||||
Items []*apiSRTConn `json:"items"`
|
||||
@@ -152,7 +156,7 @@ type apiWebRTCSession struct {
|
||||
BytesSent uint64 `json:"bytesSent"`
|
||||
}
|
||||
|
||||
type apiWebRTCSessionsList struct {
|
||||
type apiWebRTCSessionList struct {
|
||||
ItemCount int `json:"itemCount"`
|
||||
PageCount int `json:"pageCount"`
|
||||
Items []*apiWebRTCSession `json:"items"`
|
||||
|
||||
+198
-75
@@ -121,7 +121,7 @@ func TestPagination(t *testing.T) {
|
||||
require.Equal(t, []int{5}, items)
|
||||
}
|
||||
|
||||
func TestAPIConfigGet(t *testing.T) {
|
||||
func TestAPIConfigGlobalGet(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
@@ -129,33 +129,35 @@ func TestAPIConfigGet(t *testing.T) {
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
var out map[string]interface{}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/config/get", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/global/get", nil, &out)
|
||||
require.Equal(t, true, out["api"])
|
||||
}
|
||||
|
||||
func TestAPIConfigSet(t *testing.T) {
|
||||
func TestAPIConfigGlobalPatch(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v2/config/set", map[string]interface{}{
|
||||
"rtmp": false,
|
||||
"readTimeout": "7s",
|
||||
"protocols": []string{"tcp"},
|
||||
httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/global/patch", map[string]interface{}{
|
||||
"rtmp": false,
|
||||
"readTimeout": "7s",
|
||||
"protocols": []string{"tcp"},
|
||||
"readBufferCount": 4096, // test setting a deprecated parameter
|
||||
}, nil)
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
var out map[string]interface{}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/config/get", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/global/get", nil, &out)
|
||||
require.Equal(t, false, out["rtmp"])
|
||||
require.Equal(t, "7s", out["readTimeout"])
|
||||
require.Equal(t, []interface{}{"tcp"}, out["protocols"])
|
||||
require.Equal(t, float64(4096), out["readBufferCount"])
|
||||
}
|
||||
|
||||
func TestAPIConfigSetUnknownField(t *testing.T) {
|
||||
func TestAPIConfigGlobalPatchUnknownField(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
@@ -169,13 +171,103 @@ func TestAPIConfigSetUnknownField(t *testing.T) {
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
req, err := http.NewRequest("POST", "http://localhost:9997/v2/config/set", bytes.NewReader(byts))
|
||||
require.NoError(t, err)
|
||||
func() {
|
||||
req, err := http.NewRequest(http.MethodPatch, "http://localhost:9997/v3/config/global/patch", bytes.NewReader(byts))
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := hc.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
res, err := hc.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
}()
|
||||
}
|
||||
|
||||
func TestAPIConfigPathDefaultsGet(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
var out map[string]interface{}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/pathdefaults/get", nil, &out)
|
||||
require.Equal(t, "publisher", out["source"])
|
||||
}
|
||||
|
||||
func TestAPIConfigPathDefaultsPatch(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/pathdefaults/patch", map[string]interface{}{
|
||||
"readUser": "myuser",
|
||||
"readPass": "mypass",
|
||||
}, nil)
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
var out map[string]interface{}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/pathdefaults/get", nil, &out)
|
||||
require.Equal(t, "myuser", out["readUser"])
|
||||
require.Equal(t, "mypass", out["readPass"])
|
||||
}
|
||||
|
||||
func TestAPIConfigPathsList(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n" +
|
||||
"paths:\n" +
|
||||
" path1:\n" +
|
||||
" readUser: myuser\n" +
|
||||
" readPass: mypass\n" +
|
||||
" path2:\n" +
|
||||
" readUser: myuser\n" +
|
||||
" readPass: mypass\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
type pathConfig map[string]interface{}
|
||||
|
||||
type listRes struct {
|
||||
ItemCount int `json:"itemCount"`
|
||||
PageCount int `json:"pageCount"`
|
||||
Items []pathConfig `json:"items"`
|
||||
}
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
var out listRes
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/list", nil, &out)
|
||||
require.Equal(t, listRes{
|
||||
ItemCount: 2,
|
||||
PageCount: 1,
|
||||
Items: []pathConfig{
|
||||
{
|
||||
"readUser": "myuser",
|
||||
"readPass": "mypass",
|
||||
},
|
||||
{
|
||||
"readUser": "myuser",
|
||||
"readPass": "mypass",
|
||||
},
|
||||
},
|
||||
}, out)
|
||||
}
|
||||
|
||||
func TestAPIConfigPathsGet(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n" +
|
||||
"paths:\n" +
|
||||
" my/path:\n" +
|
||||
" readUser: myuser\n" +
|
||||
" readPass: mypass\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
var out map[string]interface{}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil, &out)
|
||||
require.Equal(t, "myuser", out["readUser"])
|
||||
}
|
||||
|
||||
func TestAPIConfigPathsAdd(t *testing.T) {
|
||||
@@ -185,21 +277,19 @@ func TestAPIConfigPathsAdd(t *testing.T) {
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v2/config/paths/add/my/path", map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9999/mypath",
|
||||
"sourceOnDemand": true,
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/my/path", map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9999/mypath",
|
||||
"sourceOnDemand": true,
|
||||
"disablePublisherOverride": true, // test setting a deprecated parameter
|
||||
}, nil)
|
||||
|
||||
var out struct {
|
||||
Paths map[string]struct {
|
||||
Source string `json:"source"`
|
||||
SourceOnDemandStartTimeout string `json:"sourceOnDemandStartTimeout"`
|
||||
} `json:"paths"`
|
||||
}
|
||||
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/config/get", nil, &out)
|
||||
require.Equal(t, "rtsp://127.0.0.1:9999/mypath", out.Paths["my/path"].Source)
|
||||
require.Equal(t, "10s", out.Paths["my/path"].SourceOnDemandStartTimeout)
|
||||
var out map[string]interface{}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil, &out)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9999/mypath",
|
||||
"sourceOnDemand": true,
|
||||
"disablePublisherOverride": true,
|
||||
}, out)
|
||||
}
|
||||
|
||||
func TestAPIConfigPathsAddUnknownField(t *testing.T) {
|
||||
@@ -216,61 +306,94 @@ func TestAPIConfigPathsAddUnknownField(t *testing.T) {
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
req, err := http.NewRequest("POST", "http://localhost:9997/v2/config/paths/add/my/path", bytes.NewReader(byts))
|
||||
require.NoError(t, err)
|
||||
func() {
|
||||
req, err := http.NewRequest(http.MethodPost,
|
||||
"http://localhost:9997/v3/config/paths/add/my/path", bytes.NewReader(byts))
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := hc.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
res, err := hc.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
}()
|
||||
}
|
||||
|
||||
func TestAPIConfigPathsEdit(t *testing.T) {
|
||||
func TestAPIConfigPathsPatch(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v2/config/paths/add/my/path", map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9999/mypath",
|
||||
"sourceOnDemand": true,
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/my/path", map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9999/mypath",
|
||||
"sourceOnDemand": true,
|
||||
"disablePublisherOverride": true, // test setting a deprecated parameter
|
||||
}, nil)
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v2/config/paths/edit/my/path", map[string]interface{}{
|
||||
httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/paths/patch/my/path", map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9998/mypath",
|
||||
"sourceOnDemand": true,
|
||||
}, nil)
|
||||
|
||||
var out struct {
|
||||
Paths map[string]struct {
|
||||
Source string `json:"source"`
|
||||
} `json:"paths"`
|
||||
}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/config/get", nil, &out)
|
||||
require.Equal(t, "rtsp://127.0.0.1:9998/mypath", out.Paths["my/path"].Source)
|
||||
var out map[string]interface{}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil, &out)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9998/mypath",
|
||||
"sourceOnDemand": true,
|
||||
"disablePublisherOverride": true,
|
||||
}, out)
|
||||
}
|
||||
|
||||
func TestAPIConfigPathsRemove(t *testing.T) {
|
||||
func TestAPIConfigPathsReplace(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v2/config/paths/add/my/path", map[string]interface{}{
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/my/path", map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9999/mypath",
|
||||
"sourceOnDemand": true,
|
||||
"disablePublisherOverride": true, // test setting a deprecated parameter
|
||||
}, nil)
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/replace/my/path", map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9998/mypath",
|
||||
"sourceOnDemand": true,
|
||||
}, nil)
|
||||
|
||||
var out map[string]interface{}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil, &out)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9998/mypath",
|
||||
"sourceOnDemand": true,
|
||||
}, out)
|
||||
}
|
||||
|
||||
func TestAPIConfigPathsDelete(t *testing.T) {
|
||||
p, ok := newInstance("api: yes\n")
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/config/paths/add/my/path", map[string]interface{}{
|
||||
"source": "rtsp://127.0.0.1:9999/mypath",
|
||||
"sourceOnDemand": true,
|
||||
}, nil)
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v2/config/paths/remove/my/path", nil, nil)
|
||||
httpRequest(t, hc, http.MethodDelete, "http://localhost:9997/v3/config/paths/delete/my/path", nil, nil)
|
||||
|
||||
var out struct {
|
||||
Paths map[string]interface{} `json:"paths"`
|
||||
}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/config/get", nil, &out)
|
||||
_, ok = out.Paths["my/path"]
|
||||
require.Equal(t, false, ok)
|
||||
func() {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9997/v3/config/paths/get/my/path", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := hc.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||
}()
|
||||
}
|
||||
|
||||
func TestAPIPathsList(t *testing.T) {
|
||||
@@ -323,7 +446,7 @@ func TestAPIPathsList(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
var out pathList
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/paths/list", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out)
|
||||
require.Equal(t, pathList{
|
||||
ItemCount: 1,
|
||||
PageCount: 1,
|
||||
@@ -369,7 +492,7 @@ func TestAPIPathsList(t *testing.T) {
|
||||
defer source.Close()
|
||||
|
||||
var out pathList
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/paths/list", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out)
|
||||
require.Equal(t, pathList{
|
||||
ItemCount: 1,
|
||||
PageCount: 1,
|
||||
@@ -396,7 +519,7 @@ func TestAPIPathsList(t *testing.T) {
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
var out pathList
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/paths/list", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out)
|
||||
require.Equal(t, pathList{
|
||||
ItemCount: 1,
|
||||
PageCount: 1,
|
||||
@@ -423,7 +546,7 @@ func TestAPIPathsList(t *testing.T) {
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
var out pathList
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/paths/list", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out)
|
||||
require.Equal(t, pathList{
|
||||
ItemCount: 1,
|
||||
PageCount: 1,
|
||||
@@ -450,7 +573,7 @@ func TestAPIPathsList(t *testing.T) {
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
var out pathList
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/paths/list", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out)
|
||||
require.Equal(t, pathList{
|
||||
ItemCount: 1,
|
||||
PageCount: 1,
|
||||
@@ -508,7 +631,7 @@ func TestAPIPathsGet(t *testing.T) {
|
||||
defer source.Close()
|
||||
|
||||
var out path
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/paths/get/"+pathName, nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/get/"+pathName, nil, &out)
|
||||
require.Equal(t, path{
|
||||
Name: pathName,
|
||||
Source: pathSource{
|
||||
@@ -518,7 +641,7 @@ func TestAPIPathsGet(t *testing.T) {
|
||||
Tracks: []string{"H264"},
|
||||
}, out)
|
||||
} else {
|
||||
res, err := hc.Get("http://localhost:9997/v2/paths/get/" + pathName)
|
||||
res, err := hc.Get("http://localhost:9997/v3/paths/get/" + pathName)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, 404, res.StatusCode)
|
||||
@@ -765,7 +888,7 @@ func TestAPIProtocolList(t *testing.T) {
|
||||
ItemCount int `json:"itemCount"`
|
||||
Items []item `json:"items"`
|
||||
}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/"+pa+"/list", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/list", nil, &out)
|
||||
|
||||
if ca != "rtsp conns" && ca != "rtsps conns" {
|
||||
require.Equal(t, item{
|
||||
@@ -784,7 +907,7 @@ func TestAPIProtocolList(t *testing.T) {
|
||||
ItemCount int `json:"itemCount"`
|
||||
Items []item `json:"items"`
|
||||
}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/hlsmuxers/list", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/hlsmuxers/list", nil, &out)
|
||||
|
||||
s := fmt.Sprintf("^%d-", time.Now().Year())
|
||||
require.Regexp(t, s, out.Items[0].Created)
|
||||
@@ -801,7 +924,7 @@ func TestAPIProtocolList(t *testing.T) {
|
||||
ItemCount int `json:"itemCount"`
|
||||
Items []item `json:"items"`
|
||||
}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/webrtcsessions/list", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/webrtcsessions/list", nil, &out)
|
||||
|
||||
require.Equal(t, item{
|
||||
PeerConnectionEstablished: true,
|
||||
@@ -1050,14 +1173,14 @@ func TestAPIProtocolGet(t *testing.T) {
|
||||
var out1 struct {
|
||||
Items []item `json:"items"`
|
||||
}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/"+pa+"/list", nil, &out1)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/list", nil, &out1)
|
||||
|
||||
if ca != "rtsp conns" && ca != "rtsps conns" {
|
||||
require.Equal(t, "publish", out1.Items[0].State)
|
||||
}
|
||||
|
||||
var out2 item
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/"+pa+"/get/"+out1.Items[0].ID, nil, &out2)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/get/"+out1.Items[0].ID, nil, &out2)
|
||||
|
||||
if ca != "rtsp conns" && ca != "rtsps conns" {
|
||||
require.Equal(t, "publish", out2.State)
|
||||
@@ -1070,7 +1193,7 @@ func TestAPIProtocolGet(t *testing.T) {
|
||||
}
|
||||
|
||||
var out item
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/hlsmuxers/get/mypath", nil, &out)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/hlsmuxers/get/mypath", nil, &out)
|
||||
|
||||
s := fmt.Sprintf("^%d-", time.Now().Year())
|
||||
require.Regexp(t, s, out.Created)
|
||||
@@ -1091,10 +1214,10 @@ func TestAPIProtocolGet(t *testing.T) {
|
||||
var out1 struct {
|
||||
Items []item `json:"items"`
|
||||
}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/webrtcsessions/list", nil, &out1)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/webrtcsessions/list", nil, &out1)
|
||||
|
||||
var out2 item
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/webrtcsessions/get/"+out1.Items[0].ID, nil, &out2)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/webrtcsessions/get/"+out1.Items[0].ID, nil, &out2)
|
||||
|
||||
require.Equal(t, true, out2.PeerConnectionEstablished)
|
||||
}
|
||||
@@ -1178,7 +1301,7 @@ func TestAPIProtocolGetNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func() {
|
||||
req, err := http.NewRequest("GET", "http://localhost:9997/v2/"+pa+"/get/"+uuid.New().String(), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9997/v3/"+pa+"/get/"+uuid.New().String(), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := hc.Do(req)
|
||||
@@ -1310,16 +1433,16 @@ func TestAPIProtocolKick(t *testing.T) {
|
||||
ID string `json:"id"`
|
||||
} `json:"items"`
|
||||
}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/"+pa+"/list", nil, &out1)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/list", nil, &out1)
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v2/"+pa+"/kick/"+out1.Items[0].ID, nil, nil)
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/"+pa+"/kick/"+out1.Items[0].ID, nil, nil)
|
||||
|
||||
var out2 struct {
|
||||
Items []struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"items"`
|
||||
}
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v2/"+pa+"/list", nil, &out2)
|
||||
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/list", nil, &out2)
|
||||
require.Equal(t, 0, len(out2.Items))
|
||||
})
|
||||
}
|
||||
@@ -1379,7 +1502,7 @@ func TestAPIProtocolKickNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func() {
|
||||
req, err := http.NewRequest("GET", "http://localhost:9997/v2/"+pa+"/kick/"+uuid.New().String(), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9997/v3/"+pa+"/kick/"+uuid.New().String(), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := hc.Do(req)
|
||||
|
||||
@@ -115,7 +115,7 @@ func doAuthentication(
|
||||
externalAuthenticationURL string,
|
||||
rtspAuthMethods conf.AuthMethods,
|
||||
pathName string,
|
||||
pathConf *conf.PathConf,
|
||||
pathConf *conf.Path,
|
||||
publish bool,
|
||||
credentials authCredentials,
|
||||
) error {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type hlsManagerAPIMuxersListRes struct {
|
||||
data *apiHLSMuxersList
|
||||
data *apiHLSMuxerList
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ outer:
|
||||
delete(m.muxers, c.PathName())
|
||||
|
||||
case req := <-m.chAPIMuxerList:
|
||||
data := &apiHLSMuxersList{
|
||||
data := &apiHLSMuxerList{
|
||||
Items: []*apiHLSMuxer{},
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ func (m *hlsManager) pathNotReady(pa *path) {
|
||||
}
|
||||
|
||||
// apiMuxersList is called by api.
|
||||
func (m *hlsManager) apiMuxersList() (*apiHLSMuxersList, error) {
|
||||
func (m *hlsManager) apiMuxersList() (*apiHLSMuxerList, error) {
|
||||
req := hlsManagerAPIMuxersListReq{
|
||||
res: make(chan hlsManagerAPIMuxersListRes),
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func (s *hlsSource) Log(level logger.Level, format string, args ...interface{})
|
||||
}
|
||||
|
||||
// run implements sourceStaticImpl.
|
||||
func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error {
|
||||
func (s *hlsSource) run(ctx context.Context, cnf *conf.Path, reloadConf chan *conf.Path) error {
|
||||
var stream *stream.Stream
|
||||
|
||||
defer func() {
|
||||
|
||||
@@ -86,7 +86,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) {
|
||||
if err == nil && len(data.Items) != 0 {
|
||||
for _, i := range data.Items {
|
||||
var state string
|
||||
if i.SourceReady {
|
||||
if i.Ready {
|
||||
state = "ready"
|
||||
} else {
|
||||
state = "notReady"
|
||||
|
||||
+10
-12
@@ -76,7 +76,7 @@ type pathRemovePublisherReq struct {
|
||||
}
|
||||
|
||||
type pathGetConfForPathRes struct {
|
||||
conf *conf.PathConf
|
||||
conf *conf.Path
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ type pathStopPublisherReq struct {
|
||||
}
|
||||
|
||||
type pathAPIPathsListRes struct {
|
||||
data *apiPathsList
|
||||
data *apiPathList
|
||||
paths map[string]*path
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ type path struct {
|
||||
recordPartDuration conf.StringDuration
|
||||
recordSegmentDuration conf.StringDuration
|
||||
confName string
|
||||
conf *conf.PathConf
|
||||
conf *conf.Path
|
||||
name string
|
||||
matches []string
|
||||
wg *sync.WaitGroup
|
||||
@@ -203,7 +203,7 @@ type path struct {
|
||||
onDemandPublisherCloseTimer *time.Timer
|
||||
|
||||
// in
|
||||
chReloadConf chan *conf.PathConf
|
||||
chReloadConf chan *conf.Path
|
||||
chSourceStaticSetReady chan pathSourceStaticSetReadyReq
|
||||
chSourceStaticSetNotReady chan pathSourceStaticSetNotReadyReq
|
||||
chDescribe chan pathDescribeReq
|
||||
@@ -231,7 +231,7 @@ func newPath(
|
||||
recordPartDuration conf.StringDuration,
|
||||
recordSegmentDuration conf.StringDuration,
|
||||
confName string,
|
||||
cnf *conf.PathConf,
|
||||
cnf *conf.Path,
|
||||
name string,
|
||||
matches []string,
|
||||
wg *sync.WaitGroup,
|
||||
@@ -264,7 +264,7 @@ func newPath(
|
||||
onDemandStaticSourceCloseTimer: newEmptyTimer(),
|
||||
onDemandPublisherReadyTimer: newEmptyTimer(),
|
||||
onDemandPublisherCloseTimer: newEmptyTimer(),
|
||||
chReloadConf: make(chan *conf.PathConf),
|
||||
chReloadConf: make(chan *conf.Path),
|
||||
chSourceStaticSetReady: make(chan pathSourceStaticSetReadyReq),
|
||||
chSourceStaticSetNotReady: make(chan pathSourceStaticSetNotReadyReq),
|
||||
chDescribe: make(chan pathDescribeReq),
|
||||
@@ -505,7 +505,7 @@ func (pa *path) doOnDemandPublisherCloseTimer() {
|
||||
pa.onDemandPublisherStop("not needed by anyone")
|
||||
}
|
||||
|
||||
func (pa *path) doReloadConf(newConf *conf.PathConf) {
|
||||
func (pa *path) doReloadConf(newConf *conf.Path) {
|
||||
pa.confMutex.Lock()
|
||||
pa.conf = newConf
|
||||
pa.confMutex.Unlock()
|
||||
@@ -741,7 +741,6 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) {
|
||||
data: &apiPath{
|
||||
Name: pa.name,
|
||||
ConfName: pa.confName,
|
||||
Conf: pa.conf,
|
||||
Source: func() *apiPathSourceOrReader {
|
||||
if pa.source == nil {
|
||||
return nil
|
||||
@@ -749,8 +748,7 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) {
|
||||
v := pa.source.apiSourceDescribe()
|
||||
return &v
|
||||
}(),
|
||||
SourceReady: pa.stream != nil,
|
||||
Ready: pa.stream != nil,
|
||||
Ready: pa.stream != nil,
|
||||
ReadyTime: func() *time.Time {
|
||||
if pa.stream == nil {
|
||||
return nil
|
||||
@@ -781,7 +779,7 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) {
|
||||
}
|
||||
}
|
||||
|
||||
func (pa *path) safeConf() *conf.PathConf {
|
||||
func (pa *path) safeConf() *conf.Path {
|
||||
pa.confMutex.RLock()
|
||||
defer pa.confMutex.RUnlock()
|
||||
return pa.conf
|
||||
@@ -1044,7 +1042,7 @@ func (pa *path) addReaderPost(req pathAddReaderReq) {
|
||||
}
|
||||
|
||||
// reloadConf is called by pathManager.
|
||||
func (pa *path) reloadConf(newConf *conf.PathConf) {
|
||||
func (pa *path) reloadConf(newConf *conf.Path) {
|
||||
select {
|
||||
case pa.chReloadConf <- newConf:
|
||||
case <-pa.ctx.Done():
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
)
|
||||
|
||||
func pathConfCanBeUpdated(oldPathConf *conf.PathConf, newPathConf *conf.PathConf) bool {
|
||||
func pathConfCanBeUpdated(oldPathConf *conf.Path, newPathConf *conf.Path) bool {
|
||||
clone := oldPathConf.Clone()
|
||||
|
||||
clone.Record = newPathConf.Record
|
||||
@@ -32,7 +32,7 @@ func pathConfCanBeUpdated(oldPathConf *conf.PathConf, newPathConf *conf.PathConf
|
||||
return newPathConf.Equal(clone)
|
||||
}
|
||||
|
||||
func getConfForPath(pathConfs map[string]*conf.PathConf, name string) (string, *conf.PathConf, []string, error) {
|
||||
func getConfForPath(pathConfs map[string]*conf.Path, name string) (string, *conf.Path, []string, error) {
|
||||
err := conf.IsValidPathName(name)
|
||||
if err != nil {
|
||||
return "", nil, nil, fmt.Errorf("invalid path name: %s (%s)", err, name)
|
||||
@@ -77,7 +77,7 @@ type pathManager struct {
|
||||
recordPath string
|
||||
recordPartDuration conf.StringDuration
|
||||
recordSegmentDuration conf.StringDuration
|
||||
pathConfs map[string]*conf.PathConf
|
||||
pathConfs map[string]*conf.Path
|
||||
externalCmdPool *externalcmd.Pool
|
||||
metrics *metrics
|
||||
parent pathManagerParent
|
||||
@@ -90,7 +90,7 @@ type pathManager struct {
|
||||
pathsByConf map[string]map[*path]struct{}
|
||||
|
||||
// in
|
||||
chReloadConf chan map[string]*conf.PathConf
|
||||
chReloadConf chan map[string]*conf.Path
|
||||
chSetHLSManager chan pathManagerHLSManager
|
||||
chClosePath chan *path
|
||||
chPathReady chan *path
|
||||
@@ -115,7 +115,7 @@ func newPathManager(
|
||||
recordPath string,
|
||||
recordPartDuration conf.StringDuration,
|
||||
recordSegmentDuration conf.StringDuration,
|
||||
pathConfs map[string]*conf.PathConf,
|
||||
pathConfs map[string]*conf.Path,
|
||||
externalCmdPool *externalcmd.Pool,
|
||||
metrics *metrics,
|
||||
parent pathManagerParent,
|
||||
@@ -142,7 +142,7 @@ func newPathManager(
|
||||
ctxCancel: ctxCancel,
|
||||
paths: make(map[string]*path),
|
||||
pathsByConf: make(map[string]map[*path]struct{}),
|
||||
chReloadConf: make(chan map[string]*conf.PathConf),
|
||||
chReloadConf: make(chan map[string]*conf.Path),
|
||||
chSetHLSManager: make(chan pathManagerHLSManager),
|
||||
chClosePath: make(chan *path),
|
||||
chPathReady: make(chan *path),
|
||||
@@ -190,8 +190,8 @@ func (pm *pathManager) run() {
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case newPathConfs := <-pm.chReloadConf:
|
||||
pm.doReloadConf(newPathConfs)
|
||||
case newPaths := <-pm.chReloadConf:
|
||||
pm.doReloadConf(newPaths)
|
||||
|
||||
case m := <-pm.chSetHLSManager:
|
||||
pm.doSetHLSManager(m)
|
||||
@@ -235,14 +235,14 @@ outer:
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *pathManager) doReloadConf(newPathConfs map[string]*conf.PathConf) {
|
||||
func (pm *pathManager) doReloadConf(newPaths map[string]*conf.Path) {
|
||||
for confName, pathConf := range pm.pathConfs {
|
||||
if newPathConf, ok := newPathConfs[confName]; ok {
|
||||
if newPath, ok := newPaths[confName]; ok {
|
||||
// configuration has changed
|
||||
if !newPathConf.Equal(pathConf) {
|
||||
if pathConfCanBeUpdated(pathConf, newPathConf) { // paths associated with the configuration can be updated
|
||||
if !newPath.Equal(pathConf) {
|
||||
if pathConfCanBeUpdated(pathConf, newPath) { // paths associated with the configuration can be updated
|
||||
for pa := range pm.pathsByConf[confName] {
|
||||
go pa.reloadConf(newPathConf)
|
||||
go pa.reloadConf(newPath)
|
||||
}
|
||||
} else { // paths associated with the configuration must be recreated
|
||||
for pa := range pm.pathsByConf[confName] {
|
||||
@@ -262,7 +262,7 @@ func (pm *pathManager) doReloadConf(newPathConfs map[string]*conf.PathConf) {
|
||||
}
|
||||
}
|
||||
|
||||
pm.pathConfs = newPathConfs
|
||||
pm.pathConfs = newPaths
|
||||
|
||||
// add new paths
|
||||
for pathConfName, pathConf := range pm.pathConfs {
|
||||
@@ -401,7 +401,7 @@ func (pm *pathManager) doAPIPathsGet(req pathAPIPathsGetReq) {
|
||||
|
||||
func (pm *pathManager) createPath(
|
||||
pathConfName string,
|
||||
pathConf *conf.PathConf,
|
||||
pathConf *conf.Path,
|
||||
name string,
|
||||
matches []string,
|
||||
) {
|
||||
@@ -441,7 +441,7 @@ func (pm *pathManager) removePath(pa *path) {
|
||||
}
|
||||
|
||||
// confReload is called by core.
|
||||
func (pm *pathManager) confReload(pathConfs map[string]*conf.PathConf) {
|
||||
func (pm *pathManager) confReload(pathConfs map[string]*conf.Path) {
|
||||
select {
|
||||
case pm.chReloadConf <- pathConfs:
|
||||
case <-pm.ctx.Done():
|
||||
@@ -553,7 +553,7 @@ func (pm *pathManager) setHLSManager(s pathManagerHLSManager) {
|
||||
}
|
||||
|
||||
// apiPathsList is called by api.
|
||||
func (pm *pathManager) apiPathsList() (*apiPathsList, error) {
|
||||
func (pm *pathManager) apiPathsList() (*apiPathList, error) {
|
||||
req := pathAPIPathsListReq{
|
||||
res: make(chan pathAPIPathsListRes),
|
||||
}
|
||||
@@ -562,7 +562,7 @@ func (pm *pathManager) apiPathsList() (*apiPathsList, error) {
|
||||
case pm.chAPIPathsList <- req:
|
||||
res := <-req.res
|
||||
|
||||
res.data = &apiPathsList{
|
||||
res.data = &apiPathList{
|
||||
Items: []*apiPath{},
|
||||
}
|
||||
|
||||
|
||||
@@ -455,13 +455,13 @@ func TestPathRecord(t *testing.T) {
|
||||
|
||||
hc := &http.Client{Transport: &http.Transport{}}
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v2/config/paths/edit/all", map[string]interface{}{
|
||||
httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/paths/patch/all", map[string]interface{}{
|
||||
"record": false,
|
||||
}, nil)
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v2/config/paths/edit/all", map[string]interface{}{
|
||||
httpRequest(t, hc, http.MethodPatch, "http://localhost:9997/v3/config/paths/patch/all", map[string]interface{}{
|
||||
"record": true,
|
||||
}, nil)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
|
||||
func paramsFromConf(cnf *conf.PathConf) rpicamera.Params {
|
||||
func paramsFromConf(cnf *conf.Path) rpicamera.Params {
|
||||
return rpicamera.Params{
|
||||
CameraID: cnf.RPICameraCamID,
|
||||
Width: cnf.RPICameraWidth,
|
||||
@@ -74,7 +74,7 @@ func (s *rpiCameraSource) Log(level logger.Level, format string, args ...interfa
|
||||
}
|
||||
|
||||
// run implements sourceStaticImpl.
|
||||
func (s *rpiCameraSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error {
|
||||
func (s *rpiCameraSource) run(ctx context.Context, cnf *conf.Path, reloadConf chan *conf.Path) error {
|
||||
medi := &description.Media{
|
||||
Type: description.MediaTypeVideo,
|
||||
Formats: []format.Format{&format.H264{
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
type rtmpServerAPIConnsListRes struct {
|
||||
data *apiRTMPConnsList
|
||||
data *apiRTMPConnList
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ outer:
|
||||
delete(s.conns, c)
|
||||
|
||||
case req := <-s.chAPIConnsList:
|
||||
data := &apiRTMPConnsList{
|
||||
data := &apiRTMPConnList{
|
||||
Items: []*apiRTMPConn{},
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ func (s *rtmpServer) closeConn(c *rtmpConn) {
|
||||
}
|
||||
|
||||
// apiConnsList is called by api.
|
||||
func (s *rtmpServer) apiConnsList() (*apiRTMPConnsList, error) {
|
||||
func (s *rtmpServer) apiConnsList() (*apiRTMPConnList, error) {
|
||||
req := rtmpServerAPIConnsListReq{
|
||||
res: make(chan rtmpServerAPIConnsListRes),
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (s *rtmpSource) Log(level logger.Level, format string, args ...interface{})
|
||||
}
|
||||
|
||||
// run implements sourceStaticImpl.
|
||||
func (s *rtmpSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error {
|
||||
func (s *rtmpSource) run(ctx context.Context, cnf *conf.Path, reloadConf chan *conf.Path) error {
|
||||
s.Log(logger.Debug, "connecting")
|
||||
|
||||
u, err := url.Parse(cnf.Source)
|
||||
|
||||
@@ -405,7 +405,7 @@ func (s *rtspServer) apiConnsGet(uuid uuid.UUID) (*apiRTSPConn, error) {
|
||||
}
|
||||
|
||||
// apiSessionsList is called by api and metrics.
|
||||
func (s *rtspServer) apiSessionsList() (*apiRTSPSessionsList, error) {
|
||||
func (s *rtspServer) apiSessionsList() (*apiRTSPSessionList, error) {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return nil, fmt.Errorf("terminated")
|
||||
@@ -415,7 +415,7 @@ func (s *rtspServer) apiSessionsList() (*apiRTSPSessionsList, error) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
data := &apiRTSPSessionsList{
|
||||
data := &apiRTSPSessionList{
|
||||
Items: []*apiRTSPSession{},
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
)
|
||||
|
||||
func createRangeHeader(cnf *conf.PathConf) (*headers.Range, error) {
|
||||
func createRangeHeader(cnf *conf.Path) (*headers.Range, error) {
|
||||
switch cnf.RTSPRangeType {
|
||||
case conf.RTSPRangeTypeClock:
|
||||
start, err := time.Parse("20060102T150405Z", cnf.RTSPRangeStart)
|
||||
@@ -91,7 +91,7 @@ func (s *rtspSource) Log(level logger.Level, format string, args ...interface{})
|
||||
}
|
||||
|
||||
// run implements sourceStaticImpl.
|
||||
func (s *rtspSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error {
|
||||
func (s *rtspSource) run(ctx context.Context, cnf *conf.Path, reloadConf chan *conf.Path) error {
|
||||
s.Log(logger.Debug, "connecting")
|
||||
|
||||
decodeErrLogger := logger.NewLimitedLogger(s)
|
||||
|
||||
@@ -16,7 +16,7 @@ const (
|
||||
|
||||
type sourceStaticImpl interface {
|
||||
logger.Writer
|
||||
run(context.Context, *conf.PathConf, chan *conf.PathConf) error
|
||||
run(context.Context, *conf.Path, chan *conf.Path) error
|
||||
apiSourceDescribe() apiPathSourceOrReader
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type sourceStaticParent interface {
|
||||
|
||||
// sourceStatic is a static source.
|
||||
type sourceStatic struct {
|
||||
conf *conf.PathConf
|
||||
conf *conf.Path
|
||||
parent sourceStaticParent
|
||||
|
||||
ctx context.Context
|
||||
@@ -37,7 +37,7 @@ type sourceStatic struct {
|
||||
running bool
|
||||
|
||||
// in
|
||||
chReloadConf chan *conf.PathConf
|
||||
chReloadConf chan *conf.Path
|
||||
chSourceStaticImplSetReady chan pathSourceStaticSetReadyReq
|
||||
chSourceStaticImplSetNotReady chan pathSourceStaticSetNotReadyReq
|
||||
|
||||
@@ -46,7 +46,7 @@ type sourceStatic struct {
|
||||
}
|
||||
|
||||
func newSourceStatic(
|
||||
cnf *conf.PathConf,
|
||||
cnf *conf.Path,
|
||||
readTimeout conf.StringDuration,
|
||||
writeTimeout conf.StringDuration,
|
||||
writeQueueSize int,
|
||||
@@ -55,7 +55,7 @@ func newSourceStatic(
|
||||
s := &sourceStatic{
|
||||
conf: cnf,
|
||||
parent: parent,
|
||||
chReloadConf: make(chan *conf.PathConf),
|
||||
chReloadConf: make(chan *conf.Path),
|
||||
chSourceStaticImplSetReady: make(chan pathSourceStaticSetReadyReq),
|
||||
chSourceStaticImplSetNotReady: make(chan pathSourceStaticSetNotReadyReq),
|
||||
}
|
||||
@@ -153,7 +153,7 @@ func (s *sourceStatic) run() {
|
||||
var innerCtx context.Context
|
||||
var innerCtxCancel func()
|
||||
implErr := make(chan error)
|
||||
innerReloadConf := make(chan *conf.PathConf)
|
||||
innerReloadConf := make(chan *conf.Path)
|
||||
|
||||
recreate := func() {
|
||||
innerCtx, innerCtxCancel = context.WithCancel(context.Background())
|
||||
@@ -208,7 +208,7 @@ func (s *sourceStatic) run() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sourceStatic) reloadConf(newConf *conf.PathConf) {
|
||||
func (s *sourceStatic) reloadConf(newConf *conf.Path) {
|
||||
select {
|
||||
case s.chReloadConf <- newConf:
|
||||
case <-s.ctx.Done():
|
||||
|
||||
@@ -25,7 +25,7 @@ type srtNewConnReq struct {
|
||||
}
|
||||
|
||||
type srtServerAPIConnsListRes struct {
|
||||
data *apiSRTConnsList
|
||||
data *apiSRTConnList
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ outer:
|
||||
delete(s.conns, c)
|
||||
|
||||
case req := <-s.chAPIConnsList:
|
||||
data := &apiSRTConnsList{
|
||||
data := &apiSRTConnList{
|
||||
Items: []*apiSRTConn{},
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ func (s *srtServer) closeConn(c *srtConn) {
|
||||
}
|
||||
|
||||
// apiConnsList is called by api.
|
||||
func (s *srtServer) apiConnsList() (*apiSRTConnsList, error) {
|
||||
func (s *srtServer) apiConnsList() (*apiSRTConnList, error) {
|
||||
req := srtServerAPIConnsListReq{
|
||||
res: make(chan srtServerAPIConnsListRes),
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (s *srtSource) Log(level logger.Level, format string, args ...interface{})
|
||||
}
|
||||
|
||||
// run implements sourceStaticImpl.
|
||||
func (s *srtSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error {
|
||||
func (s *srtSource) run(ctx context.Context, cnf *conf.Path, reloadConf chan *conf.Path) error {
|
||||
s.Log(logger.Debug, "connecting")
|
||||
|
||||
conf := srt.DefaultConfig()
|
||||
|
||||
@@ -66,7 +66,7 @@ func (s *udpSource) Log(level logger.Level, format string, args ...interface{})
|
||||
}
|
||||
|
||||
// run implements sourceStaticImpl.
|
||||
func (s *udpSource) run(ctx context.Context, cnf *conf.PathConf, _ chan *conf.PathConf) error {
|
||||
func (s *udpSource) run(ctx context.Context, cnf *conf.Path, _ chan *conf.Path) error {
|
||||
s.Log(logger.Debug, "connecting")
|
||||
|
||||
hostPort := cnf.Source[len("udp://"):]
|
||||
|
||||
@@ -232,7 +232,7 @@ func webrtcNewAPI(
|
||||
}
|
||||
|
||||
type webRTCManagerAPISessionsListRes struct {
|
||||
data *apiWebRTCSessionsList
|
||||
data *apiWebRTCSessionList
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -483,7 +483,7 @@ outer:
|
||||
req.res <- webRTCAddSessionCandidatesRes{sx: sx}
|
||||
|
||||
case req := <-m.chAPISessionsList:
|
||||
data := &apiWebRTCSessionsList{
|
||||
data := &apiWebRTCSessionList{
|
||||
Items: []*apiWebRTCSession{},
|
||||
}
|
||||
|
||||
@@ -620,7 +620,7 @@ func (m *webRTCManager) addSessionCandidates(
|
||||
}
|
||||
|
||||
// apiSessionsList is called by api.
|
||||
func (m *webRTCManager) apiSessionsList() (*apiWebRTCSessionsList, error) {
|
||||
func (m *webRTCManager) apiSessionsList() (*apiWebRTCSessionList, error) {
|
||||
req := webRTCManagerAPISessionsListReq{
|
||||
res: make(chan webRTCManagerAPISessionsListRes),
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func (s *webRTCSource) Log(level logger.Level, format string, args ...interface{
|
||||
}
|
||||
|
||||
// run implements sourceStaticImpl.
|
||||
func (s *webRTCSource) run(ctx context.Context, cnf *conf.PathConf, _ chan *conf.PathConf) error {
|
||||
func (s *webRTCSource) run(ctx context.Context, cnf *conf.Path, _ chan *conf.Path) error {
|
||||
s.Log(logger.Debug, "connecting")
|
||||
|
||||
u, err := url.Parse(cnf.Source)
|
||||
|
||||
+282
-263
@@ -1,7 +1,12 @@
|
||||
###############################################
|
||||
# General settings
|
||||
# Global settings
|
||||
|
||||
# Sets the verbosity of the program; available values are "error", "warn", "info", "debug".
|
||||
# Settings in this section are applied anywhere.
|
||||
|
||||
###############################################
|
||||
# Global settings -> General
|
||||
|
||||
# Verbosity of the program; available values are "error", "warn", "info", "debug".
|
||||
logLevel: info
|
||||
# Destinations of log messages; available values are "stdout", "file" and "syslog".
|
||||
logDestinations: [stdout]
|
||||
@@ -65,7 +70,7 @@ runOnConnectRestart: no
|
||||
runOnDisconnect:
|
||||
|
||||
###############################################
|
||||
# RTSP settings
|
||||
# Global settings -> RTSP
|
||||
|
||||
# Allow publishing and reading streams with the RTSP protocol.
|
||||
rtsp: yes
|
||||
@@ -105,7 +110,7 @@ serverCert: server.crt
|
||||
authMethods: [basic]
|
||||
|
||||
###############################################
|
||||
# RTMP settings
|
||||
# Global settings -> RTMP
|
||||
|
||||
# Allow publishing and reading streams with the RTMP protocol.
|
||||
rtmp: yes
|
||||
@@ -125,7 +130,7 @@ rtmpServerKey: server.key
|
||||
rtmpServerCert: server.crt
|
||||
|
||||
###############################################
|
||||
# HLS settings
|
||||
# Global settings -> HLS
|
||||
|
||||
# Allow reading streams with the HLS protocol.
|
||||
hls: yes
|
||||
@@ -181,7 +186,7 @@ hlsTrustedProxies: []
|
||||
hlsDirectory: ''
|
||||
|
||||
###############################################
|
||||
# WebRTC settings
|
||||
# Global settings -> WebRTC
|
||||
|
||||
# Allow publishing and reading streams with the WebRTC protocol.
|
||||
webrtc: yes
|
||||
@@ -233,7 +238,7 @@ webrtcICEUDPMuxAddress:
|
||||
webrtcICETCPMuxAddress:
|
||||
|
||||
###############################################
|
||||
# SRT settings
|
||||
# Global settings -> SRT
|
||||
|
||||
# Allow publishing and reading streams with the SRT protocol.
|
||||
srt: yes
|
||||
@@ -241,7 +246,7 @@ srt: yes
|
||||
srtAddress: :8890
|
||||
|
||||
###############################################
|
||||
# Recording settings
|
||||
# Global settings -> Recording
|
||||
|
||||
# Record streams to disk.
|
||||
record: no
|
||||
@@ -262,268 +267,282 @@ recordSegmentDuration: 1h
|
||||
# Set to 0s to disable automatic deletion.
|
||||
recordDeleteAfter: 24h
|
||||
|
||||
###############################################
|
||||
# Default path settings
|
||||
|
||||
# Settings in "pathDefaults" are applied anywhere,
|
||||
# unless they are overridden in "paths".
|
||||
pathDefaults:
|
||||
|
||||
###############################################
|
||||
# Default path settings -> General
|
||||
|
||||
# Source of the stream. This can be:
|
||||
# * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client
|
||||
# * rtsp://existing-url -> the stream is pulled from another RTSP server / camera
|
||||
# * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS
|
||||
# * rtmp://existing-url -> the stream is pulled from another RTMP server / camera
|
||||
# * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS
|
||||
# * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera
|
||||
# * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera with HTTPS
|
||||
# * udp://ip:port -> the stream is pulled with UDP, by listening on the specified IP and port
|
||||
# * srt://existing-url -> the stream is pulled from another SRT server / camera
|
||||
# * whep://existing-url -> the stream is pulled from another WebRTC server / camera
|
||||
# * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS
|
||||
# * redirect -> the stream is provided by another path or server
|
||||
# * rpiCamera -> the stream is provided by a Raspberry Pi Camera
|
||||
source: publisher
|
||||
# If the source is a URL, and the source certificate is self-signed
|
||||
# or invalid, you can provide the fingerprint of the certificate in order to
|
||||
# validate it anyway. It can be obtained by running:
|
||||
# openssl s_client -connect source_ip:source_port </dev/null 2>/dev/null | sed -n '/BEGIN/,/END/p' > server.crt
|
||||
# openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':'
|
||||
sourceFingerprint:
|
||||
# If the source is a URL, it will be pulled only when at least
|
||||
# one reader is connected, saving bandwidth.
|
||||
sourceOnDemand: no
|
||||
# If sourceOnDemand is "yes", readers will be put on hold until the source is
|
||||
# ready or until this amount of time has passed.
|
||||
sourceOnDemandStartTimeout: 10s
|
||||
# If sourceOnDemand is "yes", the source will be closed when there are no
|
||||
# readers connected and this amount of time has passed.
|
||||
sourceOnDemandCloseAfter: 10s
|
||||
# Maximum number of readers. Zero means no limit.
|
||||
maxReaders: 0
|
||||
# SRT encryption passphrase require to read from this path
|
||||
srtReadPassphrase:
|
||||
# Record streams to disk (if global recording is enabled).
|
||||
record: yes
|
||||
|
||||
###############################################
|
||||
# Default path settings -> Authentication
|
||||
|
||||
# Username required to publish.
|
||||
# SHA256-hashed values can be inserted with the "sha256:" prefix.
|
||||
publishUser:
|
||||
# Password required to publish.
|
||||
# SHA256-hashed values can be inserted with the "sha256:" prefix.
|
||||
publishPass:
|
||||
# IPs or networks (x.x.x.x/24) allowed to publish.
|
||||
publishIPs: []
|
||||
|
||||
# Username required to read.
|
||||
# SHA256-hashed values can be inserted with the "sha256:" prefix.
|
||||
readUser:
|
||||
# password required to read.
|
||||
# SHA256-hashed values can be inserted with the "sha256:" prefix.
|
||||
readPass:
|
||||
# IPs or networks (x.x.x.x/24) allowed to read.
|
||||
readIPs: []
|
||||
|
||||
###############################################
|
||||
# Default path settings -> Publisher source (when source is "publisher")
|
||||
|
||||
# allow another client to disconnect the current publisher and publish in its place.
|
||||
overridePublisher: yes
|
||||
# if no one is publishing, redirect readers to this path.
|
||||
# It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL.
|
||||
fallback:
|
||||
# SRT encryption passphrase required to publish to this path
|
||||
srtPublishPassphrase:
|
||||
|
||||
###############################################
|
||||
# Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL)
|
||||
|
||||
# protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp".
|
||||
sourceProtocol: automatic
|
||||
# support sources that don't provide server ports or use random server ports. This is a security issue
|
||||
# and must be used only when interacting with sources that require it.
|
||||
sourceAnyPortEnable: no
|
||||
# range header to send to the source, in order to start streaming from the specified offset.
|
||||
# available values:
|
||||
# * clock: Absolute time
|
||||
# * npt: Normal Play Time
|
||||
# * smpte: SMPTE timestamps relative to the start of the recording
|
||||
rtspRangeType:
|
||||
# available values:
|
||||
# * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z
|
||||
# * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
|
||||
# * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
|
||||
rtspRangeStart:
|
||||
|
||||
###############################################
|
||||
# Default path settings -> Redirect source (when source is "redirect")
|
||||
|
||||
# RTSP URL which clients will be redirected to.
|
||||
sourceRedirect:
|
||||
|
||||
###############################################
|
||||
# Default path settings -> Raspberry Pi Camera source (when source is "rpiCamera")
|
||||
|
||||
# ID of the camera
|
||||
rpiCameraCamID: 0
|
||||
# width of frames
|
||||
rpiCameraWidth: 1920
|
||||
# height of frames
|
||||
rpiCameraHeight: 1080
|
||||
# flip horizontally
|
||||
rpiCameraHFlip: false
|
||||
# flip vertically
|
||||
rpiCameraVFlip: false
|
||||
# brightness [-1, 1]
|
||||
rpiCameraBrightness: 0
|
||||
# contrast [0, 16]
|
||||
rpiCameraContrast: 1
|
||||
# saturation [0, 16]
|
||||
rpiCameraSaturation: 1
|
||||
# sharpness [0, 16]
|
||||
rpiCameraSharpness: 1
|
||||
# exposure mode.
|
||||
# values: normal, short, long, custom
|
||||
rpiCameraExposure: normal
|
||||
# auto-white-balance mode.
|
||||
# values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom
|
||||
rpiCameraAWB: auto
|
||||
# denoise operating mode.
|
||||
# values: off, cdn_off, cdn_fast, cdn_hq
|
||||
rpiCameraDenoise: "off"
|
||||
# fixed shutter speed, in microseconds.
|
||||
rpiCameraShutter: 0
|
||||
# metering mode of the AEC/AGC algorithm.
|
||||
# values: centre, spot, matrix, custom
|
||||
rpiCameraMetering: centre
|
||||
# fixed gain
|
||||
rpiCameraGain: 0
|
||||
# EV compensation of the image [-10, 10]
|
||||
rpiCameraEV: 0
|
||||
# Region of interest, in format x,y,width,height
|
||||
rpiCameraROI:
|
||||
# whether to enable HDR on Raspberry Camera 3.
|
||||
rpiCameraHDR: false
|
||||
# tuning file
|
||||
rpiCameraTuningFile:
|
||||
# sensor mode, in format [width]:[height]:[bit-depth]:[packing]
|
||||
# bit-depth and packing are optional.
|
||||
rpiCameraMode:
|
||||
# frames per second
|
||||
rpiCameraFPS: 30
|
||||
# period between IDR frames
|
||||
rpiCameraIDRPeriod: 60
|
||||
# bitrate
|
||||
rpiCameraBitrate: 1000000
|
||||
# H264 profile
|
||||
rpiCameraProfile: main
|
||||
# H264 level
|
||||
rpiCameraLevel: '4.1'
|
||||
# Autofocus mode
|
||||
# values: auto, manual, continuous
|
||||
rpiCameraAfMode: auto
|
||||
# Autofocus range
|
||||
# values: normal, macro, full
|
||||
rpiCameraAfRange: normal
|
||||
# Autofocus speed
|
||||
# values: normal, fast
|
||||
rpiCameraAfSpeed: normal
|
||||
# Lens position (for manual autofocus only), will be set to focus to a specific distance
|
||||
# calculated by the following formula: d = 1 / value
|
||||
# Examples: 0 moves the lens to infinity.
|
||||
# 0.5 moves the lens to focus on objects 2m away.
|
||||
# 2 moves the lens to focus on objects 50cm away.
|
||||
rpiCameraLensPosition: 0.0
|
||||
# Specifies the autofocus window, in the form x,y,width,height where the coordinates
|
||||
# are given as a proportion of the entire image.
|
||||
rpiCameraAfWindow:
|
||||
# enables printing text on each frame.
|
||||
rpiCameraTextOverlayEnable: false
|
||||
# text that is printed on each frame.
|
||||
# format is the one of the strftime() function.
|
||||
rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX'
|
||||
|
||||
###############################################
|
||||
# Default path settings -> Hooks
|
||||
|
||||
# Command to run when this path is initialized.
|
||||
# This can be used to publish a stream when the server is launched.
|
||||
# This is terminated with SIGINT when the program closes.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
runOnInit:
|
||||
# Restart the command if it exits.
|
||||
runOnInitRestart: no
|
||||
|
||||
# Command to run when this path is requested by a reader.
|
||||
# This can be used to publish a stream on demand.
|
||||
# This is terminated with SIGINT when the path is not requested anymore.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
runOnDemand:
|
||||
# Restart the command if it exits.
|
||||
runOnDemandRestart: no
|
||||
# Readers will be put on hold until the runOnDemand command starts publishing
|
||||
# or until this amount of time has passed.
|
||||
runOnDemandStartTimeout: 10s
|
||||
# The command will be closed when there are no
|
||||
# readers connected and this amount of time has passed.
|
||||
runOnDemandCloseAfter: 10s
|
||||
|
||||
# Command to run when the stream is ready to be read, whenever it is
|
||||
# published by a client or pulled from a server / camera.
|
||||
# This is terminated with SIGINT when the stream is not ready anymore.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
# * MTX_SOURCE_TYPE: source type
|
||||
# * MTX_SOURCE_ID: source ID
|
||||
runOnReady:
|
||||
# Restart the command if it exits.
|
||||
runOnReadyRestart: no
|
||||
# Command to run when the stream is not available anymore.
|
||||
# Environment variables are the same of runOnReady.
|
||||
runOnNotReady:
|
||||
|
||||
# Command to run when a client starts reading.
|
||||
# This is terminated with SIGINT when a client stops reading.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
# * MTX_READER_TYPE: reader type
|
||||
# * MTX_READER_ID: reader ID
|
||||
runOnRead:
|
||||
# Restart the command if it exits.
|
||||
runOnReadRestart: no
|
||||
# Command to run when a client stops reading.
|
||||
# Environment variables are the same of runOnRead.
|
||||
runOnUnread:
|
||||
|
||||
# Command to run when a record segment is complete.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
# * MTX_SEGMENT_PATH: segment file path
|
||||
runOnRecordSegmentComplete:
|
||||
|
||||
###############################################
|
||||
# Path settings
|
||||
|
||||
# These settings are path-dependent, and the map key is the name of the path.
|
||||
# Settings in "paths" are applied to specific paths, and the map key
|
||||
# is the name of the path.
|
||||
# It's possible to use regular expressions by using a tilde as prefix,
|
||||
# for example "~^(test1|test2)$" will match both "test1" and "test2",
|
||||
# for example "~^prefix" will match all paths that start with "prefix".
|
||||
# Settings under the path "all" are applied to all paths that do not match
|
||||
# Settings under path "all" are applied to all paths that do not match
|
||||
# another entry.
|
||||
paths:
|
||||
all:
|
||||
###############################################
|
||||
# General path settings
|
||||
|
||||
# Source of the stream. This can be:
|
||||
# * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client
|
||||
# * rtsp://existing-url -> the stream is pulled from another RTSP server / camera
|
||||
# * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS
|
||||
# * rtmp://existing-url -> the stream is pulled from another RTMP server / camera
|
||||
# * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS
|
||||
# * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera
|
||||
# * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera with HTTPS
|
||||
# * udp://ip:port -> the stream is pulled with UDP, by listening on the specified IP and port
|
||||
# * srt://existing-url -> the stream is pulled from another SRT server / camera
|
||||
# * whep://existing-url -> the stream is pulled from another WebRTC server / camera
|
||||
# * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS
|
||||
# * redirect -> the stream is provided by another path or server
|
||||
# * rpiCamera -> the stream is provided by a Raspberry Pi Camera
|
||||
source: publisher
|
||||
# If the source is a URL, and the source certificate is self-signed
|
||||
# or invalid, you can provide the fingerprint of the certificate in order to
|
||||
# validate it anyway. It can be obtained by running:
|
||||
# openssl s_client -connect source_ip:source_port </dev/null 2>/dev/null | sed -n '/BEGIN/,/END/p' > server.crt
|
||||
# openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':'
|
||||
sourceFingerprint:
|
||||
# If the source is a URL, it will be pulled only when at least
|
||||
# one reader is connected, saving bandwidth.
|
||||
sourceOnDemand: no
|
||||
# If sourceOnDemand is "yes", readers will be put on hold until the source is
|
||||
# ready or until this amount of time has passed.
|
||||
sourceOnDemandStartTimeout: 10s
|
||||
# If sourceOnDemand is "yes", the source will be closed when there are no
|
||||
# readers connected and this amount of time has passed.
|
||||
sourceOnDemandCloseAfter: 10s
|
||||
# Maximum number of readers. Zero means no limit.
|
||||
maxReaders: 0
|
||||
# SRT encryption passphrase require to read from this path
|
||||
srtReadPassphrase:
|
||||
# Record streams to disk (if global recording is enabled).
|
||||
record: yes
|
||||
|
||||
###############################################
|
||||
# Authentication path settings
|
||||
|
||||
# Username required to publish.
|
||||
# SHA256-hashed values can be inserted with the "sha256:" prefix.
|
||||
publishUser:
|
||||
# Password required to publish.
|
||||
# SHA256-hashed values can be inserted with the "sha256:" prefix.
|
||||
publishPass:
|
||||
# IPs or networks (x.x.x.x/24) allowed to publish.
|
||||
publishIPs: []
|
||||
|
||||
# Username required to read.
|
||||
# SHA256-hashed values can be inserted with the "sha256:" prefix.
|
||||
readUser:
|
||||
# password required to read.
|
||||
# SHA256-hashed values can be inserted with the "sha256:" prefix.
|
||||
readPass:
|
||||
# IPs or networks (x.x.x.x/24) allowed to read.
|
||||
readIPs: []
|
||||
|
||||
###############################################
|
||||
# Publisher path settings (when source is "publisher")
|
||||
|
||||
# allow another client to disconnect the current publisher and publish in its place.
|
||||
overridePublisher: yes
|
||||
# if no one is publishing, redirect readers to this path.
|
||||
# It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL.
|
||||
fallback:
|
||||
# SRT encryption passphrase required to publish to this path
|
||||
srtPublishPassphrase:
|
||||
|
||||
###############################################
|
||||
# RTSP path settings (when source is a RTSP or a RTSPS URL)
|
||||
|
||||
# protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp".
|
||||
sourceProtocol: automatic
|
||||
# support sources that don't provide server ports or use random server ports. This is a security issue
|
||||
# and must be used only when interacting with sources that require it.
|
||||
sourceAnyPortEnable: no
|
||||
# range header to send to the source, in order to start streaming from the specified offset.
|
||||
# available values:
|
||||
# * clock: Absolute time
|
||||
# * npt: Normal Play Time
|
||||
# * smpte: SMPTE timestamps relative to the start of the recording
|
||||
rtspRangeType:
|
||||
# available values:
|
||||
# * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z
|
||||
# * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
|
||||
# * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
|
||||
rtspRangeStart:
|
||||
|
||||
###############################################
|
||||
# Redirect path settings (when source is "redirect")
|
||||
|
||||
# RTSP URL which clients will be redirected to.
|
||||
sourceRedirect:
|
||||
|
||||
###############################################
|
||||
# Raspberry Pi Camera path settings (when source is "rpiCamera")
|
||||
|
||||
# ID of the camera
|
||||
rpiCameraCamID: 0
|
||||
# width of frames
|
||||
rpiCameraWidth: 1920
|
||||
# height of frames
|
||||
rpiCameraHeight: 1080
|
||||
# flip horizontally
|
||||
rpiCameraHFlip: false
|
||||
# flip vertically
|
||||
rpiCameraVFlip: false
|
||||
# brightness [-1, 1]
|
||||
rpiCameraBrightness: 0
|
||||
# contrast [0, 16]
|
||||
rpiCameraContrast: 1
|
||||
# saturation [0, 16]
|
||||
rpiCameraSaturation: 1
|
||||
# sharpness [0, 16]
|
||||
rpiCameraSharpness: 1
|
||||
# exposure mode.
|
||||
# values: normal, short, long, custom
|
||||
rpiCameraExposure: normal
|
||||
# auto-white-balance mode.
|
||||
# values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom
|
||||
rpiCameraAWB: auto
|
||||
# denoise operating mode.
|
||||
# values: off, cdn_off, cdn_fast, cdn_hq
|
||||
rpiCameraDenoise: "off"
|
||||
# fixed shutter speed, in microseconds.
|
||||
rpiCameraShutter: 0
|
||||
# metering mode of the AEC/AGC algorithm.
|
||||
# values: centre, spot, matrix, custom
|
||||
rpiCameraMetering: centre
|
||||
# fixed gain
|
||||
rpiCameraGain: 0
|
||||
# EV compensation of the image [-10, 10]
|
||||
rpiCameraEV: 0
|
||||
# Region of interest, in format x,y,width,height
|
||||
rpiCameraROI:
|
||||
# whether to enable HDR on Raspberry Camera 3.
|
||||
rpiCameraHDR: false
|
||||
# tuning file
|
||||
rpiCameraTuningFile:
|
||||
# sensor mode, in format [width]:[height]:[bit-depth]:[packing]
|
||||
# bit-depth and packing are optional.
|
||||
rpiCameraMode:
|
||||
# frames per second
|
||||
rpiCameraFPS: 30
|
||||
# period between IDR frames
|
||||
rpiCameraIDRPeriod: 60
|
||||
# bitrate
|
||||
rpiCameraBitrate: 1000000
|
||||
# H264 profile
|
||||
rpiCameraProfile: main
|
||||
# H264 level
|
||||
rpiCameraLevel: '4.1'
|
||||
# Autofocus mode
|
||||
# values: auto, manual, continuous
|
||||
rpiCameraAfMode: auto
|
||||
# Autofocus range
|
||||
# values: normal, macro, full
|
||||
rpiCameraAfRange: normal
|
||||
# Autofocus speed
|
||||
# values: normal, fast
|
||||
rpiCameraAfSpeed: normal
|
||||
# Lens position (for manual autofocus only), will be set to focus to a specific distance
|
||||
# calculated by the following formula: d = 1 / value
|
||||
# Examples: 0 moves the lens to infinity.
|
||||
# 0.5 moves the lens to focus on objects 2m away.
|
||||
# 2 moves the lens to focus on objects 50cm away.
|
||||
rpiCameraLensPosition: 0.0
|
||||
# Specifies the autofocus window, in the form x,y,width,height where the coordinates
|
||||
# are given as a proportion of the entire image.
|
||||
rpiCameraAfWindow:
|
||||
# enables printing text on each frame.
|
||||
rpiCameraTextOverlayEnable: false
|
||||
# text that is printed on each frame.
|
||||
# format is the one of the strftime() function.
|
||||
rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX'
|
||||
|
||||
###############################################
|
||||
# Hooks path settings
|
||||
|
||||
# Command to run when this path is initialized.
|
||||
# This can be used to publish a stream when the server is launched.
|
||||
# This is terminated with SIGINT when the program closes.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
runOnInit:
|
||||
# Restart the command if it exits.
|
||||
runOnInitRestart: no
|
||||
|
||||
# Command to run when this path is requested by a reader.
|
||||
# This can be used to publish a stream on demand.
|
||||
# This is terminated with SIGINT when the path is not requested anymore.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
runOnDemand:
|
||||
# Restart the command if it exits.
|
||||
runOnDemandRestart: no
|
||||
# Readers will be put on hold until the runOnDemand command starts publishing
|
||||
# or until this amount of time has passed.
|
||||
runOnDemandStartTimeout: 10s
|
||||
# The command will be closed when there are no
|
||||
# readers connected and this amount of time has passed.
|
||||
runOnDemandCloseAfter: 10s
|
||||
|
||||
# Command to run when the stream is ready to be read, whenever it is
|
||||
# published by a client or pulled from a server / camera.
|
||||
# This is terminated with SIGINT when the stream is not ready anymore.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
# * MTX_SOURCE_TYPE: source type
|
||||
# * MTX_SOURCE_ID: source ID
|
||||
runOnReady:
|
||||
# Restart the command if it exits.
|
||||
runOnReadyRestart: no
|
||||
# Command to run when the stream is not available anymore.
|
||||
# Environment variables are the same of runOnReady.
|
||||
runOnNotReady:
|
||||
|
||||
# Command to run when a client starts reading.
|
||||
# This is terminated with SIGINT when a client stops reading.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
# * MTX_READER_TYPE: reader type
|
||||
# * MTX_READER_ID: reader ID
|
||||
runOnRead:
|
||||
# Restart the command if it exits.
|
||||
runOnReadRestart: no
|
||||
# Command to run when a client stops reading.
|
||||
# Environment variables are the same of runOnRead.
|
||||
runOnUnread:
|
||||
|
||||
# Command to run when a record segment is complete.
|
||||
# The following environment variables are available:
|
||||
# * MTX_PATH: path name
|
||||
# * RTSP_PORT: RTSP server port
|
||||
# * G1, G2, ...: regular expression groups, if path name is
|
||||
# a regular expression.
|
||||
# * MTX_SEGMENT_PATH: segment file path
|
||||
runOnRecordSegmentComplete:
|
||||
# any setting in "pathDefaults" can be overridden here.
|
||||
# for instance:
|
||||
# my_camera:
|
||||
# source: rtsp://my_camera
|
||||
|
||||
Reference in New Issue
Block a user