Ready-to-use SRT / WebRTC / RTSP / RTMP / LL-HLS media server and media proxy that allows to read, publish, proxy, record and playback video and audio streams.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

211 lines
4.6 KiB

package core
import (
"context"
"fmt"
"strings"
"time"
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/logger"
)
const (
sourceStaticRetryPause = 5 * time.Second
)
type sourceStaticImpl interface {
Log(logger.Level, string, ...interface{})
run(context.Context) error
apiSourceDescribe() interface{}
}
type sourceStaticParent interface {
log(logger.Level, string, ...interface{})
sourceStaticSetReady(context.Context, pathSourceStaticSetReadyReq)
sourceStaticSetNotReady(context.Context, pathSourceStaticSetNotReadyReq)
}
// sourceStatic is a static source.
type sourceStatic struct {
ur string
protocol conf.SourceProtocol
anyPortEnable bool
fingerprint string
readTimeout conf.StringDuration
writeTimeout conf.StringDuration
readBufferCount int
parent sourceStaticParent
ctx context.Context
ctxCancel func()
impl sourceStaticImpl
running bool
done chan struct{}
chSourceStaticImplSetReady chan pathSourceStaticSetReadyReq
chSourceStaticImplSetNotReady chan pathSourceStaticSetNotReadyReq
}
func newSourceStatic(
ur string,
protocol conf.SourceProtocol,
anyPortEnable bool,
fingerprint string,
readTimeout conf.StringDuration,
writeTimeout conf.StringDuration,
readBufferCount int,
parent sourceStaticParent,
) *sourceStatic {
s := &sourceStatic{
ur: ur,
protocol: protocol,
anyPortEnable: anyPortEnable,
fingerprint: fingerprint,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
readBufferCount: readBufferCount,
parent: parent,
chSourceStaticImplSetReady: make(chan pathSourceStaticSetReadyReq),
chSourceStaticImplSetNotReady: make(chan pathSourceStaticSetNotReadyReq),
}
switch {
case strings.HasPrefix(s.ur, "rtsp://") ||
strings.HasPrefix(s.ur, "rtsps://"):
s.impl = newRTSPSource(
s.ur,
s.protocol,
s.anyPortEnable,
s.fingerprint,
s.readTimeout,
s.writeTimeout,
s.readBufferCount,
s)
case strings.HasPrefix(s.ur, "rtmp://"):
s.impl = newRTMPSource(
s.ur,
s.readTimeout,
s.writeTimeout,
s)
case strings.HasPrefix(s.ur, "http://") ||
strings.HasPrefix(s.ur, "https://"):
s.impl = newHLSSource(
s.ur,
s.fingerprint,
s)
}
return s
}
func (s *sourceStatic) close() {
if s.running {
s.stop()
}
}
func (s *sourceStatic) start() {
if s.running {
panic("should not happen")
}
s.running = true
s.impl.Log(logger.Info, "started")
s.ctx, s.ctxCancel = context.WithCancel(context.Background())
s.done = make(chan struct{})
go s.run()
}
func (s *sourceStatic) stop() {
if !s.running {
panic("should not happen")
}
s.running = false
s.impl.Log(logger.Info, "stopped")
s.ctxCancel()
// we must wait since s.ctx is not thread safe
<-s.done
}
func (s *sourceStatic) log(level logger.Level, format string, args ...interface{}) {
s.parent.log(level, format, args...)
}
func (s *sourceStatic) run() {
defer close(s.done)
outer:
for {
s.runInner()
select {
case <-time.After(sourceStaticRetryPause):
case <-s.ctx.Done():
break outer
}
}
s.ctxCancel()
}
func (s *sourceStatic) runInner() {
innerCtx, innerCtxCancel := context.WithCancel(context.Background())
implErr := make(chan error)
go func() {
implErr <- s.impl.run(innerCtx)
}()
for {
select {
case err := <-implErr:
innerCtxCancel()
s.impl.Log(logger.Info, "ERR: %v", err)
return
case req := <-s.chSourceStaticImplSetReady:
s.parent.sourceStaticSetReady(s.ctx, req)
case req := <-s.chSourceStaticImplSetNotReady:
s.parent.sourceStaticSetNotReady(s.ctx, req)
case <-s.ctx.Done():
innerCtxCancel()
<-implErr
return
}
}
}
// apiSourceDescribe implements source.
func (s *sourceStatic) apiSourceDescribe() interface{} {
return s.impl.apiSourceDescribe()
}
// sourceStaticImplSetReady is called by a sourceStaticImpl.
func (s *sourceStatic) sourceStaticImplSetReady(req pathSourceStaticSetReadyReq) pathSourceStaticSetReadyRes {
req.res = make(chan pathSourceStaticSetReadyRes)
select {
case s.chSourceStaticImplSetReady <- req:
return <-req.res
case <-s.ctx.Done():
return pathSourceStaticSetReadyRes{err: fmt.Errorf("terminated")}
}
}
// sourceStaticImplSetNotReady is called by a sourceStaticImpl.
func (s *sourceStatic) sourceStaticImplSetNotReady(req pathSourceStaticSetNotReadyReq) {
req.res = make(chan struct{})
select {
case s.chSourceStaticImplSetNotReady <- req:
<-req.res
case <-s.ctx.Done():
}
}