forked from External/mediamtx
add docs
This commit is contained in:
parent
0519abc106
commit
58fe1cfe77
23 changed files with 160 additions and 37 deletions
|
|
@ -5,7 +5,7 @@
|
||||||
[](https://goreportcard.com/report/github.com/aler9/rtsp-simple-server)
|
[](https://goreportcard.com/report/github.com/aler9/rtsp-simple-server)
|
||||||
[](https://hub.docker.com/r/aler9/rtsp-simple-server)
|
[](https://hub.docker.com/r/aler9/rtsp-simple-server)
|
||||||
|
|
||||||
_rtsp-simple-server_ is a simple, ready-to-use and zero-dependency RTSP server and RTSP proxy, a software that allows multiple users to publish and read live video and audio streams over time. RTSP is a standard protocol that describes how to perform these operations with the help of a server, that is contacted by both readers and publishers and relays the publisher streams to the readers.
|
_rtsp-simple-server_ is a simple, ready-to-use and zero-dependency RTSP server and RTSP proxy, a software that allows multiple users to publish, read and proxy live video and audio streams over time. RTSP is a standard protocol that describes how to perform these operations with the help of a server, that is contacted by both publishers and readers and relays the publisher's streams to the readers.
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
* Read and publish live streams with UDP and TCP
|
* Read and publish live streams with UDP and TCP
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ func (cs state) String() string {
|
||||||
return "Invalid"
|
return "Invalid"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path is implemented by path.Path.
|
||||||
type Path interface {
|
type Path interface {
|
||||||
Name() string
|
Name() string
|
||||||
SourceTrackCount() int
|
SourceTrackCount() int
|
||||||
|
|
@ -88,6 +89,7 @@ type Path interface {
|
||||||
OnFrame(int, gortsplib.StreamType, []byte)
|
OnFrame(int, gortsplib.StreamType, []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parent is implemented by clientman.ClientMan.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
OnClientClose(*Client)
|
OnClientClose(*Client)
|
||||||
|
|
@ -96,6 +98,7 @@ type Parent interface {
|
||||||
OnClientSetupPlay(*Client, string, int, *base.Request) (Path, error)
|
OnClientSetupPlay(*Client, string, int, *base.Request) (Path, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client is a RTSP client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
rtspPort int
|
rtspPort int
|
||||||
readTimeout time.Duration
|
readTimeout time.Duration
|
||||||
|
|
@ -128,6 +131,7 @@ type Client struct {
|
||||||
terminate chan struct{}
|
terminate chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a Client.
|
||||||
func New(
|
func New(
|
||||||
rtspPort int,
|
rtspPort int,
|
||||||
readTimeout time.Duration,
|
readTimeout time.Duration,
|
||||||
|
|
@ -174,11 +178,13 @@ func New(
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a Client.
|
||||||
func (c *Client) Close() {
|
func (c *Client) Close() {
|
||||||
atomic.AddInt64(c.stats.CountClients, -1)
|
atomic.AddInt64(c.stats.CountClients, -1)
|
||||||
close(c.terminate)
|
close(c.terminate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSource implementes path.source.
|
||||||
func (c *Client) IsSource() {}
|
func (c *Client) IsSource() {}
|
||||||
|
|
||||||
func (c *Client) log(format string, args ...interface{}) {
|
func (c *Client) log(format string, args ...interface{}) {
|
||||||
|
|
@ -240,22 +246,23 @@ func (c *Client) writeResError(cseq base.HeaderValue, code base.StatusCode, err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrAuthNotCritical struct {
|
type errAuthNotCritical struct {
|
||||||
*base.Response
|
*base.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ErrAuthNotCritical) Error() string {
|
func (errAuthNotCritical) Error() string {
|
||||||
return "auth not critical"
|
return "auth not critical"
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrAuthCritical struct {
|
type errAuthCritical struct {
|
||||||
*base.Response
|
*base.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ErrAuthCritical) Error() string {
|
func (errAuthCritical) Error() string {
|
||||||
return "auth critical"
|
return "auth critical"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Authenticate performs an authentication.
|
||||||
func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{}, user string, pass string, req *base.Request) error {
|
func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{}, user string, pass string, req *base.Request) error {
|
||||||
// validate ip
|
// validate ip
|
||||||
if ips != nil {
|
if ips != nil {
|
||||||
|
|
@ -264,7 +271,7 @@ func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{
|
||||||
if !ipEqualOrInRange(ip, ips) {
|
if !ipEqualOrInRange(ip, ips) {
|
||||||
c.log("ERR: ip '%s' not allowed", ip)
|
c.log("ERR: ip '%s' not allowed", ip)
|
||||||
|
|
||||||
return ErrAuthCritical{&base.Response{
|
return errAuthCritical{&base.Response{
|
||||||
StatusCode: base.StatusUnauthorized,
|
StatusCode: base.StatusUnauthorized,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": req.Header["CSeq"],
|
"CSeq": req.Header["CSeq"],
|
||||||
|
|
@ -296,7 +303,7 @@ func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{
|
||||||
if c.authFailures > 3 {
|
if c.authFailures > 3 {
|
||||||
c.log("ERR: unauthorized: %s", err)
|
c.log("ERR: unauthorized: %s", err)
|
||||||
|
|
||||||
return ErrAuthCritical{&base.Response{
|
return errAuthCritical{&base.Response{
|
||||||
StatusCode: base.StatusUnauthorized,
|
StatusCode: base.StatusUnauthorized,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": req.Header["CSeq"],
|
"CSeq": req.Header["CSeq"],
|
||||||
|
|
@ -309,7 +316,7 @@ func (c *Client) Authenticate(authMethods []headers.AuthMethod, ips []interface{
|
||||||
c.log("WARN: unauthorized: %s", err)
|
c.log("WARN: unauthorized: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ErrAuthNotCritical{&base.Response{
|
return errAuthNotCritical{&base.Response{
|
||||||
StatusCode: base.StatusUnauthorized,
|
StatusCode: base.StatusUnauthorized,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"CSeq": req.Header["CSeq"],
|
"CSeq": req.Header["CSeq"],
|
||||||
|
|
@ -382,11 +389,11 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
path, err := c.parent.OnClientDescribe(c, basePath, req)
|
path, err := c.parent.OnClientDescribe(c, basePath, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch terr := err.(type) {
|
switch terr := err.(type) {
|
||||||
case ErrAuthNotCritical:
|
case errAuthNotCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.conn.WriteResponse(terr.Response)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case ErrAuthCritical:
|
case errAuthCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.conn.WriteResponse(terr.Response)
|
||||||
return errRunTerminate
|
return errRunTerminate
|
||||||
|
|
||||||
|
|
@ -441,11 +448,11 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
path, err := c.parent.OnClientAnnounce(c, basePath, tracks, req)
|
path, err := c.parent.OnClientAnnounce(c, basePath, tracks, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch terr := err.(type) {
|
switch terr := err.(type) {
|
||||||
case ErrAuthNotCritical:
|
case errAuthNotCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.conn.WriteResponse(terr.Response)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case ErrAuthCritical:
|
case errAuthCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.conn.WriteResponse(terr.Response)
|
||||||
return errRunTerminate
|
return errRunTerminate
|
||||||
|
|
||||||
|
|
@ -534,11 +541,11 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
path, err := c.parent.OnClientSetupPlay(c, basePath, trackId, req)
|
path, err := c.parent.OnClientSetupPlay(c, basePath, trackId, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch terr := err.(type) {
|
switch terr := err.(type) {
|
||||||
case ErrAuthNotCritical:
|
case errAuthNotCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.conn.WriteResponse(terr.Response)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case ErrAuthCritical:
|
case errAuthCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.conn.WriteResponse(terr.Response)
|
||||||
return errRunTerminate
|
return errRunTerminate
|
||||||
|
|
||||||
|
|
@ -592,11 +599,11 @@ func (c *Client) handleRequest(req *base.Request) error {
|
||||||
path, err := c.parent.OnClientSetupPlay(c, basePath, trackId, req)
|
path, err := c.parent.OnClientSetupPlay(c, basePath, trackId, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch terr := err.(type) {
|
switch terr := err.(type) {
|
||||||
case ErrAuthNotCritical:
|
case errAuthNotCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.conn.WriteResponse(terr.Response)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case ErrAuthCritical:
|
case errAuthCritical:
|
||||||
c.conn.WriteResponse(terr.Response)
|
c.conn.WriteResponse(terr.Response)
|
||||||
return errRunTerminate
|
return errRunTerminate
|
||||||
|
|
||||||
|
|
@ -1288,6 +1295,7 @@ func (c *Client) runRecordTCP() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnUdpPublisherFrame implements serverudp.Publisher.
|
||||||
func (c *Client) OnUdpPublisherFrame(trackId int, streamType base.StreamType, buf []byte) {
|
func (c *Client) OnUdpPublisherFrame(trackId int, streamType base.StreamType, buf []byte) {
|
||||||
atomic.StoreInt64(c.udpLastFrameTimes[trackId], time.Now().Unix())
|
atomic.StoreInt64(c.udpLastFrameTimes[trackId], time.Now().Unix())
|
||||||
|
|
||||||
|
|
@ -1295,6 +1303,7 @@ func (c *Client) OnUdpPublisherFrame(trackId int, streamType base.StreamType, bu
|
||||||
c.path.OnFrame(trackId, streamType, buf)
|
c.path.OnFrame(trackId, streamType, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnReaderFrame implements path.Reader.
|
||||||
func (c *Client) OnReaderFrame(trackId int, streamType base.StreamType, buf []byte) {
|
func (c *Client) OnReaderFrame(trackId int, streamType base.StreamType, buf []byte) {
|
||||||
track, ok := c.streamTracks[trackId]
|
track, ok := c.streamTracks[trackId]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -1326,6 +1335,7 @@ func (c *Client) OnReaderFrame(trackId int, streamType base.StreamType, buf []by
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnPathDescribeData is called by path.Path.
|
||||||
func (c *Client) OnPathDescribeData(sdp []byte, redirect string, err error) {
|
func (c *Client) OnPathDescribeData(sdp []byte, redirect string, err error) {
|
||||||
c.describeData <- describeData{sdp, redirect, err}
|
c.describeData <- describeData{sdp, redirect, err}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,12 @@ import (
|
||||||
"github.com/aler9/rtsp-simple-server/internal/stats"
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientManager is a client.Client manager.
|
||||||
type ClientManager struct {
|
type ClientManager struct {
|
||||||
rtspPort int
|
rtspPort int
|
||||||
readTimeout time.Duration
|
readTimeout time.Duration
|
||||||
|
|
@ -44,6 +46,7 @@ type ClientManager struct {
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a ClientManager.
|
||||||
func New(
|
func New(
|
||||||
rtspPort int,
|
rtspPort int,
|
||||||
readTimeout time.Duration,
|
readTimeout time.Duration,
|
||||||
|
|
@ -81,11 +84,13 @@ func New(
|
||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a ClientManager.
|
||||||
func (cm *ClientManager) Close() {
|
func (cm *ClientManager) Close() {
|
||||||
close(cm.terminate)
|
close(cm.terminate)
|
||||||
<-cm.done
|
<-cm.done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log is the main logging function.
|
||||||
func (cm *ClientManager) Log(format string, args ...interface{}) {
|
func (cm *ClientManager) Log(format string, args ...interface{}) {
|
||||||
cm.parent.Log(format, args...)
|
cm.parent.Log(format, args...)
|
||||||
}
|
}
|
||||||
|
|
@ -137,18 +142,22 @@ outer:
|
||||||
close(cm.clientClose)
|
close(cm.clientClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientClose is called by client.Client.
|
||||||
func (cm *ClientManager) OnClientClose(c *client.Client) {
|
func (cm *ClientManager) OnClientClose(c *client.Client) {
|
||||||
cm.clientClose <- c
|
cm.clientClose <- c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientDescribe is called by client.Client.
|
||||||
func (cm *ClientManager) OnClientDescribe(c *client.Client, pathName string, req *base.Request) (client.Path, error) {
|
func (cm *ClientManager) OnClientDescribe(c *client.Client, pathName string, req *base.Request) (client.Path, error) {
|
||||||
return cm.pathMan.OnClientDescribe(c, pathName, req)
|
return cm.pathMan.OnClientDescribe(c, pathName, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientAnnounce is called by client.Client.
|
||||||
func (cm *ClientManager) OnClientAnnounce(c *client.Client, pathName string, tracks gortsplib.Tracks, req *base.Request) (client.Path, error) {
|
func (cm *ClientManager) OnClientAnnounce(c *client.Client, pathName string, tracks gortsplib.Tracks, req *base.Request) (client.Path, error) {
|
||||||
return cm.pathMan.OnClientAnnounce(c, pathName, tracks, req)
|
return cm.pathMan.OnClientAnnounce(c, pathName, tracks, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientSetupPlay is called by client.Client.
|
||||||
func (cm *ClientManager) OnClientSetupPlay(c *client.Client, pathName string, trackId int, req *base.Request) (client.Path, error) {
|
func (cm *ClientManager) OnClientSetupPlay(c *client.Client, pathName string, trackId int, req *base.Request) (client.Path, error) {
|
||||||
return cm.pathMan.OnClientSetupPlay(c, pathName, trackId, req)
|
return cm.pathMan.OnClientSetupPlay(c, pathName, trackId, req)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/aler9/rtsp-simple-server/internal/loghandler"
|
"github.com/aler9/rtsp-simple-server/internal/loghandler"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Conf is the program configuration.
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
Protocols []string `yaml:"protocols"`
|
Protocols []string `yaml:"protocols"`
|
||||||
ProtocolsParsed map[gortsplib.StreamProtocol]struct{} `yaml:"-" json:"-"`
|
ProtocolsParsed map[gortsplib.StreamProtocol]struct{} `yaml:"-" json:"-"`
|
||||||
|
|
@ -143,6 +144,7 @@ func (conf *Conf) fillAndCheck() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load loads a Conf.
|
||||||
func Load(fpath string) (*Conf, error) {
|
func Load(fpath string) (*Conf, error) {
|
||||||
conf := &Conf{}
|
conf := &Conf{}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ var reUserPass = regexp.MustCompile("^[a-zA-Z0-9!\\$\\(\\)\\*\\+\\.;<=>\\[\\]\\^
|
||||||
|
|
||||||
const userPassSupportedChars = "A-Z,0-9,!,$,(,),*,+,.,;,<,=,>,[,],^,_,-,{,}"
|
const userPassSupportedChars = "A-Z,0-9,!,$,(,),*,+,.,;,<,=,>,[,],^,_,-,{,}"
|
||||||
|
|
||||||
|
// PathConf is a path configuration.
|
||||||
type PathConf struct {
|
type PathConf struct {
|
||||||
Regexp *regexp.Regexp `yaml:"-" json:"-"`
|
Regexp *regexp.Regexp `yaml:"-" json:"-"`
|
||||||
Source string `yaml:"source"`
|
Source string `yaml:"source"`
|
||||||
|
|
@ -224,6 +225,7 @@ func (pconf *PathConf) fillAndCheck(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal checks whether two PathConfs are equal.
|
||||||
func (pconf *PathConf) Equal(other *PathConf) bool {
|
func (pconf *PathConf) Equal(other *PathConf) bool {
|
||||||
a, _ := json.Marshal(pconf)
|
a, _ := json.Marshal(pconf)
|
||||||
b, _ := json.Marshal(pconf)
|
b, _ := json.Marshal(pconf)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
var rePathName = regexp.MustCompile("^[0-9a-zA-Z_\\-/]+$")
|
var rePathName = regexp.MustCompile("^[0-9a-zA-Z_\\-/]+$")
|
||||||
|
|
||||||
|
// CheckPathName check if a path name is valid.
|
||||||
func CheckPathName(name string) error {
|
func CheckPathName(name string) error {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return fmt.Errorf("cannot be empty")
|
return fmt.Errorf("cannot be empty")
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ func load(env map[string]string, envKey string, rv reflect.Value) error {
|
||||||
return fmt.Errorf("unsupported type: %v", rt)
|
return fmt.Errorf("unsupported type: %v", rt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load fills a structure with data from the environment.
|
||||||
func Load(envKey string, v interface{}) error {
|
func Load(envKey string, v interface{}) error {
|
||||||
env := make(map[string]string)
|
env := make(map[string]string)
|
||||||
for _, kv := range os.Environ() {
|
for _, kv := range os.Environ() {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ConfWatcher is a configuration file watcher.
|
||||||
type ConfWatcher struct {
|
type ConfWatcher struct {
|
||||||
inner *fsnotify.Watcher
|
inner *fsnotify.Watcher
|
||||||
|
|
||||||
|
|
@ -15,6 +16,7 @@ type ConfWatcher struct {
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a ConfWatcher.
|
||||||
func New(confPath string) (*ConfWatcher, error) {
|
func New(confPath string) (*ConfWatcher, error) {
|
||||||
inner, err := fsnotify.NewWatcher()
|
inner, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -39,6 +41,7 @@ func New(confPath string) (*ConfWatcher, error) {
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a ConfWatcher.
|
||||||
func (w *ConfWatcher) Close() {
|
func (w *ConfWatcher) Close() {
|
||||||
go func() {
|
go func() {
|
||||||
for range w.signal {
|
for range w.signal {
|
||||||
|
|
@ -69,6 +72,7 @@ outer:
|
||||||
close(w.signal)
|
close(w.signal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watch returns when the configuration file has changed.
|
||||||
func (w *ConfWatcher) Watch() chan struct{} {
|
func (w *ConfWatcher) Watch() chan struct{} {
|
||||||
return w.signal
|
return w.signal
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,13 @@ const (
|
||||||
retryPause = 5 * time.Second
|
retryPause = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Environment is a ExternalCmd environment.
|
||||||
type Environment struct {
|
type Environment struct {
|
||||||
Path string
|
Path string
|
||||||
Port string
|
Port string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExternalCmd is an external command.
|
||||||
type ExternalCmd struct {
|
type ExternalCmd struct {
|
||||||
cmdstr string
|
cmdstr string
|
||||||
restart bool
|
restart bool
|
||||||
|
|
@ -29,6 +31,7 @@ type ExternalCmd struct {
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates an ExternalCmd.
|
||||||
func New(cmdstr string, restart bool, env Environment) *ExternalCmd {
|
func New(cmdstr string, restart bool, env Environment) *ExternalCmd {
|
||||||
e := &ExternalCmd{
|
e := &ExternalCmd{
|
||||||
cmdstr: cmdstr,
|
cmdstr: cmdstr,
|
||||||
|
|
@ -42,6 +45,7 @@ func New(cmdstr string, restart bool, env Environment) *ExternalCmd {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes an ExternalCmd.
|
||||||
func (e *ExternalCmd) Close() {
|
func (e *ExternalCmd) Close() {
|
||||||
close(e.terminate)
|
close(e.terminate)
|
||||||
<-e.done
|
<-e.done
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,17 @@ import (
|
||||||
"github.com/aler9/rtsp-simple-server/internal/syslog"
|
"github.com/aler9/rtsp-simple-server/internal/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Destination is a log destination.
|
||||||
type Destination int
|
type Destination int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// DestinationStdout writes logs to the standard output.
|
||||||
DestinationStdout Destination = iota
|
DestinationStdout Destination = iota
|
||||||
|
|
||||||
|
// DestinationFile writes logs to a file.
|
||||||
DestinationFile
|
DestinationFile
|
||||||
|
|
||||||
|
// DestinationSyslog writes logs to the system logger.
|
||||||
DestinationSyslog
|
DestinationSyslog
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -22,6 +28,7 @@ func (f writeFunc) Write(p []byte) (int, error) {
|
||||||
return f(p)
|
return f(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogHandler is a log handler.
|
||||||
type LogHandler struct {
|
type LogHandler struct {
|
||||||
destinations map[Destination]struct{}
|
destinations map[Destination]struct{}
|
||||||
|
|
||||||
|
|
@ -29,6 +36,7 @@ type LogHandler struct {
|
||||||
syslog io.WriteCloser
|
syslog io.WriteCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a log handler.
|
||||||
func New(destinations map[Destination]struct{}, filePath string) (*LogHandler, error) {
|
func New(destinations map[Destination]struct{}, filePath string) (*LogHandler, error) {
|
||||||
lh := &LogHandler{
|
lh := &LogHandler{
|
||||||
destinations: destinations,
|
destinations: destinations,
|
||||||
|
|
@ -57,6 +65,7 @@ func New(destinations map[Destination]struct{}, filePath string) (*LogHandler, e
|
||||||
return lh, nil
|
return lh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a log handler.
|
||||||
func (lh *LogHandler) Close() {
|
func (lh *LogHandler) Close() {
|
||||||
if lh.file != nil {
|
if lh.file != nil {
|
||||||
lh.file.Close()
|
lh.file.Close()
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,12 @@ const (
|
||||||
address = ":9998"
|
address = ":9998"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metrics is a metrics exporter.
|
||||||
type Metrics struct {
|
type Metrics struct {
|
||||||
stats *stats.Stats
|
stats *stats.Stats
|
||||||
|
|
||||||
|
|
@ -28,6 +30,7 @@ type Metrics struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a metrics.
|
||||||
func New(stats *stats.Stats, parent Parent) (*Metrics, error) {
|
func New(stats *stats.Stats, parent Parent) (*Metrics, error) {
|
||||||
listener, err := net.Listen("tcp", address)
|
listener, err := net.Listen("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -52,6 +55,7 @@ func New(stats *stats.Stats, parent Parent) (*Metrics, error) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a Metrics.
|
||||||
func (m *Metrics) Close() {
|
func (m *Metrics) Close() {
|
||||||
m.server.Shutdown(context.Background())
|
m.server.Shutdown(context.Background())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ func newEmptyTimer() *time.Timer {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parent is implemented by pathman.PathMan.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
OnPathClose(*Path)
|
OnPathClose(*Path)
|
||||||
|
|
@ -53,11 +54,13 @@ type sourceRedirect struct{}
|
||||||
|
|
||||||
func (*sourceRedirect) IsSource() {}
|
func (*sourceRedirect) IsSource() {}
|
||||||
|
|
||||||
|
// ClientDescribeRes is a client describe response.
|
||||||
type ClientDescribeRes struct {
|
type ClientDescribeRes struct {
|
||||||
Path client.Path
|
Path client.Path
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientDescribeReq is a client describe request.
|
||||||
type ClientDescribeReq struct {
|
type ClientDescribeReq struct {
|
||||||
Res chan ClientDescribeRes
|
Res chan ClientDescribeRes
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
|
|
@ -65,11 +68,13 @@ type ClientDescribeReq struct {
|
||||||
Req *base.Request
|
Req *base.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientAnnounceRes is a client announce response.
|
||||||
type ClientAnnounceRes struct {
|
type ClientAnnounceRes struct {
|
||||||
Path client.Path
|
Path client.Path
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientAnnounceReq is a client announce request.
|
||||||
type ClientAnnounceReq struct {
|
type ClientAnnounceReq struct {
|
||||||
Res chan ClientAnnounceRes
|
Res chan ClientAnnounceRes
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
|
|
@ -78,11 +83,13 @@ type ClientAnnounceReq struct {
|
||||||
Req *base.Request
|
Req *base.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientSetupPlayRes is a setup/play response.
|
||||||
type ClientSetupPlayRes struct {
|
type ClientSetupPlayRes struct {
|
||||||
Path client.Path
|
Path client.Path
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientSetupPlayReq is a setup/play request.
|
||||||
type ClientSetupPlayReq struct {
|
type ClientSetupPlayReq struct {
|
||||||
Res chan ClientSetupPlayRes
|
Res chan ClientSetupPlayRes
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
|
|
@ -125,6 +132,7 @@ const (
|
||||||
sourceStateReady
|
sourceStateReady
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Path is a path.
|
||||||
type Path struct {
|
type Path struct {
|
||||||
rtspPort int
|
rtspPort int
|
||||||
readTimeout time.Duration
|
readTimeout time.Duration
|
||||||
|
|
@ -166,6 +174,7 @@ type Path struct {
|
||||||
terminate chan struct{}
|
terminate chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a Path.
|
||||||
func New(
|
func New(
|
||||||
rtspPort int,
|
rtspPort int,
|
||||||
readTimeout time.Duration,
|
readTimeout time.Duration,
|
||||||
|
|
@ -209,10 +218,12 @@ func New(
|
||||||
return pa
|
return pa
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a path.
|
||||||
func (pa *Path) Close() {
|
func (pa *Path) Close() {
|
||||||
close(pa.terminate)
|
close(pa.terminate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log is the main logging function.
|
||||||
func (pa *Path) Log(format string, args ...interface{}) {
|
func (pa *Path) Log(format string, args ...interface{}) {
|
||||||
pa.parent.Log("[path "+pa.name+"] "+format, args...)
|
pa.parent.Log("[path "+pa.name+"] "+format, args...)
|
||||||
}
|
}
|
||||||
|
|
@ -747,62 +758,75 @@ func (pa *Path) scheduleClose() {
|
||||||
pa.closeTimerStarted = true
|
pa.closeTimerStarted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfName returns the configuration name of this path.
|
||||||
|
func (pa *Path) ConfName() string {
|
||||||
|
return pa.confName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conf returns the configuration of this path.
|
||||||
|
func (pa *Path) Conf() *conf.PathConf {
|
||||||
|
return pa.conf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of this path.
|
||||||
|
func (pa *Path) Name() string {
|
||||||
|
return pa.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// SourceTrackCount returns the number of tracks of the source this path.
|
||||||
|
func (pa *Path) SourceTrackCount() int {
|
||||||
|
return pa.sourceTrackCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnSourceSetReady is called by a source.
|
||||||
func (pa *Path) OnSourceSetReady(tracks gortsplib.Tracks) {
|
func (pa *Path) OnSourceSetReady(tracks gortsplib.Tracks) {
|
||||||
pa.sourceSdp = tracks.Write()
|
pa.sourceSdp = tracks.Write()
|
||||||
pa.sourceTrackCount = len(tracks)
|
pa.sourceTrackCount = len(tracks)
|
||||||
pa.sourceSetReady <- struct{}{}
|
pa.sourceSetReady <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnSourceSetNotReady is called by a source.
|
||||||
func (pa *Path) OnSourceSetNotReady() {
|
func (pa *Path) OnSourceSetNotReady() {
|
||||||
pa.sourceSetNotReady <- struct{}{}
|
pa.sourceSetNotReady <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *Path) ConfName() string {
|
// OnPathManDescribe is called by pathman.PathMan.
|
||||||
return pa.confName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pa *Path) Conf() *conf.PathConf {
|
|
||||||
return pa.conf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pa *Path) Name() string {
|
|
||||||
return pa.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pa *Path) SourceTrackCount() int {
|
|
||||||
return pa.sourceTrackCount
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pa *Path) OnPathManDescribe(req ClientDescribeReq) {
|
func (pa *Path) OnPathManDescribe(req ClientDescribeReq) {
|
||||||
pa.clientDescribe <- req
|
pa.clientDescribe <- req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnPathManSetupPlay is called by pathman.PathMan.
|
||||||
func (pa *Path) OnPathManSetupPlay(req ClientSetupPlayReq) {
|
func (pa *Path) OnPathManSetupPlay(req ClientSetupPlayReq) {
|
||||||
pa.clientSetupPlay <- req
|
pa.clientSetupPlay <- req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnPathManAnnounce is called by pathman.PathMan.
|
||||||
func (pa *Path) OnPathManAnnounce(req ClientAnnounceReq) {
|
func (pa *Path) OnPathManAnnounce(req ClientAnnounceReq) {
|
||||||
pa.clientAnnounce <- req
|
pa.clientAnnounce <- req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientRemove is called by client.Client.
|
||||||
func (pa *Path) OnClientRemove(c *client.Client) {
|
func (pa *Path) OnClientRemove(c *client.Client) {
|
||||||
res := make(chan struct{})
|
res := make(chan struct{})
|
||||||
pa.clientRemove <- clientRemoveReq{res, c}
|
pa.clientRemove <- clientRemoveReq{res, c}
|
||||||
<-res
|
<-res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientPlay is called by client.Client.
|
||||||
func (pa *Path) OnClientPlay(c *client.Client) {
|
func (pa *Path) OnClientPlay(c *client.Client) {
|
||||||
res := make(chan struct{})
|
res := make(chan struct{})
|
||||||
pa.clientPlay <- clientPlayReq{res, c}
|
pa.clientPlay <- clientPlayReq{res, c}
|
||||||
<-res
|
<-res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientRecord is called by client.Client.
|
||||||
func (pa *Path) OnClientRecord(c *client.Client) {
|
func (pa *Path) OnClientRecord(c *client.Client) {
|
||||||
res := make(chan struct{})
|
res := make(chan struct{})
|
||||||
pa.clientRecord <- clientRecordReq{res, c}
|
pa.clientRecord <- clientRecordReq{res, c}
|
||||||
<-res
|
<-res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnFrame is called by a source or by a client.Client.
|
||||||
func (pa *Path) OnFrame(trackId int, streamType gortsplib.StreamType, buf []byte) {
|
func (pa *Path) OnFrame(trackId int, streamType gortsplib.StreamType, buf []byte) {
|
||||||
pa.readers.forwardFrame(trackId, streamType, buf)
|
pa.readers.forwardFrame(trackId, streamType, buf)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,29 +7,29 @@ import (
|
||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reader interface {
|
type reader interface {
|
||||||
OnReaderFrame(int, base.StreamType, []byte)
|
OnReaderFrame(int, base.StreamType, []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
type readersMap struct {
|
type readersMap struct {
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
ma map[Reader]struct{}
|
ma map[reader]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReadersMap() *readersMap {
|
func newReadersMap() *readersMap {
|
||||||
return &readersMap{
|
return &readersMap{
|
||||||
ma: make(map[Reader]struct{}),
|
ma: make(map[reader]struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *readersMap) add(reader Reader) {
|
func (m *readersMap) add(reader reader) {
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
m.ma[reader] = struct{}{}
|
m.ma[reader] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *readersMap) remove(reader Reader) {
|
func (m *readersMap) remove(reader reader) {
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,12 @@ import (
|
||||||
"github.com/aler9/rtsp-simple-server/internal/stats"
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PathManager is a path.Path manager.
|
||||||
type PathManager struct {
|
type PathManager struct {
|
||||||
rtspPort int
|
rtspPort int
|
||||||
readTimeout time.Duration
|
readTimeout time.Duration
|
||||||
|
|
@ -44,6 +46,7 @@ type PathManager struct {
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a PathManager.
|
||||||
func New(
|
func New(
|
||||||
rtspPort int,
|
rtspPort int,
|
||||||
readTimeout time.Duration,
|
readTimeout time.Duration,
|
||||||
|
|
@ -78,6 +81,7 @@ func New(
|
||||||
return pm
|
return pm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a PathManager.
|
||||||
func (pm *PathManager) Close() {
|
func (pm *PathManager) Close() {
|
||||||
go func() {
|
go func() {
|
||||||
for range pm.clientClose {
|
for range pm.clientClose {
|
||||||
|
|
@ -87,6 +91,7 @@ func (pm *PathManager) Close() {
|
||||||
<-pm.done
|
<-pm.done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log is the main logging function.
|
||||||
func (pm *PathManager) Log(format string, args ...interface{}) {
|
func (pm *PathManager) Log(format string, args ...interface{}) {
|
||||||
pm.parent.Log(format, args...)
|
pm.parent.Log(format, args...)
|
||||||
}
|
}
|
||||||
|
|
@ -283,18 +288,22 @@ func (pm *PathManager) findPathConf(name string) (string, *conf.PathConf, error)
|
||||||
return "", nil, fmt.Errorf("unable to find a valid configuration for path '%s'", name)
|
return "", nil, fmt.Errorf("unable to find a valid configuration for path '%s'", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnProgramConfReload is called by program.
|
||||||
func (pm *PathManager) OnProgramConfReload(pathConfs map[string]*conf.PathConf) {
|
func (pm *PathManager) OnProgramConfReload(pathConfs map[string]*conf.PathConf) {
|
||||||
pm.confReload <- pathConfs
|
pm.confReload <- pathConfs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnPathClose is called by path.Path.
|
||||||
func (pm *PathManager) OnPathClose(pa *path.Path) {
|
func (pm *PathManager) OnPathClose(pa *path.Path) {
|
||||||
pm.pathClose <- pa
|
pm.pathClose <- pa
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnPathClientClose is called by path.Path.
|
||||||
func (pm *PathManager) OnPathClientClose(c *client.Client) {
|
func (pm *PathManager) OnPathClientClose(c *client.Client) {
|
||||||
pm.clientClose <- c
|
pm.clientClose <- c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientDescribe is called by client.Client.
|
||||||
func (pm *PathManager) OnClientDescribe(c *client.Client, pathName string, req *base.Request) (client.Path, error) {
|
func (pm *PathManager) OnClientDescribe(c *client.Client, pathName string, req *base.Request) (client.Path, error) {
|
||||||
res := make(chan path.ClientDescribeRes)
|
res := make(chan path.ClientDescribeRes)
|
||||||
pm.clientDescribe <- path.ClientDescribeReq{res, c, pathName, req}
|
pm.clientDescribe <- path.ClientDescribeReq{res, c, pathName, req}
|
||||||
|
|
@ -302,6 +311,7 @@ func (pm *PathManager) OnClientDescribe(c *client.Client, pathName string, req *
|
||||||
return re.Path, re.Err
|
return re.Path, re.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientAnnounce is called by client.Client.
|
||||||
func (pm *PathManager) OnClientAnnounce(c *client.Client, pathName string, tracks gortsplib.Tracks, req *base.Request) (client.Path, error) {
|
func (pm *PathManager) OnClientAnnounce(c *client.Client, pathName string, tracks gortsplib.Tracks, req *base.Request) (client.Path, error) {
|
||||||
res := make(chan path.ClientAnnounceRes)
|
res := make(chan path.ClientAnnounceRes)
|
||||||
pm.clientAnnounce <- path.ClientAnnounceReq{res, c, pathName, tracks, req}
|
pm.clientAnnounce <- path.ClientAnnounceReq{res, c, pathName, tracks, req}
|
||||||
|
|
@ -309,6 +319,7 @@ func (pm *PathManager) OnClientAnnounce(c *client.Client, pathName string, track
|
||||||
return re.Path, re.Err
|
return re.Path, re.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnClientSetupPlay is called by client.Client.
|
||||||
func (pm *PathManager) OnClientSetupPlay(c *client.Client, pathName string, trackId int, req *base.Request) (client.Path, error) {
|
func (pm *PathManager) OnClientSetupPlay(c *client.Client, pathName string, trackId int, req *base.Request) (client.Path, error) {
|
||||||
res := make(chan path.ClientSetupPlayRes)
|
res := make(chan path.ClientSetupPlayRes)
|
||||||
pm.clientSetupPlay <- path.ClientSetupPlayReq{res, c, pathName, trackId, req}
|
pm.clientSetupPlay <- path.ClientSetupPlayReq{res, c, pathName, trackId, req}
|
||||||
|
|
@ -316,6 +327,7 @@ func (pm *PathManager) OnClientSetupPlay(c *client.Client, pathName string, trac
|
||||||
return re.Path, re.Err
|
return re.Path, re.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientClose is called by client.Client.
|
||||||
func (pm *PathManager) ClientClose() chan *client.Client {
|
func (pm *PathManager) ClientClose() chan *client.Client {
|
||||||
return pm.clientClose
|
return pm.clientClose
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
// start pprof
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -11,15 +13,18 @@ const (
|
||||||
address = ":9999"
|
address = ":9999"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pprof is a performance metrics exporter.
|
||||||
type Pprof struct {
|
type Pprof struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
server *http.Server
|
server *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a Pprof.
|
||||||
func New(parent Parent) (*Pprof, error) {
|
func New(parent Parent) (*Pprof, error) {
|
||||||
listener, err := net.Listen("tcp", address)
|
listener, err := net.Listen("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -40,6 +45,7 @@ func New(parent Parent) (*Pprof, error) {
|
||||||
return pp, nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a Pprof.
|
||||||
func (pp *Pprof) Close() {
|
func (pp *Pprof) Close() {
|
||||||
pp.server.Shutdown(context.Background())
|
pp.server.Shutdown(context.Background())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@ import (
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server is a RTSP TCP server.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
parent Parent
|
parent Parent
|
||||||
|
|
||||||
|
|
@ -18,6 +20,7 @@ type Server struct {
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a Server.
|
||||||
func New(port int, parent Parent) (*Server, error) {
|
func New(port int, parent Parent) (*Server, error) {
|
||||||
listener, err := net.ListenTCP("tcp", &net.TCPAddr{
|
listener, err := net.ListenTCP("tcp", &net.TCPAddr{
|
||||||
Port: port,
|
Port: port,
|
||||||
|
|
@ -39,6 +42,7 @@ func New(port int, parent Parent) (*Server, error) {
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a Server.
|
||||||
func (s *Server) Close() {
|
func (s *Server) Close() {
|
||||||
go func() {
|
go func() {
|
||||||
for co := range s.accept {
|
for co := range s.accept {
|
||||||
|
|
@ -64,6 +68,7 @@ func (s *Server) run() {
|
||||||
close(s.accept)
|
close(s.accept)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accept returns a channel to accept incoming connections.
|
||||||
func (s *Server) Accept() <-chan net.Conn {
|
func (s *Server) Accept() <-chan net.Conn {
|
||||||
return s.accept
|
return s.accept
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const (
|
||||||
readBufferSize = 2048
|
readBufferSize = 2048
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Publisher is implemented by client.Client.
|
||||||
type Publisher interface {
|
type Publisher interface {
|
||||||
OnUdpPublisherFrame(int, base.StreamType, []byte)
|
OnUdpPublisherFrame(int, base.StreamType, []byte)
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +29,7 @@ type bufAddrPair struct {
|
||||||
addr *net.UDPAddr
|
addr *net.UDPAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parent is implemented by program.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
@ -48,6 +50,7 @@ func (p *publisherAddr) fill(ip net.IP, port int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server is a RTSP UDP server.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
writeTimeout time.Duration
|
writeTimeout time.Duration
|
||||||
streamType gortsplib.StreamType
|
streamType gortsplib.StreamType
|
||||||
|
|
@ -64,6 +67,7 @@ type Server struct {
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a Server.
|
||||||
func New(writeTimeout time.Duration,
|
func New(writeTimeout time.Duration,
|
||||||
port int,
|
port int,
|
||||||
streamType gortsplib.StreamType,
|
streamType gortsplib.StreamType,
|
||||||
|
|
@ -98,6 +102,7 @@ func New(writeTimeout time.Duration,
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a Server.
|
||||||
func (s *Server) Close() {
|
func (s *Server) Close() {
|
||||||
s.pc.Close()
|
s.pc.Close()
|
||||||
<-s.done
|
<-s.done
|
||||||
|
|
@ -142,14 +147,17 @@ func (s *Server) run() {
|
||||||
<-writeDone
|
<-writeDone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Port returns the server local port.
|
||||||
func (s *Server) Port() int {
|
func (s *Server) Port() int {
|
||||||
return s.pc.LocalAddr().(*net.UDPAddr).Port
|
return s.pc.LocalAddr().(*net.UDPAddr).Port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write writes a UDP packet.
|
||||||
func (s *Server) Write(data []byte, addr *net.UDPAddr) {
|
func (s *Server) Write(data []byte, addr *net.UDPAddr) {
|
||||||
s.write <- bufAddrPair{data, addr}
|
s.write <- bufAddrPair{data, addr}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddPublisher adds a publisher.
|
||||||
func (s *Server) AddPublisher(ip net.IP, port int, publisher Publisher, trackId int) {
|
func (s *Server) AddPublisher(ip net.IP, port int, publisher Publisher, trackId int) {
|
||||||
s.publishersMutex.Lock()
|
s.publishersMutex.Lock()
|
||||||
defer s.publishersMutex.Unlock()
|
defer s.publishersMutex.Unlock()
|
||||||
|
|
@ -163,6 +171,7 @@ func (s *Server) AddPublisher(ip net.IP, port int, publisher Publisher, trackId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemovePublisher removes a publisher.
|
||||||
func (s *Server) RemovePublisher(ip net.IP, port int, publisher Publisher) {
|
func (s *Server) RemovePublisher(ip net.IP, port int, publisher Publisher) {
|
||||||
s.publishersMutex.Lock()
|
s.publishersMutex.Lock()
|
||||||
defer s.publishersMutex.Unlock()
|
defer s.publishersMutex.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const (
|
||||||
retryPause = 5 * time.Second
|
retryPause = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parent is implemeneted by path.Path.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
OnSourceSetReady(gortsplib.Tracks)
|
OnSourceSetReady(gortsplib.Tracks)
|
||||||
|
|
@ -28,6 +29,7 @@ type Parent interface {
|
||||||
OnFrame(int, gortsplib.StreamType, []byte)
|
OnFrame(int, gortsplib.StreamType, []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Source is a RTMP source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
ur string
|
ur string
|
||||||
state bool
|
state bool
|
||||||
|
|
@ -39,6 +41,7 @@ type Source struct {
|
||||||
terminate chan struct{}
|
terminate chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a Source.
|
||||||
func New(ur string,
|
func New(ur string,
|
||||||
wg *sync.WaitGroup,
|
wg *sync.WaitGroup,
|
||||||
stats *stats.Stats,
|
stats *stats.Stats,
|
||||||
|
|
@ -59,14 +62,17 @@ func New(ur string,
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a Source.
|
||||||
func (s *Source) Close() {
|
func (s *Source) Close() {
|
||||||
atomic.AddInt64(s.stats.CountSourcesRtmpRunning, -1)
|
atomic.AddInt64(s.stats.CountSourcesRtmpRunning, -1)
|
||||||
s.parent.Log("rtmp source stopped")
|
s.parent.Log("rtmp source stopped")
|
||||||
close(s.terminate)
|
close(s.terminate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSource implements path.source.
|
||||||
func (s *Source) IsSource() {}
|
func (s *Source) IsSource() {}
|
||||||
|
|
||||||
|
// IsSourceExternal implements path.sourceExternal.
|
||||||
func (s *Source) IsSourceExternal() {}
|
func (s *Source) IsSourceExternal() {}
|
||||||
|
|
||||||
func (s *Source) run() {
|
func (s *Source) run() {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const (
|
||||||
retryPause = 5 * time.Second
|
retryPause = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parent is implemented by path.Path.
|
||||||
type Parent interface {
|
type Parent interface {
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
OnSourceSetReady(gortsplib.Tracks)
|
OnSourceSetReady(gortsplib.Tracks)
|
||||||
|
|
@ -21,6 +22,7 @@ type Parent interface {
|
||||||
OnFrame(int, gortsplib.StreamType, []byte)
|
OnFrame(int, gortsplib.StreamType, []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Source is a RTSP source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
ur string
|
ur string
|
||||||
proto gortsplib.StreamProtocol
|
proto gortsplib.StreamProtocol
|
||||||
|
|
@ -37,6 +39,7 @@ type Source struct {
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a Source.
|
||||||
func New(ur string,
|
func New(ur string,
|
||||||
proto gortsplib.StreamProtocol,
|
proto gortsplib.StreamProtocol,
|
||||||
readTimeout time.Duration,
|
readTimeout time.Duration,
|
||||||
|
|
@ -63,14 +66,17 @@ func New(ur string,
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes a Source.
|
||||||
func (s *Source) Close() {
|
func (s *Source) Close() {
|
||||||
atomic.AddInt64(s.stats.CountSourcesRtsp, -1)
|
atomic.AddInt64(s.stats.CountSourcesRtsp, -1)
|
||||||
s.parent.Log("rtsp source stopped")
|
s.parent.Log("rtsp source stopped")
|
||||||
close(s.terminate)
|
close(s.terminate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSource implements path.source.
|
||||||
func (s *Source) IsSource() {}
|
func (s *Source) IsSource() {}
|
||||||
|
|
||||||
|
// IsSourceExternal implements path.sourceExternal.
|
||||||
func (s *Source) IsSourceExternal() {}
|
func (s *Source) IsSourceExternal() {}
|
||||||
|
|
||||||
func (s *Source) run() {
|
func (s *Source) run() {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ func ptrInt64() *int64 {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stats contains statistics.
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
// use pointers to avoid a crash on 32bit platforms
|
// use pointers to avoid a crash on 32bit platforms
|
||||||
// https://github.com/golang/go/issues/9959
|
// https://github.com/golang/go/issues/9959
|
||||||
|
|
@ -17,6 +18,7 @@ type Stats struct {
|
||||||
CountSourcesRtmpRunning *int64
|
CountSourcesRtmpRunning *int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a Stats.
|
||||||
func New() *Stats {
|
func New() *Stats {
|
||||||
return &Stats{
|
return &Stats{
|
||||||
CountClients: ptrInt64(),
|
CountClients: ptrInt64(),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ type syslog struct {
|
||||||
inner *native.Writer
|
inner *native.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a io.WriteCloser that writes to the system log.
|
||||||
func New(prefix string) (io.WriteCloser, error) {
|
func New(prefix string) (io.WriteCloser, error) {
|
||||||
inner, err := native.New(native.LOG_INFO|native.LOG_DAEMON, prefix)
|
inner, err := native.New(native.LOG_INFO|native.LOG_DAEMON, prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -22,10 +23,12 @@ func New(prefix string) (io.WriteCloser, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close implements io.WriteCloser.
|
||||||
func (ls *syslog) Close() error {
|
func (ls *syslog) Close() error {
|
||||||
return ls.inner.Close()
|
return ls.inner.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write implements io.WriteCloser.
|
||||||
func (ls *syslog) Write(p []byte) (int, error) {
|
func (ls *syslog) Write(p []byte) (int, error) {
|
||||||
return ls.inner.Write(p)
|
return ls.inner.Write(p)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,17 @@ import (
|
||||||
type syslog struct {
|
type syslog struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New allocates a io.WriteCloser that writes to the system log.
|
||||||
func New(prefix string) (io.WriteCloser, error) {
|
func New(prefix string) (io.WriteCloser, error) {
|
||||||
return nil, fmt.Errorf("not implemented on windows")
|
return nil, fmt.Errorf("not implemented on windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close implements io.WriteCloser.
|
||||||
func (ls *syslog) Close() error {
|
func (ls *syslog) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write implements io.WriteCloser.
|
||||||
func (ls *syslog) Write(p []byte) (int, error) {
|
func (ls *syslog) Write(p []byte) (int, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
main.go
1
main.go
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/aler9/rtsp-simple-server/internal/stats"
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Version can be overridden by build flags.
|
||||||
var Version = "v0.0.0"
|
var Version = "v0.0.0"
|
||||||
|
|
||||||
type program struct {
|
type program struct {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue