2021-07-24 13:55:42 +00:00
|
|
|
package core
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
import (
|
2021-05-10 19:32:59 +00:00
|
|
|
"context"
|
2020-10-19 20:17:48 +00:00
|
|
|
"fmt"
|
2021-04-24 16:25:19 +00:00
|
|
|
"net"
|
2021-12-08 19:50:09 +00:00
|
|
|
"strconv"
|
2020-10-19 20:17:48 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/aler9/gortsplib"
|
2021-02-09 21:33:50 +00:00
|
|
|
"github.com/aler9/gortsplib/pkg/base"
|
2022-06-04 23:36:29 +00:00
|
|
|
"github.com/aler9/gortsplib/pkg/url"
|
2020-10-19 20:17:48 +00:00
|
|
|
|
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-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
|
|
|
|
2021-12-22 18:13:56 +00:00
|
|
|
type authenticateFunc func(
|
2022-06-21 11:41:15 +00:00
|
|
|
pathIPs []fmt.Stringer,
|
2021-12-22 18:13:56 +00:00
|
|
|
pathUser conf.Credential,
|
|
|
|
pathPass conf.Credential,
|
|
|
|
) error
|
|
|
|
|
2021-07-31 18:46:06 +00:00
|
|
|
type pathErrNoOnePublishing struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
pathName string
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Error implements the error interface.
|
|
|
|
func (e pathErrNoOnePublishing) Error() string {
|
2022-01-14 22:42:41 +00:00
|
|
|
return fmt.Sprintf("no one is publishing to path '%s'", e.pathName)
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathErrAuthNotCritical struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
message string
|
|
|
|
response *base.Response
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Error implements the error interface.
|
|
|
|
func (pathErrAuthNotCritical) Error() string {
|
|
|
|
return "non-critical authentication error"
|
|
|
|
}
|
|
|
|
|
|
|
|
type pathErrAuthCritical struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
message string
|
|
|
|
response *base.Response
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Error implements the error interface.
|
|
|
|
func (pathErrAuthCritical) Error() string {
|
|
|
|
return "critical authentication error"
|
|
|
|
}
|
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
type pathParent interface {
|
2021-10-27 19:01:00 +00:00
|
|
|
log(logger.Level, string, ...interface{})
|
2022-08-04 19:07:17 +00:00
|
|
|
pathSourceReady(*path)
|
|
|
|
pathSourceNotReady(*path)
|
2021-10-27 19:01:00 +00:00
|
|
|
onPathClose(*path)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
type pathRTSPSession interface {
|
2022-08-05 12:39:07 +00:00
|
|
|
isRTSPSession()
|
2021-06-15 20:15:51 +00:00
|
|
|
}
|
|
|
|
|
2021-07-31 18:46:06 +00:00
|
|
|
type pathReaderState int
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
const (
|
2021-07-31 18:46:06 +00:00
|
|
|
pathReaderStatePrePlay pathReaderState = iota
|
|
|
|
pathReaderStatePlay
|
2020-10-19 20:17:48 +00:00
|
|
|
)
|
|
|
|
|
2021-08-03 20:40:47 +00:00
|
|
|
type pathOnDemandState int
|
2020-11-01 15:51:12 +00:00
|
|
|
|
|
|
|
const (
|
2021-08-03 20:40:47 +00:00
|
|
|
pathOnDemandStateInitial pathOnDemandState = iota
|
|
|
|
pathOnDemandStateWaitingReady
|
|
|
|
pathOnDemandStateReady
|
|
|
|
pathOnDemandStateClosing
|
2020-11-01 15:51:12 +00:00
|
|
|
)
|
|
|
|
|
2021-08-10 16:34:10 +00:00
|
|
|
type pathSourceStaticSetReadyRes struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
stream *stream
|
|
|
|
err error
|
2021-08-10 16:34:10 +00:00
|
|
|
}
|
|
|
|
|
2021-07-31 18:46:06 +00:00
|
|
|
type pathSourceStaticSetReadyReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
tracks gortsplib.Tracks
|
|
|
|
res chan pathSourceStaticSetReadyRes
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathSourceStaticSetNotReadyReq struct {
|
2022-08-04 18:28:10 +00:00
|
|
|
res chan struct{}
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathReaderRemoveReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
author reader
|
|
|
|
res chan struct{}
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathPublisherRemoveReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
author publisher
|
|
|
|
res chan struct{}
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathDescribeRes struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
path *path
|
|
|
|
stream *stream
|
|
|
|
redirect string
|
|
|
|
err error
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathDescribeReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
pathName string
|
2022-06-04 23:36:29 +00:00
|
|
|
url *url.URL
|
2022-01-14 22:42:41 +00:00
|
|
|
authenticate authenticateFunc
|
|
|
|
res chan pathDescribeRes
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathReaderSetupPlayRes struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
path *path
|
|
|
|
stream *stream
|
|
|
|
err error
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathReaderSetupPlayReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
author reader
|
|
|
|
pathName string
|
|
|
|
authenticate authenticateFunc
|
|
|
|
res chan pathReaderSetupPlayRes
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathPublisherAnnounceRes struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
path *path
|
|
|
|
err error
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathPublisherAnnounceReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
author publisher
|
|
|
|
pathName string
|
|
|
|
authenticate authenticateFunc
|
|
|
|
res chan pathPublisherAnnounceRes
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathReaderPlayReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
author reader
|
|
|
|
res chan struct{}
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathPublisherRecordRes struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
stream *stream
|
|
|
|
err error
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathPublisherRecordReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
author publisher
|
|
|
|
tracks gortsplib.Tracks
|
|
|
|
res chan pathPublisherRecordRes
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathReaderPauseReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
author reader
|
|
|
|
res chan struct{}
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathPublisherPauseReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
author publisher
|
|
|
|
res chan struct{}
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
|
2021-11-05 16:53:24 +00:00
|
|
|
type pathAPIPathsListItem struct {
|
|
|
|
ConfName string `json:"confName"`
|
|
|
|
Conf *conf.PathConf `json:"conf"`
|
|
|
|
Source interface{} `json:"source"`
|
|
|
|
SourceReady bool `json:"sourceReady"`
|
|
|
|
Readers []interface{} `json:"readers"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type pathAPIPathsListData struct {
|
|
|
|
Items map[string]pathAPIPathsListItem `json:"items"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type pathAPIPathsListRes struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
data *pathAPIPathsListData
|
|
|
|
paths map[string]*path
|
|
|
|
err error
|
2021-11-05 16:53:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathAPIPathsListReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
res chan pathAPIPathsListRes
|
2021-11-05 16:53:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pathAPIPathsListSubReq struct {
|
2022-01-14 22:42:41 +00:00
|
|
|
data *pathAPIPathsListData
|
|
|
|
res chan struct{}
|
2021-11-05 16:53:24 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
type path struct {
|
2021-04-24 16:25:19 +00:00
|
|
|
rtspAddress string
|
2021-09-26 21:06:40 +00:00
|
|
|
readTimeout conf.StringDuration
|
|
|
|
writeTimeout conf.StringDuration
|
2021-02-18 22:26:45 +00:00
|
|
|
readBufferCount int
|
2021-01-10 11:55:53 +00:00
|
|
|
confName string
|
|
|
|
conf *conf.PathConf
|
|
|
|
name string
|
2021-12-08 19:50:09 +00:00
|
|
|
matches []string
|
2021-01-10 11:55:53 +00:00
|
|
|
wg *sync.WaitGroup
|
2021-12-22 18:13:56 +00:00
|
|
|
externalCmdPool *externalcmd.Pool
|
2021-07-24 13:55:42 +00:00
|
|
|
parent pathParent
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
ctx context.Context
|
|
|
|
ctxCancel func()
|
|
|
|
source source
|
|
|
|
sourceReady bool
|
|
|
|
stream *stream
|
|
|
|
readers map[reader]pathReaderState
|
2022-08-14 09:24:05 +00:00
|
|
|
describeRequestsOnHold []pathDescribeReq
|
2022-05-03 12:38:45 +00:00
|
|
|
setupPlayRequestsOnHold []pathReaderSetupPlayReq
|
|
|
|
onDemandCmd *externalcmd.Cmd
|
|
|
|
onReadyCmd *externalcmd.Cmd
|
|
|
|
onDemandStaticSourceState pathOnDemandState
|
|
|
|
onDemandStaticSourceReadyTimer *time.Timer
|
|
|
|
onDemandStaticSourceCloseTimer *time.Timer
|
|
|
|
onDemandPublisherState pathOnDemandState
|
|
|
|
onDemandPublisherReadyTimer *time.Timer
|
|
|
|
onDemandPublisherCloseTimer *time.Timer
|
2020-10-19 20:17:48 +00:00
|
|
|
|
|
|
|
// in
|
2022-08-04 19:07:17 +00:00
|
|
|
chSourceStaticSetReady chan pathSourceStaticSetReadyReq
|
|
|
|
chSourceStaticSetNotReady chan pathSourceStaticSetNotReadyReq
|
|
|
|
chDescribe chan pathDescribeReq
|
|
|
|
chPublisherRemove chan pathPublisherRemoveReq
|
|
|
|
chPublisherAnnounce chan pathPublisherAnnounceReq
|
|
|
|
chPublisherRecord chan pathPublisherRecordReq
|
|
|
|
chPublisherPause chan pathPublisherPauseReq
|
|
|
|
chReaderRemove chan pathReaderRemoveReq
|
|
|
|
chReaderSetupPlay chan pathReaderSetupPlayReq
|
|
|
|
chReaderPlay chan pathReaderPlayReq
|
|
|
|
chReaderPause chan pathReaderPauseReq
|
|
|
|
chAPIPathsList chan pathAPIPathsListSubReq
|
2021-07-24 13:55:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newPath(
|
|
|
|
parentCtx context.Context,
|
2021-04-24 16:25:19 +00:00
|
|
|
rtspAddress string,
|
2021-09-26 21:06:40 +00:00
|
|
|
readTimeout conf.StringDuration,
|
|
|
|
writeTimeout conf.StringDuration,
|
2021-02-18 22:26:45 +00:00
|
|
|
readBufferCount int,
|
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,
|
2021-12-08 19:50:09 +00:00
|
|
|
matches []string,
|
2020-10-24 17:55:47 +00:00
|
|
|
wg *sync.WaitGroup,
|
2021-12-22 18:13:56 +00:00
|
|
|
externalCmdPool *externalcmd.Pool,
|
2022-04-07 10:50:35 +00:00
|
|
|
parent pathParent,
|
|
|
|
) *path {
|
2021-07-24 13:55:42 +00:00
|
|
|
ctx, ctxCancel := context.WithCancel(parentCtx)
|
2021-05-10 19:32:59 +00:00
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
pa := &path{
|
2022-05-03 12:38:45 +00:00
|
|
|
rtspAddress: rtspAddress,
|
|
|
|
readTimeout: readTimeout,
|
|
|
|
writeTimeout: writeTimeout,
|
|
|
|
readBufferCount: readBufferCount,
|
|
|
|
confName: confName,
|
|
|
|
conf: conf,
|
|
|
|
name: name,
|
|
|
|
matches: matches,
|
|
|
|
wg: wg,
|
|
|
|
externalCmdPool: externalCmdPool,
|
|
|
|
parent: parent,
|
|
|
|
ctx: ctx,
|
|
|
|
ctxCancel: ctxCancel,
|
|
|
|
readers: make(map[reader]pathReaderState),
|
|
|
|
onDemandStaticSourceReadyTimer: newEmptyTimer(),
|
|
|
|
onDemandStaticSourceCloseTimer: newEmptyTimer(),
|
|
|
|
onDemandPublisherReadyTimer: newEmptyTimer(),
|
|
|
|
onDemandPublisherCloseTimer: newEmptyTimer(),
|
2022-08-04 19:07:17 +00:00
|
|
|
chSourceStaticSetReady: make(chan pathSourceStaticSetReadyReq),
|
|
|
|
chSourceStaticSetNotReady: make(chan pathSourceStaticSetNotReadyReq),
|
|
|
|
chDescribe: make(chan pathDescribeReq),
|
|
|
|
chPublisherRemove: make(chan pathPublisherRemoveReq),
|
|
|
|
chPublisherAnnounce: make(chan pathPublisherAnnounceReq),
|
|
|
|
chPublisherRecord: make(chan pathPublisherRecordReq),
|
|
|
|
chPublisherPause: make(chan pathPublisherPauseReq),
|
|
|
|
chReaderRemove: make(chan pathReaderRemoveReq),
|
|
|
|
chReaderSetupPlay: make(chan pathReaderSetupPlayReq),
|
|
|
|
chReaderPlay: make(chan pathReaderPlayReq),
|
|
|
|
chReaderPause: make(chan pathReaderPauseReq),
|
|
|
|
chAPIPathsList: make(chan pathAPIPathsListSubReq),
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2022-02-19 21:15:37 +00:00
|
|
|
pa.log(logger.Debug, "created")
|
2021-07-04 16:13:49 +00:00
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
pa.wg.Add(1)
|
|
|
|
go pa.run()
|
2021-08-01 14:58:46 +00:00
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
return pa
|
|
|
|
}
|
|
|
|
|
2021-10-27 19:01:00 +00:00
|
|
|
func (pa *path) close() {
|
2021-05-10 19:32:59 +00:00
|
|
|
pa.ctxCancel()
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 11:30:25 +00:00
|
|
|
// Log is the main logging function.
|
2021-10-27 19:01:00 +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
|
|
|
}
|
|
|
|
|
2021-07-31 16:32:00 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
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 != ""
|
|
|
|
}
|
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
func (pa *path) run() {
|
2020-10-19 20:17:48 +00:00
|
|
|
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{}
|
2022-08-04 18:28:10 +00:00
|
|
|
} else if pa.hasStaticSource() {
|
|
|
|
pa.source = newSourceStatic(
|
|
|
|
pa.conf.Source,
|
|
|
|
pa.conf.SourceProtocol,
|
|
|
|
pa.conf.SourceAnyPortEnable,
|
|
|
|
pa.conf.SourceFingerprint,
|
|
|
|
pa.readTimeout,
|
|
|
|
pa.writeTimeout,
|
|
|
|
pa.readBufferCount,
|
|
|
|
pa)
|
|
|
|
|
|
|
|
if !pa.conf.SourceOnDemand {
|
|
|
|
pa.source.(*sourceStatic).start()
|
|
|
|
}
|
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 != "" {
|
2021-10-27 19:01:00 +00:00
|
|
|
pa.log(logger.Info, "runOnInit command started")
|
2021-12-21 11:39:32 +00:00
|
|
|
onInitCmd = externalcmd.NewCmd(
|
|
|
|
pa.externalCmdPool,
|
2021-12-08 19:50:09 +00:00
|
|
|
pa.conf.RunOnInit,
|
|
|
|
pa.conf.RunOnInitRestart,
|
2021-12-08 20:23:45 +00:00
|
|
|
pa.externalCmdEnv(),
|
|
|
|
func(co int) {
|
|
|
|
pa.log(logger.Info, "runOnInit command exited with code %d", co)
|
|
|
|
})
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-11-03 21:41:23 +00:00
|
|
|
err := func() error {
|
|
|
|
for {
|
|
|
|
select {
|
2022-05-03 12:38:45 +00:00
|
|
|
case <-pa.onDemandStaticSourceReadyTimer.C:
|
2022-08-14 09:24:05 +00:00
|
|
|
for _, req := range pa.describeRequestsOnHold {
|
2022-05-03 12:38:45 +00:00
|
|
|
req.res <- pathDescribeRes{err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
|
|
|
|
}
|
2022-08-14 09:24:05 +00:00
|
|
|
pa.describeRequestsOnHold = nil
|
2022-05-03 12:38:45 +00:00
|
|
|
|
|
|
|
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:
|
2022-08-14 09:24:05 +00:00
|
|
|
for _, req := range pa.describeRequestsOnHold {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathDescribeRes{err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
|
2021-11-03 21:41:23 +00:00
|
|
|
}
|
2022-08-14 09:24:05 +00:00
|
|
|
pa.describeRequestsOnHold = nil
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
for _, req := range pa.setupPlayRequestsOnHold {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathReaderSetupPlayRes{err: fmt.Errorf("source of path '%s' has timed out", pa.name)}
|
2021-11-03 21:41:23 +00:00
|
|
|
}
|
2022-05-03 12:38:45 +00:00
|
|
|
pa.setupPlayRequestsOnHold = nil
|
2020-11-01 15:51:12 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
pa.onDemandPublisherStop()
|
2020-11-01 15:51:12 +00:00
|
|
|
|
2021-11-03 21:41:23 +00:00
|
|
|
if pa.shouldClose() {
|
|
|
|
return fmt.Errorf("not in use")
|
|
|
|
}
|
2020-11-01 15:51:12 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
case <-pa.onDemandPublisherCloseTimer.C:
|
|
|
|
pa.onDemandPublisherStop()
|
2020-11-01 15:51:12 +00:00
|
|
|
|
2021-11-03 21:41:23 +00:00
|
|
|
if pa.shouldClose() {
|
|
|
|
return fmt.Errorf("not in use")
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chSourceStaticSetReady:
|
2022-08-04 18:28:10 +00:00
|
|
|
pa.sourceSetReady(req.tracks)
|
2022-05-03 12:38:45 +00:00
|
|
|
|
2022-08-04 18:28:10 +00:00
|
|
|
if pa.hasOnDemandStaticSource() {
|
|
|
|
pa.onDemandStaticSourceReadyTimer.Stop()
|
|
|
|
pa.onDemandStaticSourceReadyTimer = newEmptyTimer()
|
2022-05-03 12:38:45 +00:00
|
|
|
|
2022-08-04 18:28:10 +00:00
|
|
|
pa.onDemandStaticSourceScheduleClose()
|
2022-05-28 11:54:04 +00:00
|
|
|
|
2022-08-14 09:24:05 +00:00
|
|
|
for _, req := range pa.describeRequestsOnHold {
|
2022-08-04 18:28:10 +00:00
|
|
|
req.res <- pathDescribeRes{
|
|
|
|
stream: pa.stream,
|
2022-05-03 12:38:45 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-14 09:24:05 +00:00
|
|
|
pa.describeRequestsOnHold = nil
|
2022-05-03 12:38:45 +00:00
|
|
|
|
2022-08-04 18:28:10 +00:00
|
|
|
for _, req := range pa.setupPlayRequestsOnHold {
|
|
|
|
pa.handleReaderSetupPlayPost(req)
|
|
|
|
}
|
|
|
|
pa.setupPlayRequestsOnHold = nil
|
2021-08-10 16:34:10 +00:00
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2022-08-04 18:28:10 +00:00
|
|
|
req.res <- pathSourceStaticSetReadyRes{stream: pa.stream}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chSourceStaticSetNotReady:
|
2022-08-04 18:28:10 +00:00
|
|
|
pa.sourceSetNotReady()
|
|
|
|
|
|
|
|
if pa.hasOnDemandStaticSource() && pa.onDemandStaticSourceState != pathOnDemandStateInitial {
|
|
|
|
pa.onDemandStaticSourceStop()
|
2021-11-03 21:41:23 +00:00
|
|
|
}
|
2022-08-04 18:28:10 +00:00
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
close(req.res)
|
2021-11-03 21:41:23 +00:00
|
|
|
|
|
|
|
if pa.shouldClose() {
|
|
|
|
return fmt.Errorf("not in use")
|
|
|
|
}
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chDescribe:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handleDescribe(req)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-11-03 21:41:23 +00:00
|
|
|
if pa.shouldClose() {
|
|
|
|
return fmt.Errorf("not in use")
|
|
|
|
}
|
2021-11-03 21:28:50 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chPublisherRemove:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handlePublisherRemove(req)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-11-03 21:41:23 +00:00
|
|
|
if pa.shouldClose() {
|
|
|
|
return fmt.Errorf("not in use")
|
|
|
|
}
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chPublisherAnnounce:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handlePublisherAnnounce(req)
|
2021-03-10 14:06:45 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chPublisherRecord:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handlePublisherRecord(req)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chPublisherPause:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handlePublisherPause(req)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-11-03 21:41:23 +00:00
|
|
|
if pa.shouldClose() {
|
|
|
|
return fmt.Errorf("not in use")
|
|
|
|
}
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chReaderRemove:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handleReaderRemove(req)
|
2021-03-19 21:52:10 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chReaderSetupPlay:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handleReaderSetupPlay(req)
|
2020-10-24 17:55:47 +00:00
|
|
|
|
2021-11-03 21:41:23 +00:00
|
|
|
if pa.shouldClose() {
|
|
|
|
return fmt.Errorf("not in use")
|
|
|
|
}
|
2021-11-03 21:28:50 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chReaderPlay:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handleReaderPlay(req)
|
2020-10-24 17:55:47 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chReaderPause:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handleReaderPause(req)
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
case req := <-pa.chAPIPathsList:
|
2021-11-03 21:41:23 +00:00
|
|
|
pa.handleAPIPathsList(req)
|
2021-07-04 16:13:49 +00:00
|
|
|
|
2021-11-03 21:41:23 +00:00
|
|
|
case <-pa.ctx.Done():
|
|
|
|
return fmt.Errorf("terminated")
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2021-11-03 21:41:23 +00:00
|
|
|
}()
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-05-10 19:32:59 +00:00
|
|
|
pa.ctxCancel()
|
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
pa.onDemandStaticSourceReadyTimer.Stop()
|
|
|
|
pa.onDemandStaticSourceCloseTimer.Stop()
|
|
|
|
pa.onDemandPublisherReadyTimer.Stop()
|
|
|
|
pa.onDemandPublisherCloseTimer.Stop()
|
2020-11-01 15:51:12 +00:00
|
|
|
|
2020-11-21 12:34:27 +00:00
|
|
|
if onInitCmd != nil {
|
|
|
|
onInitCmd.Close()
|
2021-10-27 19:01:00 +00:00
|
|
|
pa.log(logger.Info, "runOnInit command stopped")
|
2020-10-24 17:55:47 +00:00
|
|
|
}
|
|
|
|
|
2022-08-14 09:24:05 +00:00
|
|
|
for _, req := range pa.describeRequestsOnHold {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathDescribeRes{err: fmt.Errorf("terminated")}
|
2021-01-31 11:23:54 +00:00
|
|
|
}
|
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
for _, req := range pa.setupPlayRequestsOnHold {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathReaderSetupPlayRes{err: fmt.Errorf("terminated")}
|
2021-01-31 11:23:54 +00:00
|
|
|
}
|
|
|
|
|
2022-07-24 11:05:15 +00:00
|
|
|
if pa.sourceReady {
|
|
|
|
pa.sourceSetNotReady()
|
|
|
|
}
|
2021-08-10 16:34:10 +00:00
|
|
|
|
2021-07-31 18:46:06 +00:00
|
|
|
if pa.source != nil {
|
2022-07-30 19:51:38 +00:00
|
|
|
if source, ok := pa.source.(*sourceStatic); ok {
|
2021-10-27 19:01:00 +00:00
|
|
|
source.close()
|
2021-07-31 18:46:06 +00:00
|
|
|
} else if source, ok := pa.source.(publisher); ok {
|
2021-10-27 19:01:00 +00:00
|
|
|
source.close()
|
2020-10-24 17:55:47 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-10-03 14:05:37 +00:00
|
|
|
if pa.onDemandCmd != nil {
|
|
|
|
pa.onDemandCmd.Close()
|
2021-10-27 19:01:00 +00:00
|
|
|
pa.log(logger.Info, "runOnDemand command stopped")
|
2021-10-03 14:05:37 +00:00
|
|
|
}
|
|
|
|
|
2022-02-19 21:15:37 +00:00
|
|
|
pa.log(logger.Debug, "destroyed (%v)", err)
|
2021-11-03 21:41:23 +00:00
|
|
|
|
2021-10-27 19:01:00 +00:00
|
|
|
pa.parent.onPathClose(pa)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-11-03 21:41:23 +00:00
|
|
|
func (pa *path) shouldClose() bool {
|
|
|
|
return pa.conf.Regexp != nil &&
|
|
|
|
pa.source == nil &&
|
|
|
|
len(pa.readers) == 0 &&
|
2022-08-14 09:24:05 +00:00
|
|
|
len(pa.describeRequestsOnHold) == 0 &&
|
2022-05-03 12:38:45 +00:00
|
|
|
len(pa.setupPlayRequestsOnHold) == 0
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
2021-12-08 19:50:09 +00:00
|
|
|
func (pa *path) externalCmdEnv() externalcmd.Environment {
|
|
|
|
_, port, _ := net.SplitHostPort(pa.rtspAddress)
|
|
|
|
env := externalcmd.Environment{
|
|
|
|
"RTSP_PATH": pa.name,
|
|
|
|
"RTSP_PORT": port,
|
|
|
|
}
|
|
|
|
|
2021-12-08 20:14:03 +00:00
|
|
|
if len(pa.matches) > 1 {
|
|
|
|
for i, ma := range pa.matches[1:] {
|
2022-01-25 13:38:57 +00:00
|
|
|
env["G"+strconv.FormatInt(int64(i+1), 10)] = ma
|
2021-12-08 20:14:03 +00:00
|
|
|
}
|
2021-12-08 19:50:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return env
|
|
|
|
}
|
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
func (pa *path) onDemandStaticSourceStart() {
|
2022-08-04 18:28:10 +00:00
|
|
|
pa.source.(*sourceStatic).start()
|
2022-05-03 12:38:45 +00:00
|
|
|
|
|
|
|
pa.onDemandStaticSourceReadyTimer.Stop()
|
|
|
|
pa.onDemandStaticSourceReadyTimer = time.NewTimer(time.Duration(pa.conf.SourceOnDemandStartTimeout))
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
pa.onDemandStaticSourceState = pathOnDemandStateWaitingReady
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
func (pa *path) onDemandStaticSourceScheduleClose() {
|
|
|
|
pa.onDemandStaticSourceCloseTimer.Stop()
|
|
|
|
pa.onDemandStaticSourceCloseTimer = time.NewTimer(time.Duration(pa.conf.SourceOnDemandCloseAfter))
|
|
|
|
|
|
|
|
pa.onDemandStaticSourceState = pathOnDemandStateClosing
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *path) onDemandStaticSourceStop() {
|
|
|
|
if pa.onDemandStaticSourceState == pathOnDemandStateClosing {
|
|
|
|
pa.onDemandStaticSourceCloseTimer.Stop()
|
|
|
|
pa.onDemandStaticSourceCloseTimer = newEmptyTimer()
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
pa.onDemandStaticSourceState = pathOnDemandStateInitial
|
|
|
|
|
2022-08-04 18:28:10 +00:00
|
|
|
pa.source.(*sourceStatic).stop()
|
2022-05-03 12:38:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
func (pa *path) onDemandPublisherScheduleClose() {
|
|
|
|
pa.onDemandPublisherCloseTimer.Stop()
|
|
|
|
pa.onDemandPublisherCloseTimer = time.NewTimer(time.Duration(pa.conf.RunOnDemandCloseAfter))
|
|
|
|
|
|
|
|
pa.onDemandPublisherState = pathOnDemandStateClosing
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *path) onDemandPublisherStop() {
|
|
|
|
if pa.onDemandPublisherState == pathOnDemandStateClosing {
|
|
|
|
pa.onDemandPublisherCloseTimer.Stop()
|
|
|
|
pa.onDemandPublisherCloseTimer = newEmptyTimer()
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// set state before doPublisherRemove()
|
2022-05-03 12:38:45 +00:00
|
|
|
pa.onDemandPublisherState = pathOnDemandStateInitial
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
if pa.source != nil {
|
|
|
|
pa.source.(publisher).close()
|
|
|
|
pa.doPublisherRemove()
|
|
|
|
}
|
2021-10-03 14:05:37 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
if pa.onDemandCmd != nil {
|
|
|
|
pa.onDemandCmd.Close()
|
|
|
|
pa.onDemandCmd = nil
|
|
|
|
pa.log(logger.Info, "runOnDemand command stopped")
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-10 16:34:10 +00:00
|
|
|
func (pa *path) sourceSetReady(tracks gortsplib.Tracks) {
|
2021-08-03 20:40:47 +00:00
|
|
|
pa.sourceReady = true
|
2021-08-10 16:34:10 +00:00
|
|
|
pa.stream = newStream(tracks)
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-01-19 21:50:32 +00:00
|
|
|
if pa.conf.RunOnReady != "" {
|
|
|
|
pa.log(logger.Info, "runOnReady command started")
|
|
|
|
pa.onReadyCmd = externalcmd.NewCmd(
|
|
|
|
pa.externalCmdPool,
|
|
|
|
pa.conf.RunOnReady,
|
|
|
|
pa.conf.RunOnReadyRestart,
|
|
|
|
pa.externalCmdEnv(),
|
|
|
|
func(co int) {
|
|
|
|
pa.log(logger.Info, "runOnReady command exited with code %d", co)
|
|
|
|
})
|
|
|
|
}
|
2022-07-24 11:05:15 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
pa.parent.pathSourceReady(pa)
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *path) sourceSetNotReady() {
|
2022-08-04 19:07:17 +00:00
|
|
|
pa.parent.pathSourceNotReady(pa)
|
2022-07-24 11:05:15 +00:00
|
|
|
|
2021-10-03 13:58:24 +00:00
|
|
|
for r := range pa.readers {
|
|
|
|
pa.doReaderRemove(r)
|
2021-10-27 19:01:00 +00:00
|
|
|
r.close()
|
2021-10-03 13:58:24 +00:00
|
|
|
}
|
|
|
|
|
2022-01-19 21:50:32 +00:00
|
|
|
if pa.onReadyCmd != nil {
|
|
|
|
pa.onReadyCmd.Close()
|
|
|
|
pa.onReadyCmd = nil
|
2022-01-19 21:19:38 +00:00
|
|
|
pa.log(logger.Info, "runOnReady command stopped")
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
2021-08-10 16:34:10 +00:00
|
|
|
pa.sourceReady = false
|
2022-01-19 21:19:38 +00:00
|
|
|
|
|
|
|
if pa.stream != nil {
|
|
|
|
pa.stream.close()
|
|
|
|
pa.stream = nil
|
|
|
|
}
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *path) doReaderRemove(r reader) {
|
2021-07-31 18:46:06 +00:00
|
|
|
state := pa.readers[r]
|
2020-11-01 15:51:12 +00:00
|
|
|
|
2021-07-31 18:46:06 +00:00
|
|
|
if state == pathReaderStatePlay {
|
2021-08-10 16:34:10 +00:00
|
|
|
pa.stream.readerRemove(r)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-07-31 18:46:06 +00:00
|
|
|
delete(pa.readers, r)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 20:40:47 +00:00
|
|
|
func (pa *path) doPublisherRemove() {
|
|
|
|
if pa.sourceReady {
|
2022-05-03 12:38:45 +00:00
|
|
|
if pa.hasOnDemandPublisher() && pa.onDemandPublisherState != pathOnDemandStateInitial {
|
|
|
|
pa.onDemandPublisherStop()
|
2021-08-10 16:34:10 +00:00
|
|
|
} else {
|
|
|
|
pa.sourceSetNotReady()
|
|
|
|
}
|
2020-11-01 15:51:12 +00:00
|
|
|
}
|
|
|
|
|
2021-07-31 18:46:06 +00:00
|
|
|
pa.source = nil
|
|
|
|
}
|
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handleDescribe(req pathDescribeReq) {
|
2020-11-01 15:51:12 +00:00
|
|
|
if _, ok := pa.source.(*sourceRedirect); ok {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathDescribeRes{
|
|
|
|
redirect: pa.conf.SourceRedirect,
|
2021-06-15 20:15:51 +00:00
|
|
|
}
|
2020-11-01 15:51:12 +00:00
|
|
|
return
|
|
|
|
}
|
2020-10-24 17:55:47 +00:00
|
|
|
|
2021-08-03 20:40:47 +00:00
|
|
|
if pa.sourceReady {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathDescribeRes{
|
|
|
|
stream: pa.stream,
|
2021-06-15 20:15:51 +00:00
|
|
|
}
|
2020-11-01 15:51:12 +00:00
|
|
|
return
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
2020-11-01 15:51:12 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
if pa.hasOnDemandStaticSource() {
|
|
|
|
if pa.onDemandStaticSourceState == pathOnDemandStateInitial {
|
|
|
|
pa.onDemandStaticSourceStart()
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
2022-08-14 09:24:05 +00:00
|
|
|
pa.describeRequestsOnHold = append(pa.describeRequestsOnHold, req)
|
2022-05-03 12:38:45 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if pa.hasOnDemandPublisher() {
|
|
|
|
if pa.onDemandPublisherState == pathOnDemandStateInitial {
|
|
|
|
pa.onDemandPublisherStart()
|
|
|
|
}
|
2022-08-14 09:24:05 +00:00
|
|
|
pa.describeRequestsOnHold = append(pa.describeRequestsOnHold, req)
|
2020-11-01 15:51:12 +00:00
|
|
|
return
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
2020-11-01 15:51:12 +00:00
|
|
|
|
2021-08-03 20:40:47 +00:00
|
|
|
if pa.conf.Fallback != "" {
|
|
|
|
fallbackURL := func() string {
|
|
|
|
if strings.HasPrefix(pa.conf.Fallback, "/") {
|
2022-06-04 23:36:29 +00:00
|
|
|
ur := url.URL{
|
2022-01-14 22:42:41 +00:00
|
|
|
Scheme: req.url.Scheme,
|
|
|
|
User: req.url.User,
|
|
|
|
Host: req.url.Host,
|
2021-08-03 20:40:47 +00:00
|
|
|
Path: pa.conf.Fallback,
|
2021-02-09 21:33:50 +00:00
|
|
|
}
|
2021-08-03 20:40:47 +00:00
|
|
|
return ur.String()
|
|
|
|
}
|
|
|
|
return pa.conf.Fallback
|
|
|
|
}()
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathDescribeRes{redirect: fallbackURL}
|
2020-11-01 15:51:12 +00:00
|
|
|
return
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathDescribeRes{err: pathErrNoOnePublishing{pathName: pa.name}}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handlePublisherRemove(req pathPublisherRemoveReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
if pa.source == req.author {
|
2021-08-03 20:40:47 +00:00
|
|
|
pa.doPublisherRemove()
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
2022-01-14 22:42:41 +00:00
|
|
|
close(req.res)
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handlePublisherAnnounce(req pathPublisherAnnounceReq) {
|
2022-05-03 12:38:45 +00:00
|
|
|
if pa.conf.Source != "publisher" {
|
|
|
|
req.res <- pathPublisherAnnounceRes{
|
|
|
|
err: fmt.Errorf("can't publish to path '%s' since 'source' is not 'publisher'", pa.name),
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
2022-05-03 12:38:45 +00:00
|
|
|
return
|
|
|
|
}
|
2021-08-01 14:37:37 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
if pa.source != nil {
|
2021-08-01 14:37:37 +00:00
|
|
|
if pa.conf.DisablePublisherOverride {
|
2022-05-03 09:45:47 +00:00
|
|
|
req.res <- pathPublisherAnnounceRes{err: fmt.Errorf("someone is already publishing to path '%s'", pa.name)}
|
2021-08-01 14:37:37 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-10-27 19:01:00 +00:00
|
|
|
pa.log(logger.Info, "closing existing publisher")
|
|
|
|
pa.source.(publisher).close()
|
2021-08-03 20:40:47 +00:00
|
|
|
pa.doPublisherRemove()
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
pa.source = req.author
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathPublisherAnnounceRes{path: pa}
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handlePublisherRecord(req pathPublisherRecordReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
if pa.source != req.author {
|
|
|
|
req.res <- pathPublisherRecordRes{err: fmt.Errorf("publisher is not assigned to this path anymore")}
|
2021-08-01 14:37:37 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
req.author.onPublisherAccepted(len(req.tracks))
|
2021-08-01 14:37:37 +00:00
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
pa.sourceSetReady(req.tracks)
|
2021-08-01 14:37:37 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
if pa.hasOnDemandPublisher() {
|
|
|
|
pa.onDemandPublisherReadyTimer.Stop()
|
|
|
|
pa.onDemandPublisherReadyTimer = newEmptyTimer()
|
|
|
|
|
2022-06-24 16:04:45 +00:00
|
|
|
pa.onDemandPublisherScheduleClose()
|
|
|
|
|
2022-08-14 09:24:05 +00:00
|
|
|
for _, req := range pa.describeRequestsOnHold {
|
2022-05-03 12:38:45 +00:00
|
|
|
req.res <- pathDescribeRes{
|
|
|
|
stream: pa.stream,
|
|
|
|
}
|
|
|
|
}
|
2022-08-14 09:24:05 +00:00
|
|
|
pa.describeRequestsOnHold = nil
|
2022-05-03 12:38:45 +00:00
|
|
|
|
|
|
|
for _, req := range pa.setupPlayRequestsOnHold {
|
|
|
|
pa.handleReaderSetupPlayPost(req)
|
|
|
|
}
|
|
|
|
pa.setupPlayRequestsOnHold = nil
|
|
|
|
}
|
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathPublisherRecordRes{stream: pa.stream}
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handlePublisherPause(req pathPublisherPauseReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
if req.author == pa.source && pa.sourceReady {
|
2022-05-03 12:38:45 +00:00
|
|
|
if pa.hasOnDemandPublisher() && pa.onDemandPublisherState != pathOnDemandStateInitial {
|
|
|
|
pa.onDemandPublisherStop()
|
2021-08-10 16:34:10 +00:00
|
|
|
} else {
|
|
|
|
pa.sourceSetNotReady()
|
|
|
|
}
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
2022-01-14 22:42:41 +00:00
|
|
|
close(req.res)
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handleReaderRemove(req pathReaderRemoveReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
if _, ok := pa.readers[req.author]; ok {
|
|
|
|
pa.doReaderRemove(req.author)
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
2022-01-14 22:42:41 +00:00
|
|
|
close(req.res)
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
if len(pa.readers) == 0 {
|
2022-05-20 21:41:30 +00:00
|
|
|
if pa.hasOnDemandStaticSource() {
|
|
|
|
if pa.onDemandStaticSourceState == pathOnDemandStateReady {
|
|
|
|
pa.onDemandStaticSourceScheduleClose()
|
|
|
|
}
|
|
|
|
} else if pa.hasOnDemandPublisher() {
|
|
|
|
if pa.onDemandPublisherState == pathOnDemandStateReady {
|
|
|
|
pa.onDemandPublisherScheduleClose()
|
|
|
|
}
|
2022-05-03 12:38:45 +00:00
|
|
|
}
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
2021-08-01 14:37:37 +00:00
|
|
|
}
|
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handleReaderSetupPlay(req pathReaderSetupPlayReq) {
|
2021-08-03 20:40:47 +00:00
|
|
|
if pa.sourceReady {
|
2021-08-11 10:45:53 +00:00
|
|
|
pa.handleReaderSetupPlayPost(req)
|
2021-03-10 14:06:45 +00:00
|
|
|
return
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
2021-03-10 14:06:45 +00:00
|
|
|
|
2022-05-03 12:38:45 +00:00
|
|
|
if pa.hasOnDemandStaticSource() {
|
|
|
|
if pa.onDemandStaticSourceState == pathOnDemandStateInitial {
|
|
|
|
pa.onDemandStaticSourceStart()
|
|
|
|
}
|
|
|
|
pa.setupPlayRequestsOnHold = append(pa.setupPlayRequestsOnHold, req)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if pa.hasOnDemandPublisher() {
|
|
|
|
if pa.onDemandPublisherState == pathOnDemandStateInitial {
|
|
|
|
pa.onDemandPublisherStart()
|
2021-08-03 20:40:47 +00:00
|
|
|
}
|
2022-05-03 12:38:45 +00:00
|
|
|
pa.setupPlayRequestsOnHold = append(pa.setupPlayRequestsOnHold, req)
|
2021-03-10 14:06:45 +00:00
|
|
|
return
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2021-08-03 20:40:47 +00:00
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathReaderSetupPlayRes{err: pathErrNoOnePublishing{pathName: pa.name}}
|
2021-03-10 14:06:45 +00:00
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handleReaderSetupPlayPost(req pathReaderSetupPlayReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
pa.readers[req.author] = pathReaderStatePrePlay
|
2020-11-01 15:51:12 +00:00
|
|
|
|
2022-05-20 21:41:30 +00:00
|
|
|
if pa.hasOnDemandStaticSource() {
|
|
|
|
if pa.onDemandStaticSourceState == pathOnDemandStateClosing {
|
|
|
|
pa.onDemandStaticSourceState = pathOnDemandStateReady
|
|
|
|
pa.onDemandStaticSourceCloseTimer.Stop()
|
|
|
|
pa.onDemandStaticSourceCloseTimer = newEmptyTimer()
|
|
|
|
}
|
|
|
|
} else if pa.hasOnDemandPublisher() {
|
|
|
|
if pa.onDemandPublisherState == pathOnDemandStateClosing {
|
|
|
|
pa.onDemandPublisherState = pathOnDemandStateReady
|
|
|
|
pa.onDemandPublisherCloseTimer.Stop()
|
|
|
|
pa.onDemandPublisherCloseTimer = newEmptyTimer()
|
|
|
|
}
|
2020-10-24 17:55:47 +00:00
|
|
|
}
|
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res <- pathReaderSetupPlayRes{
|
|
|
|
path: pa,
|
|
|
|
stream: pa.stream,
|
2021-05-16 14:25:22 +00:00
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handleReaderPlay(req pathReaderPlayReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
pa.readers[req.author] = pathReaderStatePlay
|
2020-10-24 17:55:47 +00:00
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
pa.stream.readerAdd(req.author)
|
2021-06-15 20:15:51 +00:00
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
req.author.onReaderAccepted()
|
2021-07-29 14:56:40 +00:00
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
close(req.res)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-08-11 10:45:53 +00:00
|
|
|
func (pa *path) handleReaderPause(req pathReaderPauseReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
if state, ok := pa.readers[req.author]; ok && state == pathReaderStatePlay {
|
|
|
|
pa.readers[req.author] = pathReaderStatePrePlay
|
|
|
|
pa.stream.readerRemove(req.author)
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
2022-01-14 22:42:41 +00:00
|
|
|
close(req.res)
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
2020-11-07 21:47:10 +00:00
|
|
|
|
2021-11-05 16:53:24 +00:00
|
|
|
func (pa *path) handleAPIPathsList(req pathAPIPathsListSubReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.data.Items[pa.name] = pathAPIPathsListItem{
|
2021-08-11 10:45:53 +00:00
|
|
|
ConfName: pa.confName,
|
|
|
|
Conf: pa.conf,
|
|
|
|
Source: func() interface{} {
|
|
|
|
if pa.source == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-08-04 19:07:17 +00:00
|
|
|
return pa.source.apiSourceDescribe()
|
2021-08-11 10:45:53 +00:00
|
|
|
}(),
|
|
|
|
SourceReady: pa.sourceReady,
|
|
|
|
Readers: func() []interface{} {
|
|
|
|
ret := []interface{}{}
|
|
|
|
for r := range pa.readers {
|
2022-08-04 19:07:17 +00:00
|
|
|
ret = append(ret, r.apiReaderDescribe())
|
2021-08-11 10:45:53 +00:00
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}(),
|
|
|
|
}
|
2022-01-14 22:42:41 +00:00
|
|
|
close(req.res)
|
2021-08-11 10:45:53 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// sourceStaticSetReady is called by sourceStatic.
|
|
|
|
func (pa *path) sourceStaticSetReady(sourceStaticCtx context.Context, req pathSourceStaticSetReadyReq) {
|
2021-05-10 19:32:59 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chSourceStaticSetReady <- req:
|
2022-08-04 18:28:10 +00:00
|
|
|
|
2021-05-10 19:32:59 +00:00
|
|
|
case <-pa.ctx.Done():
|
2022-08-04 18:28:10 +00:00
|
|
|
req.res <- pathSourceStaticSetReadyRes{err: fmt.Errorf("terminated")}
|
|
|
|
|
|
|
|
// this avoids:
|
|
|
|
// - invalid requests sent after the source has been terminated
|
|
|
|
// - freezes caused by <-done inside stop()
|
|
|
|
case <-sourceStaticCtx.Done():
|
|
|
|
req.res <- pathSourceStaticSetReadyRes{err: fmt.Errorf("terminated")}
|
2021-05-10 19:32:59 +00:00
|
|
|
}
|
2021-03-22 20:40:07 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// sourceStaticSetNotReady is called by sourceStatic.
|
|
|
|
func (pa *path) sourceStaticSetNotReady(sourceStaticCtx context.Context, req pathSourceStaticSetNotReadyReq) {
|
2021-05-10 19:32:59 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chSourceStaticSetNotReady <- req:
|
2022-08-04 18:28:10 +00:00
|
|
|
|
2021-05-10 19:32:59 +00:00
|
|
|
case <-pa.ctx.Done():
|
2022-08-04 18:28:10 +00:00
|
|
|
close(req.res)
|
|
|
|
|
|
|
|
// this avoids:
|
|
|
|
// - invalid requests sent after the source has been terminated
|
|
|
|
// - freezes caused by <-done inside stop()
|
|
|
|
case <-sourceStaticCtx.Done():
|
|
|
|
close(req.res)
|
2021-05-10 19:32:59 +00:00
|
|
|
}
|
2020-11-05 11:30:25 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// describe is called by a reader or publisher through pathManager.
|
|
|
|
func (pa *path) describe(req pathDescribeReq) pathDescribeRes {
|
2021-05-10 19:32:59 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chDescribe <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
return <-req.res
|
2021-05-10 19:32:59 +00:00
|
|
|
case <-pa.ctx.Done():
|
2022-01-14 22:42:41 +00:00
|
|
|
return pathDescribeRes{err: fmt.Errorf("terminated")}
|
2021-05-10 19:32:59 +00:00
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// publisherRemove is called by a publisher.
|
|
|
|
func (pa *path) publisherRemove(req pathPublisherRemoveReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res = make(chan struct{})
|
2021-05-10 19:32:59 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chPublisherRemove <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
<-req.res
|
2021-05-10 19:32:59 +00:00
|
|
|
case <-pa.ctx.Done():
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// publisherAnnounce is called by a publisher through pathManager.
|
|
|
|
func (pa *path) publisherAnnounce(req pathPublisherAnnounceReq) pathPublisherAnnounceRes {
|
2021-05-10 19:32:59 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chPublisherAnnounce <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
return <-req.res
|
2021-05-10 19:32:59 +00:00
|
|
|
case <-pa.ctx.Done():
|
2022-01-14 22:42:41 +00:00
|
|
|
return pathPublisherAnnounceRes{err: fmt.Errorf("terminated")}
|
2021-05-10 19:32:59 +00:00
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// publisherRecord is called by a publisher.
|
|
|
|
func (pa *path) publisherRecord(req pathPublisherRecordReq) pathPublisherRecordRes {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res = make(chan pathPublisherRecordRes)
|
2021-07-31 18:46:06 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chPublisherRecord <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
return <-req.res
|
2021-07-31 18:46:06 +00:00
|
|
|
case <-pa.ctx.Done():
|
2022-01-14 22:42:41 +00:00
|
|
|
return pathPublisherRecordRes{err: fmt.Errorf("terminated")}
|
2021-07-31 18:46:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// publisherPause is called by a publisher.
|
|
|
|
func (pa *path) publisherPause(req pathPublisherPauseReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res = make(chan struct{})
|
2021-05-10 19:32:59 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chPublisherPause <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
<-req.res
|
2021-05-10 19:32:59 +00:00
|
|
|
case <-pa.ctx.Done():
|
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// readerRemove is called by a reader.
|
|
|
|
func (pa *path) readerRemove(req pathReaderRemoveReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res = make(chan struct{})
|
2021-05-10 19:32:59 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chReaderRemove <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
<-req.res
|
2021-07-31 18:46:06 +00:00
|
|
|
case <-pa.ctx.Done():
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// readerSetupPlay is called by a reader through pathManager.
|
|
|
|
func (pa *path) readerSetupPlay(req pathReaderSetupPlayReq) pathReaderSetupPlayRes {
|
2021-07-31 18:46:06 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chReaderSetupPlay <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
return <-req.res
|
2021-05-10 19:32:59 +00:00
|
|
|
case <-pa.ctx.Done():
|
2022-01-14 22:42:41 +00:00
|
|
|
return pathReaderSetupPlayRes{err: fmt.Errorf("terminated")}
|
2021-05-10 19:32:59 +00:00
|
|
|
}
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// readerPlay is called by a reader.
|
|
|
|
func (pa *path) readerPlay(req pathReaderPlayReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res = make(chan struct{})
|
2021-05-10 19:32:59 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chReaderPlay <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
<-req.res
|
2021-05-10 19:32:59 +00:00
|
|
|
case <-pa.ctx.Done():
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// readerPause is called by a reader.
|
|
|
|
func (pa *path) readerPause(req pathReaderPauseReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res = make(chan struct{})
|
2021-05-10 19:32:59 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chReaderPause <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
<-req.res
|
2021-05-10 19:32:59 +00:00
|
|
|
case <-pa.ctx.Done():
|
|
|
|
}
|
2020-11-07 21:47:10 +00:00
|
|
|
}
|
2021-03-23 19:37:43 +00:00
|
|
|
|
2022-08-04 19:07:17 +00:00
|
|
|
// apiPathsList is called by api.
|
|
|
|
func (pa *path) apiPathsList(req pathAPIPathsListSubReq) {
|
2022-01-14 22:42:41 +00:00
|
|
|
req.res = make(chan struct{})
|
2021-07-04 16:13:49 +00:00
|
|
|
select {
|
2022-08-04 19:07:17 +00:00
|
|
|
case pa.chAPIPathsList <- req:
|
2022-01-14 22:42:41 +00:00
|
|
|
<-req.res
|
2021-08-12 09:48:47 +00:00
|
|
|
|
2021-07-04 16:13:49 +00:00
|
|
|
case <-pa.ctx.Done():
|
|
|
|
}
|
|
|
|
}
|