|
|
@ -68,15 +68,6 @@ type pathRTSPSession interface { |
|
|
|
IsRTSPSession() |
|
|
|
IsRTSPSession() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type sourceRedirect struct{} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// onSourceAPIDescribe implements source.
|
|
|
|
|
|
|
|
func (*sourceRedirect) onSourceAPIDescribe() interface{} { |
|
|
|
|
|
|
|
return struct { |
|
|
|
|
|
|
|
Type string `json:"type"` |
|
|
|
|
|
|
|
}{"redirect"} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type pathReaderState int |
|
|
|
type pathReaderState int |
|
|
|
|
|
|
|
|
|
|
|
const ( |
|
|
|
const ( |
|
|
@ -224,20 +215,23 @@ type path struct { |
|
|
|
externalCmdPool *externalcmd.Pool |
|
|
|
externalCmdPool *externalcmd.Pool |
|
|
|
parent pathParent |
|
|
|
parent pathParent |
|
|
|
|
|
|
|
|
|
|
|
ctx context.Context |
|
|
|
ctx context.Context |
|
|
|
ctxCancel func() |
|
|
|
ctxCancel func() |
|
|
|
source source |
|
|
|
source source |
|
|
|
sourceReady bool |
|
|
|
sourceReady bool |
|
|
|
sourceStaticWg sync.WaitGroup |
|
|
|
sourceStaticWg sync.WaitGroup |
|
|
|
readers map[reader]pathReaderState |
|
|
|
stream *stream |
|
|
|
describeRequests []pathDescribeReq |
|
|
|
readers map[reader]pathReaderState |
|
|
|
setupPlayRequests []pathReaderSetupPlayReq |
|
|
|
describeRequestsOnHold []pathDescribeReq |
|
|
|
stream *stream |
|
|
|
setupPlayRequestsOnHold []pathReaderSetupPlayReq |
|
|
|
onDemandCmd *externalcmd.Cmd |
|
|
|
onDemandCmd *externalcmd.Cmd |
|
|
|
onReadyCmd *externalcmd.Cmd |
|
|
|
onReadyCmd *externalcmd.Cmd |
|
|
|
onDemandReadyTimer *time.Timer |
|
|
|
onDemandStaticSourceState pathOnDemandState |
|
|
|
onDemandCloseTimer *time.Timer |
|
|
|
onDemandStaticSourceReadyTimer *time.Timer |
|
|
|
onDemandState pathOnDemandState |
|
|
|
onDemandStaticSourceCloseTimer *time.Timer |
|
|
|
|
|
|
|
onDemandPublisherState pathOnDemandState |
|
|
|
|
|
|
|
onDemandPublisherReadyTimer *time.Timer |
|
|
|
|
|
|
|
onDemandPublisherCloseTimer *time.Timer |
|
|
|
|
|
|
|
|
|
|
|
// in
|
|
|
|
// in
|
|
|
|
sourceStaticSetReady chan pathSourceStaticSetReadyReq |
|
|
|
sourceStaticSetReady chan pathSourceStaticSetReadyReq |
|
|
@ -271,34 +265,36 @@ func newPath( |
|
|
|
ctx, ctxCancel := context.WithCancel(parentCtx) |
|
|
|
ctx, ctxCancel := context.WithCancel(parentCtx) |
|
|
|
|
|
|
|
|
|
|
|
pa := &path{ |
|
|
|
pa := &path{ |
|
|
|
rtspAddress: rtspAddress, |
|
|
|
rtspAddress: rtspAddress, |
|
|
|
readTimeout: readTimeout, |
|
|
|
readTimeout: readTimeout, |
|
|
|
writeTimeout: writeTimeout, |
|
|
|
writeTimeout: writeTimeout, |
|
|
|
readBufferCount: readBufferCount, |
|
|
|
readBufferCount: readBufferCount, |
|
|
|
confName: confName, |
|
|
|
confName: confName, |
|
|
|
conf: conf, |
|
|
|
conf: conf, |
|
|
|
name: name, |
|
|
|
name: name, |
|
|
|
matches: matches, |
|
|
|
matches: matches, |
|
|
|
wg: wg, |
|
|
|
wg: wg, |
|
|
|
externalCmdPool: externalCmdPool, |
|
|
|
externalCmdPool: externalCmdPool, |
|
|
|
parent: parent, |
|
|
|
parent: parent, |
|
|
|
ctx: ctx, |
|
|
|
ctx: ctx, |
|
|
|
ctxCancel: ctxCancel, |
|
|
|
ctxCancel: ctxCancel, |
|
|
|
readers: make(map[reader]pathReaderState), |
|
|
|
readers: make(map[reader]pathReaderState), |
|
|
|
onDemandReadyTimer: newEmptyTimer(), |
|
|
|
onDemandStaticSourceReadyTimer: newEmptyTimer(), |
|
|
|
onDemandCloseTimer: newEmptyTimer(), |
|
|
|
onDemandStaticSourceCloseTimer: newEmptyTimer(), |
|
|
|
sourceStaticSetReady: make(chan pathSourceStaticSetReadyReq), |
|
|
|
onDemandPublisherReadyTimer: newEmptyTimer(), |
|
|
|
sourceStaticSetNotReady: make(chan pathSourceStaticSetNotReadyReq), |
|
|
|
onDemandPublisherCloseTimer: newEmptyTimer(), |
|
|
|
describe: make(chan pathDescribeReq), |
|
|
|
sourceStaticSetReady: make(chan pathSourceStaticSetReadyReq), |
|
|
|
publisherRemove: make(chan pathPublisherRemoveReq), |
|
|
|
sourceStaticSetNotReady: make(chan pathSourceStaticSetNotReadyReq), |
|
|
|
publisherAnnounce: make(chan pathPublisherAnnounceReq), |
|
|
|
describe: make(chan pathDescribeReq), |
|
|
|
publisherRecord: make(chan pathPublisherRecordReq), |
|
|
|
publisherRemove: make(chan pathPublisherRemoveReq), |
|
|
|
publisherPause: make(chan pathPublisherPauseReq), |
|
|
|
publisherAnnounce: make(chan pathPublisherAnnounceReq), |
|
|
|
readerRemove: make(chan pathReaderRemoveReq), |
|
|
|
publisherRecord: make(chan pathPublisherRecordReq), |
|
|
|
readerSetupPlay: make(chan pathReaderSetupPlayReq), |
|
|
|
publisherPause: make(chan pathPublisherPauseReq), |
|
|
|
readerPlay: make(chan pathReaderPlayReq), |
|
|
|
readerRemove: make(chan pathReaderRemoveReq), |
|
|
|
readerPause: make(chan pathReaderPauseReq), |
|
|
|
readerSetupPlay: make(chan pathReaderSetupPlayReq), |
|
|
|
apiPathsList: make(chan pathAPIPathsListSubReq), |
|
|
|
readerPlay: make(chan pathReaderPlayReq), |
|
|
|
|
|
|
|
readerPause: make(chan pathReaderPauseReq), |
|
|
|
|
|
|
|
apiPathsList: make(chan pathAPIPathsListSubReq), |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pa.log(logger.Debug, "created") |
|
|
|
pa.log(logger.Debug, "created") |
|
|
@ -333,6 +329,22 @@ func (pa *path) Name() string { |
|
|
|
return pa.name |
|
|
|
return pa.name |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) hasStaticSource() bool { |
|
|
|
|
|
|
|
return strings.HasPrefix(pa.conf.Source, "rtsp://") || |
|
|
|
|
|
|
|
strings.HasPrefix(pa.conf.Source, "rtsps://") || |
|
|
|
|
|
|
|
strings.HasPrefix(pa.conf.Source, "rtmp://") || |
|
|
|
|
|
|
|
strings.HasPrefix(pa.conf.Source, "http://") || |
|
|
|
|
|
|
|
strings.HasPrefix(pa.conf.Source, "https://") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) hasOnDemandStaticSource() bool { |
|
|
|
|
|
|
|
return pa.hasStaticSource() && pa.conf.SourceOnDemand |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) hasOnDemandPublisher() bool { |
|
|
|
|
|
|
|
return pa.conf.RunOnDemand != "" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) run() { |
|
|
|
func (pa *path) run() { |
|
|
|
defer pa.wg.Done() |
|
|
|
defer pa.wg.Done() |
|
|
|
|
|
|
|
|
|
|
@ -358,25 +370,50 @@ func (pa *path) run() { |
|
|
|
err := func() error { |
|
|
|
err := func() error { |
|
|
|
for { |
|
|
|
for { |
|
|
|
select { |
|
|
|
select { |
|
|
|
case <-pa.onDemandReadyTimer.C: |
|
|
|
case <-pa.onDemandStaticSourceReadyTimer.C: |
|
|
|
for _, req := range pa.describeRequests { |
|
|
|
for _, req := range pa.describeRequestsOnHold { |
|
|
|
|
|
|
|
req.res <- pathDescribeRes{err: fmt.Errorf("source of path '%s' has timed out", pa.name)} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pa.describeRequestsOnHold = nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, req := range pa.setupPlayRequestsOnHold { |
|
|
|
|
|
|
|
req.res <- pathReaderSetupPlayRes{err: fmt.Errorf("source of path '%s' has timed out", pa.name)} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pa.setupPlayRequestsOnHold = nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pa.onDemandStaticSourceStop() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if pa.shouldClose() { |
|
|
|
|
|
|
|
return fmt.Errorf("not in use") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case <-pa.onDemandStaticSourceCloseTimer.C: |
|
|
|
|
|
|
|
pa.sourceSetNotReady() |
|
|
|
|
|
|
|
pa.onDemandStaticSourceStop() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if pa.shouldClose() { |
|
|
|
|
|
|
|
return fmt.Errorf("not in use") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case <-pa.onDemandPublisherReadyTimer.C: |
|
|
|
|
|
|
|
for _, req := range pa.describeRequestsOnHold { |
|
|
|
req.res <- pathDescribeRes{err: fmt.Errorf("source 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.describeRequestsOnHold = nil |
|
|
|
|
|
|
|
|
|
|
|
for _, req := range pa.setupPlayRequests { |
|
|
|
for _, req := range pa.setupPlayRequestsOnHold { |
|
|
|
req.res <- pathReaderSetupPlayRes{err: fmt.Errorf("source 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.setupPlayRequestsOnHold = nil |
|
|
|
|
|
|
|
|
|
|
|
pa.onDemandCloseSource() |
|
|
|
pa.onDemandPublisherStop() |
|
|
|
|
|
|
|
|
|
|
|
if pa.shouldClose() { |
|
|
|
if pa.shouldClose() { |
|
|
|
return fmt.Errorf("not in use") |
|
|
|
return fmt.Errorf("not in use") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
case <-pa.onDemandCloseTimer.C: |
|
|
|
case <-pa.onDemandPublisherCloseTimer.C: |
|
|
|
pa.onDemandCloseSource() |
|
|
|
pa.onDemandPublisherStop() |
|
|
|
|
|
|
|
|
|
|
|
if pa.shouldClose() { |
|
|
|
if pa.shouldClose() { |
|
|
|
return fmt.Errorf("not in use") |
|
|
|
return fmt.Errorf("not in use") |
|
|
@ -385,6 +422,30 @@ func (pa *path) run() { |
|
|
|
case req := <-pa.sourceStaticSetReady: |
|
|
|
case req := <-pa.sourceStaticSetReady: |
|
|
|
if req.source == pa.source { |
|
|
|
if req.source == pa.source { |
|
|
|
pa.sourceSetReady(req.tracks) |
|
|
|
pa.sourceSetReady(req.tracks) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if pa.hasOnDemandStaticSource() { |
|
|
|
|
|
|
|
pa.onDemandStaticSourceReadyTimer.Stop() |
|
|
|
|
|
|
|
pa.onDemandStaticSourceReadyTimer = newEmptyTimer() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, req := range pa.describeRequestsOnHold { |
|
|
|
|
|
|
|
req.res <- pathDescribeRes{ |
|
|
|
|
|
|
|
stream: pa.stream, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pa.describeRequestsOnHold = nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, req := range pa.setupPlayRequestsOnHold { |
|
|
|
|
|
|
|
pa.handleReaderSetupPlayPost(req) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pa.setupPlayRequestsOnHold = nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(pa.readers) > 0 { |
|
|
|
|
|
|
|
pa.onDemandStaticSourceState = pathOnDemandStateReady |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
pa.onDemandStaticSourceScheduleClose() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
req.res <- pathSourceStaticSetReadyRes{stream: pa.stream} |
|
|
|
req.res <- pathSourceStaticSetReadyRes{stream: pa.stream} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
req.res <- pathSourceStaticSetReadyRes{err: fmt.Errorf("terminated")} |
|
|
|
req.res <- pathSourceStaticSetReadyRes{err: fmt.Errorf("terminated")} |
|
|
@ -392,10 +453,9 @@ func (pa *path) run() { |
|
|
|
|
|
|
|
|
|
|
|
case req := <-pa.sourceStaticSetNotReady: |
|
|
|
case req := <-pa.sourceStaticSetNotReady: |
|
|
|
if req.source == pa.source { |
|
|
|
if req.source == pa.source { |
|
|
|
if pa.isOnDemand() && pa.onDemandState != pathOnDemandStateInitial { |
|
|
|
pa.sourceSetNotReady() |
|
|
|
pa.onDemandCloseSource() |
|
|
|
if pa.hasOnDemandStaticSource() && pa.onDemandStaticSourceState != pathOnDemandStateInitial { |
|
|
|
} else { |
|
|
|
pa.onDemandStaticSourceStop() |
|
|
|
pa.sourceSetNotReady() |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
close(req.res) |
|
|
|
close(req.res) |
|
|
@ -458,19 +518,21 @@ func (pa *path) run() { |
|
|
|
|
|
|
|
|
|
|
|
pa.ctxCancel() |
|
|
|
pa.ctxCancel() |
|
|
|
|
|
|
|
|
|
|
|
pa.onDemandReadyTimer.Stop() |
|
|
|
pa.onDemandStaticSourceReadyTimer.Stop() |
|
|
|
pa.onDemandCloseTimer.Stop() |
|
|
|
pa.onDemandStaticSourceCloseTimer.Stop() |
|
|
|
|
|
|
|
pa.onDemandPublisherReadyTimer.Stop() |
|
|
|
|
|
|
|
pa.onDemandPublisherCloseTimer.Stop() |
|
|
|
|
|
|
|
|
|
|
|
if onInitCmd != nil { |
|
|
|
if onInitCmd != nil { |
|
|
|
onInitCmd.Close() |
|
|
|
onInitCmd.Close() |
|
|
|
pa.log(logger.Info, "runOnInit command stopped") |
|
|
|
pa.log(logger.Info, "runOnInit command stopped") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for _, req := range pa.describeRequests { |
|
|
|
for _, req := range pa.describeRequestsOnHold { |
|
|
|
req.res <- pathDescribeRes{err: fmt.Errorf("terminated")} |
|
|
|
req.res <- pathDescribeRes{err: fmt.Errorf("terminated")} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for _, req := range pa.setupPlayRequests { |
|
|
|
for _, req := range pa.setupPlayRequestsOnHold { |
|
|
|
req.res <- pathReaderSetupPlayRes{err: fmt.Errorf("terminated")} |
|
|
|
req.res <- pathReaderSetupPlayRes{err: fmt.Errorf("terminated")} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -499,20 +561,8 @@ func (pa *path) shouldClose() bool { |
|
|
|
return pa.conf.Regexp != nil && |
|
|
|
return pa.conf.Regexp != nil && |
|
|
|
pa.source == nil && |
|
|
|
pa.source == nil && |
|
|
|
len(pa.readers) == 0 && |
|
|
|
len(pa.readers) == 0 && |
|
|
|
len(pa.describeRequests) == 0 && |
|
|
|
len(pa.describeRequestsOnHold) == 0 && |
|
|
|
len(pa.setupPlayRequests) == 0 |
|
|
|
len(pa.setupPlayRequestsOnHold) == 0 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) hasStaticSource() bool { |
|
|
|
|
|
|
|
return strings.HasPrefix(pa.conf.Source, "rtsp://") || |
|
|
|
|
|
|
|
strings.HasPrefix(pa.conf.Source, "rtsps://") || |
|
|
|
|
|
|
|
strings.HasPrefix(pa.conf.Source, "rtmp://") || |
|
|
|
|
|
|
|
strings.HasPrefix(pa.conf.Source, "http://") || |
|
|
|
|
|
|
|
strings.HasPrefix(pa.conf.Source, "https://") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) isOnDemand() bool { |
|
|
|
|
|
|
|
return (pa.hasStaticSource() && pa.conf.SourceOnDemand) || pa.conf.RunOnDemand != "" |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) externalCmdEnv() externalcmd.Environment { |
|
|
|
func (pa *path) externalCmdEnv() externalcmd.Environment { |
|
|
@ -531,64 +581,76 @@ func (pa *path) externalCmdEnv() externalcmd.Environment { |
|
|
|
return env |
|
|
|
return env |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) onDemandStartSource() { |
|
|
|
func (pa *path) onDemandStaticSourceStart() { |
|
|
|
pa.onDemandReadyTimer.Stop() |
|
|
|
pa.staticSourceCreate() |
|
|
|
if pa.hasStaticSource() { |
|
|
|
|
|
|
|
pa.staticSourceCreate() |
|
|
|
pa.onDemandStaticSourceReadyTimer.Stop() |
|
|
|
pa.onDemandReadyTimer = time.NewTimer(time.Duration(pa.conf.SourceOnDemandStartTimeout)) |
|
|
|
pa.onDemandStaticSourceReadyTimer = time.NewTimer(time.Duration(pa.conf.SourceOnDemandStartTimeout)) |
|
|
|
} else { |
|
|
|
|
|
|
|
pa.log(logger.Info, "runOnDemand command started") |
|
|
|
|
|
|
|
pa.onDemandCmd = externalcmd.NewCmd( |
|
|
|
|
|
|
|
pa.externalCmdPool, |
|
|
|
|
|
|
|
pa.conf.RunOnDemand, |
|
|
|
|
|
|
|
pa.conf.RunOnDemandRestart, |
|
|
|
|
|
|
|
pa.externalCmdEnv(), |
|
|
|
|
|
|
|
func(co int) { |
|
|
|
|
|
|
|
pa.log(logger.Info, "runOnDemand command exited with code %d", co) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
pa.onDemandReadyTimer = time.NewTimer(time.Duration(pa.conf.RunOnDemandStartTimeout)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pa.onDemandState = pathOnDemandStateWaitingReady |
|
|
|
pa.onDemandStaticSourceState = pathOnDemandStateWaitingReady |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) onDemandScheduleClose() { |
|
|
|
func (pa *path) onDemandStaticSourceScheduleClose() { |
|
|
|
pa.onDemandCloseTimer.Stop() |
|
|
|
pa.onDemandStaticSourceCloseTimer.Stop() |
|
|
|
if pa.hasStaticSource() { |
|
|
|
pa.onDemandStaticSourceCloseTimer = time.NewTimer(time.Duration(pa.conf.SourceOnDemandCloseAfter)) |
|
|
|
pa.onDemandCloseTimer = time.NewTimer(time.Duration(pa.conf.SourceOnDemandCloseAfter)) |
|
|
|
|
|
|
|
} else { |
|
|
|
pa.onDemandStaticSourceState = pathOnDemandStateClosing |
|
|
|
pa.onDemandCloseTimer = time.NewTimer(time.Duration(pa.conf.RunOnDemandCloseAfter)) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) onDemandStaticSourceStop() { |
|
|
|
|
|
|
|
if pa.onDemandStaticSourceState == pathOnDemandStateClosing { |
|
|
|
|
|
|
|
pa.onDemandStaticSourceCloseTimer.Stop() |
|
|
|
|
|
|
|
pa.onDemandStaticSourceCloseTimer = newEmptyTimer() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pa.onDemandState = pathOnDemandStateClosing |
|
|
|
pa.onDemandStaticSourceState = pathOnDemandStateInitial |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pa.source.(sourceStatic).close() |
|
|
|
|
|
|
|
pa.source = nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) onDemandPublisherStart() { |
|
|
|
|
|
|
|
pa.log(logger.Info, "runOnDemand command started") |
|
|
|
|
|
|
|
pa.onDemandCmd = externalcmd.NewCmd( |
|
|
|
|
|
|
|
pa.externalCmdPool, |
|
|
|
|
|
|
|
pa.conf.RunOnDemand, |
|
|
|
|
|
|
|
pa.conf.RunOnDemandRestart, |
|
|
|
|
|
|
|
pa.externalCmdEnv(), |
|
|
|
|
|
|
|
func(co int) { |
|
|
|
|
|
|
|
pa.log(logger.Info, "runOnDemand command exited with code %d", co) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pa.onDemandPublisherReadyTimer.Stop() |
|
|
|
|
|
|
|
pa.onDemandPublisherReadyTimer = time.NewTimer(time.Duration(pa.conf.RunOnDemandStartTimeout)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pa.onDemandPublisherState = pathOnDemandStateWaitingReady |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) onDemandCloseSource() { |
|
|
|
func (pa *path) onDemandPublisherScheduleClose() { |
|
|
|
if pa.onDemandState == pathOnDemandStateClosing { |
|
|
|
pa.onDemandPublisherCloseTimer.Stop() |
|
|
|
pa.onDemandCloseTimer.Stop() |
|
|
|
pa.onDemandPublisherCloseTimer = time.NewTimer(time.Duration(pa.conf.RunOnDemandCloseAfter)) |
|
|
|
pa.onDemandCloseTimer = newEmptyTimer() |
|
|
|
|
|
|
|
|
|
|
|
pa.onDemandPublisherState = pathOnDemandStateClosing |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) onDemandPublisherStop() { |
|
|
|
|
|
|
|
if pa.onDemandPublisherState == pathOnDemandStateClosing { |
|
|
|
|
|
|
|
pa.onDemandPublisherCloseTimer.Stop() |
|
|
|
|
|
|
|
pa.onDemandPublisherCloseTimer = newEmptyTimer() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// set state before doPublisherRemove()
|
|
|
|
// set state before doPublisherRemove()
|
|
|
|
pa.onDemandState = pathOnDemandStateInitial |
|
|
|
pa.onDemandPublisherState = pathOnDemandStateInitial |
|
|
|
|
|
|
|
|
|
|
|
if pa.hasStaticSource() { |
|
|
|
if pa.source != nil { |
|
|
|
if pa.sourceReady { |
|
|
|
pa.source.(publisher).close() |
|
|
|
pa.sourceSetNotReady() |
|
|
|
pa.doPublisherRemove() |
|
|
|
} |
|
|
|
} |
|
|
|
pa.source.(sourceStatic).close() |
|
|
|
|
|
|
|
pa.source = nil |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
if pa.source != nil { |
|
|
|
|
|
|
|
pa.source.(publisher).close() |
|
|
|
|
|
|
|
pa.doPublisherRemove() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if pa.onDemandCmd != nil { |
|
|
|
if pa.onDemandCmd != nil { |
|
|
|
pa.onDemandCmd.Close() |
|
|
|
pa.onDemandCmd.Close() |
|
|
|
pa.onDemandCmd = nil |
|
|
|
pa.onDemandCmd = nil |
|
|
|
pa.log(logger.Info, "runOnDemand command stopped") |
|
|
|
pa.log(logger.Info, "runOnDemand command stopped") |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -596,29 +658,6 @@ func (pa *path) sourceSetReady(tracks gortsplib.Tracks) { |
|
|
|
pa.sourceReady = true |
|
|
|
pa.sourceReady = true |
|
|
|
pa.stream = newStream(tracks) |
|
|
|
pa.stream = newStream(tracks) |
|
|
|
|
|
|
|
|
|
|
|
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.handleReaderSetupPlayPost(req) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pa.setupPlayRequests = nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(pa.readers) > 0 { |
|
|
|
|
|
|
|
pa.onDemandState = pathOnDemandStateReady |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
pa.onDemandScheduleClose() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pa.parent.onPathSourceReady(pa) |
|
|
|
pa.parent.onPathSourceReady(pa) |
|
|
|
|
|
|
|
|
|
|
|
if pa.conf.RunOnReady != "" { |
|
|
|
if pa.conf.RunOnReady != "" { |
|
|
@ -669,6 +708,7 @@ func (pa *path) staticSourceCreate() { |
|
|
|
pa.readBufferCount, |
|
|
|
pa.readBufferCount, |
|
|
|
&pa.sourceStaticWg, |
|
|
|
&pa.sourceStaticWg, |
|
|
|
pa) |
|
|
|
pa) |
|
|
|
|
|
|
|
|
|
|
|
case strings.HasPrefix(pa.conf.Source, "rtmp://"): |
|
|
|
case strings.HasPrefix(pa.conf.Source, "rtmp://"): |
|
|
|
pa.source = newRTMPSource( |
|
|
|
pa.source = newRTMPSource( |
|
|
|
pa.ctx, |
|
|
|
pa.ctx, |
|
|
@ -677,6 +717,7 @@ func (pa *path) staticSourceCreate() { |
|
|
|
pa.writeTimeout, |
|
|
|
pa.writeTimeout, |
|
|
|
&pa.sourceStaticWg, |
|
|
|
&pa.sourceStaticWg, |
|
|
|
pa) |
|
|
|
pa) |
|
|
|
|
|
|
|
|
|
|
|
case strings.HasPrefix(pa.conf.Source, "http://") || |
|
|
|
case strings.HasPrefix(pa.conf.Source, "http://") || |
|
|
|
strings.HasPrefix(pa.conf.Source, "https://"): |
|
|
|
strings.HasPrefix(pa.conf.Source, "https://"): |
|
|
|
pa.source = newHLSSource( |
|
|
|
pa.source = newHLSSource( |
|
|
@ -700,8 +741,8 @@ func (pa *path) doReaderRemove(r reader) { |
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) doPublisherRemove() { |
|
|
|
func (pa *path) doPublisherRemove() { |
|
|
|
if pa.sourceReady { |
|
|
|
if pa.sourceReady { |
|
|
|
if pa.isOnDemand() && pa.onDemandState != pathOnDemandStateInitial { |
|
|
|
if pa.hasOnDemandPublisher() && pa.onDemandPublisherState != pathOnDemandStateInitial { |
|
|
|
pa.onDemandCloseSource() |
|
|
|
pa.onDemandPublisherStop() |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
pa.sourceSetNotReady() |
|
|
|
pa.sourceSetNotReady() |
|
|
|
} |
|
|
|
} |
|
|
@ -725,11 +766,19 @@ func (pa *path) handleDescribe(req pathDescribeReq) { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if pa.isOnDemand() { |
|
|
|
if pa.hasOnDemandStaticSource() { |
|
|
|
if pa.onDemandState == pathOnDemandStateInitial { |
|
|
|
if pa.onDemandStaticSourceState == pathOnDemandStateInitial { |
|
|
|
pa.onDemandStartSource() |
|
|
|
pa.onDemandStaticSourceStart() |
|
|
|
} |
|
|
|
} |
|
|
|
pa.describeRequests = append(pa.describeRequests, req) |
|
|
|
pa.describeRequestsOnHold = append(pa.describeRequestsOnHold, req) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if pa.hasOnDemandPublisher() { |
|
|
|
|
|
|
|
if pa.onDemandPublisherState == pathOnDemandStateInitial { |
|
|
|
|
|
|
|
pa.onDemandPublisherStart() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pa.describeRequestsOnHold = append(pa.describeRequestsOnHold, req) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -761,14 +810,14 @@ func (pa *path) handlePublisherRemove(req pathPublisherRemoveReq) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) handlePublisherAnnounce(req pathPublisherAnnounceReq) { |
|
|
|
func (pa *path) handlePublisherAnnounce(req pathPublisherAnnounceReq) { |
|
|
|
if pa.source != nil { |
|
|
|
if pa.conf.Source != "publisher" { |
|
|
|
if pa.conf.Source != "publisher" { |
|
|
|
req.res <- pathPublisherAnnounceRes{ |
|
|
|
req.res <- pathPublisherAnnounceRes{ |
|
|
|
err: fmt.Errorf("can't publish to path '%s' since 'source' is not 'publisher'", pa.name), |
|
|
|
err: fmt.Errorf("can't publish to path '%s' since 'source' is not 'publisher'", pa.name), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if pa.source != nil { |
|
|
|
if pa.conf.DisablePublisherOverride { |
|
|
|
if pa.conf.DisablePublisherOverride { |
|
|
|
req.res <- pathPublisherAnnounceRes{err: fmt.Errorf("someone is already publishing to path '%s'", pa.name)} |
|
|
|
req.res <- pathPublisherAnnounceRes{err: fmt.Errorf("someone is already publishing to path '%s'", pa.name)} |
|
|
|
return |
|
|
|
return |
|
|
@ -794,13 +843,36 @@ func (pa *path) handlePublisherRecord(req pathPublisherRecordReq) { |
|
|
|
|
|
|
|
|
|
|
|
pa.sourceSetReady(req.tracks) |
|
|
|
pa.sourceSetReady(req.tracks) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if pa.hasOnDemandPublisher() { |
|
|
|
|
|
|
|
pa.onDemandPublisherReadyTimer.Stop() |
|
|
|
|
|
|
|
pa.onDemandPublisherReadyTimer = newEmptyTimer() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, req := range pa.describeRequestsOnHold { |
|
|
|
|
|
|
|
req.res <- pathDescribeRes{ |
|
|
|
|
|
|
|
stream: pa.stream, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pa.describeRequestsOnHold = nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, req := range pa.setupPlayRequestsOnHold { |
|
|
|
|
|
|
|
pa.handleReaderSetupPlayPost(req) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pa.setupPlayRequestsOnHold = nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(pa.readers) > 0 { |
|
|
|
|
|
|
|
pa.onDemandPublisherState = pathOnDemandStateReady |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
pa.onDemandPublisherScheduleClose() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
req.res <- pathPublisherRecordRes{stream: pa.stream} |
|
|
|
req.res <- pathPublisherRecordRes{stream: pa.stream} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (pa *path) handlePublisherPause(req pathPublisherPauseReq) { |
|
|
|
func (pa *path) handlePublisherPause(req pathPublisherPauseReq) { |
|
|
|
if req.author == pa.source && pa.sourceReady { |
|
|
|
if req.author == pa.source && pa.sourceReady { |
|
|
|
if pa.isOnDemand() && pa.onDemandState != pathOnDemandStateInitial { |
|
|
|
if pa.hasOnDemandPublisher() && pa.onDemandPublisherState != pathOnDemandStateInitial { |
|
|
|
pa.onDemandCloseSource() |
|
|
|
pa.onDemandPublisherStop() |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
pa.sourceSetNotReady() |
|
|
|
pa.sourceSetNotReady() |
|
|
|
} |
|
|
|
} |
|
|
@ -814,10 +886,12 @@ func (pa *path) handleReaderRemove(req pathReaderRemoveReq) { |
|
|
|
} |
|
|
|
} |
|
|
|
close(req.res) |
|
|
|
close(req.res) |
|
|
|
|
|
|
|
|
|
|
|
if pa.isOnDemand() && |
|
|
|
if len(pa.readers) == 0 { |
|
|
|
len(pa.readers) == 0 && |
|
|
|
if pa.hasOnDemandStaticSource() && pa.onDemandStaticSourceState == pathOnDemandStateReady { |
|
|
|
pa.onDemandState == pathOnDemandStateReady { |
|
|
|
pa.onDemandStaticSourceScheduleClose() |
|
|
|
pa.onDemandScheduleClose() |
|
|
|
} else if pa.hasOnDemandPublisher() && pa.onDemandPublisherState == pathOnDemandStateReady { |
|
|
|
|
|
|
|
pa.onDemandPublisherScheduleClose() |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -827,11 +901,19 @@ func (pa *path) handleReaderSetupPlay(req pathReaderSetupPlayReq) { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if pa.isOnDemand() { |
|
|
|
if pa.hasOnDemandStaticSource() { |
|
|
|
if pa.onDemandState == pathOnDemandStateInitial { |
|
|
|
if pa.onDemandStaticSourceState == pathOnDemandStateInitial { |
|
|
|
pa.onDemandStartSource() |
|
|
|
pa.onDemandStaticSourceStart() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pa.setupPlayRequestsOnHold = append(pa.setupPlayRequestsOnHold, req) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if pa.hasOnDemandPublisher() { |
|
|
|
|
|
|
|
if pa.onDemandPublisherState == pathOnDemandStateInitial { |
|
|
|
|
|
|
|
pa.onDemandPublisherStart() |
|
|
|
} |
|
|
|
} |
|
|
|
pa.setupPlayRequests = append(pa.setupPlayRequests, req) |
|
|
|
pa.setupPlayRequestsOnHold = append(pa.setupPlayRequestsOnHold, req) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -841,10 +923,10 @@ func (pa *path) handleReaderSetupPlay(req pathReaderSetupPlayReq) { |
|
|
|
func (pa *path) handleReaderSetupPlayPost(req pathReaderSetupPlayReq) { |
|
|
|
func (pa *path) handleReaderSetupPlayPost(req pathReaderSetupPlayReq) { |
|
|
|
pa.readers[req.author] = pathReaderStatePrePlay |
|
|
|
pa.readers[req.author] = pathReaderStatePrePlay |
|
|
|
|
|
|
|
|
|
|
|
if pa.isOnDemand() && pa.onDemandState == pathOnDemandStateClosing { |
|
|
|
if pa.hasOnDemandPublisher() && pa.onDemandPublisherState == pathOnDemandStateClosing { |
|
|
|
pa.onDemandState = pathOnDemandStateReady |
|
|
|
pa.onDemandPublisherState = pathOnDemandStateReady |
|
|
|
pa.onDemandCloseTimer.Stop() |
|
|
|
pa.onDemandPublisherCloseTimer.Stop() |
|
|
|
pa.onDemandCloseTimer = newEmptyTimer() |
|
|
|
pa.onDemandPublisherCloseTimer = newEmptyTimer() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
req.res <- pathReaderSetupPlayRes{ |
|
|
|
req.res <- pathReaderSetupPlayRes{ |
|
|
|