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.
 
 
 
 
 
 

227 lines
5.2 KiB

package core
import (
"context"
"fmt"
"strings"
"time"
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/rpicamera"
)
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 {
parent sourceStaticParent
ctx context.Context
ctxCancel func()
impl sourceStaticImpl
running bool
done chan struct{}
chSourceStaticImplSetReady chan pathSourceStaticSetReadyReq
chSourceStaticImplSetNotReady chan pathSourceStaticSetNotReadyReq
}
func newSourceStatic(
conf *conf.PathConf,
readTimeout conf.StringDuration,
writeTimeout conf.StringDuration,
readBufferCount int,
parent sourceStaticParent,
) *sourceStatic {
s := &sourceStatic{
parent: parent,
chSourceStaticImplSetReady: make(chan pathSourceStaticSetReadyReq),
chSourceStaticImplSetNotReady: make(chan pathSourceStaticSetNotReadyReq),
}
switch {
case strings.HasPrefix(conf.Source, "rtsp://") ||
strings.HasPrefix(conf.Source, "rtsps://"):
s.impl = newRTSPSource(
conf.Source,
conf.SourceProtocol,
conf.SourceAnyPortEnable,
conf.SourceFingerprint,
readTimeout,
writeTimeout,
readBufferCount,
s)
case strings.HasPrefix(conf.Source, "rtmp://") ||
strings.HasPrefix(conf.Source, "rtmps://"):
s.impl = newRTMPSource(
conf.Source,
conf.SourceFingerprint,
readTimeout,
writeTimeout,
s)
case strings.HasPrefix(conf.Source, "http://") ||
strings.HasPrefix(conf.Source, "https://"):
s.impl = newHLSSource(
conf.Source,
conf.SourceFingerprint,
s)
case conf.Source == "rpiCamera":
s.impl = newRPICameraSource(
rpicamera.Params{
CameraID: conf.RPICameraCamID,
Width: conf.RPICameraWidth,
Height: conf.RPICameraHeight,
HFlip: conf.RPICameraHFlip,
VFlip: conf.RPICameraVFlip,
Brightness: conf.RPICameraBrightness,
Contrast: conf.RPICameraContrast,
Saturation: conf.RPICameraSaturation,
Sharpness: conf.RPICameraSharpness,
Exposure: conf.RPICameraExposure,
AWB: conf.RPICameraAWB,
Denoise: conf.RPICameraDenoise,
Shutter: conf.RPICameraShutter,
Metering: conf.RPICameraMetering,
Gain: conf.RPICameraGain,
EV: conf.RPICameraEV,
ROI: conf.RPICameraROI,
TuningFile: conf.RPICameraTuningFile,
Mode: conf.RPICameraMode,
FPS: conf.RPICameraFPS,
IDRPeriod: conf.RPICameraIDRPeriod,
Bitrate: conf.RPICameraBitrate,
Profile: conf.RPICameraProfile,
Level: conf.RPICameraLevel,
},
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():
}
}