2020-10-19 20:17:48 +00:00
|
|
|
package path
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2020-11-01 16:33:06 +00:00
|
|
|
"strconv"
|
2020-10-19 20:17:48 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/aler9/gortsplib"
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
"github.com/aler9/rtsp-simple-server/internal/client"
|
2021-01-31 16:06:34 +00:00
|
|
|
"github.com/aler9/rtsp-simple-server/internal/clientrtsp"
|
2020-11-01 21:56:56 +00:00
|
|
|
"github.com/aler9/rtsp-simple-server/internal/conf"
|
|
|
|
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
|
2020-12-08 11:21:06 +00:00
|
|
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
2020-11-01 21:56:56 +00:00
|
|
|
"github.com/aler9/rtsp-simple-server/internal/sourcertmp"
|
|
|
|
"github.com/aler9/rtsp-simple-server/internal/sourcertsp"
|
|
|
|
"github.com/aler9/rtsp-simple-server/internal/stats"
|
2020-10-19 20:17:48 +00:00
|
|
|
)
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
func newEmptyTimer() *time.Timer {
|
|
|
|
t := time.NewTimer(0)
|
|
|
|
<-t.C
|
|
|
|
return t
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// Parent is implemented by pathman.PathMan.
|
2020-10-19 20:17:48 +00:00
|
|
|
type Parent interface {
|
2020-12-08 11:21:06 +00:00
|
|
|
Log(logger.Level, string, ...interface{})
|
2020-10-19 20:17:48 +00:00
|
|
|
OnPathClose(*Path)
|
2021-01-31 20:42:27 +00:00
|
|
|
OnPathClientClose(client.Client)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
// source can be
|
|
|
|
// * client.Client
|
2020-10-27 23:29:53 +00:00
|
|
|
// * sourcertsp.Source
|
|
|
|
// * sourcertmp.Source
|
|
|
|
// * sourceRedirect
|
2020-10-19 20:17:48 +00:00
|
|
|
type source interface {
|
|
|
|
IsSource()
|
|
|
|
}
|
|
|
|
|
2020-10-27 23:29:53 +00:00
|
|
|
// a sourceExternal can be
|
|
|
|
// * sourcertsp.Source
|
|
|
|
// * sourcertmp.Source
|
2020-10-25 10:41:41 +00:00
|
|
|
type sourceExternal interface {
|
|
|
|
IsSource()
|
2020-11-01 15:51:12 +00:00
|
|
|
IsSourceExternal()
|
2020-10-25 10:41:41 +00:00
|
|
|
Close()
|
|
|
|
}
|
|
|
|
|
2020-10-27 23:29:53 +00:00
|
|
|
type sourceRedirect struct{}
|
|
|
|
|
|
|
|
func (*sourceRedirect) IsSource() {}
|
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
type clientState int
|
|
|
|
|
|
|
|
const (
|
2021-01-31 11:23:54 +00:00
|
|
|
clientStatePrePlay clientState = iota
|
2020-10-19 20:17:48 +00:00
|
|
|
clientStatePlay
|
|
|
|
clientStatePreRecord
|
|
|
|
clientStateRecord
|
2020-10-24 17:55:47 +00:00
|
|
|
clientStatePreRemove
|
2020-10-19 20:17:48 +00:00
|
|
|
)
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
type sourceState int
|
|
|
|
|
|
|
|
const (
|
|
|
|
sourceStateNotReady sourceState = iota
|
|
|
|
sourceStateWaitingDescribe
|
|
|
|
sourceStateReady
|
|
|
|
)
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// Path is a path.
|
2020-10-19 20:17:48 +00:00
|
|
|
type Path struct {
|
2021-01-10 11:55:53 +00:00
|
|
|
rtspPort int
|
|
|
|
readTimeout time.Duration
|
|
|
|
writeTimeout time.Duration
|
|
|
|
readBufferCount uint64
|
|
|
|
confName string
|
|
|
|
conf *conf.PathConf
|
|
|
|
name string
|
|
|
|
wg *sync.WaitGroup
|
|
|
|
stats *stats.Stats
|
|
|
|
parent Parent
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
clients map[client.Client]clientState
|
2020-11-01 15:51:12 +00:00
|
|
|
clientsWg sync.WaitGroup
|
2021-01-31 20:42:27 +00:00
|
|
|
describeRequests []client.DescribeReq
|
|
|
|
setupPlayRequests []client.SetupPlayReq
|
2020-11-01 15:51:12 +00:00
|
|
|
source source
|
|
|
|
sourceTrackCount int
|
|
|
|
sourceSdp []byte
|
|
|
|
readers *readersMap
|
2020-11-21 12:35:33 +00:00
|
|
|
onDemandCmd *externalcmd.Cmd
|
2020-11-01 15:51:12 +00:00
|
|
|
describeTimer *time.Timer
|
|
|
|
sourceCloseTimer *time.Timer
|
|
|
|
sourceCloseTimerStarted bool
|
|
|
|
sourceState sourceState
|
|
|
|
sourceWg sync.WaitGroup
|
|
|
|
runOnDemandCloseTimer *time.Timer
|
|
|
|
runOnDemandCloseTimerStarted bool
|
|
|
|
closeTimer *time.Timer
|
|
|
|
closeTimerStarted bool
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
// in
|
2021-01-31 20:42:27 +00:00
|
|
|
sourceSetReady chan struct{} // from source
|
|
|
|
sourceSetNotReady chan struct{} // from source
|
|
|
|
clientDescribe chan client.DescribeReq // from program
|
|
|
|
clientAnnounce chan client.AnnounceReq // from program
|
|
|
|
clientSetupPlay chan client.SetupPlayReq // from program
|
|
|
|
clientPlay chan client.PlayReq // from client
|
|
|
|
clientRecord chan client.RecordReq // from client
|
|
|
|
clientPause chan client.PauseReq // from client
|
|
|
|
clientRemove chan client.RemoveReq // from client
|
2020-10-19 20:17:48 +00:00
|
|
|
terminate chan struct{}
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// New allocates a Path.
|
2020-10-19 20:17:48 +00:00
|
|
|
func New(
|
2020-11-01 16:33:06 +00:00
|
|
|
rtspPort int,
|
2020-10-19 20:17:48 +00:00
|
|
|
readTimeout time.Duration,
|
|
|
|
writeTimeout time.Duration,
|
2021-01-10 11:55:53 +00:00
|
|
|
readBufferCount uint64,
|
2020-10-24 17:55:47 +00:00
|
|
|
confName string,
|
2020-10-19 20:17:48 +00:00
|
|
|
conf *conf.PathConf,
|
2020-10-24 17:55:47 +00:00
|
|
|
name string,
|
|
|
|
wg *sync.WaitGroup,
|
|
|
|
stats *stats.Stats,
|
2020-10-19 20:17:48 +00:00
|
|
|
parent Parent) *Path {
|
|
|
|
|
|
|
|
pa := &Path{
|
2020-11-01 16:33:06 +00:00
|
|
|
rtspPort: rtspPort,
|
2020-11-01 15:51:12 +00:00
|
|
|
readTimeout: readTimeout,
|
|
|
|
writeTimeout: writeTimeout,
|
2021-01-10 11:55:53 +00:00
|
|
|
readBufferCount: readBufferCount,
|
2020-11-01 15:51:12 +00:00
|
|
|
confName: confName,
|
|
|
|
conf: conf,
|
|
|
|
name: name,
|
|
|
|
wg: wg,
|
|
|
|
stats: stats,
|
|
|
|
parent: parent,
|
2021-01-31 20:42:27 +00:00
|
|
|
clients: make(map[client.Client]clientState),
|
2020-11-01 15:51:12 +00:00
|
|
|
readers: newReadersMap(),
|
|
|
|
describeTimer: newEmptyTimer(),
|
|
|
|
sourceCloseTimer: newEmptyTimer(),
|
|
|
|
runOnDemandCloseTimer: newEmptyTimer(),
|
|
|
|
closeTimer: newEmptyTimer(),
|
|
|
|
sourceSetReady: make(chan struct{}),
|
|
|
|
sourceSetNotReady: make(chan struct{}),
|
2021-01-31 20:42:27 +00:00
|
|
|
clientDescribe: make(chan client.DescribeReq),
|
|
|
|
clientAnnounce: make(chan client.AnnounceReq),
|
|
|
|
clientSetupPlay: make(chan client.SetupPlayReq),
|
|
|
|
clientPlay: make(chan client.PlayReq),
|
|
|
|
clientRecord: make(chan client.RecordReq),
|
|
|
|
clientPause: make(chan client.PauseReq),
|
|
|
|
clientRemove: make(chan client.RemoveReq),
|
2020-11-01 15:51:12 +00:00
|
|
|
terminate: make(chan struct{}),
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pa.wg.Add(1)
|
|
|
|
go pa.run()
|
|
|
|
return pa
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// Close closes a path.
|
2020-10-19 20:17:48 +00:00
|
|
|
func (pa *Path) Close() {
|
|
|
|
close(pa.terminate)
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// Log is the main logging function.
|
2020-12-08 11:21:06 +00:00
|
|
|
func (pa *Path) Log(level logger.Level, format string, args ...interface{}) {
|
|
|
|
pa.parent.Log(level, "[path "+pa.name+"] "+format, args...)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *Path) run() {
|
|
|
|
defer pa.wg.Done()
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.conf.Source == "redirect" {
|
2020-10-27 23:29:53 +00:00
|
|
|
pa.source = &sourceRedirect{}
|
2020-11-01 15:51:12 +00:00
|
|
|
|
|
|
|
} else if pa.hasExternalSource() && !pa.conf.SourceOnDemand {
|
|
|
|
pa.startExternalSource()
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-21 12:35:33 +00:00
|
|
|
var onInitCmd *externalcmd.Cmd
|
2020-10-19 20:17:48 +00:00
|
|
|
if pa.conf.RunOnInit != "" {
|
2020-12-08 11:21:06 +00:00
|
|
|
pa.Log(logger.Info, "on init command started")
|
2020-11-21 12:34:27 +00:00
|
|
|
onInitCmd = externalcmd.New(pa.conf.RunOnInit, pa.conf.RunOnInitRestart, externalcmd.Environment{
|
2020-11-01 16:33:06 +00:00
|
|
|
Path: pa.name,
|
|
|
|
Port: strconv.FormatInt(int64(pa.rtspPort), 10),
|
|
|
|
})
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
outer:
|
|
|
|
for {
|
|
|
|
select {
|
2020-11-01 15:51:12 +00:00
|
|
|
case <-pa.describeTimer.C:
|
2021-01-31 11:23:54 +00:00
|
|
|
for _, req := range pa.describeRequests {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.DescribeRes{nil, "", fmt.Errorf("publisher of path '%s' has timed out", pa.name)} //nolint:govet
|
2021-01-31 11:23:54 +00:00
|
|
|
}
|
|
|
|
pa.describeRequests = nil
|
|
|
|
|
|
|
|
for _, req := range pa.setupPlayRequests {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.SetupPlayRes{nil, fmt.Errorf("publisher of path '%s' has timed out", pa.name)} //nolint:govet
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2021-01-31 11:23:54 +00:00
|
|
|
pa.setupPlayRequests = nil
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
// set state after removeClient(), so schedule* works once
|
|
|
|
pa.sourceState = sourceStateNotReady
|
|
|
|
|
|
|
|
pa.scheduleSourceClose()
|
|
|
|
pa.scheduleRunOnDemandClose()
|
|
|
|
pa.scheduleClose()
|
|
|
|
|
|
|
|
case <-pa.sourceCloseTimer.C:
|
|
|
|
pa.sourceCloseTimerStarted = false
|
|
|
|
pa.source.(sourceExternal).Close()
|
|
|
|
pa.source = nil
|
|
|
|
|
|
|
|
pa.scheduleClose()
|
|
|
|
|
|
|
|
case <-pa.runOnDemandCloseTimer.C:
|
|
|
|
pa.runOnDemandCloseTimerStarted = false
|
2020-12-08 11:21:06 +00:00
|
|
|
pa.Log(logger.Info, "on demand command stopped")
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.onDemandCmd.Close()
|
|
|
|
pa.onDemandCmd = nil
|
|
|
|
|
|
|
|
pa.scheduleClose()
|
|
|
|
|
|
|
|
case <-pa.closeTimer.C:
|
|
|
|
pa.exhaustChannels()
|
|
|
|
pa.parent.OnPathClose(pa)
|
|
|
|
<-pa.terminate
|
|
|
|
break outer
|
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
case <-pa.sourceSetReady:
|
|
|
|
pa.onSourceSetReady()
|
|
|
|
|
|
|
|
case <-pa.sourceSetNotReady:
|
|
|
|
pa.onSourceSetNotReady()
|
|
|
|
|
|
|
|
case req := <-pa.clientDescribe:
|
2021-01-31 11:23:54 +00:00
|
|
|
pa.onClientDescribe(req)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
case req := <-pa.clientSetupPlay:
|
2021-01-31 11:23:54 +00:00
|
|
|
pa.onClientSetupPlay(req)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
case req := <-pa.clientPlay:
|
2021-01-30 21:06:02 +00:00
|
|
|
pa.onClientPlay(req.Client)
|
|
|
|
close(req.Res)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
case req := <-pa.clientAnnounce:
|
|
|
|
err := pa.onClientAnnounce(req.Client, req.Tracks)
|
|
|
|
if err != nil {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.AnnounceRes{nil, err} //nolint:govet
|
2020-10-19 20:17:48 +00:00
|
|
|
continue
|
|
|
|
}
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.AnnounceRes{pa, nil} //nolint:govet
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
case req := <-pa.clientRecord:
|
2021-01-30 21:06:02 +00:00
|
|
|
pa.onClientRecord(req.Client)
|
|
|
|
close(req.Res)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-11-07 21:47:10 +00:00
|
|
|
case req := <-pa.clientPause:
|
2021-01-30 21:06:02 +00:00
|
|
|
pa.onClientPause(req.Client)
|
|
|
|
close(req.Res)
|
2020-11-07 21:47:10 +00:00
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
case req := <-pa.clientRemove:
|
2021-01-30 21:06:02 +00:00
|
|
|
if _, ok := pa.clients[req.Client]; !ok {
|
|
|
|
close(req.Res)
|
2020-10-24 17:55:47 +00:00
|
|
|
continue
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2020-10-24 17:55:47 +00:00
|
|
|
|
2021-01-30 21:06:02 +00:00
|
|
|
if pa.clients[req.Client] != clientStatePreRemove {
|
|
|
|
pa.removeClient(req.Client)
|
2020-10-24 17:55:47 +00:00
|
|
|
}
|
|
|
|
|
2021-01-30 21:06:02 +00:00
|
|
|
delete(pa.clients, req.Client)
|
2020-10-24 17:55:47 +00:00
|
|
|
pa.clientsWg.Done()
|
|
|
|
|
2021-01-30 21:06:02 +00:00
|
|
|
close(req.Res)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
case <-pa.terminate:
|
2020-10-24 17:55:47 +00:00
|
|
|
pa.exhaustChannels()
|
2020-10-19 20:17:48 +00:00
|
|
|
break outer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.describeTimer.Stop()
|
|
|
|
pa.sourceCloseTimer.Stop()
|
|
|
|
pa.runOnDemandCloseTimer.Stop()
|
|
|
|
pa.closeTimer.Stop()
|
|
|
|
|
2020-11-21 12:34:27 +00:00
|
|
|
if onInitCmd != nil {
|
2020-12-08 11:21:06 +00:00
|
|
|
pa.Log(logger.Info, "on init command stopped")
|
2020-11-21 12:34:27 +00:00
|
|
|
onInitCmd.Close()
|
2020-10-24 17:55:47 +00:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:41:41 +00:00
|
|
|
if source, ok := pa.source.(sourceExternal); ok {
|
2020-10-24 17:55:47 +00:00
|
|
|
source.Close()
|
|
|
|
}
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.sourceWg.Wait()
|
2020-10-24 17:55:47 +00:00
|
|
|
|
|
|
|
if pa.onDemandCmd != nil {
|
2020-12-08 11:21:06 +00:00
|
|
|
pa.Log(logger.Info, "on demand command stopped")
|
2020-10-24 17:55:47 +00:00
|
|
|
pa.onDemandCmd.Close()
|
|
|
|
}
|
|
|
|
|
2021-01-31 11:23:54 +00:00
|
|
|
for _, req := range pa.describeRequests {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.DescribeRes{nil, "", fmt.Errorf("terminated")} //nolint:govet
|
2021-01-31 11:23:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, req := range pa.setupPlayRequests {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.SetupPlayRes{nil, fmt.Errorf("terminated")} //nolint:govet
|
2021-01-31 11:23:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-24 17:55:47 +00:00
|
|
|
for c, state := range pa.clients {
|
|
|
|
if state != clientStatePreRemove {
|
|
|
|
switch state {
|
|
|
|
case clientStatePlay:
|
|
|
|
atomic.AddInt64(pa.stats.CountReaders, -1)
|
|
|
|
pa.readers.remove(c)
|
|
|
|
|
|
|
|
case clientStateRecord:
|
|
|
|
atomic.AddInt64(pa.stats.CountPublishers, -1)
|
|
|
|
}
|
|
|
|
pa.parent.OnPathClientClose(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pa.clientsWg.Wait()
|
|
|
|
|
|
|
|
close(pa.sourceSetReady)
|
|
|
|
close(pa.sourceSetNotReady)
|
|
|
|
close(pa.clientDescribe)
|
|
|
|
close(pa.clientAnnounce)
|
|
|
|
close(pa.clientSetupPlay)
|
|
|
|
close(pa.clientPlay)
|
|
|
|
close(pa.clientRecord)
|
2020-11-07 21:47:10 +00:00
|
|
|
close(pa.clientPause)
|
2020-10-24 17:55:47 +00:00
|
|
|
close(pa.clientRemove)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *Path) exhaustChannels() {
|
2020-10-19 20:17:48 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case _, ok := <-pa.sourceSetReady:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
case _, ok := <-pa.sourceSetNotReady:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
case req, ok := <-pa.clientDescribe:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.DescribeRes{nil, "", fmt.Errorf("terminated")} //nolint:govet
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
case req, ok := <-pa.clientAnnounce:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.AnnounceRes{nil, fmt.Errorf("terminated")} //nolint:govet
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
case req, ok := <-pa.clientSetupPlay:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.SetupPlayRes{nil, fmt.Errorf("terminated")} //nolint:govet
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
case req, ok := <-pa.clientPlay:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2021-01-30 21:06:02 +00:00
|
|
|
close(req.Res)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
case req, ok := <-pa.clientRecord:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2021-01-30 21:06:02 +00:00
|
|
|
close(req.Res)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-11-07 21:47:10 +00:00
|
|
|
case req, ok := <-pa.clientPause:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2021-01-30 21:06:02 +00:00
|
|
|
close(req.Res)
|
2020-11-07 21:47:10 +00:00
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
case req, ok := <-pa.clientRemove:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-30 21:06:02 +00:00
|
|
|
if _, ok := pa.clients[req.Client]; !ok {
|
|
|
|
close(req.Res)
|
2020-10-24 17:55:47 +00:00
|
|
|
continue
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-10-24 17:55:47 +00:00
|
|
|
pa.clientsWg.Done()
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-01-30 21:06:02 +00:00
|
|
|
close(req.Res)
|
2020-10-24 17:55:47 +00:00
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2020-10-24 17:55:47 +00:00
|
|
|
}()
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
func (pa *Path) hasExternalSource() bool {
|
|
|
|
return strings.HasPrefix(pa.conf.Source, "rtsp://") ||
|
2020-12-14 22:32:24 +00:00
|
|
|
strings.HasPrefix(pa.conf.Source, "rtsps://") ||
|
2020-11-01 15:51:12 +00:00
|
|
|
strings.HasPrefix(pa.conf.Source, "rtmp://")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *Path) startExternalSource() {
|
2020-12-14 22:32:24 +00:00
|
|
|
if strings.HasPrefix(pa.conf.Source, "rtsp://") ||
|
|
|
|
strings.HasPrefix(pa.conf.Source, "rtsps://") {
|
2021-01-10 11:55:53 +00:00
|
|
|
pa.source = sourcertsp.New(
|
|
|
|
pa.conf.Source,
|
|
|
|
pa.conf.SourceProtocolParsed,
|
|
|
|
pa.readTimeout,
|
|
|
|
pa.writeTimeout,
|
|
|
|
pa.readBufferCount,
|
|
|
|
&pa.sourceWg,
|
|
|
|
pa.stats,
|
|
|
|
pa)
|
2020-11-01 15:51:12 +00:00
|
|
|
|
|
|
|
} else if strings.HasPrefix(pa.conf.Source, "rtmp://") {
|
2021-01-10 11:55:53 +00:00
|
|
|
pa.source = sourcertmp.New(
|
|
|
|
pa.conf.Source,
|
2021-01-31 15:24:58 +00:00
|
|
|
pa.readTimeout,
|
2021-01-10 11:55:53 +00:00
|
|
|
&pa.sourceWg,
|
|
|
|
pa.stats,
|
|
|
|
pa)
|
2020-10-24 17:55:47 +00:00
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
func (pa *Path) hasClients() bool {
|
2020-10-19 20:17:48 +00:00
|
|
|
for _, state := range pa.clients {
|
2020-11-01 15:51:12 +00:00
|
|
|
if state != clientStatePreRemove {
|
2020-10-19 20:17:48 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
func (pa *Path) hasClientsNotSources() bool {
|
2020-10-24 17:55:47 +00:00
|
|
|
for c, state := range pa.clients {
|
|
|
|
if state != clientStatePreRemove && c != pa.source {
|
2020-10-19 20:17:48 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) addClient(c client.Client, state clientState) {
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.clients[c] = state
|
|
|
|
pa.clientsWg.Add(1)
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) removeClient(c client.Client) {
|
2020-11-01 15:51:12 +00:00
|
|
|
state := pa.clients[c]
|
|
|
|
pa.clients[c] = clientStatePreRemove
|
|
|
|
|
|
|
|
switch state {
|
|
|
|
case clientStatePlay:
|
|
|
|
atomic.AddInt64(pa.stats.CountReaders, -1)
|
|
|
|
pa.readers.remove(c)
|
|
|
|
|
|
|
|
case clientStateRecord:
|
|
|
|
atomic.AddInt64(pa.stats.CountPublishers, -1)
|
|
|
|
pa.onSourceSetNotReady()
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.source == c {
|
|
|
|
pa.source = nil
|
|
|
|
|
|
|
|
// close all clients that are reading or waiting to read
|
|
|
|
for oc, state := range pa.clients {
|
2021-01-31 11:23:54 +00:00
|
|
|
if state != clientStatePreRemove {
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.removeClient(oc)
|
|
|
|
pa.parent.OnPathClientClose(oc)
|
|
|
|
}
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.scheduleSourceClose()
|
|
|
|
pa.scheduleRunOnDemandClose()
|
|
|
|
pa.scheduleClose()
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *Path) onSourceSetReady() {
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.sourceState == sourceStateWaitingDescribe {
|
|
|
|
pa.describeTimer.Stop()
|
|
|
|
pa.describeTimer = newEmptyTimer()
|
|
|
|
}
|
|
|
|
|
|
|
|
pa.sourceState = sourceStateReady
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-01-31 11:23:54 +00:00
|
|
|
for _, req := range pa.describeRequests {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.DescribeRes{pa.sourceSdp, "", nil} //nolint:govet
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2021-01-31 11:23:54 +00:00
|
|
|
pa.describeRequests = nil
|
|
|
|
|
|
|
|
for _, req := range pa.setupPlayRequests {
|
|
|
|
pa.onClientSetupPlayPost(req)
|
|
|
|
}
|
|
|
|
pa.setupPlayRequests = nil
|
2020-11-01 15:51:12 +00:00
|
|
|
|
|
|
|
pa.scheduleSourceClose()
|
|
|
|
pa.scheduleRunOnDemandClose()
|
|
|
|
pa.scheduleClose()
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *Path) onSourceSetNotReady() {
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.sourceState = sourceStateNotReady
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
// close all clients that are reading or waiting to read
|
|
|
|
for c, state := range pa.clients {
|
2020-11-01 15:51:12 +00:00
|
|
|
if c != pa.source && state != clientStatePreRemove {
|
|
|
|
pa.removeClient(c)
|
2020-10-19 20:17:48 +00:00
|
|
|
pa.parent.OnPathClientClose(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 11:23:54 +00:00
|
|
|
func (pa *Path) fixedPublisherStart() {
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.hasExternalSource() {
|
2021-01-31 11:23:54 +00:00
|
|
|
// start on-demand source
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.source == nil {
|
|
|
|
pa.startExternalSource()
|
2020-10-24 17:55:47 +00:00
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.sourceState != sourceStateWaitingDescribe {
|
|
|
|
pa.describeTimer = time.NewTimer(pa.conf.SourceOnDemandStartTimeout)
|
|
|
|
pa.sourceState = sourceStateWaitingDescribe
|
|
|
|
}
|
2021-01-31 11:23:54 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
// reset timer
|
|
|
|
if pa.sourceCloseTimerStarted {
|
|
|
|
pa.sourceCloseTimer.Stop()
|
|
|
|
pa.sourceCloseTimer = time.NewTimer(pa.conf.SourceOnDemandCloseAfter)
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2020-11-01 15:51:12 +00:00
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.conf.RunOnDemand != "" {
|
2021-01-31 11:23:54 +00:00
|
|
|
// start on-demand command
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.onDemandCmd == nil {
|
2020-12-08 11:21:06 +00:00
|
|
|
pa.Log(logger.Info, "on demand command started")
|
2020-11-01 16:33:06 +00:00
|
|
|
pa.onDemandCmd = externalcmd.New(pa.conf.RunOnDemand, pa.conf.RunOnDemandRestart, externalcmd.Environment{
|
|
|
|
Path: pa.name,
|
|
|
|
Port: strconv.FormatInt(int64(pa.rtspPort), 10),
|
|
|
|
})
|
2020-10-27 23:29:53 +00:00
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.sourceState != sourceStateWaitingDescribe {
|
|
|
|
pa.describeTimer = time.NewTimer(pa.conf.RunOnDemandStartTimeout)
|
|
|
|
pa.sourceState = sourceStateWaitingDescribe
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2021-01-31 11:23:54 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
// reset timer
|
|
|
|
if pa.runOnDemandCloseTimerStarted {
|
|
|
|
pa.runOnDemandCloseTimer.Stop()
|
|
|
|
pa.runOnDemandCloseTimer = time.NewTimer(pa.conf.RunOnDemandCloseAfter)
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2020-11-01 15:51:12 +00:00
|
|
|
}
|
2021-01-31 11:23:54 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) onClientDescribe(req client.DescribeReq) {
|
2021-01-31 11:23:54 +00:00
|
|
|
if _, ok := pa.clients[req.Client]; ok {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.DescribeRes{nil, "", fmt.Errorf("already subscribed")} //nolint:govet
|
2021-01-31 11:23:54 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
pa.fixedPublisherStart()
|
|
|
|
pa.scheduleClose()
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
if _, ok := pa.source.(*sourceRedirect); ok {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.DescribeRes{nil, pa.conf.SourceRedirect, nil} //nolint:govet
|
2020-11-01 15:51:12 +00:00
|
|
|
return
|
|
|
|
}
|
2020-10-24 17:55:47 +00:00
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
switch pa.sourceState {
|
|
|
|
case sourceStateReady:
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.DescribeRes{pa.sourceSdp, "", nil} //nolint:govet
|
2020-11-01 15:51:12 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
case sourceStateWaitingDescribe:
|
2021-01-31 11:23:54 +00:00
|
|
|
pa.describeRequests = append(pa.describeRequests, req)
|
2020-11-01 15:51:12 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
case sourceStateNotReady:
|
2020-11-01 16:48:00 +00:00
|
|
|
if pa.conf.Fallback != "" {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.DescribeRes{nil, pa.conf.Fallback, nil} //nolint:govet
|
2020-11-01 16:48:00 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.DescribeRes{nil, "", clientrtsp.ErrNoOnePublishing{pa.name}} //nolint:govet
|
2020-11-01 15:51:12 +00:00
|
|
|
return
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) onClientSetupPlayPost(req client.SetupPlayReq) {
|
2021-01-31 11:23:54 +00:00
|
|
|
if req.TrackID >= pa.sourceTrackCount {
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.SetupPlayRes{nil, fmt.Errorf("track %d does not exist", req.TrackID)} //nolint:govet
|
2021-01-31 11:23:54 +00:00
|
|
|
return
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 11:23:54 +00:00
|
|
|
if _, ok := pa.clients[req.Client]; !ok {
|
2020-11-01 15:51:12 +00:00
|
|
|
// prevent on-demand source from closing
|
|
|
|
if pa.sourceCloseTimerStarted {
|
|
|
|
pa.sourceCloseTimer = newEmptyTimer()
|
|
|
|
pa.sourceCloseTimerStarted = false
|
|
|
|
}
|
|
|
|
|
|
|
|
// prevent on-demand command from closing
|
|
|
|
if pa.runOnDemandCloseTimerStarted {
|
|
|
|
pa.runOnDemandCloseTimer = newEmptyTimer()
|
|
|
|
pa.runOnDemandCloseTimerStarted = false
|
|
|
|
}
|
|
|
|
|
2021-01-31 11:23:54 +00:00
|
|
|
pa.addClient(req.Client, clientStatePrePlay)
|
2020-10-24 17:55:47 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.SetupPlayRes{pa, nil} //nolint:govet
|
2021-01-31 11:23:54 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) onClientSetupPlay(req client.SetupPlayReq) {
|
2021-01-31 11:23:54 +00:00
|
|
|
pa.fixedPublisherStart()
|
|
|
|
pa.scheduleClose()
|
|
|
|
|
|
|
|
switch pa.sourceState {
|
|
|
|
case sourceStateReady:
|
|
|
|
pa.onClientSetupPlayPost(req)
|
|
|
|
return
|
|
|
|
|
|
|
|
case sourceStateWaitingDescribe:
|
|
|
|
pa.setupPlayRequests = append(pa.setupPlayRequests, req)
|
|
|
|
return
|
|
|
|
|
|
|
|
case sourceStateNotReady:
|
2021-01-31 20:42:27 +00:00
|
|
|
req.Res <- client.SetupPlayRes{nil, clientrtsp.ErrNoOnePublishing{pa.name}} //nolint:govet
|
2021-01-31 11:23:54 +00:00
|
|
|
return
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) onClientPlay(c client.Client) {
|
2020-10-24 17:55:47 +00:00
|
|
|
state, ok := pa.clients[c]
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if state != clientStatePrePlay {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
atomic.AddInt64(pa.stats.CountReaders, 1)
|
|
|
|
pa.clients[c] = clientStatePlay
|
2020-11-07 21:47:10 +00:00
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
pa.readers.add(c)
|
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) onClientAnnounce(c client.Client, tracks gortsplib.Tracks) error {
|
2020-10-24 17:55:47 +00:00
|
|
|
if _, ok := pa.clients[c]; ok {
|
|
|
|
return fmt.Errorf("already subscribed")
|
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.source != nil || pa.hasExternalSource() {
|
2020-10-24 17:55:47 +00:00
|
|
|
return fmt.Errorf("someone is already publishing to path '%s'", pa.name)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.addClient(c, clientStatePreRecord)
|
2020-10-24 17:55:47 +00:00
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
pa.source = c
|
|
|
|
pa.sourceTrackCount = len(tracks)
|
|
|
|
pa.sourceSdp = tracks.Write()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) onClientRecord(c client.Client) {
|
2020-10-24 17:55:47 +00:00
|
|
|
state, ok := pa.clients[c]
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if state != clientStatePreRecord {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
atomic.AddInt64(pa.stats.CountPublishers, 1)
|
|
|
|
pa.clients[c] = clientStateRecord
|
2020-10-24 17:55:47 +00:00
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
pa.onSourceSetReady()
|
|
|
|
}
|
|
|
|
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) onClientPause(c client.Client) {
|
2020-11-07 21:47:10 +00:00
|
|
|
state, ok := pa.clients[c]
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if state == clientStatePlay {
|
|
|
|
atomic.AddInt64(pa.stats.CountReaders, -1)
|
|
|
|
pa.clients[c] = clientStatePrePlay
|
|
|
|
|
|
|
|
pa.readers.remove(c)
|
|
|
|
|
|
|
|
} else if state == clientStateRecord {
|
|
|
|
atomic.AddInt64(pa.stats.CountPublishers, -1)
|
|
|
|
pa.clients[c] = clientStatePreRecord
|
|
|
|
|
|
|
|
pa.onSourceSetNotReady()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
func (pa *Path) scheduleSourceClose() {
|
|
|
|
if !pa.hasExternalSource() || !pa.conf.SourceOnDemand || pa.source == nil {
|
|
|
|
return
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.sourceCloseTimerStarted ||
|
|
|
|
pa.sourceState == sourceStateWaitingDescribe ||
|
|
|
|
pa.hasClients() {
|
|
|
|
return
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.sourceCloseTimer.Stop()
|
|
|
|
pa.sourceCloseTimer = time.NewTimer(pa.conf.SourceOnDemandCloseAfter)
|
|
|
|
pa.sourceCloseTimerStarted = true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *Path) scheduleRunOnDemandClose() {
|
|
|
|
if pa.conf.RunOnDemand == "" || pa.onDemandCmd == nil {
|
|
|
|
return
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
if pa.runOnDemandCloseTimerStarted ||
|
|
|
|
pa.sourceState == sourceStateWaitingDescribe ||
|
|
|
|
pa.hasClientsNotSources() {
|
|
|
|
return
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2020-11-01 15:51:12 +00:00
|
|
|
pa.runOnDemandCloseTimer.Stop()
|
|
|
|
pa.runOnDemandCloseTimer = time.NewTimer(pa.conf.RunOnDemandCloseAfter)
|
|
|
|
pa.runOnDemandCloseTimerStarted = true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *Path) scheduleClose() {
|
2021-01-31 11:23:54 +00:00
|
|
|
if pa.conf.Regexp != nil &&
|
|
|
|
!pa.hasClients() &&
|
|
|
|
pa.source == nil &&
|
|
|
|
pa.sourceState != sourceStateWaitingDescribe &&
|
|
|
|
!pa.sourceCloseTimerStarted &&
|
|
|
|
!pa.runOnDemandCloseTimerStarted &&
|
|
|
|
!pa.closeTimerStarted {
|
|
|
|
|
|
|
|
pa.closeTimer.Stop()
|
|
|
|
pa.closeTimer = time.NewTimer(0)
|
|
|
|
pa.closeTimerStarted = true
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// ConfName returns the configuration name of this path.
|
2020-10-24 17:55:47 +00:00
|
|
|
func (pa *Path) ConfName() string {
|
|
|
|
return pa.confName
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// Conf returns the configuration of this path.
|
2020-10-24 17:55:47 +00:00
|
|
|
func (pa *Path) Conf() *conf.PathConf {
|
|
|
|
return pa.conf
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// Name returns the name of this path.
|
2020-10-19 20:17:48 +00:00
|
|
|
func (pa *Path) Name() string {
|
|
|
|
return pa.name
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// SourceTrackCount returns the number of tracks of the source this path.
|
2020-10-19 20:17:48 +00:00
|
|
|
func (pa *Path) SourceTrackCount() int {
|
|
|
|
return pa.sourceTrackCount
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// OnSourceSetReady is called by a source.
|
|
|
|
func (pa *Path) OnSourceSetReady(tracks gortsplib.Tracks) {
|
|
|
|
pa.sourceSdp = tracks.Write()
|
|
|
|
pa.sourceTrackCount = len(tracks)
|
|
|
|
pa.sourceSetReady <- struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// OnSourceSetNotReady is called by a source.
|
|
|
|
func (pa *Path) OnSourceSetNotReady() {
|
|
|
|
pa.sourceSetNotReady <- struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// OnPathManDescribe is called by pathman.PathMan.
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) OnPathManDescribe(req client.DescribeReq) {
|
2020-10-19 20:17:48 +00:00
|
|
|
pa.clientDescribe <- req
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// OnPathManSetupPlay is called by pathman.PathMan.
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) OnPathManSetupPlay(req client.SetupPlayReq) {
|
2020-10-19 20:17:48 +00:00
|
|
|
pa.clientSetupPlay <- req
|
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// OnPathManAnnounce is called by pathman.PathMan.
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) OnPathManAnnounce(req client.AnnounceReq) {
|
2020-10-19 20:17:48 +00:00
|
|
|
pa.clientAnnounce <- req
|
|
|
|
}
|
|
|
|
|
2021-01-31 16:06:34 +00:00
|
|
|
// OnClientRemove is called by clientrtsp.Client.
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) OnClientRemove(req client.RemoveReq) {
|
2021-01-30 21:06:02 +00:00
|
|
|
pa.clientRemove <- req
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 16:06:34 +00:00
|
|
|
// OnClientPlay is called by clientrtsp.Client.
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) OnClientPlay(req client.PlayReq) {
|
2021-01-30 21:06:02 +00:00
|
|
|
pa.clientPlay <- req
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 16:06:34 +00:00
|
|
|
// OnClientRecord is called by clientrtsp.Client.
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) OnClientRecord(req client.RecordReq) {
|
2021-01-30 21:06:02 +00:00
|
|
|
pa.clientRecord <- req
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 16:06:34 +00:00
|
|
|
// OnClientPause is called by clientrtsp.Client.
|
2021-01-31 20:42:27 +00:00
|
|
|
func (pa *Path) OnClientPause(req client.PauseReq) {
|
2021-01-30 21:06:02 +00:00
|
|
|
pa.clientPause <- req
|
2020-11-07 21:47:10 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 16:06:34 +00:00
|
|
|
// OnFrame is called by a source or by a clientrtsp.Client.
|
2020-12-05 19:42:59 +00:00
|
|
|
func (pa *Path) OnFrame(trackID int, streamType gortsplib.StreamType, buf []byte) {
|
|
|
|
pa.readers.forwardFrame(trackID, streamType, buf)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|