mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-10-25 16:50:27 +08:00
refactor ondemand system
This commit is contained in:
@@ -306,6 +306,10 @@ func (pconf *PathConf) fillAndCheck(name string) error {
|
|||||||
return fmt.Errorf("'runOnPublish' is useless when source is not 'record', since the stream is not provided by a publisher, but by a fixed source")
|
return fmt.Errorf("'runOnPublish' is useless when source is not 'record', since the stream is not provided by a publisher, but by a fixed source")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pconf.RunOnDemand != "" && pconf.Source != "record" {
|
||||||
|
return fmt.Errorf("'runOnDemand' can be used only when source is 'record'")
|
||||||
|
}
|
||||||
|
|
||||||
if pconf.RunOnDemandStartTimeout == 0 {
|
if pconf.RunOnDemandStartTimeout == 0 {
|
||||||
pconf.RunOnDemandStartTimeout = 10 * time.Second
|
pconf.RunOnDemandStartTimeout = 10 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,12 +72,13 @@ const (
|
|||||||
pathReaderStatePlay
|
pathReaderStatePlay
|
||||||
)
|
)
|
||||||
|
|
||||||
type pathSourceState int
|
type pathOnDemandState int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
pathSourceStateNotReady pathSourceState = iota
|
pathOnDemandStateInitial pathOnDemandState = iota
|
||||||
pathSourceStateCreating
|
pathOnDemandStateWaitingReady
|
||||||
pathSourceStateReady
|
pathOnDemandStateReady
|
||||||
|
pathOnDemandStateClosing
|
||||||
)
|
)
|
||||||
|
|
||||||
type pathSourceStaticSetReadyReq struct {
|
type pathSourceStaticSetReadyReq struct {
|
||||||
@@ -86,7 +87,8 @@ type pathSourceStaticSetReadyReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type pathSourceStaticSetNotReadyReq struct {
|
type pathSourceStaticSetNotReadyReq struct {
|
||||||
Res chan struct{}
|
Source sourceStatic
|
||||||
|
Res chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type pathReaderRemoveReq struct {
|
type pathReaderRemoveReq struct {
|
||||||
@@ -211,25 +213,21 @@ type path struct {
|
|||||||
stats *stats
|
stats *stats
|
||||||
parent pathParent
|
parent pathParent
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
ctxCancel func()
|
ctxCancel func()
|
||||||
readers map[reader]pathReaderState
|
source source
|
||||||
describeRequests []pathDescribeReq
|
sourceReady bool
|
||||||
setupPlayRequests []pathReaderSetupPlayReq
|
sourceStaticWg sync.WaitGroup
|
||||||
source source
|
stream *gortsplib.ServerStream
|
||||||
sourceStaticWg sync.WaitGroup
|
readers map[reader]pathReaderState
|
||||||
stream *gortsplib.ServerStream
|
describeRequests []pathDescribeReq
|
||||||
nonRTSPReaders *pathReadersMap
|
setupPlayRequests []pathReaderSetupPlayReq
|
||||||
onDemandCmd *externalcmd.Cmd
|
nonRTSPReaders *pathReadersMap
|
||||||
onPublishCmd *externalcmd.Cmd
|
onDemandCmd *externalcmd.Cmd
|
||||||
describeTimer *time.Timer
|
onPublishCmd *externalcmd.Cmd
|
||||||
sourceCloseTimer *time.Timer
|
onDemandReadyTimer *time.Timer
|
||||||
sourceCloseTimerStarted bool
|
onDemandCloseTimer *time.Timer
|
||||||
sourceState pathSourceState
|
onDemandState pathOnDemandState
|
||||||
runOnDemandCloseTimer *time.Timer
|
|
||||||
runOnDemandCloseTimerStarted bool
|
|
||||||
closeTimer *time.Timer
|
|
||||||
closeTimerStarted bool
|
|
||||||
|
|
||||||
// in
|
// in
|
||||||
sourceStaticSetReady chan pathSourceStaticSetReadyReq
|
sourceStaticSetReady chan pathSourceStaticSetReadyReq
|
||||||
@@ -276,10 +274,8 @@ func newPath(
|
|||||||
ctxCancel: ctxCancel,
|
ctxCancel: ctxCancel,
|
||||||
readers: make(map[reader]pathReaderState),
|
readers: make(map[reader]pathReaderState),
|
||||||
nonRTSPReaders: newPathReadersMap(),
|
nonRTSPReaders: newPathReadersMap(),
|
||||||
describeTimer: newEmptyTimer(),
|
onDemandReadyTimer: newEmptyTimer(),
|
||||||
sourceCloseTimer: newEmptyTimer(),
|
onDemandCloseTimer: newEmptyTimer(),
|
||||||
runOnDemandCloseTimer: newEmptyTimer(),
|
|
||||||
closeTimer: newEmptyTimer(),
|
|
||||||
sourceStaticSetReady: make(chan pathSourceStaticSetReadyReq),
|
sourceStaticSetReady: make(chan pathSourceStaticSetReadyReq),
|
||||||
sourceStaticSetNotReady: make(chan pathSourceStaticSetNotReadyReq),
|
sourceStaticSetNotReady: make(chan pathSourceStaticSetNotReadyReq),
|
||||||
describe: make(chan pathDescribeReq),
|
describe: make(chan pathDescribeReq),
|
||||||
@@ -328,8 +324,8 @@ func (pa *path) run() {
|
|||||||
|
|
||||||
if pa.conf.Source == "redirect" {
|
if pa.conf.Source == "redirect" {
|
||||||
pa.source = &sourceRedirect{}
|
pa.source = &sourceRedirect{}
|
||||||
} else if pa.hasStaticSource() && !pa.conf.SourceOnDemand {
|
} else if !pa.conf.SourceOnDemand && pa.hasStaticSource() {
|
||||||
pa.startStaticSource()
|
pa.staticSourceCreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var onInitCmd *externalcmd.Cmd
|
var onInitCmd *externalcmd.Cmd
|
||||||
@@ -345,57 +341,55 @@ func (pa *path) run() {
|
|||||||
outer:
|
outer:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-pa.describeTimer.C:
|
case <-pa.onDemandReadyTimer.C:
|
||||||
for _, req := range pa.describeRequests {
|
for _, req := range pa.describeRequests {
|
||||||
req.Res <- pathDescribeRes{Err: fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
|
req.Res <- pathDescribeRes{Err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
|
||||||
}
|
}
|
||||||
pa.describeRequests = nil
|
pa.describeRequests = nil
|
||||||
|
|
||||||
for _, req := range pa.setupPlayRequests {
|
for _, req := range pa.setupPlayRequests {
|
||||||
req.Res <- pathReaderSetupPlayRes{Err: fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
|
req.Res <- pathReaderSetupPlayRes{Err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
|
||||||
}
|
}
|
||||||
pa.setupPlayRequests = nil
|
pa.setupPlayRequests = nil
|
||||||
|
|
||||||
// set state after removeReader(), so schedule* works once
|
pa.onDemandCloseSource()
|
||||||
pa.sourceState = pathSourceStateNotReady
|
|
||||||
|
|
||||||
pa.scheduleSourceClose()
|
if pa.conf.Regexp != nil {
|
||||||
pa.scheduleRunOnDemandClose()
|
break outer
|
||||||
pa.scheduleClose()
|
}
|
||||||
|
|
||||||
case <-pa.sourceCloseTimer.C:
|
case <-pa.onDemandCloseTimer.C:
|
||||||
pa.sourceCloseTimerStarted = false
|
pa.onDemandCloseSource()
|
||||||
pa.source.(sourceStatic).Close()
|
|
||||||
pa.source = nil
|
|
||||||
|
|
||||||
pa.scheduleClose()
|
if pa.conf.Regexp != nil {
|
||||||
|
break outer
|
||||||
case <-pa.runOnDemandCloseTimer.C:
|
}
|
||||||
pa.runOnDemandCloseTimerStarted = false
|
|
||||||
pa.Log(logger.Info, "on demand command stopped")
|
|
||||||
pa.onDemandCmd.Close()
|
|
||||||
pa.onDemandCmd = nil
|
|
||||||
|
|
||||||
pa.scheduleClose()
|
|
||||||
|
|
||||||
case <-pa.closeTimer.C:
|
|
||||||
break outer
|
|
||||||
|
|
||||||
case req := <-pa.sourceStaticSetReady:
|
case req := <-pa.sourceStaticSetReady:
|
||||||
pa.stream = gortsplib.NewServerStream(req.Tracks)
|
pa.stream = gortsplib.NewServerStream(req.Tracks)
|
||||||
pa.onSourceSetReady()
|
pa.sourceSetReady()
|
||||||
close(req.Res)
|
close(req.Res)
|
||||||
|
|
||||||
case req := <-pa.sourceStaticSetNotReady:
|
case req := <-pa.sourceStaticSetNotReady:
|
||||||
pa.onSourceSetNotReady()
|
if req.Source == pa.source {
|
||||||
|
pa.sourceSetNotReady()
|
||||||
|
}
|
||||||
close(req.Res)
|
close(req.Res)
|
||||||
|
|
||||||
|
if pa.source == nil && pa.conf.Regexp != nil {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
|
||||||
case req := <-pa.describe:
|
case req := <-pa.describe:
|
||||||
pa.onDescribe(req)
|
pa.onDescribe(req)
|
||||||
|
|
||||||
case req := <-pa.publisherRemove:
|
case req := <-pa.publisherRemove:
|
||||||
pa.onPublisherRemove(req)
|
pa.onPublisherRemove(req)
|
||||||
|
|
||||||
|
if pa.source == nil && pa.conf.Regexp != nil {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
|
||||||
case req := <-pa.publisherAnnounce:
|
case req := <-pa.publisherAnnounce:
|
||||||
pa.onPublisherAnnounce(req)
|
pa.onPublisherAnnounce(req)
|
||||||
|
|
||||||
@@ -405,6 +399,10 @@ outer:
|
|||||||
case req := <-pa.publisherPause:
|
case req := <-pa.publisherPause:
|
||||||
pa.onPublisherPause(req)
|
pa.onPublisherPause(req)
|
||||||
|
|
||||||
|
if pa.source == nil && pa.conf.Regexp != nil {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
|
||||||
case req := <-pa.readerRemove:
|
case req := <-pa.readerRemove:
|
||||||
pa.onReaderRemove(req)
|
pa.onReaderRemove(req)
|
||||||
|
|
||||||
@@ -424,10 +422,8 @@ outer:
|
|||||||
|
|
||||||
pa.ctxCancel()
|
pa.ctxCancel()
|
||||||
|
|
||||||
pa.describeTimer.Stop()
|
pa.onDemandReadyTimer.Stop()
|
||||||
pa.sourceCloseTimer.Stop()
|
pa.onDemandCloseTimer.Stop()
|
||||||
pa.runOnDemandCloseTimer.Stop()
|
|
||||||
pa.closeTimer.Stop()
|
|
||||||
|
|
||||||
if onInitCmd != nil {
|
if onInitCmd != nil {
|
||||||
pa.Log(logger.Info, "on init command stopped")
|
pa.Log(logger.Info, "on init command stopped")
|
||||||
@@ -463,7 +459,7 @@ outer:
|
|||||||
source.Close()
|
source.Close()
|
||||||
pa.sourceStaticWg.Wait()
|
pa.sourceStaticWg.Wait()
|
||||||
} else if source, ok := pa.source.(publisher); ok {
|
} else if source, ok := pa.source.(publisher); ok {
|
||||||
if pa.sourceState == pathSourceStateReady {
|
if pa.sourceReady {
|
||||||
atomic.AddInt64(pa.stats.CountPublishers, -1)
|
atomic.AddInt64(pa.stats.CountPublishers, -1)
|
||||||
}
|
}
|
||||||
source.Close()
|
source.Close()
|
||||||
@@ -479,7 +475,111 @@ func (pa *path) hasStaticSource() bool {
|
|||||||
strings.HasPrefix(pa.conf.Source, "rtmp://")
|
strings.HasPrefix(pa.conf.Source, "rtmp://")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) startStaticSource() {
|
func (pa *path) isOnDemand() bool {
|
||||||
|
return (pa.hasStaticSource() && pa.conf.SourceOnDemand) || pa.conf.RunOnDemand != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pa *path) onDemandStartSource() {
|
||||||
|
pa.onDemandReadyTimer.Stop()
|
||||||
|
if pa.hasStaticSource() {
|
||||||
|
pa.staticSourceCreate()
|
||||||
|
pa.onDemandReadyTimer = time.NewTimer(pa.conf.SourceOnDemandStartTimeout)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pa.Log(logger.Info, "on demand command started")
|
||||||
|
_, port, _ := net.SplitHostPort(pa.rtspAddress)
|
||||||
|
pa.onDemandCmd = externalcmd.New(pa.conf.RunOnDemand, pa.conf.RunOnDemandRestart, externalcmd.Environment{
|
||||||
|
Path: pa.name,
|
||||||
|
Port: port,
|
||||||
|
})
|
||||||
|
pa.onDemandReadyTimer = time.NewTimer(pa.conf.RunOnDemandStartTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
pa.onDemandState = pathOnDemandStateWaitingReady
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pa *path) onDemandScheduleClose() {
|
||||||
|
pa.onDemandCloseTimer.Stop()
|
||||||
|
if pa.hasStaticSource() {
|
||||||
|
pa.onDemandCloseTimer = time.NewTimer(pa.conf.SourceOnDemandCloseAfter)
|
||||||
|
} else {
|
||||||
|
pa.onDemandCloseTimer = time.NewTimer(pa.conf.RunOnDemandCloseAfter)
|
||||||
|
}
|
||||||
|
|
||||||
|
pa.onDemandState = pathOnDemandStateClosing
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pa *path) onDemandCloseSource() {
|
||||||
|
if pa.onDemandState == pathOnDemandStateClosing {
|
||||||
|
pa.onDemandCloseTimer.Stop()
|
||||||
|
pa.onDemandCloseTimer = newEmptyTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// set state before doPublisherRemove()
|
||||||
|
pa.onDemandState = pathOnDemandStateInitial
|
||||||
|
|
||||||
|
if pa.hasStaticSource() {
|
||||||
|
pa.staticSourceDelete()
|
||||||
|
} else {
|
||||||
|
pa.Log(logger.Info, "on demand command stopped")
|
||||||
|
pa.onDemandCmd.Close()
|
||||||
|
pa.onDemandCmd = nil
|
||||||
|
|
||||||
|
if pa.source != nil {
|
||||||
|
pa.source.(publisher).Close()
|
||||||
|
pa.doPublisherRemove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pa *path) sourceSetReady() {
|
||||||
|
pa.sourceReady = true
|
||||||
|
|
||||||
|
if pa.isOnDemand() {
|
||||||
|
pa.onDemandReadyTimer.Stop()
|
||||||
|
pa.onDemandReadyTimer = newEmptyTimer()
|
||||||
|
|
||||||
|
for _, req := range pa.describeRequests {
|
||||||
|
req.Res <- pathDescribeRes{
|
||||||
|
Stream: pa.stream,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pa.describeRequests = nil
|
||||||
|
|
||||||
|
for _, req := range pa.setupPlayRequests {
|
||||||
|
pa.onReaderSetupPlayPost(req)
|
||||||
|
}
|
||||||
|
pa.setupPlayRequests = nil
|
||||||
|
|
||||||
|
if len(pa.readers) > 0 {
|
||||||
|
pa.onDemandState = pathOnDemandStateReady
|
||||||
|
} else {
|
||||||
|
pa.onDemandScheduleClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pa.parent.OnPathSourceReady(pa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pa *path) sourceSetNotReady() {
|
||||||
|
pa.sourceReady = false
|
||||||
|
|
||||||
|
if pa.isOnDemand() && pa.onDemandState != pathOnDemandStateInitial {
|
||||||
|
pa.onDemandCloseSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
if pa.onPublishCmd != nil {
|
||||||
|
pa.onPublishCmd.Close()
|
||||||
|
pa.onPublishCmd = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for r := range pa.readers {
|
||||||
|
pa.doReaderRemove(r)
|
||||||
|
r.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pa *path) staticSourceCreate() {
|
||||||
if strings.HasPrefix(pa.conf.Source, "rtsp://") ||
|
if strings.HasPrefix(pa.conf.Source, "rtsp://") ||
|
||||||
strings.HasPrefix(pa.conf.Source, "rtsps://") {
|
strings.HasPrefix(pa.conf.Source, "rtsps://") {
|
||||||
pa.source = newRTSPSource(
|
pa.source = newRTSPSource(
|
||||||
@@ -507,7 +607,17 @@ func (pa *path) startStaticSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) removeReader(r reader) {
|
func (pa *path) staticSourceDelete() {
|
||||||
|
pa.sourceReady = false
|
||||||
|
|
||||||
|
pa.source.(sourceStatic).Close()
|
||||||
|
pa.source = nil
|
||||||
|
|
||||||
|
pa.stream.Close()
|
||||||
|
pa.stream = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pa *path) doReaderRemove(r reader) {
|
||||||
state := pa.readers[r]
|
state := pa.readers[r]
|
||||||
|
|
||||||
if state == pathReaderStatePlay {
|
if state == pathReaderStatePlay {
|
||||||
@@ -519,16 +629,12 @@ func (pa *path) removeReader(r reader) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete(pa.readers, r)
|
delete(pa.readers, r)
|
||||||
|
|
||||||
pa.scheduleSourceClose()
|
|
||||||
pa.scheduleRunOnDemandClose()
|
|
||||||
pa.scheduleClose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) removePublisher(p publisher) {
|
func (pa *path) doPublisherRemove() {
|
||||||
if pa.sourceState == pathSourceStateReady {
|
if pa.sourceReady {
|
||||||
atomic.AddInt64(pa.stats.CountPublishers, -1)
|
atomic.AddInt64(pa.stats.CountPublishers, -1)
|
||||||
pa.onSourceSetNotReady()
|
pa.sourceSetNotReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
pa.source = nil
|
pa.source = nil
|
||||||
@@ -536,101 +642,12 @@ func (pa *path) removePublisher(p publisher) {
|
|||||||
pa.stream = nil
|
pa.stream = nil
|
||||||
|
|
||||||
for r := range pa.readers {
|
for r := range pa.readers {
|
||||||
pa.removeReader(r)
|
pa.doReaderRemove(r)
|
||||||
r.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
pa.scheduleSourceClose()
|
|
||||||
pa.scheduleRunOnDemandClose()
|
|
||||||
pa.scheduleClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pa *path) fixedPublisherStart() {
|
|
||||||
if pa.hasStaticSource() {
|
|
||||||
// start on-demand source
|
|
||||||
if pa.source == nil {
|
|
||||||
pa.startStaticSource()
|
|
||||||
|
|
||||||
if pa.sourceState != pathSourceStateCreating {
|
|
||||||
pa.describeTimer = time.NewTimer(pa.conf.SourceOnDemandStartTimeout)
|
|
||||||
pa.sourceState = pathSourceStateCreating
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset timer
|
|
||||||
} else if pa.sourceCloseTimerStarted {
|
|
||||||
pa.sourceCloseTimer.Stop()
|
|
||||||
pa.sourceCloseTimer = time.NewTimer(pa.conf.SourceOnDemandCloseAfter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pa.conf.RunOnDemand != "" {
|
|
||||||
// start on-demand command
|
|
||||||
if pa.onDemandCmd == nil {
|
|
||||||
pa.Log(logger.Info, "on demand command started")
|
|
||||||
_, port, _ := net.SplitHostPort(pa.rtspAddress)
|
|
||||||
pa.onDemandCmd = externalcmd.New(pa.conf.RunOnDemand, pa.conf.RunOnDemandRestart, externalcmd.Environment{
|
|
||||||
Path: pa.name,
|
|
||||||
Port: port,
|
|
||||||
})
|
|
||||||
|
|
||||||
if pa.sourceState != pathSourceStateCreating {
|
|
||||||
pa.describeTimer = time.NewTimer(pa.conf.RunOnDemandStartTimeout)
|
|
||||||
pa.sourceState = pathSourceStateCreating
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset timer
|
|
||||||
} else if pa.runOnDemandCloseTimerStarted {
|
|
||||||
pa.runOnDemandCloseTimer.Stop()
|
|
||||||
pa.runOnDemandCloseTimer = time.NewTimer(pa.conf.RunOnDemandCloseAfter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pa *path) onSourceSetReady() {
|
|
||||||
if pa.sourceState == pathSourceStateCreating {
|
|
||||||
pa.describeTimer.Stop()
|
|
||||||
pa.describeTimer = newEmptyTimer()
|
|
||||||
}
|
|
||||||
|
|
||||||
pa.sourceState = pathSourceStateReady
|
|
||||||
|
|
||||||
for _, req := range pa.describeRequests {
|
|
||||||
req.Res <- pathDescribeRes{
|
|
||||||
Stream: pa.stream,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pa.describeRequests = nil
|
|
||||||
|
|
||||||
for _, req := range pa.setupPlayRequests {
|
|
||||||
pa.onReaderSetupPlayPost(req)
|
|
||||||
}
|
|
||||||
pa.setupPlayRequests = nil
|
|
||||||
|
|
||||||
pa.scheduleSourceClose()
|
|
||||||
pa.scheduleRunOnDemandClose()
|
|
||||||
pa.scheduleClose()
|
|
||||||
|
|
||||||
pa.parent.OnPathSourceReady(pa)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pa *path) onSourceSetNotReady() {
|
|
||||||
pa.sourceState = pathSourceStateNotReady
|
|
||||||
|
|
||||||
if pa.onPublishCmd != nil {
|
|
||||||
pa.onPublishCmd.Close()
|
|
||||||
pa.onPublishCmd = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for r := range pa.readers {
|
|
||||||
pa.removeReader(r)
|
|
||||||
r.Close()
|
r.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) onDescribe(req pathDescribeReq) {
|
func (pa *path) onDescribe(req pathDescribeReq) {
|
||||||
pa.fixedPublisherStart()
|
|
||||||
pa.scheduleClose()
|
|
||||||
|
|
||||||
if _, ok := pa.source.(*sourceRedirect); ok {
|
if _, ok := pa.source.(*sourceRedirect); ok {
|
||||||
req.Res <- pathDescribeRes{
|
req.Res <- pathDescribeRes{
|
||||||
Redirect: pa.conf.SourceRedirect,
|
Redirect: pa.conf.SourceRedirect,
|
||||||
@@ -638,43 +655,44 @@ func (pa *path) onDescribe(req pathDescribeReq) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch pa.sourceState {
|
if pa.sourceReady {
|
||||||
case pathSourceStateReady:
|
|
||||||
req.Res <- pathDescribeRes{
|
req.Res <- pathDescribeRes{
|
||||||
Stream: pa.stream,
|
Stream: pa.stream,
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case pathSourceStateCreating:
|
if pa.isOnDemand() {
|
||||||
|
if pa.onDemandState == pathOnDemandStateInitial {
|
||||||
|
pa.onDemandStartSource()
|
||||||
|
}
|
||||||
pa.describeRequests = append(pa.describeRequests, req)
|
pa.describeRequests = append(pa.describeRequests, req)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case pathSourceStateNotReady:
|
if pa.conf.Fallback != "" {
|
||||||
if pa.conf.Fallback != "" {
|
fallbackURL := func() string {
|
||||||
fallbackURL := func() string {
|
if strings.HasPrefix(pa.conf.Fallback, "/") {
|
||||||
if strings.HasPrefix(pa.conf.Fallback, "/") {
|
ur := base.URL{
|
||||||
ur := base.URL{
|
Scheme: req.URL.Scheme,
|
||||||
Scheme: req.URL.Scheme,
|
User: req.URL.User,
|
||||||
User: req.URL.User,
|
Host: req.URL.Host,
|
||||||
Host: req.URL.Host,
|
Path: pa.conf.Fallback,
|
||||||
Path: pa.conf.Fallback,
|
|
||||||
}
|
|
||||||
return ur.String()
|
|
||||||
}
|
}
|
||||||
return pa.conf.Fallback
|
return ur.String()
|
||||||
}()
|
}
|
||||||
req.Res <- pathDescribeRes{Redirect: fallbackURL}
|
return pa.conf.Fallback
|
||||||
return
|
}()
|
||||||
}
|
req.Res <- pathDescribeRes{Redirect: fallbackURL}
|
||||||
|
|
||||||
req.Res <- pathDescribeRes{Err: pathErrNoOnePublishing{PathName: pa.name}}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Res <- pathDescribeRes{Err: pathErrNoOnePublishing{PathName: pa.name}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) onPublisherRemove(req pathPublisherRemoveReq) {
|
func (pa *path) onPublisherRemove(req pathPublisherRemoveReq) {
|
||||||
if pa.source == req.Author {
|
if pa.source == req.Author {
|
||||||
pa.removePublisher(req.Author)
|
pa.doPublisherRemove()
|
||||||
}
|
}
|
||||||
close(req.Res)
|
close(req.Res)
|
||||||
}
|
}
|
||||||
@@ -692,20 +710,13 @@ func (pa *path) onPublisherAnnounce(req pathPublisherAnnounceReq) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pa.Log(logger.Info, "closing existing publisher")
|
pa.Log(logger.Info, "closing existing publisher")
|
||||||
curPub := pa.source.(publisher)
|
pa.source.(publisher).Close()
|
||||||
pa.removePublisher(curPub)
|
pa.doPublisherRemove()
|
||||||
curPub.Close()
|
|
||||||
|
|
||||||
// prevent path closure
|
|
||||||
if pa.closeTimerStarted {
|
|
||||||
pa.closeTimer.Stop()
|
|
||||||
pa.closeTimer = newEmptyTimer()
|
|
||||||
pa.closeTimerStarted = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pa.source = req.Author
|
pa.source = req.Author
|
||||||
pa.stream = gortsplib.NewServerStream(req.Tracks)
|
pa.stream = gortsplib.NewServerStream(req.Tracks)
|
||||||
|
|
||||||
req.Res <- pathPublisherAnnounceRes{Path: pa}
|
req.Res <- pathPublisherAnnounceRes{Path: pa}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,7 +730,7 @@ func (pa *path) onPublisherRecord(req pathPublisherRecordReq) {
|
|||||||
|
|
||||||
req.Author.OnPublisherAccepted(len(pa.stream.Tracks()))
|
req.Author.OnPublisherAccepted(len(pa.stream.Tracks()))
|
||||||
|
|
||||||
pa.onSourceSetReady()
|
pa.sourceSetReady()
|
||||||
|
|
||||||
if pa.conf.RunOnPublish != "" {
|
if pa.conf.RunOnPublish != "" {
|
||||||
_, port, _ := net.SplitHostPort(pa.rtspAddress)
|
_, port, _ := net.SplitHostPort(pa.rtspAddress)
|
||||||
@@ -733,54 +744,50 @@ func (pa *path) onPublisherRecord(req pathPublisherRecordReq) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) onPublisherPause(req pathPublisherPauseReq) {
|
func (pa *path) onPublisherPause(req pathPublisherPauseReq) {
|
||||||
if req.Author == pa.source && pa.sourceState == pathSourceStateReady {
|
if req.Author == pa.source && pa.sourceReady {
|
||||||
atomic.AddInt64(pa.stats.CountPublishers, -1)
|
atomic.AddInt64(pa.stats.CountPublishers, -1)
|
||||||
pa.onSourceSetNotReady()
|
pa.sourceSetNotReady()
|
||||||
}
|
}
|
||||||
close(req.Res)
|
close(req.Res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) onReaderRemove(req pathReaderRemoveReq) {
|
func (pa *path) onReaderRemove(req pathReaderRemoveReq) {
|
||||||
if _, ok := pa.readers[req.Author]; ok {
|
if _, ok := pa.readers[req.Author]; ok {
|
||||||
pa.removeReader(req.Author)
|
pa.doReaderRemove(req.Author)
|
||||||
}
|
}
|
||||||
close(req.Res)
|
close(req.Res)
|
||||||
|
|
||||||
|
if pa.isOnDemand() &&
|
||||||
|
len(pa.readers) == 0 &&
|
||||||
|
pa.onDemandState == pathOnDemandStateReady {
|
||||||
|
pa.onDemandScheduleClose()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) onReaderSetupPlay(req pathReaderSetupPlayReq) {
|
func (pa *path) onReaderSetupPlay(req pathReaderSetupPlayReq) {
|
||||||
pa.fixedPublisherStart()
|
if pa.sourceReady {
|
||||||
pa.scheduleClose()
|
|
||||||
|
|
||||||
switch pa.sourceState {
|
|
||||||
case pathSourceStateReady:
|
|
||||||
pa.onReaderSetupPlayPost(req)
|
pa.onReaderSetupPlayPost(req)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case pathSourceStateCreating:
|
if pa.isOnDemand() {
|
||||||
|
if pa.onDemandState == pathOnDemandStateInitial {
|
||||||
|
pa.onDemandStartSource()
|
||||||
|
}
|
||||||
pa.setupPlayRequests = append(pa.setupPlayRequests, req)
|
pa.setupPlayRequests = append(pa.setupPlayRequests, req)
|
||||||
return
|
return
|
||||||
|
|
||||||
case pathSourceStateNotReady:
|
|
||||||
req.Res <- pathReaderSetupPlayRes{Err: pathErrNoOnePublishing{PathName: pa.name}}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Res <- pathReaderSetupPlayRes{Err: pathErrNoOnePublishing{PathName: pa.name}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) onReaderSetupPlayPost(req pathReaderSetupPlayReq) {
|
func (pa *path) onReaderSetupPlayPost(req pathReaderSetupPlayReq) {
|
||||||
if _, ok := pa.readers[req.Author]; !ok {
|
pa.readers[req.Author] = pathReaderStatePrePlay
|
||||||
// prevent on-demand source from closing
|
|
||||||
if pa.sourceCloseTimerStarted {
|
|
||||||
pa.sourceCloseTimer = newEmptyTimer()
|
|
||||||
pa.sourceCloseTimerStarted = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent on-demand command from closing
|
if pa.isOnDemand() && pa.onDemandState == pathOnDemandStateClosing {
|
||||||
if pa.runOnDemandCloseTimerStarted {
|
pa.onDemandState = pathOnDemandStateReady
|
||||||
pa.runOnDemandCloseTimer = newEmptyTimer()
|
pa.onDemandCloseTimer.Stop()
|
||||||
pa.runOnDemandCloseTimerStarted = false
|
pa.onDemandCloseTimer = newEmptyTimer()
|
||||||
}
|
|
||||||
|
|
||||||
pa.readers[req.Author] = pathReaderStatePrePlay
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Res <- pathReaderSetupPlayRes{
|
req.Res <- pathReaderSetupPlayRes{
|
||||||
@@ -814,54 +821,6 @@ func (pa *path) onReaderPause(req pathReaderPauseReq) {
|
|||||||
close(req.Res)
|
close(req.Res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) scheduleSourceClose() {
|
|
||||||
if !pa.hasStaticSource() || !pa.conf.SourceOnDemand || pa.source == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if pa.sourceCloseTimerStarted ||
|
|
||||||
pa.sourceState == pathSourceStateCreating ||
|
|
||||||
len(pa.readers) > 0 ||
|
|
||||||
pa.source != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if pa.runOnDemandCloseTimerStarted ||
|
|
||||||
pa.sourceState == pathSourceStateCreating ||
|
|
||||||
len(pa.readers) > 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pa.runOnDemandCloseTimer.Stop()
|
|
||||||
pa.runOnDemandCloseTimer = time.NewTimer(pa.conf.RunOnDemandCloseAfter)
|
|
||||||
pa.runOnDemandCloseTimerStarted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pa *path) scheduleClose() {
|
|
||||||
if pa.conf.Regexp != nil &&
|
|
||||||
len(pa.readers) == 0 &&
|
|
||||||
pa.source == nil &&
|
|
||||||
pa.sourceState != pathSourceStateCreating &&
|
|
||||||
!pa.sourceCloseTimerStarted &&
|
|
||||||
!pa.runOnDemandCloseTimerStarted &&
|
|
||||||
!pa.closeTimerStarted {
|
|
||||||
|
|
||||||
pa.closeTimer.Stop()
|
|
||||||
pa.closeTimer = time.NewTimer(0)
|
|
||||||
pa.closeTimerStarted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnSourceStaticSetReady is called by a sourceStatic.
|
// OnSourceStaticSetReady is called by a sourceStatic.
|
||||||
func (pa *path) OnSourceStaticSetReady(req pathSourceStaticSetReadyReq) {
|
func (pa *path) OnSourceStaticSetReady(req pathSourceStaticSetReadyReq) {
|
||||||
req.Res = make(chan struct{})
|
req.Res = make(chan struct{})
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ func (s *rtmpSource) runInner() bool {
|
|||||||
})
|
})
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
s.parent.OnSourceStaticSetNotReady(pathSourceStaticSetNotReadyReq{})
|
s.parent.OnSourceStaticSetNotReady(pathSourceStaticSetNotReadyReq{Source: s})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
rtcpSenders := rtcpsenderset.New(tracks, s.parent.OnSourceFrame)
|
rtcpSenders := rtcpsenderset.New(tracks, s.parent.OnSourceFrame)
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ func (s *rtspSource) runInner() bool {
|
|||||||
})
|
})
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
s.parent.OnSourceStaticSetNotReady(pathSourceStaticSetNotReadyReq{})
|
s.parent.OnSourceStaticSetNotReady(pathSourceStaticSetNotReadyReq{Source: s})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
readErr := make(chan error)
|
readErr := make(chan error)
|
||||||
|
|||||||
Reference in New Issue
Block a user