golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
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.
264 lines
6.4 KiB
264 lines
6.4 KiB
package core |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
"strings" |
|
"time" |
|
|
|
"github.com/bluenviron/mediamtx/internal/conf" |
|
"github.com/bluenviron/mediamtx/internal/defs" |
|
"github.com/bluenviron/mediamtx/internal/logger" |
|
hlssource "github.com/bluenviron/mediamtx/internal/staticsources/hls" |
|
rpicamerasource "github.com/bluenviron/mediamtx/internal/staticsources/rpicamera" |
|
rtmpsource "github.com/bluenviron/mediamtx/internal/staticsources/rtmp" |
|
rtspsource "github.com/bluenviron/mediamtx/internal/staticsources/rtsp" |
|
srtsource "github.com/bluenviron/mediamtx/internal/staticsources/srt" |
|
udpsource "github.com/bluenviron/mediamtx/internal/staticsources/udp" |
|
webrtcsource "github.com/bluenviron/mediamtx/internal/staticsources/webrtc" |
|
) |
|
|
|
const ( |
|
staticSourceHandlerRetryPause = 5 * time.Second |
|
) |
|
|
|
type staticSourceHandlerParent interface { |
|
logger.Writer |
|
staticSourceHandlerSetReady(context.Context, defs.PathSourceStaticSetReadyReq) |
|
staticSourceHandlerSetNotReady(context.Context, defs.PathSourceStaticSetNotReadyReq) |
|
} |
|
|
|
// staticSourceHandler is a static source handler. |
|
type staticSourceHandler struct { |
|
conf *conf.Path |
|
logLevel conf.LogLevel |
|
readTimeout conf.StringDuration |
|
writeTimeout conf.StringDuration |
|
writeQueueSize int |
|
resolvedSource string |
|
parent staticSourceHandlerParent |
|
|
|
ctx context.Context |
|
ctxCancel func() |
|
instance defs.StaticSource |
|
running bool |
|
|
|
// in |
|
chReloadConf chan *conf.Path |
|
chInstanceSetReady chan defs.PathSourceStaticSetReadyReq |
|
chInstanceSetNotReady chan defs.PathSourceStaticSetNotReadyReq |
|
|
|
// out |
|
done chan struct{} |
|
} |
|
|
|
func (s *staticSourceHandler) initialize() { |
|
s.chReloadConf = make(chan *conf.Path) |
|
s.chInstanceSetReady = make(chan defs.PathSourceStaticSetReadyReq) |
|
s.chInstanceSetNotReady = make(chan defs.PathSourceStaticSetNotReadyReq) |
|
|
|
switch { |
|
case strings.HasPrefix(s.resolvedSource, "rtsp://") || |
|
strings.HasPrefix(s.resolvedSource, "rtsps://"): |
|
s.instance = &rtspsource.Source{ |
|
ResolvedSource: s.resolvedSource, |
|
ReadTimeout: s.readTimeout, |
|
WriteTimeout: s.writeTimeout, |
|
WriteQueueSize: s.writeQueueSize, |
|
Parent: s, |
|
} |
|
|
|
case strings.HasPrefix(s.resolvedSource, "rtmp://") || |
|
strings.HasPrefix(s.resolvedSource, "rtmps://"): |
|
s.instance = &rtmpsource.Source{ |
|
ResolvedSource: s.resolvedSource, |
|
ReadTimeout: s.readTimeout, |
|
WriteTimeout: s.writeTimeout, |
|
Parent: s, |
|
} |
|
|
|
case strings.HasPrefix(s.resolvedSource, "http://") || |
|
strings.HasPrefix(s.resolvedSource, "https://"): |
|
s.instance = &hlssource.Source{ |
|
ResolvedSource: s.resolvedSource, |
|
ReadTimeout: s.readTimeout, |
|
Parent: s, |
|
} |
|
|
|
case strings.HasPrefix(s.resolvedSource, "udp://"): |
|
s.instance = &udpsource.Source{ |
|
ResolvedSource: s.resolvedSource, |
|
ReadTimeout: s.readTimeout, |
|
Parent: s, |
|
} |
|
|
|
case strings.HasPrefix(s.resolvedSource, "srt://"): |
|
s.instance = &srtsource.Source{ |
|
ResolvedSource: s.resolvedSource, |
|
ReadTimeout: s.readTimeout, |
|
Parent: s, |
|
} |
|
|
|
case strings.HasPrefix(s.resolvedSource, "whep://") || |
|
strings.HasPrefix(s.resolvedSource, "wheps://"): |
|
s.instance = &webrtcsource.Source{ |
|
ResolvedSource: s.resolvedSource, |
|
ReadTimeout: s.readTimeout, |
|
Parent: s, |
|
} |
|
|
|
case s.resolvedSource == "rpiCamera": |
|
s.instance = &rpicamerasource.Source{ |
|
LogLevel: s.logLevel, |
|
Parent: s, |
|
} |
|
} |
|
} |
|
|
|
func (s *staticSourceHandler) close(reason string) { |
|
s.stop(reason) |
|
} |
|
|
|
func (s *staticSourceHandler) start(onDemand bool) { |
|
if s.running { |
|
panic("should not happen") |
|
} |
|
|
|
s.running = true |
|
s.instance.Log(logger.Info, "started%s", |
|
func() string { |
|
if onDemand { |
|
return " on demand" |
|
} |
|
return "" |
|
}()) |
|
|
|
s.ctx, s.ctxCancel = context.WithCancel(context.Background()) |
|
s.done = make(chan struct{}) |
|
|
|
go s.run() |
|
} |
|
|
|
func (s *staticSourceHandler) stop(reason string) { |
|
if !s.running { |
|
panic("should not happen") |
|
} |
|
|
|
s.running = false |
|
s.instance.Log(logger.Info, "stopped: %s", reason) |
|
|
|
s.ctxCancel() |
|
|
|
// we must wait since s.ctx is not thread safe |
|
<-s.done |
|
} |
|
|
|
// Log implements logger.Writer. |
|
func (s *staticSourceHandler) Log(level logger.Level, format string, args ...interface{}) { |
|
s.parent.Log(level, format, args...) |
|
} |
|
|
|
func (s *staticSourceHandler) run() { |
|
defer close(s.done) |
|
|
|
var runCtx context.Context |
|
var runCtxCancel func() |
|
runErr := make(chan error) |
|
runReloadConf := make(chan *conf.Path) |
|
|
|
recreate := func() { |
|
runCtx, runCtxCancel = context.WithCancel(context.Background()) |
|
go func() { |
|
runErr <- s.instance.Run(defs.StaticSourceRunParams{ |
|
Context: runCtx, |
|
Conf: s.conf, |
|
ReloadConf: runReloadConf, |
|
}) |
|
}() |
|
} |
|
|
|
recreate() |
|
|
|
recreating := false |
|
recreateTimer := emptyTimer() |
|
|
|
for { |
|
select { |
|
case err := <-runErr: |
|
runCtxCancel() |
|
s.instance.Log(logger.Error, err.Error()) |
|
recreating = true |
|
recreateTimer = time.NewTimer(staticSourceHandlerRetryPause) |
|
|
|
case req := <-s.chInstanceSetReady: |
|
s.parent.staticSourceHandlerSetReady(s.ctx, req) |
|
|
|
case req := <-s.chInstanceSetNotReady: |
|
s.parent.staticSourceHandlerSetNotReady(s.ctx, req) |
|
|
|
case newConf := <-s.chReloadConf: |
|
s.conf = newConf |
|
if !recreating { |
|
cReloadConf := runReloadConf |
|
cInnerCtx := runCtx |
|
go func() { |
|
select { |
|
case cReloadConf <- newConf: |
|
case <-cInnerCtx.Done(): |
|
} |
|
}() |
|
} |
|
|
|
case <-recreateTimer.C: |
|
recreate() |
|
recreating = false |
|
|
|
case <-s.ctx.Done(): |
|
if !recreating { |
|
runCtxCancel() |
|
<-runErr |
|
} |
|
return |
|
} |
|
} |
|
} |
|
|
|
func (s *staticSourceHandler) reloadConf(newConf *conf.Path) { |
|
select { |
|
case s.chReloadConf <- newConf: |
|
case <-s.ctx.Done(): |
|
} |
|
} |
|
|
|
// APISourceDescribe instanceements source. |
|
func (s *staticSourceHandler) APISourceDescribe() defs.APIPathSourceOrReader { |
|
return s.instance.APISourceDescribe() |
|
} |
|
|
|
// setReady is called by a staticSource. |
|
func (s *staticSourceHandler) SetReady(req defs.PathSourceStaticSetReadyReq) defs.PathSourceStaticSetReadyRes { |
|
req.Res = make(chan defs.PathSourceStaticSetReadyRes) |
|
select { |
|
case s.chInstanceSetReady <- req: |
|
res := <-req.Res |
|
|
|
if res.Err == nil { |
|
s.instance.Log(logger.Info, "ready: %s", defs.MediasInfo(req.Desc.Medias)) |
|
} |
|
|
|
return res |
|
|
|
case <-s.ctx.Done(): |
|
return defs.PathSourceStaticSetReadyRes{Err: fmt.Errorf("terminated")} |
|
} |
|
} |
|
|
|
// setNotReady is called by a staticSource. |
|
func (s *staticSourceHandler) SetNotReady(req defs.PathSourceStaticSetNotReadyReq) { |
|
req.Res = make(chan struct{}) |
|
select { |
|
case s.chInstanceSetNotReady <- req: |
|
<-req.Res |
|
case <-s.ctx.Done(): |
|
} |
|
}
|
|
|