Browse Source

move most components into internal/core

in this way coverage can be computed correctly.
pull/474/head
aler9 4 years ago committed by Alessandro Ros
parent
commit
9062dbf883
  1. 33
      Makefile
  2. 495
      internal/core/core.go
  3. 16
      internal/core/core_test.go
  4. 160
      internal/core/hls_converter.go
  5. 68
      internal/core/hls_server.go
  6. 10
      internal/core/hls_server_test.go
  7. 22
      internal/core/ip.go
  8. 30
      internal/core/metrics.go
  9. 265
      internal/core/path.go
  10. 116
      internal/core/path_manager.go
  11. 23
      internal/core/pprof.go
  12. 125
      internal/core/read_publisher.go
  13. 116
      internal/core/rtmp_conn.go
  14. 55
      internal/core/rtmp_server.go
  15. 22
      internal/core/rtmp_server_test.go
  16. 57
      internal/core/rtmp_source.go
  17. 6
      internal/core/rtmp_source_test.go
  18. 66
      internal/core/rtsp_conn.go
  19. 81
      internal/core/rtsp_server.go
  20. 58
      internal/core/rtsp_server_test.go
  21. 98
      internal/core/rtsp_session.go
  22. 58
      internal/core/rtsp_source.go
  23. 12
      internal/core/rtsp_source_test.go
  24. 26
      internal/core/source.go
  25. 13
      internal/core/stats.go
  26. 2
      internal/hls/multiaccessbuffer.go
  27. 2
      internal/hls/multiaccessbuffer_test.go
  28. 39
      internal/hls/tsfile.go
  29. 45
      internal/path/readersmap.go
  30. 142
      internal/readpublisher/readpublisher.go
  31. 2
      internal/rtmp/client.go
  32. 31
      internal/source/source.go
  33. 493
      main.go

33
Makefile

@ -49,7 +49,6 @@ RUN apk add --no-cache make docker-cli ffmpeg gcc musl-dev @@ -49,7 +49,6 @@ RUN apk add --no-cache make docker-cli ffmpeg gcc musl-dev
WORKDIR /s
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
endef
export DOCKERFILE_TEST
@ -58,26 +57,44 @@ test: @@ -58,26 +57,44 @@ test:
docker run --rm \
--network=host \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v $(PWD):/s \
temp \
make test-nodocker OPTS="-race -coverprofile=coverage-internal.txt"
make test-nodocker COVERAGE=1
test32:
echo "$$DOCKERFILE_TEST" | docker build -q . -f - -t temp --build-arg ARCH=i386
docker run --rm \
--network=host \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v $(PWD):/s \
temp \
make test-nodocker
make test-nodocker COVERAGE=0
test-internal:
go test -v $(OPTS) ./internal/...
ifeq ($(COVERAGE),1)
TEST_INTERNAL_OPTS=-race -coverprofile=coverage-internal.txt
TEST_CORE_OPTS=-race -coverprofile=coverage-core.txt
endif
test-root:
test-internal:
go test -v $(TEST_INTERNAL_OPTS) \
./internal/aac \
./internal/conf \
./internal/confenv \
./internal/confwatcher \
./internal/externalcmd \
./internal/h264 \
./internal/hls \
./internal/logger \
./internal/rlimit \
./internal/rtcpsenderset \
./internal/rtmp
test-core:
$(foreach IMG,$(shell echo testimages/*/ | xargs -n1 basename), \
docker build -q testimages/$(IMG) -t rtsp-simple-server-test-$(IMG)$(NL))
go test -v $(OPTS) .
go test -v $(TEST_CORE_OPTS) ./internal/core
test-nodocker: test-internal test-root
test-nodocker: test-internal test-core
lint:
docker run --rm -v $(PWD):/app -w /app \

495
internal/core/core.go

@ -0,0 +1,495 @@ @@ -0,0 +1,495 @@
package core
import (
"context"
"fmt"
"os"
"reflect"
"sync/atomic"
"gopkg.in/alecthomas/kingpin.v2"
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/confwatcher"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/rlimit"
)
var version = "v0.0.0"
// Core is an instance of rtsp-simple-server.
type Core struct {
ctx context.Context
ctxCancel func()
confPath string
conf *conf.Conf
confFound bool
stats *stats
logger *logger.Logger
metrics *metrics
pprof *pprof
pathMan *pathManager
rtspServerPlain *rtspServer
rtspServerTLS *rtspServer
rtmpServer *rtmpServer
hlsServer *hlsServer
confWatcher *confwatcher.ConfWatcher
// out
done chan struct{}
}
// New allocates a core.
func New(args []string) (*Core, bool) {
k := kingpin.New("rtsp-simple-server",
"rtsp-simple-server "+version+"\n\nRTSP server.")
argVersion := k.Flag("version", "print version").Bool()
argConfPath := k.Arg("confpath", "path to a config file. The default is rtsp-simple-server.yml.").Default("rtsp-simple-server.yml").String()
kingpin.MustParse(k.Parse(args))
if *argVersion {
fmt.Println(version)
os.Exit(0)
}
// on Linux, try to raise the number of file descriptors that can be opened
// to allow the maximum possible number of clients
// do not check for errors
rlimit.Raise()
ctx, ctxCancel := context.WithCancel(context.Background())
p := &Core{
ctx: ctx,
ctxCancel: ctxCancel,
confPath: *argConfPath,
done: make(chan struct{}),
}
var err error
p.conf, p.confFound, err = conf.Load(p.confPath)
if err != nil {
fmt.Printf("ERR: %s\n", err)
return nil, false
}
err = p.createResources(true)
if err != nil {
p.Log(logger.Info, "ERR: %s", err)
p.closeResources(nil)
return nil, false
}
if p.confFound {
p.confWatcher, err = confwatcher.New(p.confPath)
if err != nil {
p.Log(logger.Info, "ERR: %s", err)
p.closeResources(nil)
return nil, false
}
}
go p.run()
return p, true
}
func (p *Core) close() {
p.ctxCancel()
<-p.done
}
// Wait waits for the Core to exit.
func (p *Core) Wait() {
<-p.done
}
// Log is the main logging function.
func (p *Core) Log(level logger.Level, format string, args ...interface{}) {
countPublishers := atomic.LoadInt64(p.stats.CountPublishers)
countReaders := atomic.LoadInt64(p.stats.CountReaders)
p.logger.Log(level, "[%d/%d] "+format, append([]interface{}{
countPublishers, countReaders,
}, args...)...)
}
func (p *Core) run() {
defer close(p.done)
confChanged := func() chan struct{} {
if p.confWatcher != nil {
return p.confWatcher.Watch()
}
return make(chan struct{})
}()
outer:
for {
select {
case <-confChanged:
err := p.reloadConf()
if err != nil {
p.Log(logger.Info, "ERR: %s", err)
break outer
}
case <-p.ctx.Done():
break outer
}
}
p.ctxCancel()
p.closeResources(nil)
if p.confWatcher != nil {
p.confWatcher.Close()
}
}
func (p *Core) createResources(initial bool) error {
var err error
if p.stats == nil {
p.stats = newStats()
}
if p.logger == nil {
p.logger, err = logger.New(
p.conf.LogLevelParsed,
p.conf.LogDestinationsParsed,
p.conf.LogFile)
if err != nil {
return err
}
}
if initial {
p.Log(logger.Info, "rtsp-simple-server %s", version)
if !p.confFound {
p.Log(logger.Warn, "configuration file not found, using the default one")
}
}
if p.conf.Metrics {
if p.metrics == nil {
p.metrics, err = newMetrics(
p.conf.MetricsAddress,
p.stats,
p)
if err != nil {
return err
}
}
}
if p.conf.PPROF {
if p.pprof == nil {
p.pprof, err = newPPROF(
p.conf.PPROFAddress,
p)
if err != nil {
return err
}
}
}
if p.pathMan == nil {
p.pathMan = newPathManager(
p.ctx,
p.conf.RTSPAddress,
p.conf.ReadTimeout,
p.conf.WriteTimeout,
p.conf.ReadBufferCount,
p.conf.ReadBufferSize,
p.conf.AuthMethodsParsed,
p.conf.Paths,
p.stats,
p)
}
if !p.conf.RTSPDisable &&
(p.conf.EncryptionParsed == conf.EncryptionNo ||
p.conf.EncryptionParsed == conf.EncryptionOptional) {
if p.rtspServerPlain == nil {
_, useUDP := p.conf.ProtocolsParsed[conf.ProtocolUDP]
_, useMulticast := p.conf.ProtocolsParsed[conf.ProtocolMulticast]
p.rtspServerPlain, err = newRTSPServer(
p.ctx,
p.conf.RTSPAddress,
p.conf.ReadTimeout,
p.conf.WriteTimeout,
p.conf.ReadBufferCount,
p.conf.ReadBufferSize,
useUDP,
useMulticast,
p.conf.RTPAddress,
p.conf.RTCPAddress,
p.conf.MulticastIPRange,
p.conf.MulticastRTPPort,
p.conf.MulticastRTCPPort,
false,
"",
"",
p.conf.RTSPAddress,
p.conf.ProtocolsParsed,
p.conf.RunOnConnect,
p.conf.RunOnConnectRestart,
p.stats,
p.pathMan,
p)
if err != nil {
return err
}
}
}
if !p.conf.RTSPDisable &&
(p.conf.EncryptionParsed == conf.EncryptionStrict ||
p.conf.EncryptionParsed == conf.EncryptionOptional) {
if p.rtspServerTLS == nil {
p.rtspServerTLS, err = newRTSPServer(
p.ctx,
p.conf.RTSPSAddress,
p.conf.ReadTimeout,
p.conf.WriteTimeout,
p.conf.ReadBufferCount,
p.conf.ReadBufferSize,
false,
false,
"",
"",
"",
0,
0,
true,
p.conf.ServerCert,
p.conf.ServerKey,
p.conf.RTSPAddress,
p.conf.ProtocolsParsed,
p.conf.RunOnConnect,
p.conf.RunOnConnectRestart,
p.stats,
p.pathMan,
p)
if err != nil {
return err
}
}
}
if !p.conf.RTMPDisable {
if p.rtmpServer == nil {
p.rtmpServer, err = newRTMPServer(
p.ctx,
p.conf.RTMPAddress,
p.conf.ReadTimeout,
p.conf.WriteTimeout,
p.conf.ReadBufferCount,
p.conf.RTSPAddress,
p.conf.RunOnConnect,
p.conf.RunOnConnectRestart,
p.stats,
p.pathMan,
p)
if err != nil {
return err
}
}
}
if !p.conf.HLSDisable {
if p.hlsServer == nil {
p.hlsServer, err = newHLSServer(
p.ctx,
p.conf.HLSAddress,
p.conf.HLSSegmentCount,
p.conf.HLSSegmentDuration,
p.conf.HLSAllowOrigin,
p.conf.ReadBufferCount,
p.stats,
p.pathMan,
p)
if err != nil {
return err
}
}
}
return nil
}
func (p *Core) closeResources(newConf *conf.Conf) {
closeStats := false
if newConf == nil {
closeStats = true
}
closeLogger := false
if newConf == nil ||
!reflect.DeepEqual(newConf.LogDestinationsParsed, p.conf.LogDestinationsParsed) ||
newConf.LogFile != p.conf.LogFile {
closeLogger = true
}
closeMetrics := false
if newConf == nil ||
newConf.Metrics != p.conf.Metrics ||
newConf.MetricsAddress != p.conf.MetricsAddress ||
closeStats {
closeMetrics = true
}
closePPROF := false
if newConf == nil ||
newConf.PPROF != p.conf.PPROF ||
newConf.PPROFAddress != p.conf.PPROFAddress ||
closeStats {
closePPROF = true
}
closePathMan := false
if newConf == nil ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteTimeout != p.conf.WriteTimeout ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
newConf.ReadBufferSize != p.conf.ReadBufferSize ||
!reflect.DeepEqual(newConf.AuthMethodsParsed, p.conf.AuthMethodsParsed) ||
closeStats {
closePathMan = true
} else if !reflect.DeepEqual(newConf.Paths, p.conf.Paths) {
p.pathMan.OnConfReload(newConf.Paths)
}
closeServerPlain := false
if newConf == nil ||
newConf.RTSPDisable != p.conf.RTSPDisable ||
newConf.EncryptionParsed != p.conf.EncryptionParsed ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteTimeout != p.conf.WriteTimeout ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
!reflect.DeepEqual(newConf.ProtocolsParsed, p.conf.ProtocolsParsed) ||
newConf.RTPAddress != p.conf.RTPAddress ||
newConf.RTCPAddress != p.conf.RTCPAddress ||
newConf.MulticastIPRange != p.conf.MulticastIPRange ||
newConf.MulticastRTPPort != p.conf.MulticastRTPPort ||
newConf.MulticastRTCPPort != p.conf.MulticastRTCPPort ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
!reflect.DeepEqual(newConf.ProtocolsParsed, p.conf.ProtocolsParsed) ||
newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
closeStats ||
closePathMan {
closeServerPlain = true
}
closeServerTLS := false
if newConf == nil ||
newConf.RTSPDisable != p.conf.RTSPDisable ||
newConf.EncryptionParsed != p.conf.EncryptionParsed ||
newConf.RTSPSAddress != p.conf.RTSPSAddress ||
newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteTimeout != p.conf.WriteTimeout ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
newConf.ServerCert != p.conf.ServerCert ||
newConf.ServerKey != p.conf.ServerKey ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
!reflect.DeepEqual(newConf.ProtocolsParsed, p.conf.ProtocolsParsed) ||
newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
closeStats ||
closePathMan {
closeServerTLS = true
}
closeServerRTMP := false
if newConf == nil ||
newConf.RTMPDisable != p.conf.RTMPDisable ||
newConf.RTMPAddress != p.conf.RTMPAddress ||
newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteTimeout != p.conf.WriteTimeout ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
closeStats ||
closePathMan {
closeServerRTMP = true
}
closeServerHLS := false
if newConf == nil ||
newConf.HLSDisable != p.conf.HLSDisable ||
newConf.HLSAddress != p.conf.HLSAddress ||
newConf.HLSSegmentCount != p.conf.HLSSegmentCount ||
newConf.HLSSegmentDuration != p.conf.HLSSegmentDuration ||
newConf.HLSAllowOrigin != p.conf.HLSAllowOrigin ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
closeStats ||
closePathMan {
closeServerHLS = true
}
if closeServerTLS && p.rtspServerTLS != nil {
p.rtspServerTLS.close()
p.rtspServerTLS = nil
}
if closeServerPlain && p.rtspServerPlain != nil {
p.rtspServerPlain.close()
p.rtspServerPlain = nil
}
if closePathMan && p.pathMan != nil {
p.pathMan.close()
p.pathMan = nil
}
if closeServerHLS && p.hlsServer != nil {
p.hlsServer.close()
p.hlsServer = nil
}
if closeServerRTMP && p.rtmpServer != nil {
p.rtmpServer.close()
p.rtmpServer = nil
}
if closePPROF && p.pprof != nil {
p.pprof.close()
p.pprof = nil
}
if closeMetrics && p.metrics != nil {
p.metrics.close()
p.metrics = nil
}
if closeLogger && p.logger != nil {
p.logger.Close()
p.logger = nil
}
if closeStats && p.stats != nil {
p.stats.close()
}
}
func (p *Core) reloadConf() error {
p.Log(logger.Info, "reloading configuration")
newConf, _, err := conf.Load(p.confPath)
if err != nil {
return err
}
p.closeResources(newConf)
p.conf = newConf
return p.createResources(false)
}

16
main_test.go → internal/core/core_test.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package main
package core
import (
"io/ioutil"
@ -81,9 +81,9 @@ func writeTempFile(byts []byte) (string, error) { @@ -81,9 +81,9 @@ func writeTempFile(byts []byte) (string, error) {
return tmpf.Name(), nil
}
func testProgram(conf string) (*program, bool) {
func newInstance(conf string) (*Core, bool) {
if conf == "" {
return newProgram([]string{})
return New([]string{})
}
tmpf, err := writeTempFile([]byte(conf))
@ -92,7 +92,7 @@ func testProgram(conf string) (*program, bool) { @@ -92,7 +92,7 @@ func testProgram(conf string) (*program, bool) {
}
defer os.Remove(tmpf)
return newProgram([]string{tmpf})
return New([]string{tmpf})
}
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
@ -147,12 +147,12 @@ y++U32uuSFiXDcSLarfIsE992MEJLSAynbF1Rsgsr3gXbGiuToJRyxbIeVy7gwzD @@ -147,12 +147,12 @@ y++U32uuSFiXDcSLarfIsE992MEJLSAynbF1Rsgsr3gXbGiuToJRyxbIeVy7gwzD
-----END RSA PRIVATE KEY-----
`)
func TestHotReloading(t *testing.T) {
func TestCoreHotReloading(t *testing.T) {
confPath := filepath.Join(os.TempDir(), "rtsp-conf")
err := ioutil.WriteFile(confPath, []byte("paths:\n"+
" test1:\n"+
" runOnDemand: ffmpeg -hide_banner -loglevel error -re -i testimages/ffmpeg/emptyvideo.mkv -c copy -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH\n"+
" runOnDemand: ffmpeg -hide_banner -loglevel error -re -i ../../testimages/ffmpeg/emptyvideo.mkv -c copy -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH\n"+
" test3:\n"+
" runOnInit: echo aaa\n"+
" test4:\n"+
@ -161,7 +161,7 @@ func TestHotReloading(t *testing.T) { @@ -161,7 +161,7 @@ func TestHotReloading(t *testing.T) {
require.NoError(t, err)
defer os.Remove(confPath)
p, ok := newProgram([]string{confPath})
p, ok := New([]string{confPath})
require.Equal(t, true, ok)
defer p.close()
@ -181,7 +181,7 @@ func TestHotReloading(t *testing.T) { @@ -181,7 +181,7 @@ func TestHotReloading(t *testing.T) {
err = ioutil.WriteFile(confPath, []byte("paths:\n"+
" test2:\n"+
" runOnDemand: ffmpeg -hide_banner -loglevel error -re -i testimages/ffmpeg/emptyvideo.mkv -c copy -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH\n"+
" runOnDemand: ffmpeg -hide_banner -loglevel error -re -i ../../testimages/ffmpeg/emptyvideo.mkv -c copy -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH\n"+
" test3:\n"+
" test4:\n"+
" runOnInit: echo bbb\n"),

160
internal/hlsconverter/converter.go → internal/core/hls_converter.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package hlsconverter
package core
import (
"bytes"
@ -21,16 +21,15 @@ import ( @@ -21,16 +21,15 @@ import (
"github.com/pion/rtp"
"github.com/aler9/rtsp-simple-server/internal/h264"
"github.com/aler9/rtsp-simple-server/internal/hls"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/readpublisher"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
const (
// an offset is needed to
// - avoid negative PTS values
// - avoid PTS < DTS during startup
ptsOffset = 2 * time.Second
hlsConverterPTSOffset = 2 * time.Second
segmentMinAUCount = 100
closeCheckPeriod = 1 * time.Second
@ -85,25 +84,7 @@ create(); @@ -85,25 +84,7 @@ create();
</html>
`
func ipEqualOrInRange(ip net.IP, ips []interface{}) bool {
for _, item := range ips {
switch titem := item.(type) {
case net.IP:
if titem.Equal(ip) {
return true
}
case *net.IPNet:
if titem.Contains(ip) {
return true
}
}
}
return false
}
// Request is an HTTP request received by an HLS server.
type Request struct {
type hlsConverterRequest struct {
Dir string
File string
Req *http.Request
@ -111,61 +92,57 @@ type Request struct { @@ -111,61 +92,57 @@ type Request struct {
Res chan io.Reader
}
type trackIDPayloadPair struct {
type hlsConverterTrackIDPayloadPair struct {
trackID int
buf []byte
}
// PathMan is implemented by pathman.PathMan.
type PathMan interface {
OnReadPublisherSetupPlay(readpublisher.SetupPlayReq)
type hlsConverterPathMan interface {
OnReadPublisherSetupPlay(readPublisherSetupPlayReq)
}
// Parent is implemented by hlsserver.Server.
type Parent interface {
type hlsConverterParent interface {
Log(logger.Level, string, ...interface{})
OnConverterClose(*Converter)
OnConverterClose(*hlsConverter)
}
// Converter is an HLS converter.
type Converter struct {
type hlsConverter struct {
hlsSegmentCount int
hlsSegmentDuration time.Duration
readBufferCount int
wg *sync.WaitGroup
stats *stats.Stats
stats *stats
pathName string
pathMan PathMan
parent Parent
ctx context.Context
ctxCancel func()
path readpublisher.Path
ringBuffer *ringbuffer.RingBuffer
tsQueue []*tsFile
tsByName map[string]*tsFile
tsDeleteCount int
tsMutex sync.RWMutex
lastRequestTime *int64
pathMan hlsConverterPathMan
parent hlsConverterParent
ctx context.Context
ctxCancel func()
path readPublisherPath
ringBuffer *ringbuffer.RingBuffer
tsQueue []*hls.TSFile
tsByName map[string]*hls.TSFile
tsDeleteCount int
tsMutex sync.RWMutex
lasthlsConverterRequestTime *int64
// in
request chan Request
request chan hlsConverterRequest
}
// New allocates a Converter.
func New(
ctxParent context.Context,
func newHLSConverter(
parentCtx context.Context,
hlsSegmentCount int,
hlsSegmentDuration time.Duration,
readBufferCount int,
wg *sync.WaitGroup,
stats *stats.Stats,
stats *stats,
pathName string,
pathMan PathMan,
parent Parent) *Converter {
ctx, ctxCancel := context.WithCancel(ctxParent)
pathMan hlsConverterPathMan,
parent hlsConverterParent) *hlsConverter {
ctx, ctxCancel := context.WithCancel(parentCtx)
c := &Converter{
c := &hlsConverter{
hlsSegmentCount: hlsSegmentCount,
hlsSegmentDuration: hlsSegmentDuration,
readBufferCount: readBufferCount,
@ -176,12 +153,12 @@ func New( @@ -176,12 +153,12 @@ func New(
parent: parent,
ctx: ctx,
ctxCancel: ctxCancel,
lastRequestTime: func() *int64 {
lasthlsConverterRequestTime: func() *int64 {
v := time.Now().Unix()
return &v
}(),
tsByName: make(map[string]*tsFile),
request: make(chan Request),
tsByName: make(map[string]*hls.TSFile),
request: make(chan hlsConverterRequest),
}
c.log(logger.Info, "opened")
@ -193,31 +170,30 @@ func New( @@ -193,31 +170,30 @@ func New(
}
// ParentClose closes a Converter.
func (c *Converter) ParentClose() {
func (c *hlsConverter) ParentClose() {
c.log(logger.Info, "closed")
}
// Close closes a Converter.
func (c *Converter) Close() {
func (c *hlsConverter) Close() {
c.ctxCancel()
}
// IsReadPublisher implements readpublisher.ReadPublisher.
func (c *Converter) IsReadPublisher() {}
// IsReadPublisher implements readPublisher.
func (c *hlsConverter) IsReadPublisher() {}
// IsSource implements source.Source.
func (c *Converter) IsSource() {}
// IsSource implements source.
func (c *hlsConverter) IsSource() {}
func (c *Converter) log(level logger.Level, format string, args ...interface{}) {
func (c *hlsConverter) log(level logger.Level, format string, args ...interface{}) {
c.parent.Log(level, "[converter %s] "+format, append([]interface{}{c.pathName}, args...)...)
}
// PathName returns the path name of the readpublisher.
func (c *Converter) PathName() string {
// PathName returns the path name of the readPublisher
func (c *hlsConverter) PathName() string {
return c.pathName
}
func (c *Converter) run() {
func (c *hlsConverter) run() {
defer c.wg.Done()
innerCtx, innerCtxCancel := context.WithCancel(context.Background())
@ -242,16 +218,16 @@ func (c *Converter) run() { @@ -242,16 +218,16 @@ func (c *Converter) run() {
if c.path != nil {
res := make(chan struct{})
c.path.OnReadPublisherRemove(readpublisher.RemoveReq{c, res}) //nolint:govet
c.path.OnReadPublisherRemove(readPublisherRemoveReq{c, res}) //nolint:govet
<-res
}
c.parent.OnConverterClose(c)
}
func (c *Converter) runInner(innerCtx context.Context) error {
pres := make(chan readpublisher.SetupPlayRes)
c.pathMan.OnReadPublisherSetupPlay(readpublisher.SetupPlayReq{
func (c *hlsConverter) runInner(innerCtx context.Context) error {
pres := make(chan readPublisherSetupPlayRes)
c.pathMan.OnReadPublisherSetupPlay(readPublisherSetupPlayReq{
Author: c,
PathName: c.pathName,
IP: nil,
@ -318,7 +294,7 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -318,7 +294,7 @@ func (c *Converter) runInner(innerCtx context.Context) error {
return fmt.Errorf("unable to find a video or audio track")
}
curTSFile := newTSFile(videoTrack, audioTrack)
curTSFile := hls.NewTSFile(videoTrack, audioTrack)
c.tsByName[curTSFile.Name()] = curTSFile
c.tsQueue = append(c.tsQueue, curTSFile)
@ -337,8 +313,8 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -337,8 +313,8 @@ func (c *Converter) runInner(innerCtx context.Context) error {
c.ringBuffer = ringbuffer.New(uint64(c.readBufferCount))
resc := make(chan readpublisher.PlayRes)
c.path.OnReadPublisherPlay(readpublisher.PlayReq{c, resc}) //nolint:govet
resc := make(chan readPublisherPlayRes)
c.path.OnReadPublisherPlay(readPublisherPlayReq{c, resc}) //nolint:govet
<-resc
c.log(logger.Info, "is converting into HLS")
@ -357,7 +333,7 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -357,7 +333,7 @@ func (c *Converter) runInner(innerCtx context.Context) error {
if !ok {
return fmt.Errorf("terminated")
}
pair := data.(trackIDPayloadPair)
pair := data.(hlsConverterTrackIDPayloadPair)
if videoTrack != nil && pair.trackID == videoTrackID {
var pkt rtp.Packet
@ -422,7 +398,7 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -422,7 +398,7 @@ func (c *Converter) runInner(innerCtx context.Context) error {
// - no IDR has been stored yet in current file
// - there's no IDR in the buffer
// data cannot be parsed, clear buffer
if !bufferHasIDR && !curTSFile.firstPacketWritten {
if !bufferHasIDR && !curTSFile.FirstPacketWritten() {
videoBuf = nil
continue
}
@ -432,13 +408,13 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -432,13 +408,13 @@ func (c *Converter) runInner(innerCtx context.Context) error {
defer c.tsMutex.Unlock()
if bufferHasIDR {
if curTSFile.firstPacketWritten &&
if curTSFile.FirstPacketWritten() &&
curTSFile.Duration() >= c.hlsSegmentDuration {
if curTSFile != nil {
curTSFile.Close()
}
curTSFile = newTSFile(videoTrack, audioTrack)
curTSFile = hls.NewTSFile(videoTrack, audioTrack)
c.tsByName[curTSFile.Name()] = curTSFile
c.tsQueue = append(c.tsQueue, curTSFile)
@ -452,8 +428,8 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -452,8 +428,8 @@ func (c *Converter) runInner(innerCtx context.Context) error {
curTSFile.SetPCR(time.Since(startPCR))
err := curTSFile.WriteH264(
videoDTSEst.Feed(pts+ptsOffset),
pts+ptsOffset,
videoDTSEst.Feed(pts+hlsConverterPTSOffset),
pts+hlsConverterPTSOffset,
bufferHasIDR,
videoBuf)
if err != nil {
@ -482,7 +458,7 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -482,7 +458,7 @@ func (c *Converter) runInner(innerCtx context.Context) error {
defer c.tsMutex.Unlock()
if videoTrack == nil {
if curTSFile.firstPacketWritten &&
if curTSFile.FirstPacketWritten() &&
curTSFile.Duration() >= c.hlsSegmentDuration &&
audioAUCount >= segmentMinAUCount {
@ -491,7 +467,7 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -491,7 +467,7 @@ func (c *Converter) runInner(innerCtx context.Context) error {
}
audioAUCount = 0
curTSFile = newTSFile(videoTrack, audioTrack)
curTSFile = hls.NewTSFile(videoTrack, audioTrack)
c.tsByName[curTSFile.Name()] = curTSFile
c.tsQueue = append(c.tsQueue, curTSFile)
if len(c.tsQueue) > c.hlsSegmentCount {
@ -501,7 +477,7 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -501,7 +477,7 @@ func (c *Converter) runInner(innerCtx context.Context) error {
}
}
} else {
if !curTSFile.firstPacketWritten {
if !curTSFile.FirstPacketWritten() {
return nil
}
}
@ -514,7 +490,7 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -514,7 +490,7 @@ func (c *Converter) runInner(innerCtx context.Context) error {
err := curTSFile.WriteAAC(
aacConfig.SampleRate,
aacConfig.ChannelCount,
auPTS+ptsOffset,
auPTS+hlsConverterPTSOffset,
au)
if err != nil {
return err
@ -537,7 +513,7 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -537,7 +513,7 @@ func (c *Converter) runInner(innerCtx context.Context) error {
for {
select {
case <-closeCheckTicker.C:
t := time.Unix(atomic.LoadInt64(c.lastRequestTime), 0)
t := time.Unix(atomic.LoadInt64(c.lasthlsConverterRequestTime), 0)
if time.Since(t) >= closeAfterInactivity {
c.ringBuffer.Close()
<-writerDone
@ -555,7 +531,7 @@ func (c *Converter) runInner(innerCtx context.Context) error { @@ -555,7 +531,7 @@ func (c *Converter) runInner(innerCtx context.Context) error {
}
}
func (c *Converter) runRequestHandler(terminate chan struct{}, done chan struct{}) {
func (c *hlsConverter) runRequestHandler(terminate chan struct{}, done chan struct{}) {
defer close(done)
for {
@ -566,7 +542,7 @@ func (c *Converter) runRequestHandler(terminate chan struct{}, done chan struct{ @@ -566,7 +542,7 @@ func (c *Converter) runRequestHandler(terminate chan struct{}, done chan struct{
case preq := <-c.request:
req := preq
atomic.StoreInt64(c.lastRequestTime, time.Now().Unix())
atomic.StoreInt64(c.lasthlsConverterRequestTime, time.Now().Unix())
conf := c.path.Conf()
@ -647,7 +623,7 @@ func (c *Converter) runRequestHandler(terminate chan struct{}, done chan struct{ @@ -647,7 +623,7 @@ func (c *Converter) runRequestHandler(terminate chan struct{}, done chan struct{
}
req.W.Header().Set("Content-Type", `video/MP2T`)
req.Res <- f.buf.NewReader()
req.Res <- f.NewReader()
case req.File == "":
req.Res <- bytes.NewReader([]byte(index))
@ -661,7 +637,7 @@ func (c *Converter) runRequestHandler(terminate chan struct{}, done chan struct{ @@ -661,7 +637,7 @@ func (c *Converter) runRequestHandler(terminate chan struct{}, done chan struct{
}
// OnRequest is called by hlsserver.Server.
func (c *Converter) OnRequest(req Request) {
func (c *hlsConverter) OnRequest(req hlsConverterRequest) {
select {
case c.request <- req:
case <-c.ctx.Done():
@ -671,8 +647,8 @@ func (c *Converter) OnRequest(req Request) { @@ -671,8 +647,8 @@ func (c *Converter) OnRequest(req Request) {
}
// OnFrame implements path.Reader.
func (c *Converter) OnFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
func (c *hlsConverter) OnFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
if streamType == gortsplib.StreamTypeRTP {
c.ringBuffer.Push(trackIDPayloadPair{trackID, payload})
c.ringBuffer.Push(hlsConverterTrackIDPayloadPair{trackID, payload})
}
}

68
internal/hlsserver/server.go → internal/core/hls_server.go

@ -1,67 +1,61 @@ @@ -1,67 +1,61 @@
package hlsserver
package core
import (
"context"
"io"
"net"
"net/http"
"path"
gopath "path"
"strings"
"sync"
"time"
"github.com/aler9/rtsp-simple-server/internal/hlsconverter"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/pathman"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
// Parent is implemented by program.
type Parent interface {
type hlsServerParent interface {
Log(logger.Level, string, ...interface{})
}
// Server is an HLS server.
type Server struct {
type hlsServer struct {
hlsSegmentCount int
hlsSegmentDuration time.Duration
hlsAllowOrigin string
readBufferCount int
stats *stats.Stats
pathMan *pathman.PathManager
parent Parent
stats *stats
pathMan *pathManager
parent hlsServerParent
ctx context.Context
ctxCancel func()
wg sync.WaitGroup
ln net.Listener
converters map[string]*hlsconverter.Converter
converters map[string]*hlsConverter
// in
request chan hlsconverter.Request
connClose chan *hlsconverter.Converter
request chan hlsConverterRequest
connClose chan *hlsConverter
}
// New allocates a Server.
func New(
ctxParent context.Context,
func newHLSServer(
parentCtx context.Context,
address string,
hlsSegmentCount int,
hlsSegmentDuration time.Duration,
hlsAllowOrigin string,
readBufferCount int,
stats *stats.Stats,
pathMan *pathman.PathManager,
parent Parent,
) (*Server, error) {
stats *stats,
pathMan *pathManager,
parent hlsServerParent,
) (*hlsServer, error) {
ln, err := net.Listen("tcp", address)
if err != nil {
return nil, err
}
ctx, ctxCancel := context.WithCancel(ctxParent)
ctx, ctxCancel := context.WithCancel(parentCtx)
s := &Server{
s := &hlsServer{
hlsSegmentCount: hlsSegmentCount,
hlsSegmentDuration: hlsSegmentDuration,
hlsAllowOrigin: hlsAllowOrigin,
@ -72,9 +66,9 @@ func New( @@ -72,9 +66,9 @@ func New(
ctx: ctx,
ctxCancel: ctxCancel,
ln: ln,
converters: make(map[string]*hlsconverter.Converter),
request: make(chan hlsconverter.Request),
connClose: make(chan *hlsconverter.Converter),
converters: make(map[string]*hlsConverter),
request: make(chan hlsConverterRequest),
connClose: make(chan *hlsConverter),
}
s.Log(logger.Info, "listener opened on "+address)
@ -86,17 +80,16 @@ func New( @@ -86,17 +80,16 @@ func New(
}
// Log is the main logging function.
func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
func (s *hlsServer) Log(level logger.Level, format string, args ...interface{}) {
s.parent.Log(level, "[HLS] "+format, append([]interface{}{}, args...)...)
}
// Close closes all the server resources.
func (s *Server) Close() {
func (s *hlsServer) close() {
s.ctxCancel()
s.wg.Wait()
}
func (s *Server) run() {
func (s *hlsServer) run() {
defer s.wg.Done()
hs := &http.Server{Handler: s}
@ -108,7 +101,7 @@ outer: @@ -108,7 +101,7 @@ outer:
case req := <-s.request:
c, ok := s.converters[req.Dir]
if !ok {
c = hlsconverter.New(
c = newHLSConverter(
s.ctx,
s.hlsSegmentCount,
s.hlsSegmentDuration,
@ -143,7 +136,7 @@ outer: @@ -143,7 +136,7 @@ outer:
}
// ServeHTTP implements http.Handler.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (s *hlsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.Log(logger.Info, "[conn %v] %s %s", r.RemoteAddr, r.Method, r.URL.Path)
// remove leading prefix
@ -166,7 +159,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -166,7 +159,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
dir, fname := func() (string, string) {
if strings.HasSuffix(pa, ".ts") || strings.HasSuffix(pa, ".m3u8") {
return path.Dir(pa), path.Base(pa)
return gopath.Dir(pa), gopath.Base(pa)
}
return pa, ""
}()
@ -180,7 +173,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -180,7 +173,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
dir = strings.TrimSuffix(dir, "/")
cres := make(chan io.Reader)
hreq := hlsconverter.Request{
hreq := hlsConverterRequest{
Dir: dir,
File: fname,
Req: r,
@ -213,13 +206,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -213,13 +206,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
func (s *Server) doConverterClose(c *hlsconverter.Converter) {
func (s *hlsServer) doConverterClose(c *hlsConverter) {
delete(s.converters, c.PathName())
c.ParentClose()
}
// OnConverterClose is called by hlsconverter.Converter.
func (s *Server) OnConverterClose(c *hlsconverter.Converter) {
func (s *hlsServer) OnConverterClose(c *hlsConverter) {
select {
case s.connClose <- c:
case <-s.ctx.Done():

10
main_hlsreader_test.go → internal/core/hls_server_test.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package main
package core
import (
"testing"
@ -7,8 +7,8 @@ import ( @@ -7,8 +7,8 @@ import (
"github.com/stretchr/testify/require"
)
func TestClientHLSRead(t *testing.T) {
p, ok := testProgram("")
func TestHLSServerRead(t *testing.T) {
p, ok := newInstance("")
require.Equal(t, true, ok)
defer p.close()
@ -36,8 +36,8 @@ func TestClientHLSRead(t *testing.T) { @@ -36,8 +36,8 @@ func TestClientHLSRead(t *testing.T) {
require.Equal(t, 0, cnt2.wait())
}
func TestClientHLSReadAuth(t *testing.T) {
p, ok := testProgram(
func TestHLSServerReadAuth(t *testing.T) {
p, ok := newInstance(
"paths:\n" +
" all:\n" +
" readUser: testuser\n" +

22
internal/core/ip.go

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
package core
import (
"net"
)
func ipEqualOrInRange(ip net.IP, ips []interface{}) bool {
for _, item := range ips {
switch titem := item.(type) {
case net.IP:
if titem.Equal(ip) {
return true
}
case *net.IPNet:
if titem.Contains(ip) {
return true
}
}
}
return false
}

30
internal/metrics/metrics.go → internal/core/metrics.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package metrics
package core
import (
"context"
@ -10,7 +10,6 @@ import ( @@ -10,7 +10,6 @@ import (
"time"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
func formatMetric(key string, value int64, nowUnix int64) string {
@ -18,32 +17,29 @@ func formatMetric(key string, value int64, nowUnix int64) string { @@ -18,32 +17,29 @@ func formatMetric(key string, value int64, nowUnix int64) string {
strconv.FormatInt(nowUnix, 10) + "\n"
}
// Parent is implemented by program.
type Parent interface {
type metricsParent interface {
Log(logger.Level, string, ...interface{})
}
// Metrics is a metrics exporter.
type Metrics struct {
stats *stats.Stats
type metrics struct {
stats *stats
listener net.Listener
mux *http.ServeMux
server *http.Server
}
// New allocates a metrics.
func New(
func newMetrics(
address string,
stats *stats.Stats,
parent Parent,
) (*Metrics, error) {
stats *stats,
parent metricsParent,
) (*metrics, error) {
listener, err := net.Listen("tcp", address)
if err != nil {
return nil, err
}
m := &Metrics{
m := &metrics{
stats: stats,
listener: listener,
}
@ -58,22 +54,22 @@ func New( @@ -58,22 +54,22 @@ func New(
parent.Log(logger.Info, "[metrics] opened on "+address)
go m.run()
return m, nil
}
// Close closes a Metrics.
func (m *Metrics) Close() {
func (m *metrics) close() {
m.server.Shutdown(context.Background())
}
func (m *Metrics) run() {
func (m *metrics) run() {
err := m.server.Serve(m.listener)
if err != http.ErrServerClosed {
panic(err)
}
}
func (m *Metrics) onMetrics(w http.ResponseWriter, req *http.Request) {
func (m *metrics) onMetrics(w http.ResponseWriter, req *http.Request) {
nowUnix := time.Now().UnixNano() / 1000000
countPublishers := atomic.LoadInt64(m.stats.CountPublishers)

265
internal/path/path.go → internal/core/path.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package path
package core
import (
"context"
@ -15,11 +15,6 @@ import ( @@ -15,11 +15,6 @@ import (
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/readpublisher"
"github.com/aler9/rtsp-simple-server/internal/rtmpsource"
"github.com/aler9/rtsp-simple-server/internal/rtspsource"
"github.com/aler9/rtsp-simple-server/internal/source"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
func newEmptyTimer() *time.Timer {
@ -28,13 +23,12 @@ func newEmptyTimer() *time.Timer { @@ -28,13 +23,12 @@ func newEmptyTimer() *time.Timer {
return t
}
// Parent is implemented by pathman.PathMan.
type Parent interface {
type pathParent interface {
Log(logger.Level, string, ...interface{})
OnPathClose(*Path)
OnPathClose(*path)
}
type rtspSession interface {
type pathRTSPSession interface {
IsRTSPSession()
}
@ -60,8 +54,43 @@ const ( @@ -60,8 +54,43 @@ const (
sourceStateReady
)
// Path is a path.
type Path struct {
type reader interface {
OnFrame(int, gortsplib.StreamType, []byte)
}
type readersMap struct {
mutex sync.RWMutex
ma map[reader]struct{}
}
func newReadersMap() *readersMap {
return &readersMap{
ma: make(map[reader]struct{}),
}
}
func (m *readersMap) add(reader reader) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.ma[reader] = struct{}{}
}
func (m *readersMap) remove(reader reader) {
m.mutex.Lock()
defer m.mutex.Unlock()
delete(m.ma, reader)
}
func (m *readersMap) forwardFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
m.mutex.RLock()
defer m.mutex.RUnlock()
for c := range m.ma {
c.OnFrame(trackID, streamType, payload)
}
}
type path struct {
rtspAddress string
readTimeout time.Duration
writeTimeout time.Duration
@ -71,15 +100,15 @@ type Path struct { @@ -71,15 +100,15 @@ type Path struct {
conf *conf.PathConf
name string
wg *sync.WaitGroup
stats *stats.Stats
parent Parent
stats *stats
parent pathParent
ctx context.Context
ctxCancel func()
readPublishers map[readpublisher.ReadPublisher]readPublisherState
describeRequests []readpublisher.DescribeReq
setupPlayRequests []readpublisher.SetupPlayReq
source source.Source
readPublishers map[readPublisher]readPublisherState
describeRequests []readPublisherDescribeReq
setupPlayRequests []readPublisherSetupPlayReq
source source
sourceStream *gortsplib.ServerStream
nonRTSPReaders *readersMap
onDemandCmd *externalcmd.Cmd
@ -94,20 +123,19 @@ type Path struct { @@ -94,20 +123,19 @@ type Path struct {
closeTimerStarted bool
// in
extSourceSetReady chan source.ExtSetReadyReq
extSourceSetNotReady chan source.ExtSetNotReadyReq
describeReq chan readpublisher.DescribeReq
setupPlayReq chan readpublisher.SetupPlayReq
announceReq chan readpublisher.AnnounceReq
playReq chan readpublisher.PlayReq
recordReq chan readpublisher.RecordReq
pauseReq chan readpublisher.PauseReq
removeReq chan readpublisher.RemoveReq
}
// New allocates a Path.
func New(
ctxParent context.Context,
extSourceSetReady chan sourceExtSetReadyReq
extSourceSetNotReady chan sourceExtSetNotReadyReq
describeReq chan readPublisherDescribeReq
setupPlayReq chan readPublisherSetupPlayReq
announceReq chan readPublisherAnnounceReq
playReq chan readPublisherPlayReq
recordReq chan readPublisherRecordReq
pauseReq chan readPublisherPauseReq
removeReq chan readPublisherRemoveReq
}
func newPath(
parentCtx context.Context,
rtspAddress string,
readTimeout time.Duration,
writeTimeout time.Duration,
@ -117,11 +145,11 @@ func New( @@ -117,11 +145,11 @@ func New(
conf *conf.PathConf,
name string,
wg *sync.WaitGroup,
stats *stats.Stats,
parent Parent) *Path {
ctx, ctxCancel := context.WithCancel(ctxParent)
stats *stats,
parent pathParent) *path {
ctx, ctxCancel := context.WithCancel(parentCtx)
pa := &Path{
pa := &path{
rtspAddress: rtspAddress,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
@ -135,21 +163,21 @@ func New( @@ -135,21 +163,21 @@ func New(
parent: parent,
ctx: ctx,
ctxCancel: ctxCancel,
readPublishers: make(map[readpublisher.ReadPublisher]readPublisherState),
readPublishers: make(map[readPublisher]readPublisherState),
nonRTSPReaders: newReadersMap(),
describeTimer: newEmptyTimer(),
sourceCloseTimer: newEmptyTimer(),
runOnDemandCloseTimer: newEmptyTimer(),
closeTimer: newEmptyTimer(),
extSourceSetReady: make(chan source.ExtSetReadyReq),
extSourceSetNotReady: make(chan source.ExtSetNotReadyReq),
describeReq: make(chan readpublisher.DescribeReq),
setupPlayReq: make(chan readpublisher.SetupPlayReq),
announceReq: make(chan readpublisher.AnnounceReq),
playReq: make(chan readpublisher.PlayReq),
recordReq: make(chan readpublisher.RecordReq),
pauseReq: make(chan readpublisher.PauseReq),
removeReq: make(chan readpublisher.RemoveReq),
extSourceSetReady: make(chan sourceExtSetReadyReq),
extSourceSetNotReady: make(chan sourceExtSetNotReadyReq),
describeReq: make(chan readPublisherDescribeReq),
setupPlayReq: make(chan readPublisherSetupPlayReq),
announceReq: make(chan readPublisherAnnounceReq),
playReq: make(chan readPublisherPlayReq),
recordReq: make(chan readPublisherRecordReq),
pauseReq: make(chan readPublisherPauseReq),
removeReq: make(chan readPublisherRemoveReq),
}
pa.wg.Add(1)
@ -157,17 +185,16 @@ func New( @@ -157,17 +185,16 @@ func New(
return pa
}
// Close closes a path.
func (pa *Path) Close() {
func (pa *path) Close() {
pa.ctxCancel()
}
// Log is the main logging function.
func (pa *Path) Log(level logger.Level, format string, args ...interface{}) {
func (pa *path) Log(level logger.Level, format string, args ...interface{}) {
pa.parent.Log(level, "[path "+pa.name+"] "+format, args...)
}
func (pa *Path) run() {
func (pa *path) run() {
defer pa.wg.Done()
if pa.conf.Source == "redirect" {
@ -191,12 +218,12 @@ outer: @@ -191,12 +218,12 @@ outer:
select {
case <-pa.describeTimer.C:
for _, req := range pa.describeRequests {
req.Res <- readpublisher.DescribeRes{Err: fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
req.Res <- readPublisherDescribeRes{Err: fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
}
pa.describeRequests = nil
for _, req := range pa.setupPlayRequests {
req.Res <- readpublisher.SetupPlayRes{Err: fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
req.Res <- readPublisherSetupPlayRes{Err: fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
}
pa.setupPlayRequests = nil
@ -209,7 +236,7 @@ outer: @@ -209,7 +236,7 @@ outer:
case <-pa.sourceCloseTimer.C:
pa.sourceCloseTimerStarted = false
pa.source.(source.ExtSource).Close()
pa.source.(sourceExternal).Close()
pa.source = nil
pa.scheduleClose()
@ -228,7 +255,7 @@ outer: @@ -228,7 +255,7 @@ outer:
case req := <-pa.extSourceSetReady:
pa.sourceStream = gortsplib.NewServerStream(req.Tracks)
pa.onSourceSetReady()
req.Res <- source.ExtSetReadyRes{}
req.Res <- sourceExtSetReadyRes{}
case req := <-pa.extSourceSetNotReady:
pa.onSourceSetNotReady()
@ -282,7 +309,7 @@ outer: @@ -282,7 +309,7 @@ outer:
onInitCmd.Close()
}
if source, ok := pa.source.(source.ExtSource); ok {
if source, ok := pa.source.(sourceExternal); ok {
source.Close()
}
pa.sourceWg.Wait()
@ -293,11 +320,11 @@ outer: @@ -293,11 +320,11 @@ outer:
}
for _, req := range pa.describeRequests {
req.Res <- readpublisher.DescribeRes{Err: fmt.Errorf("terminated")}
req.Res <- readPublisherDescribeRes{Err: fmt.Errorf("terminated")}
}
for _, req := range pa.setupPlayRequests {
req.Res <- readpublisher.SetupPlayRes{Err: fmt.Errorf("terminated")}
req.Res <- readPublisherSetupPlayRes{Err: fmt.Errorf("terminated")}
}
for rp, state := range pa.readPublishers {
@ -306,7 +333,7 @@ outer: @@ -306,7 +333,7 @@ outer:
case readPublisherStatePlay:
atomic.AddInt64(pa.stats.CountReaders, -1)
if _, ok := rp.(rtspSession); !ok {
if _, ok := rp.(pathRTSPSession); !ok {
pa.nonRTSPReaders.remove(rp)
}
@ -320,16 +347,16 @@ outer: @@ -320,16 +347,16 @@ outer:
pa.parent.OnPathClose(pa)
}
func (pa *Path) hasExternalSource() bool {
func (pa *path) hasExternalSource() bool {
return strings.HasPrefix(pa.conf.Source, "rtsp://") ||
strings.HasPrefix(pa.conf.Source, "rtsps://") ||
strings.HasPrefix(pa.conf.Source, "rtmp://")
}
func (pa *Path) startExternalSource() {
func (pa *path) startExternalSource() {
if strings.HasPrefix(pa.conf.Source, "rtsp://") ||
strings.HasPrefix(pa.conf.Source, "rtsps://") {
pa.source = rtspsource.New(
pa.source = newRTSPSource(
pa.ctx,
pa.conf.Source,
pa.conf.SourceProtocolParsed,
@ -343,7 +370,7 @@ func (pa *Path) startExternalSource() { @@ -343,7 +370,7 @@ func (pa *Path) startExternalSource() {
pa.stats,
pa)
} else if strings.HasPrefix(pa.conf.Source, "rtmp://") {
pa.source = rtmpsource.New(
pa.source = newRTMPSource(
pa.ctx,
pa.conf.Source,
pa.readTimeout,
@ -354,7 +381,7 @@ func (pa *Path) startExternalSource() { @@ -354,7 +381,7 @@ func (pa *Path) startExternalSource() {
}
}
func (pa *Path) hasReadPublishers() bool {
func (pa *path) hasReadPublishers() bool {
for _, state := range pa.readPublishers {
if state != readPublisherStatePreRemove {
return true
@ -363,7 +390,7 @@ func (pa *Path) hasReadPublishers() bool { @@ -363,7 +390,7 @@ func (pa *Path) hasReadPublishers() bool {
return false
}
func (pa *Path) hasReadPublishersNotSources() bool {
func (pa *path) hasReadPublishersNotSources() bool {
for c, state := range pa.readPublishers {
if state != readPublisherStatePreRemove && c != pa.source {
return true
@ -372,11 +399,11 @@ func (pa *Path) hasReadPublishersNotSources() bool { @@ -372,11 +399,11 @@ func (pa *Path) hasReadPublishersNotSources() bool {
return false
}
func (pa *Path) addReadPublisher(c readpublisher.ReadPublisher, state readPublisherState) {
func (pa *path) addReadPublisher(c readPublisher, state readPublisherState) {
pa.readPublishers[c] = state
}
func (pa *Path) removeReadPublisher(rp readpublisher.ReadPublisher) {
func (pa *path) removeReadPublisher(rp readPublisher) {
state := pa.readPublishers[rp]
pa.readPublishers[rp] = readPublisherStatePreRemove
@ -384,7 +411,7 @@ func (pa *Path) removeReadPublisher(rp readpublisher.ReadPublisher) { @@ -384,7 +411,7 @@ func (pa *Path) removeReadPublisher(rp readpublisher.ReadPublisher) {
case readPublisherStatePlay:
atomic.AddInt64(pa.stats.CountReaders, -1)
if _, ok := rp.(rtspSession); !ok {
if _, ok := rp.(pathRTSPSession); !ok {
pa.nonRTSPReaders.remove(rp)
}
@ -412,7 +439,7 @@ func (pa *Path) removeReadPublisher(rp readpublisher.ReadPublisher) { @@ -412,7 +439,7 @@ func (pa *Path) removeReadPublisher(rp readpublisher.ReadPublisher) {
pa.scheduleClose()
}
func (pa *Path) onSourceSetReady() {
func (pa *path) onSourceSetReady() {
if pa.sourceState == sourceStateWaitingDescribe {
pa.describeTimer.Stop()
pa.describeTimer = newEmptyTimer()
@ -421,7 +448,7 @@ func (pa *Path) onSourceSetReady() { @@ -421,7 +448,7 @@ func (pa *Path) onSourceSetReady() {
pa.sourceState = sourceStateReady
for _, req := range pa.describeRequests {
req.Res <- readpublisher.DescribeRes{
req.Res <- readPublisherDescribeRes{
Stream: pa.sourceStream,
}
}
@ -437,7 +464,7 @@ func (pa *Path) onSourceSetReady() { @@ -437,7 +464,7 @@ func (pa *Path) onSourceSetReady() {
pa.scheduleClose()
}
func (pa *Path) onSourceSetNotReady() {
func (pa *path) onSourceSetNotReady() {
pa.sourceState = sourceStateNotReady
// close all readPublishers that are reading or waiting to read
@ -449,7 +476,7 @@ func (pa *Path) onSourceSetNotReady() { @@ -449,7 +476,7 @@ func (pa *Path) onSourceSetNotReady() {
}
}
func (pa *Path) fixedPublisherStart() {
func (pa *path) fixedPublisherStart() {
if pa.hasExternalSource() {
// start on-demand source
if pa.source == nil {
@ -490,12 +517,12 @@ func (pa *Path) fixedPublisherStart() { @@ -490,12 +517,12 @@ func (pa *Path) fixedPublisherStart() {
}
}
func (pa *Path) onReadPublisherDescribe(req readpublisher.DescribeReq) {
func (pa *path) onReadPublisherDescribe(req readPublisherDescribeReq) {
pa.fixedPublisherStart()
pa.scheduleClose()
if _, ok := pa.source.(*sourceRedirect); ok {
req.Res <- readpublisher.DescribeRes{
req.Res <- readPublisherDescribeRes{
Redirect: pa.conf.SourceRedirect,
}
return
@ -503,7 +530,7 @@ func (pa *Path) onReadPublisherDescribe(req readpublisher.DescribeReq) { @@ -503,7 +530,7 @@ func (pa *Path) onReadPublisherDescribe(req readpublisher.DescribeReq) {
switch pa.sourceState {
case sourceStateReady:
req.Res <- readpublisher.DescribeRes{
req.Res <- readPublisherDescribeRes{
Stream: pa.sourceStream,
}
return
@ -526,16 +553,16 @@ func (pa *Path) onReadPublisherDescribe(req readpublisher.DescribeReq) { @@ -526,16 +553,16 @@ func (pa *Path) onReadPublisherDescribe(req readpublisher.DescribeReq) {
}
return pa.conf.Fallback
}()
req.Res <- readpublisher.DescribeRes{nil, fallbackURL, nil} //nolint:govet
req.Res <- readPublisherDescribeRes{nil, fallbackURL, nil} //nolint:govet
return
}
req.Res <- readpublisher.DescribeRes{Err: readpublisher.ErrNoOnePublishing{PathName: pa.name}}
req.Res <- readPublisherDescribeRes{Err: readPublisherErrNoOnePublishing{PathName: pa.name}}
return
}
}
func (pa *Path) onReadPublisherSetupPlay(req readpublisher.SetupPlayReq) {
func (pa *path) onReadPublisherSetupPlay(req readPublisherSetupPlayReq) {
pa.fixedPublisherStart()
pa.scheduleClose()
@ -549,12 +576,12 @@ func (pa *Path) onReadPublisherSetupPlay(req readpublisher.SetupPlayReq) { @@ -549,12 +576,12 @@ func (pa *Path) onReadPublisherSetupPlay(req readpublisher.SetupPlayReq) {
return
case sourceStateNotReady:
req.Res <- readpublisher.SetupPlayRes{Err: readpublisher.ErrNoOnePublishing{PathName: pa.name}}
req.Res <- readPublisherSetupPlayRes{Err: readPublisherErrNoOnePublishing{PathName: pa.name}}
return
}
}
func (pa *Path) onReadPublisherSetupPlayPost(req readpublisher.SetupPlayReq) {
func (pa *path) onReadPublisherSetupPlayPost(req readPublisherSetupPlayReq) {
if _, ok := pa.readPublishers[req.Author]; !ok {
// prevent on-demand source from closing
if pa.sourceCloseTimerStarted {
@ -571,42 +598,42 @@ func (pa *Path) onReadPublisherSetupPlayPost(req readpublisher.SetupPlayReq) { @@ -571,42 +598,42 @@ func (pa *Path) onReadPublisherSetupPlayPost(req readpublisher.SetupPlayReq) {
pa.addReadPublisher(req.Author, readPublisherStatePrePlay)
}
req.Res <- readpublisher.SetupPlayRes{
req.Res <- readPublisherSetupPlayRes{
Path: pa,
Stream: pa.sourceStream,
}
}
func (pa *Path) onReadPublisherPlay(req readpublisher.PlayReq) {
func (pa *path) onReadPublisherPlay(req readPublisherPlayReq) {
atomic.AddInt64(pa.stats.CountReaders, 1)
pa.readPublishers[req.Author] = readPublisherStatePlay
if _, ok := req.Author.(rtspSession); !ok {
if _, ok := req.Author.(pathRTSPSession); !ok {
pa.nonRTSPReaders.add(req.Author)
}
req.Res <- readpublisher.PlayRes{}
req.Res <- readPublisherPlayRes{}
}
func (pa *Path) onReadPublisherAnnounce(req readpublisher.AnnounceReq) {
func (pa *path) onReadPublisherAnnounce(req readPublisherAnnounceReq) {
if _, ok := pa.readPublishers[req.Author]; ok {
req.Res <- readpublisher.AnnounceRes{Err: fmt.Errorf("already publishing or reading")}
req.Res <- readPublisherAnnounceRes{Err: fmt.Errorf("already publishing or reading")}
return
}
if pa.hasExternalSource() {
req.Res <- readpublisher.AnnounceRes{Err: fmt.Errorf("path '%s' is assigned to an external source", pa.name)}
req.Res <- readPublisherAnnounceRes{Err: fmt.Errorf("path '%s' is assigned to an external source", pa.name)}
return
}
if pa.source != nil {
if pa.conf.DisablePublisherOverride {
req.Res <- readpublisher.AnnounceRes{Err: fmt.Errorf("another client is already publishing on path '%s'", pa.name)}
req.Res <- readPublisherAnnounceRes{Err: fmt.Errorf("another client is already publishing on path '%s'", pa.name)}
return
}
pa.Log(logger.Info, "closing existing publisher")
curPublisher := pa.source.(readpublisher.ReadPublisher)
curPublisher := pa.source.(readPublisher)
pa.removeReadPublisher(curPublisher)
curPublisher.Close()
@ -622,12 +649,12 @@ func (pa *Path) onReadPublisherAnnounce(req readpublisher.AnnounceReq) { @@ -622,12 +649,12 @@ func (pa *Path) onReadPublisherAnnounce(req readpublisher.AnnounceReq) {
pa.source = req.Author
pa.sourceStream = gortsplib.NewServerStream(req.Tracks)
req.Res <- readpublisher.AnnounceRes{pa, nil} //nolint:govet
req.Res <- readPublisherAnnounceRes{pa, nil} //nolint:govet
}
func (pa *Path) onReadPublisherRecord(req readpublisher.RecordReq) {
func (pa *path) onReadPublisherRecord(req readPublisherRecordReq) {
if state, ok := pa.readPublishers[req.Author]; !ok || state != readPublisherStatePreRecord {
req.Res <- readpublisher.RecordRes{Err: fmt.Errorf("not recording anymore")}
req.Res <- readPublisherRecordRes{Err: fmt.Errorf("not recording anymore")}
return
}
@ -635,10 +662,10 @@ func (pa *Path) onReadPublisherRecord(req readpublisher.RecordReq) { @@ -635,10 +662,10 @@ func (pa *Path) onReadPublisherRecord(req readpublisher.RecordReq) {
pa.readPublishers[req.Author] = readPublisherStateRecord
pa.onSourceSetReady()
req.Res <- readpublisher.RecordRes{}
req.Res <- readPublisherRecordRes{}
}
func (pa *Path) onReadPublisherPause(req readpublisher.PauseReq) {
func (pa *path) onReadPublisherPause(req readPublisherPauseReq) {
state, ok := pa.readPublishers[req.Author]
if !ok {
close(req.Res)
@ -649,7 +676,7 @@ func (pa *Path) onReadPublisherPause(req readpublisher.PauseReq) { @@ -649,7 +676,7 @@ func (pa *Path) onReadPublisherPause(req readpublisher.PauseReq) {
atomic.AddInt64(pa.stats.CountReaders, -1)
pa.readPublishers[req.Author] = readPublisherStatePrePlay
if _, ok := req.Author.(rtspSession); !ok {
if _, ok := req.Author.(pathRTSPSession); !ok {
pa.nonRTSPReaders.remove(req.Author)
}
@ -662,7 +689,7 @@ func (pa *Path) onReadPublisherPause(req readpublisher.PauseReq) { @@ -662,7 +689,7 @@ func (pa *Path) onReadPublisherPause(req readpublisher.PauseReq) {
close(req.Res)
}
func (pa *Path) scheduleSourceClose() {
func (pa *path) scheduleSourceClose() {
if !pa.hasExternalSource() || !pa.conf.SourceOnDemand || pa.source == nil {
return
}
@ -678,7 +705,7 @@ func (pa *Path) scheduleSourceClose() { @@ -678,7 +705,7 @@ func (pa *Path) scheduleSourceClose() {
pa.sourceCloseTimerStarted = true
}
func (pa *Path) scheduleRunOnDemandClose() {
func (pa *path) scheduleRunOnDemandClose() {
if pa.conf.RunOnDemand == "" || pa.onDemandCmd == nil {
return
}
@ -694,7 +721,7 @@ func (pa *Path) scheduleRunOnDemandClose() { @@ -694,7 +721,7 @@ func (pa *Path) scheduleRunOnDemandClose() {
pa.runOnDemandCloseTimerStarted = true
}
func (pa *Path) scheduleClose() {
func (pa *path) scheduleClose() {
if pa.conf.Regexp != nil &&
!pa.hasReadPublishers() &&
pa.source == nil &&
@ -710,22 +737,22 @@ func (pa *Path) scheduleClose() { @@ -710,22 +737,22 @@ func (pa *Path) scheduleClose() {
}
// ConfName returns the configuration name of this path.
func (pa *Path) ConfName() string {
func (pa *path) ConfName() string {
return pa.confName
}
// Conf returns the configuration of this path.
func (pa *Path) Conf() *conf.PathConf {
func (pa *path) Conf() *conf.PathConf {
return pa.conf
}
// Name returns the name of this path.
func (pa *Path) Name() string {
func (pa *path) Name() string {
return pa.name
}
// OnExtSourceSetReady is called by an external source.
func (pa *Path) OnExtSourceSetReady(req source.ExtSetReadyReq) {
// OnsourceExternalSetReady is called by an external source.
func (pa *path) OnsourceExternalSetReady(req sourceExtSetReadyReq) {
select {
case pa.extSourceSetReady <- req:
case <-pa.ctx.Done():
@ -733,8 +760,8 @@ func (pa *Path) OnExtSourceSetReady(req source.ExtSetReadyReq) { @@ -733,8 +760,8 @@ func (pa *Path) OnExtSourceSetReady(req source.ExtSetReadyReq) {
}
}
// OnExtSourceSetNotReady is called by an external source.
func (pa *Path) OnExtSourceSetNotReady(req source.ExtSetNotReadyReq) {
// OnsourceExternalSetNotReady is called by an external source.
func (pa *path) OnsourceExternalSetNotReady(req sourceExtSetNotReadyReq) {
select {
case pa.extSourceSetNotReady <- req:
case <-pa.ctx.Done():
@ -743,34 +770,34 @@ func (pa *Path) OnExtSourceSetNotReady(req source.ExtSetNotReadyReq) { @@ -743,34 +770,34 @@ func (pa *Path) OnExtSourceSetNotReady(req source.ExtSetNotReadyReq) {
}
// OnPathManDescribe is called by pathman.PathMan.
func (pa *Path) OnPathManDescribe(req readpublisher.DescribeReq) {
func (pa *path) OnPathManDescribe(req readPublisherDescribeReq) {
select {
case pa.describeReq <- req:
case <-pa.ctx.Done():
req.Res <- readpublisher.DescribeRes{Err: fmt.Errorf("terminated")}
req.Res <- readPublisherDescribeRes{Err: fmt.Errorf("terminated")}
}
}
// OnPathManSetupPlay is called by pathman.PathMan.
func (pa *Path) OnPathManSetupPlay(req readpublisher.SetupPlayReq) {
func (pa *path) OnPathManSetupPlay(req readPublisherSetupPlayReq) {
select {
case pa.setupPlayReq <- req:
case <-pa.ctx.Done():
req.Res <- readpublisher.SetupPlayRes{Err: fmt.Errorf("terminated")}
req.Res <- readPublisherSetupPlayRes{Err: fmt.Errorf("terminated")}
}
}
// OnPathManAnnounce is called by pathman.PathMan.
func (pa *Path) OnPathManAnnounce(req readpublisher.AnnounceReq) {
func (pa *path) OnPathManAnnounce(req readPublisherAnnounceReq) {
select {
case pa.announceReq <- req:
case <-pa.ctx.Done():
req.Res <- readpublisher.AnnounceRes{Err: fmt.Errorf("terminated")}
req.Res <- readPublisherAnnounceRes{Err: fmt.Errorf("terminated")}
}
}
// OnReadPublisherPlay is called by a readpublisher.
func (pa *Path) OnReadPublisherPlay(req readpublisher.PlayReq) {
// OnReadPublisherPlay is called by a readPublisher
func (pa *path) OnReadPublisherPlay(req readPublisherPlayReq) {
select {
case pa.playReq <- req:
case <-pa.ctx.Done():
@ -778,8 +805,8 @@ func (pa *Path) OnReadPublisherPlay(req readpublisher.PlayReq) { @@ -778,8 +805,8 @@ func (pa *Path) OnReadPublisherPlay(req readpublisher.PlayReq) {
}
}
// OnReadPublisherRecord is called by a readpublisher.
func (pa *Path) OnReadPublisherRecord(req readpublisher.RecordReq) {
// OnReadPublisherRecord is called by a readPublisher
func (pa *path) OnReadPublisherRecord(req readPublisherRecordReq) {
select {
case pa.recordReq <- req:
case <-pa.ctx.Done():
@ -787,8 +814,8 @@ func (pa *Path) OnReadPublisherRecord(req readpublisher.RecordReq) { @@ -787,8 +814,8 @@ func (pa *Path) OnReadPublisherRecord(req readpublisher.RecordReq) {
}
}
// OnReadPublisherPause is called by a readpublisher.
func (pa *Path) OnReadPublisherPause(req readpublisher.PauseReq) {
// OnReadPublisherPause is called by a readPublisher
func (pa *path) OnReadPublisherPause(req readPublisherPauseReq) {
select {
case pa.pauseReq <- req:
case <-pa.ctx.Done():
@ -796,8 +823,8 @@ func (pa *Path) OnReadPublisherPause(req readpublisher.PauseReq) { @@ -796,8 +823,8 @@ func (pa *Path) OnReadPublisherPause(req readpublisher.PauseReq) {
}
}
// OnReadPublisherRemove is called by a readpublisher.
func (pa *Path) OnReadPublisherRemove(req readpublisher.RemoveReq) {
// OnReadPublisherRemove is called by a readPublisher
func (pa *path) OnReadPublisherRemove(req readPublisherRemoveReq) {
select {
case pa.removeReq <- req:
case <-pa.ctx.Done():
@ -806,7 +833,7 @@ func (pa *Path) OnReadPublisherRemove(req readpublisher.RemoveReq) { @@ -806,7 +833,7 @@ func (pa *Path) OnReadPublisherRemove(req readpublisher.RemoveReq) {
}
// OnFrame is called by a readpublisher
func (pa *Path) OnFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
func (pa *path) OnFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
pa.sourceStream.WriteFrame(trackID, streamType, payload)
pa.nonRTSPReaders.forwardFrame(trackID, streamType, payload)

116
internal/pathman/pathman.go → internal/core/path_manager.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package pathman
package core
import (
"context"
@ -12,35 +12,13 @@ import ( @@ -12,35 +12,13 @@ import (
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/path"
"github.com/aler9/rtsp-simple-server/internal/readpublisher"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
func ipEqualOrInRange(ip net.IP, ips []interface{}) bool {
for _, item := range ips {
switch titem := item.(type) {
case net.IP:
if titem.Equal(ip) {
return true
}
case *net.IPNet:
if titem.Contains(ip) {
return true
}
}
}
return false
}
// Parent is implemented by program.
type Parent interface {
type pathManagerParent interface {
Log(logger.Level, string, ...interface{})
}
// PathManager is a path.Path manager.
type PathManager struct {
type pathManager struct {
rtspAddress string
readTimeout time.Duration
writeTimeout time.Duration
@ -48,25 +26,24 @@ type PathManager struct { @@ -48,25 +26,24 @@ type PathManager struct {
readBufferSize int
authMethods []headers.AuthMethod
pathConfs map[string]*conf.PathConf
stats *stats.Stats
parent Parent
stats *stats
parent pathManagerParent
ctx context.Context
ctxCancel func()
wg sync.WaitGroup
paths map[string]*path.Path
paths map[string]*path
// in
confReload chan map[string]*conf.PathConf
pathClose chan *path.Path
rpDescribe chan readpublisher.DescribeReq
rpSetupPlay chan readpublisher.SetupPlayReq
rpAnnounce chan readpublisher.AnnounceReq
pathClose chan *path
rpDescribe chan readPublisherDescribeReq
rpSetupPlay chan readPublisherSetupPlayReq
rpAnnounce chan readPublisherAnnounceReq
}
// New allocates a PathManager.
func New(
ctxParent context.Context,
func newPathManager(
parentCtx context.Context,
rtspAddress string,
readTimeout time.Duration,
writeTimeout time.Duration,
@ -74,11 +51,11 @@ func New( @@ -74,11 +51,11 @@ func New(
readBufferSize int,
authMethods []headers.AuthMethod,
pathConfs map[string]*conf.PathConf,
stats *stats.Stats,
parent Parent) *PathManager {
ctx, ctxCancel := context.WithCancel(ctxParent)
stats *stats,
parent pathManagerParent) *pathManager {
ctx, ctxCancel := context.WithCancel(parentCtx)
pm := &PathManager{
pm := &pathManager{
rtspAddress: rtspAddress,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
@ -90,12 +67,12 @@ func New( @@ -90,12 +67,12 @@ func New(
parent: parent,
ctx: ctx,
ctxCancel: ctxCancel,
paths: make(map[string]*path.Path),
paths: make(map[string]*path),
confReload: make(chan map[string]*conf.PathConf),
pathClose: make(chan *path.Path),
rpDescribe: make(chan readpublisher.DescribeReq),
rpSetupPlay: make(chan readpublisher.SetupPlayReq),
rpAnnounce: make(chan readpublisher.AnnounceReq),
pathClose: make(chan *path),
rpDescribe: make(chan readPublisherDescribeReq),
rpSetupPlay: make(chan readPublisherSetupPlayReq),
rpAnnounce: make(chan readPublisherAnnounceReq),
}
pm.createPaths()
@ -106,18 +83,17 @@ func New( @@ -106,18 +83,17 @@ func New(
return pm
}
// Close closes a PathManager.
func (pm *PathManager) Close() {
func (pm *pathManager) close() {
pm.ctxCancel()
pm.wg.Wait()
}
// Log is the main logging function.
func (pm *PathManager) Log(level logger.Level, format string, args ...interface{}) {
func (pm *pathManager) Log(level logger.Level, format string, args ...interface{}) {
pm.parent.Log(level, format, args...)
}
func (pm *PathManager) run() {
func (pm *pathManager) run() {
defer pm.wg.Done()
outer:
@ -167,7 +143,7 @@ outer: @@ -167,7 +143,7 @@ outer:
case req := <-pm.rpDescribe:
pathName, pathConf, err := pm.findPathConf(req.PathName)
if err != nil {
req.Res <- readpublisher.DescribeRes{Err: err}
req.Res <- readPublisherDescribeRes{Err: err}
continue
}
@ -180,7 +156,7 @@ outer: @@ -180,7 +156,7 @@ outer:
pathConf.ReadPass,
)
if err != nil {
req.Res <- readpublisher.DescribeRes{Err: err}
req.Res <- readPublisherDescribeRes{Err: err}
continue
}
@ -194,7 +170,7 @@ outer: @@ -194,7 +170,7 @@ outer:
case req := <-pm.rpSetupPlay:
pathName, pathConf, err := pm.findPathConf(req.PathName)
if err != nil {
req.Res <- readpublisher.SetupPlayRes{Err: err}
req.Res <- readPublisherSetupPlayRes{Err: err}
continue
}
@ -207,7 +183,7 @@ outer: @@ -207,7 +183,7 @@ outer:
pathConf.ReadPass,
)
if err != nil {
req.Res <- readpublisher.SetupPlayRes{Err: err}
req.Res <- readPublisherSetupPlayRes{Err: err}
continue
}
@ -221,7 +197,7 @@ outer: @@ -221,7 +197,7 @@ outer:
case req := <-pm.rpAnnounce:
pathName, pathConf, err := pm.findPathConf(req.PathName)
if err != nil {
req.Res <- readpublisher.AnnounceRes{Err: err}
req.Res <- readPublisherAnnounceRes{Err: err}
continue
}
@ -234,7 +210,7 @@ outer: @@ -234,7 +210,7 @@ outer:
pathConf.PublishPass,
)
if err != nil {
req.Res <- readpublisher.AnnounceRes{Err: err}
req.Res <- readPublisherAnnounceRes{Err: err}
continue
}
@ -253,8 +229,8 @@ outer: @@ -253,8 +229,8 @@ outer:
pm.ctxCancel()
}
func (pm *PathManager) createPath(confName string, conf *conf.PathConf, name string) {
pm.paths[name] = path.New(
func (pm *pathManager) createPath(confName string, conf *conf.PathConf, name string) {
pm.paths[name] = newPath(
pm.ctx,
pm.rtspAddress,
pm.readTimeout,
@ -269,7 +245,7 @@ func (pm *PathManager) createPath(confName string, conf *conf.PathConf, name str @@ -269,7 +245,7 @@ func (pm *PathManager) createPath(confName string, conf *conf.PathConf, name str
pm)
}
func (pm *PathManager) createPaths() {
func (pm *pathManager) createPaths() {
for pathName, pathConf := range pm.pathConfs {
if _, ok := pm.paths[pathName]; !ok && pathConf.Regexp == nil {
pm.createPath(pathName, pathConf, pathName)
@ -277,7 +253,7 @@ func (pm *PathManager) createPaths() { @@ -277,7 +253,7 @@ func (pm *PathManager) createPaths() {
}
}
func (pm *PathManager) findPathConf(name string) (string, *conf.PathConf, error) {
func (pm *pathManager) findPathConf(name string) (string, *conf.PathConf, error) {
err := conf.CheckPathName(name)
if err != nil {
return "", nil, fmt.Errorf("invalid path name: %s (%s)", err, name)
@ -298,16 +274,16 @@ func (pm *PathManager) findPathConf(name string) (string, *conf.PathConf, error) @@ -298,16 +274,16 @@ func (pm *PathManager) findPathConf(name string) (string, *conf.PathConf, error)
return "", nil, fmt.Errorf("unable to find a valid configuration for path '%s'", name)
}
// OnProgramConfReload is called by program.
func (pm *PathManager) OnProgramConfReload(pathConfs map[string]*conf.PathConf) {
// OnConfReload is called by core.
func (pm *pathManager) OnConfReload(pathConfs map[string]*conf.PathConf) {
select {
case pm.confReload <- pathConfs:
case <-pm.ctx.Done():
}
}
// OnPathClose is called by path.Path.
func (pm *PathManager) OnPathClose(pa *path.Path) {
// OnPathClose is called by path.
func (pm *pathManager) OnPathClose(pa *path) {
select {
case pm.pathClose <- pa:
case <-pm.ctx.Done():
@ -315,33 +291,33 @@ func (pm *PathManager) OnPathClose(pa *path.Path) { @@ -315,33 +291,33 @@ func (pm *PathManager) OnPathClose(pa *path.Path) {
}
// OnReadPublisherDescribe is called by a ReadPublisher.
func (pm *PathManager) OnReadPublisherDescribe(req readpublisher.DescribeReq) {
func (pm *pathManager) OnReadPublisherDescribe(req readPublisherDescribeReq) {
select {
case pm.rpDescribe <- req:
case <-pm.ctx.Done():
req.Res <- readpublisher.DescribeRes{Err: fmt.Errorf("terminated")}
req.Res <- readPublisherDescribeRes{Err: fmt.Errorf("terminated")}
}
}
// OnReadPublisherAnnounce is called by a ReadPublisher.
func (pm *PathManager) OnReadPublisherAnnounce(req readpublisher.AnnounceReq) {
func (pm *pathManager) OnReadPublisherAnnounce(req readPublisherAnnounceReq) {
select {
case pm.rpAnnounce <- req:
case <-pm.ctx.Done():
req.Res <- readpublisher.AnnounceRes{Err: fmt.Errorf("terminated")}
req.Res <- readPublisherAnnounceRes{Err: fmt.Errorf("terminated")}
}
}
// OnReadPublisherSetupPlay is called by a ReadPublisher.
func (pm *PathManager) OnReadPublisherSetupPlay(req readpublisher.SetupPlayReq) {
func (pm *pathManager) OnReadPublisherSetupPlay(req readPublisherSetupPlayReq) {
select {
case pm.rpSetupPlay <- req:
case <-pm.ctx.Done():
req.Res <- readpublisher.SetupPlayRes{Err: fmt.Errorf("terminated")}
req.Res <- readPublisherSetupPlayRes{Err: fmt.Errorf("terminated")}
}
}
func (pm *PathManager) authenticate(
func (pm *pathManager) authenticate(
ip net.IP,
validateCredentials func(authMethods []headers.AuthMethod, pathUser string, pathPass string) error,
pathName string,
@ -352,7 +328,7 @@ func (pm *PathManager) authenticate( @@ -352,7 +328,7 @@ func (pm *PathManager) authenticate(
// validate ip
if pathIPs != nil && ip != nil {
if !ipEqualOrInRange(ip, pathIPs) {
return readpublisher.ErrAuthCritical{
return readPublisherErrAuthCritical{
Message: fmt.Sprintf("IP '%s' not allowed", ip),
Response: &base.Response{
StatusCode: base.StatusUnauthorized,

23
internal/pprof/pprof.go → internal/core/pprof.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package pprof
package core
import (
"context"
@ -11,28 +11,25 @@ import ( @@ -11,28 +11,25 @@ import (
"github.com/aler9/rtsp-simple-server/internal/logger"
)
// Parent is implemented by program.
type Parent interface {
type pprofParent interface {
Log(logger.Level, string, ...interface{})
}
// PPROF is a performance metrics exporter.
type PPROF struct {
type pprof struct {
listener net.Listener
server *http.Server
}
// New allocates a PPROF.
func New(
func newPPROF(
address string,
parent Parent,
) (*PPROF, error) {
parent pprofParent,
) (*pprof, error) {
listener, err := net.Listen("tcp", address)
if err != nil {
return nil, err
}
pp := &PPROF{
pp := &pprof{
listener: listener,
}
@ -43,15 +40,15 @@ func New( @@ -43,15 +40,15 @@ func New(
parent.Log(logger.Info, "[pprof] opened on "+address)
go pp.run()
return pp, nil
}
// Close closes a PPROF.
func (pp *PPROF) Close() {
func (pp *pprof) close() {
pp.server.Shutdown(context.Background())
}
func (pp *PPROF) run() {
func (pp *pprof) run() {
err := pp.server.Serve(pp.listener)
if err != http.ErrServerClosed {
panic(err)

125
internal/core/read_publisher.go

@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
package core
import (
"fmt"
"net"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/base"
"github.com/aler9/gortsplib/pkg/headers"
"github.com/aler9/rtsp-simple-server/internal/conf"
)
type readPublisherPath interface {
Name() string
Conf() *conf.PathConf
OnReadPublisherRemove(readPublisherRemoveReq)
OnReadPublisherPlay(readPublisherPlayReq)
OnReadPublisherRecord(readPublisherRecordReq)
OnReadPublisherPause(readPublisherPauseReq)
OnFrame(int, gortsplib.StreamType, []byte)
}
type readPublisherErrNoOnePublishing struct {
PathName string
}
// Error implements the error interface.
func (e readPublisherErrNoOnePublishing) Error() string {
return fmt.Sprintf("no one is publishing to path '%s'", e.PathName)
}
type readPublisherErrAuthNotCritical struct {
*base.Response
}
// Error implements the error interface.
func (readPublisherErrAuthNotCritical) Error() string {
return "non-critical authentication error"
}
type readPublisherErrAuthCritical struct {
Message string
Response *base.Response
}
// Error implements the error interface.
func (readPublisherErrAuthCritical) Error() string {
return "critical authentication error"
}
type readPublisher interface {
IsReadPublisher()
IsSource()
Close()
OnFrame(int, gortsplib.StreamType, []byte)
}
type readPublisherDescribeRes struct {
Stream *gortsplib.ServerStream
Redirect string
Err error
}
type readPublisherDescribeReq struct {
PathName string
URL *base.URL
IP net.IP
ValidateCredentials func(authMethods []headers.AuthMethod, pathUser string, pathPass string) error
Res chan readPublisherDescribeRes
}
type readPublisherSetupPlayRes struct {
Path readPublisherPath
Stream *gortsplib.ServerStream
Err error
}
type readPublisherSetupPlayReq struct {
Author readPublisher
PathName string
IP net.IP
ValidateCredentials func(authMethods []headers.AuthMethod, pathUser string, pathPass string) error
Res chan readPublisherSetupPlayRes
}
type readPublisherAnnounceRes struct {
Path readPublisherPath
Err error
}
type readPublisherAnnounceReq struct {
Author readPublisher
PathName string
Tracks gortsplib.Tracks
IP net.IP
ValidateCredentials func(authMethods []headers.AuthMethod, pathUser string, pathPass string) error
Res chan readPublisherAnnounceRes
}
type readPublisherRemoveReq struct {
Author readPublisher
Res chan struct{}
}
type readPublisherPlayRes struct{}
type readPublisherPlayReq struct {
Author readPublisher
Res chan readPublisherPlayRes
}
type readPublisherRecordRes struct {
Err error
}
type readPublisherRecordReq struct {
Author readPublisher
Res chan readPublisherRecordRes
}
type readPublisherPauseReq struct {
Author readPublisher
Res chan struct{}
}

116
internal/rtmpconn/conn.go → internal/core/rtmp_conn.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package rtmpconn
package core
import (
"context"
@ -21,19 +21,17 @@ import ( @@ -21,19 +21,17 @@ import (
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
"github.com/aler9/rtsp-simple-server/internal/h264"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/readpublisher"
"github.com/aler9/rtsp-simple-server/internal/rtcpsenderset"
"github.com/aler9/rtsp-simple-server/internal/rtmp"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
const (
pauseAfterAuthError = 2 * time.Second
rtmpConnPauseAfterAuthError = 2 * time.Second
// an offset is needed to
// - avoid negative PTS values
// - avoid PTS < DTS during startup
ptsOffset = 2 * time.Second
rtmpConnPTSOffset = 2 * time.Second
)
func pathNameAndQuery(inURL *url.URL) (string, url.Values) {
@ -44,25 +42,22 @@ func pathNameAndQuery(inURL *url.URL) (string, url.Values) { @@ -44,25 +42,22 @@ func pathNameAndQuery(inURL *url.URL) (string, url.Values) {
return pathName, ur.Query()
}
type trackIDPayloadPair struct {
type rtmpConnTrackIDPayloadPair struct {
trackID int
buf []byte
}
// PathMan is implemented by pathman.PathMan.
type PathMan interface {
OnReadPublisherSetupPlay(readpublisher.SetupPlayReq)
OnReadPublisherAnnounce(readpublisher.AnnounceReq)
type rtmpConnPathMan interface {
OnReadPublisherSetupPlay(readPublisherSetupPlayReq)
OnReadPublisherAnnounce(readPublisherAnnounceReq)
}
// Parent is implemented by rtmpserver.Server.
type Parent interface {
type rtmpConnParent interface {
Log(logger.Level, string, ...interface{})
OnConnClose(*Conn)
OnConnClose(*rtmpConn)
}
// Conn is a server-side RTMP connection.
type Conn struct {
type rtmpConn struct {
rtspAddress string
readTimeout time.Duration
writeTimeout time.Duration
@ -70,20 +65,19 @@ type Conn struct { @@ -70,20 +65,19 @@ type Conn struct {
runOnConnect string
runOnConnectRestart bool
wg *sync.WaitGroup
stats *stats.Stats
stats *stats
conn *rtmp.Conn
pathMan PathMan
parent Parent
pathMan rtmpConnPathMan
parent rtmpConnParent
ctx context.Context
ctxCancel func()
path readpublisher.Path
path readPublisherPath
ringBuffer *ringbuffer.RingBuffer // read
}
// New allocates a Conn.
func New(
ctxParent context.Context,
func newRTMPConn(
parentCtx context.Context,
rtspAddress string,
readTimeout time.Duration,
writeTimeout time.Duration,
@ -91,13 +85,13 @@ func New( @@ -91,13 +85,13 @@ func New(
runOnConnect string,
runOnConnectRestart bool,
wg *sync.WaitGroup,
stats *stats.Stats,
stats *stats,
nconn net.Conn,
pathMan PathMan,
parent Parent) *Conn {
ctx, ctxCancel := context.WithCancel(ctxParent)
pathMan rtmpConnPathMan,
parent rtmpConnParent) *rtmpConn {
ctx, ctxCancel := context.WithCancel(parentCtx)
c := &Conn{
c := &rtmpConn{
rtspAddress: rtspAddress,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
@ -122,30 +116,30 @@ func New( @@ -122,30 +116,30 @@ func New(
}
// ParentClose closes a Conn.
func (c *Conn) ParentClose() {
func (c *rtmpConn) ParentClose() {
c.log(logger.Info, "closed")
}
// Close closes a Conn.
func (c *Conn) Close() {
func (c *rtmpConn) Close() {
c.ctxCancel()
}
// IsReadPublisher implements readpublisher.ReadPublisher.
func (c *Conn) IsReadPublisher() {}
// IsReadPublisher implements readPublisher.
func (c *rtmpConn) IsReadPublisher() {}
// IsSource implements source.Source.
func (c *Conn) IsSource() {}
// IsSource implements source.
func (c *rtmpConn) IsSource() {}
func (c *Conn) log(level logger.Level, format string, args ...interface{}) {
func (c *rtmpConn) log(level logger.Level, format string, args ...interface{}) {
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.conn.NetConn().RemoteAddr()}, args...)...)
}
func (c *Conn) ip() net.IP {
func (c *rtmpConn) ip() net.IP {
return c.conn.NetConn().RemoteAddr().(*net.TCPAddr).IP
}
func (c *Conn) run() {
func (c *rtmpConn) run() {
defer c.wg.Done()
if c.runOnConnect != "" {
@ -180,14 +174,14 @@ func (c *Conn) run() { @@ -180,14 +174,14 @@ func (c *Conn) run() {
if c.path != nil {
res := make(chan struct{})
c.path.OnReadPublisherRemove(readpublisher.RemoveReq{c, res}) //nolint:govet
c.path.OnReadPublisherRemove(readPublisherRemoveReq{c, res}) //nolint:govet
<-res
}
c.parent.OnConnClose(c)
}
func (c *Conn) runInner(ctx context.Context) error {
func (c *rtmpConn) runInner(ctx context.Context) error {
go func() {
<-ctx.Done()
c.conn.NetConn().Close()
@ -206,11 +200,11 @@ func (c *Conn) runInner(ctx context.Context) error { @@ -206,11 +200,11 @@ func (c *Conn) runInner(ctx context.Context) error {
return c.runRead(ctx)
}
func (c *Conn) runRead(ctx context.Context) error {
func (c *rtmpConn) runRead(ctx context.Context) error {
pathName, query := pathNameAndQuery(c.conn.URL())
sres := make(chan readpublisher.SetupPlayRes)
c.pathMan.OnReadPublisherSetupPlay(readpublisher.SetupPlayReq{
sres := make(chan readPublisherSetupPlayRes)
c.pathMan.OnReadPublisherSetupPlay(readPublisherSetupPlayReq{
Author: c,
PathName: pathName,
IP: c.ip(),
@ -222,9 +216,9 @@ func (c *Conn) runRead(ctx context.Context) error { @@ -222,9 +216,9 @@ func (c *Conn) runRead(ctx context.Context) error {
res := <-sres
if res.Err != nil {
if terr, ok := res.Err.(readpublisher.ErrAuthCritical); ok {
if terr, ok := res.Err.(readPublisherErrAuthCritical); ok {
// wait some seconds to stop brute force attacks
<-time.After(pauseAfterAuthError)
<-time.After(rtmpConnPauseAfterAuthError)
return errors.New(terr.Message)
}
return res.Err
@ -276,8 +270,8 @@ func (c *Conn) runRead(ctx context.Context) error { @@ -276,8 +270,8 @@ func (c *Conn) runRead(ctx context.Context) error {
c.ringBuffer.Close()
}()
pres := make(chan readpublisher.PlayRes)
c.path.OnReadPublisherPlay(readpublisher.PlayReq{c, pres}) //nolint:govet
pres := make(chan readPublisherPlayRes)
c.path.OnReadPublisherPlay(readPublisherPlayReq{c, pres}) //nolint:govet
<-pres
c.log(logger.Info, "is reading from path '%s'", c.path.Name())
@ -293,7 +287,7 @@ func (c *Conn) runRead(ctx context.Context) error { @@ -293,7 +287,7 @@ func (c *Conn) runRead(ctx context.Context) error {
if !ok {
return fmt.Errorf("terminated")
}
pair := data.(trackIDPayloadPair)
pair := data.(rtmpConnTrackIDPayloadPair)
if videoTrack != nil && pair.trackID == videoTrackID {
nalus, pts, err := h264Decoder.Decode(pair.buf)
@ -324,13 +318,13 @@ func (c *Conn) runRead(ctx context.Context) error { @@ -324,13 +318,13 @@ func (c *Conn) runRead(ctx context.Context) error {
return err
}
dts := videoDTSEst.Feed(pts + ptsOffset)
dts := videoDTSEst.Feed(pts + rtmpConnPTSOffset)
c.conn.NetConn().SetWriteDeadline(time.Now().Add(c.writeTimeout))
err = c.conn.WritePacket(av.Packet{
Type: av.H264,
Data: data,
Time: dts,
CTime: pts + ptsOffset - dts,
CTime: pts + rtmpConnPTSOffset - dts,
})
if err != nil {
return err
@ -349,7 +343,7 @@ func (c *Conn) runRead(ctx context.Context) error { @@ -349,7 +343,7 @@ func (c *Conn) runRead(ctx context.Context) error {
}
for i, au := range aus {
auPTS := pts + ptsOffset + time.Duration(i)*1000*time.Second/time.Duration(audioClockRate)
auPTS := pts + rtmpConnPTSOffset + time.Duration(i)*1000*time.Second/time.Duration(audioClockRate)
c.conn.NetConn().SetWriteDeadline(time.Now().Add(c.writeTimeout))
err := c.conn.WritePacket(av.Packet{
@ -365,7 +359,7 @@ func (c *Conn) runRead(ctx context.Context) error { @@ -365,7 +359,7 @@ func (c *Conn) runRead(ctx context.Context) error {
}
}
func (c *Conn) runPublish(ctx context.Context) error {
func (c *rtmpConn) runPublish(ctx context.Context) error {
c.conn.NetConn().SetReadDeadline(time.Now().Add(c.readTimeout))
videoTrack, audioTrack, err := c.conn.ReadMetadata()
if err != nil {
@ -393,8 +387,8 @@ func (c *Conn) runPublish(ctx context.Context) error { @@ -393,8 +387,8 @@ func (c *Conn) runPublish(ctx context.Context) error {
pathName, query := pathNameAndQuery(c.conn.URL())
resc := make(chan readpublisher.AnnounceRes)
c.pathMan.OnReadPublisherAnnounce(readpublisher.AnnounceReq{
resc := make(chan readPublisherAnnounceRes)
c.pathMan.OnReadPublisherAnnounce(readPublisherAnnounceReq{
Author: c,
PathName: pathName,
Tracks: tracks,
@ -407,9 +401,9 @@ func (c *Conn) runPublish(ctx context.Context) error { @@ -407,9 +401,9 @@ func (c *Conn) runPublish(ctx context.Context) error {
res := <-resc
if res.Err != nil {
if terr, ok := res.Err.(readpublisher.ErrAuthCritical); ok {
if terr, ok := res.Err.(readPublisherErrAuthCritical); ok {
// wait some seconds to stop brute force attacks
<-time.After(pauseAfterAuthError)
<-time.After(rtmpConnPauseAfterAuthError)
return errors.New(terr.Message)
}
return res.Err
@ -420,8 +414,8 @@ func (c *Conn) runPublish(ctx context.Context) error { @@ -420,8 +414,8 @@ func (c *Conn) runPublish(ctx context.Context) error {
// disable write deadline
c.conn.NetConn().SetWriteDeadline(time.Time{})
rresc := make(chan readpublisher.RecordRes)
c.path.OnReadPublisherRecord(readpublisher.RecordReq{Author: c, Res: rresc})
rresc := make(chan readPublisherRecordRes)
c.path.OnReadPublisherRecord(readPublisherRecordReq{Author: c, Res: rresc})
rres := <-rresc
if rres.Err != nil {
@ -448,7 +442,7 @@ func (c *Conn) runPublish(ctx context.Context) error { @@ -448,7 +442,7 @@ func (c *Conn) runPublish(ctx context.Context) error {
})
}
defer func(path readpublisher.Path) {
defer func(path readPublisherPath) {
if path.Conf().RunOnPublish != "" {
onPublishCmd.Close()
}
@ -526,14 +520,14 @@ func (c *Conn) runPublish(ctx context.Context) error { @@ -526,14 +520,14 @@ func (c *Conn) runPublish(ctx context.Context) error {
}
}
func (c *Conn) validateCredentials(
func (c *rtmpConn) validateCredentials(
pathUser string,
pathPass string,
query url.Values,
) error {
if query.Get("user") != pathUser ||
query.Get("pass") != pathPass {
return readpublisher.ErrAuthCritical{
return readPublisherErrAuthCritical{
Message: "wrong username or password",
}
}
@ -542,8 +536,8 @@ func (c *Conn) validateCredentials( @@ -542,8 +536,8 @@ func (c *Conn) validateCredentials(
}
// OnFrame implements path.Reader.
func (c *Conn) OnFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
func (c *rtmpConn) OnFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
if streamType == gortsplib.StreamTypeRTP {
c.ringBuffer.Push(trackIDPayloadPair{trackID, payload})
c.ringBuffer.Push(rtmpConnTrackIDPayloadPair{trackID, payload})
}
}

55
internal/rtmpserver/server.go → internal/core/rtmp_server.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package rtmpserver
package core
import (
"context"
@ -7,41 +7,35 @@ import ( @@ -7,41 +7,35 @@ import (
"time"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/pathman"
"github.com/aler9/rtsp-simple-server/internal/rtmpconn"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
// Parent is implemented by program.
type Parent interface {
type rtmpServerParent interface {
Log(logger.Level, string, ...interface{})
}
// Server is a RTMP server.
type Server struct {
type rtmpServer struct {
readTimeout time.Duration
writeTimeout time.Duration
readBufferCount int
rtspAddress string
runOnConnect string
runOnConnectRestart bool
stats *stats.Stats
pathMan *pathman.PathManager
parent Parent
stats *stats
pathMan *pathManager
parent rtmpServerParent
ctx context.Context
ctxCancel func()
wg sync.WaitGroup
l net.Listener
conns map[*rtmpconn.Conn]struct{}
conns map[*rtmpConn]struct{}
// in
connClose chan *rtmpconn.Conn
connClose chan *rtmpConn
}
// New allocates a Server.
func New(
ctxParent context.Context,
func newRTMPServer(
parentCtx context.Context,
address string,
readTimeout time.Duration,
writeTimeout time.Duration,
@ -49,17 +43,17 @@ func New( @@ -49,17 +43,17 @@ func New(
rtspAddress string,
runOnConnect string,
runOnConnectRestart bool,
stats *stats.Stats,
pathMan *pathman.PathManager,
parent Parent) (*Server, error) {
stats *stats,
pathMan *pathManager,
parent rtmpServerParent) (*rtmpServer, error) {
l, err := net.Listen("tcp", address)
if err != nil {
return nil, err
}
ctx, ctxCancel := context.WithCancel(ctxParent)
ctx, ctxCancel := context.WithCancel(parentCtx)
s := &Server{
s := &rtmpServer{
readTimeout: readTimeout,
writeTimeout: writeTimeout,
readBufferCount: readBufferCount,
@ -72,8 +66,8 @@ func New( @@ -72,8 +66,8 @@ func New(
ctx: ctx,
ctxCancel: ctxCancel,
l: l,
conns: make(map[*rtmpconn.Conn]struct{}),
connClose: make(chan *rtmpconn.Conn),
conns: make(map[*rtmpConn]struct{}),
connClose: make(chan *rtmpConn),
}
s.Log(logger.Info, "listener opened on %s", address)
@ -84,18 +78,16 @@ func New( @@ -84,18 +78,16 @@ func New(
return s, nil
}
// Log is the main logging function.
func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
func (s *rtmpServer) Log(level logger.Level, format string, args ...interface{}) {
s.parent.Log(level, "[RTMP] "+format, append([]interface{}{}, args...)...)
}
// Close closes a Server.
func (s *Server) Close() {
func (s *rtmpServer) close() {
s.ctxCancel()
s.wg.Wait()
}
func (s *Server) run() {
func (s *rtmpServer) run() {
defer s.wg.Done()
s.wg.Add(1)
@ -132,7 +124,7 @@ outer: @@ -132,7 +124,7 @@ outer:
break outer
case nconn := <-connNew:
c := rtmpconn.New(
c := newRTMPConn(
s.ctx,
s.rtspAddress,
s.readTimeout,
@ -167,14 +159,13 @@ outer: @@ -167,14 +159,13 @@ outer:
}
}
func (s *Server) doConnClose(c *rtmpconn.Conn) {
func (s *rtmpServer) doConnClose(c *rtmpConn) {
delete(s.conns, c)
c.ParentClose()
c.Close()
}
// OnConnClose is called by rtmpconn.Conn.
func (s *Server) OnConnClose(c *rtmpconn.Conn) {
func (s *rtmpServer) OnConnClose(c *rtmpConn) {
select {
case s.connClose <- c:
case <-s.ctx.Done():

22
main_rtmpreadpub_test.go → internal/core/rtmp_server_test.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package main
package core
import (
"testing"
@ -7,13 +7,13 @@ import ( @@ -7,13 +7,13 @@ import (
"github.com/stretchr/testify/require"
)
func TestClientRTMPPublish(t *testing.T) {
func TestRTMPServerPublish(t *testing.T) {
for _, source := range []string{
"videoaudio",
"video",
} {
t.Run(source, func(t *testing.T) {
p, ok := testProgram("hlsDisable: yes\n")
p, ok := newInstance("hlsDisable: yes\n")
require.Equal(t, true, ok)
defer p.close()
@ -44,8 +44,8 @@ func TestClientRTMPPublish(t *testing.T) { @@ -44,8 +44,8 @@ func TestClientRTMPPublish(t *testing.T) {
}
}
func TestClientRTMPRead(t *testing.T) {
p, ok := testProgram("hlsDisable: yes\n")
func TestRTMPServerRead(t *testing.T) {
p, ok := newInstance("hlsDisable: yes\n")
require.Equal(t, true, ok)
defer p.close()
@ -73,9 +73,9 @@ func TestClientRTMPRead(t *testing.T) { @@ -73,9 +73,9 @@ func TestClientRTMPRead(t *testing.T) {
require.Equal(t, 0, cnt2.wait())
}
func TestClientRTMPAuth(t *testing.T) {
func TestRTMPServerAuth(t *testing.T) {
t.Run("publish", func(t *testing.T) {
p, ok := testProgram("rtspDisable: yes\n" +
p, ok := newInstance("rtspDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +
@ -110,7 +110,7 @@ func TestClientRTMPAuth(t *testing.T) { @@ -110,7 +110,7 @@ func TestClientRTMPAuth(t *testing.T) {
})
t.Run("read", func(t *testing.T) {
p, ok := testProgram("rtspDisable: yes\n" +
p, ok := newInstance("rtspDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +
@ -145,9 +145,9 @@ func TestClientRTMPAuth(t *testing.T) { @@ -145,9 +145,9 @@ func TestClientRTMPAuth(t *testing.T) {
})
}
func TestClientRTMPAuthFail(t *testing.T) {
func TestRTMPServerAuthFail(t *testing.T) {
t.Run("publish", func(t *testing.T) {
p, ok := testProgram("rtspDisable: yes\n" +
p, ok := newInstance("rtspDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +
@ -181,7 +181,7 @@ func TestClientRTMPAuthFail(t *testing.T) { @@ -181,7 +181,7 @@ func TestClientRTMPAuthFail(t *testing.T) {
})
t.Run("read", func(t *testing.T) {
p, ok := testProgram("rtspDisable: yes\n" +
p, ok := newInstance("rtspDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +

57
internal/rtmpsource/source.go → internal/core/rtmp_source.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package rtmpsource
package core
import (
"context"
@ -16,47 +16,42 @@ import ( @@ -16,47 +16,42 @@ import (
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/rtcpsenderset"
"github.com/aler9/rtsp-simple-server/internal/rtmp"
"github.com/aler9/rtsp-simple-server/internal/source"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
const (
retryPause = 5 * time.Second
rtmpSourceRetryPause = 5 * time.Second
)
// Parent is implemented by path.Path.
type Parent interface {
type rtmpSourceParent interface {
Log(logger.Level, string, ...interface{})
OnExtSourceSetReady(req source.ExtSetReadyReq)
OnExtSourceSetNotReady(req source.ExtSetNotReadyReq)
OnsourceExternalSetReady(req sourceExtSetReadyReq)
OnsourceExternalSetNotReady(req sourceExtSetNotReadyReq)
OnFrame(int, gortsplib.StreamType, []byte)
}
// Source is a RTMP external source.
type Source struct {
type rtmpSource struct {
ur string
readTimeout time.Duration
writeTimeout time.Duration
wg *sync.WaitGroup
stats *stats.Stats
parent Parent
stats *stats
parent rtmpSourceParent
ctx context.Context
ctxCancel func()
}
// New allocates a Source.
func New(
ctxParent context.Context,
func newRTMPSource(
parentCtx context.Context,
ur string,
readTimeout time.Duration,
writeTimeout time.Duration,
wg *sync.WaitGroup,
stats *stats.Stats,
parent Parent) *Source {
ctx, ctxCancel := context.WithCancel(ctxParent)
stats *stats,
parent rtmpSourceParent) *rtmpSource {
ctx, ctxCancel := context.WithCancel(parentCtx)
s := &Source{
s := &rtmpSource{
ur: ur,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
@ -77,23 +72,23 @@ func New( @@ -77,23 +72,23 @@ func New(
}
// Close closes a Source.
func (s *Source) Close() {
func (s *rtmpSource) Close() {
atomic.AddInt64(s.stats.CountSourcesRTMPRunning, -1)
s.log(logger.Info, "stopped")
s.ctxCancel()
}
// IsSource implements source.Source.
func (s *Source) IsSource() {}
// IsSource implements source.
func (s *rtmpSource) IsSource() {}
// IsExtSource implements source.ExtSource.
func (s *Source) IsExtSource() {}
// IsSourceExternal implements sourceExternal.
func (s *rtmpSource) IsSourceExternal() {}
func (s *Source) log(level logger.Level, format string, args ...interface{}) {
func (s *rtmpSource) log(level logger.Level, format string, args ...interface{}) {
s.parent.Log(level, "[rtmp source] "+format, args...)
}
func (s *Source) run() {
func (s *rtmpSource) run() {
defer s.wg.Done()
for {
@ -104,7 +99,7 @@ func (s *Source) run() { @@ -104,7 +99,7 @@ func (s *Source) run() {
}
select {
case <-time.After(retryPause):
case <-time.After(rtmpSourceRetryPause):
return true
case <-s.ctx.Done():
return false
@ -118,7 +113,7 @@ func (s *Source) run() { @@ -118,7 +113,7 @@ func (s *Source) run() {
s.ctxCancel()
}
func (s *Source) runInner() bool {
func (s *rtmpSource) runInner() bool {
innerCtx, innerCtxCancel := context.WithCancel(s.ctx)
runErr := make(chan error)
@ -173,8 +168,8 @@ func (s *Source) runInner() bool { @@ -173,8 +168,8 @@ func (s *Source) runInner() bool {
s.log(logger.Info, "ready")
cres := make(chan source.ExtSetReadyRes)
s.parent.OnExtSourceSetReady(source.ExtSetReadyReq{
cres := make(chan sourceExtSetReadyRes)
s.parent.OnsourceExternalSetReady(sourceExtSetReadyReq{
Tracks: tracks,
Res: cres,
})
@ -182,7 +177,7 @@ func (s *Source) runInner() bool { @@ -182,7 +177,7 @@ func (s *Source) runInner() bool {
defer func() {
res := make(chan struct{})
s.parent.OnExtSourceSetNotReady(source.ExtSetNotReadyReq{
s.parent.OnsourceExternalSetNotReady(sourceExtSetNotReadyReq{
Res: res,
})
<-res

6
main_rtmpsource_test.go → internal/core/rtmp_source_test.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package main
package core
import (
"testing"
@ -7,7 +7,7 @@ import ( @@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestSourceRTMP(t *testing.T) {
func TestRTMPSource(t *testing.T) {
for _, source := range []string{
"videoaudio",
"video",
@ -32,7 +32,7 @@ func TestSourceRTMP(t *testing.T) { @@ -32,7 +32,7 @@ func TestSourceRTMP(t *testing.T) {
time.Sleep(1 * time.Second)
p, ok := testProgram("hlsDisable: yes\n" +
p, ok := newInstance("hlsDisable: yes\n" +
"rtmpDisable: yes\n" +
"paths:\n" +
" proxied:\n" +

66
internal/rtspconn/conn.go → internal/core/rtsp_conn.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package rtspconn
package core
import (
"errors"
@ -14,12 +14,10 @@ import ( @@ -14,12 +14,10 @@ import (
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/readpublisher"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
const (
pauseAfterAuthError = 2 * time.Second
rtspConnPauseAfterAuthError = 2 * time.Second
)
func isTeardownErr(err error) bool {
@ -32,26 +30,23 @@ func isTerminatedErr(err error) bool { @@ -32,26 +30,23 @@ func isTerminatedErr(err error) bool {
return ok
}
// PathMan is implemented by pathman.PathMan.
type PathMan interface {
OnReadPublisherDescribe(readpublisher.DescribeReq)
type rtspConnPathMan interface {
OnReadPublisherDescribe(readPublisherDescribeReq)
}
// Parent is implemented by rtspserver.Server.
type Parent interface {
type rtspConnParent interface {
Log(logger.Level, string, ...interface{})
}
// Conn is a RTSP server-side connection.
type Conn struct {
type rtspConn struct {
rtspAddress string
readTimeout time.Duration
runOnConnect string
runOnConnectRestart bool
pathMan PathMan
stats *stats.Stats
pathMan rtspConnPathMan
stats *stats
conn *gortsplib.ServerConn
parent Parent
parent rtspConnParent
onConnectCmd *externalcmd.Cmd
authUser string
@ -60,17 +55,16 @@ type Conn struct { @@ -60,17 +55,16 @@ type Conn struct {
authFailures int
}
// New allocates a Conn.
func New(
func newRTSPConn(
rtspAddress string,
readTimeout time.Duration,
runOnConnect string,
runOnConnectRestart bool,
pathMan PathMan,
stats *stats.Stats,
pathMan rtspConnPathMan,
stats *stats,
conn *gortsplib.ServerConn,
parent Parent) *Conn {
c := &Conn{
parent rtspConnParent) *rtspConn {
c := &rtspConn{
rtspAddress: rtspAddress,
readTimeout: readTimeout,
runOnConnect: runOnConnect,
@ -95,7 +89,7 @@ func New( @@ -95,7 +89,7 @@ func New(
}
// ParentClose closes a Conn.
func (c *Conn) ParentClose(err error) {
func (c *rtspConn) ParentClose(err error) {
if err != io.EOF && !isTeardownErr(err) && !isTerminatedErr(err) {
c.log(logger.Info, "ERR: %v", err)
}
@ -107,33 +101,33 @@ func (c *Conn) ParentClose(err error) { @@ -107,33 +101,33 @@ func (c *Conn) ParentClose(err error) {
}
}
func (c *Conn) log(level logger.Level, format string, args ...interface{}) {
func (c *rtspConn) log(level logger.Level, format string, args ...interface{}) {
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.conn.NetConn().RemoteAddr()}, args...)...)
}
// Conn returns the RTSP connection.
func (c *Conn) Conn() *gortsplib.ServerConn {
func (c *rtspConn) Conn() *gortsplib.ServerConn {
return c.conn
}
func (c *Conn) ip() net.IP {
func (c *rtspConn) ip() net.IP {
return c.conn.NetConn().RemoteAddr().(*net.TCPAddr).IP
}
// OnRequest is called by rtspserver.Server.
func (c *Conn) OnRequest(req *base.Request) {
func (c *rtspConn) OnRequest(req *base.Request) {
c.log(logger.Debug, "[c->s] %v", req)
}
// OnResponse is called by rtspserver.Server.
func (c *Conn) OnResponse(res *base.Response) {
func (c *rtspConn) OnResponse(res *base.Response) {
c.log(logger.Debug, "[s->c] %v", res)
}
// OnDescribe is called by rtspserver.Server.
func (c *Conn) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
resc := make(chan readpublisher.DescribeRes)
c.pathMan.OnReadPublisherDescribe(readpublisher.DescribeReq{
func (c *rtspConn) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
resc := make(chan readPublisherDescribeRes)
c.pathMan.OnReadPublisherDescribe(readPublisherDescribeReq{
PathName: ctx.Path,
URL: ctx.Req.URL,
IP: c.ip(),
@ -146,16 +140,16 @@ func (c *Conn) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Resp @@ -146,16 +140,16 @@ func (c *Conn) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Resp
if res.Err != nil {
switch terr := res.Err.(type) {
case readpublisher.ErrAuthNotCritical:
case readPublisherErrAuthNotCritical:
return terr.Response, nil, nil
case readpublisher.ErrAuthCritical:
case readPublisherErrAuthCritical:
// wait some seconds to stop brute force attacks
<-time.After(pauseAfterAuthError)
<-time.After(rtspConnPauseAfterAuthError)
return terr.Response, nil, errors.New(terr.Message)
case readpublisher.ErrNoOnePublishing:
case readPublisherErrNoOnePublishing:
return &base.Response{
StatusCode: base.StatusNotFound,
}, nil, res.Err
@ -182,7 +176,7 @@ func (c *Conn) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Resp @@ -182,7 +176,7 @@ func (c *Conn) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Resp
}
// ValidateCredentials allows to validate the credentials of a path.
func (c *Conn) ValidateCredentials(
func (c *rtspConn) ValidateCredentials(
authMethods []headers.AuthMethod,
pathUser string,
pathPass string,
@ -220,7 +214,7 @@ func (c *Conn) ValidateCredentials( @@ -220,7 +214,7 @@ func (c *Conn) ValidateCredentials(
// 4) with password and username
// therefore we must allow up to 3 failures
if c.authFailures > 3 {
return readpublisher.ErrAuthCritical{
return readPublisherErrAuthCritical{
Message: "unauthorized: " + err.Error(),
Response: &base.Response{
StatusCode: base.StatusUnauthorized,
@ -232,7 +226,7 @@ func (c *Conn) ValidateCredentials( @@ -232,7 +226,7 @@ func (c *Conn) ValidateCredentials(
c.log(logger.Debug, "WARN: unauthorized: %s", err)
}
return readpublisher.ErrAuthNotCritical{
return readPublisherErrAuthNotCritical{
Response: &base.Response{
StatusCode: base.StatusUnauthorized,
Header: base.Header{

81
internal/rtspserver/server.go → internal/core/rtsp_server.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package rtspserver
package core
import (
"context"
@ -14,13 +14,9 @@ import ( @@ -14,13 +14,9 @@ import (
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/pathman"
"github.com/aler9/rtsp-simple-server/internal/rtspconn"
"github.com/aler9/rtsp-simple-server/internal/rtspsession"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
func newSessionVisualID(sessions map[*gortsplib.ServerSession]*rtspsession.Session) (string, error) {
func newSessionVisualID(sessions map[*gortsplib.ServerSession]*rtspSession) (string, error) {
for {
b := make([]byte, 4)
_, err := rand.Read(b)
@ -44,35 +40,32 @@ func newSessionVisualID(sessions map[*gortsplib.ServerSession]*rtspsession.Sessi @@ -44,35 +40,32 @@ func newSessionVisualID(sessions map[*gortsplib.ServerSession]*rtspsession.Sessi
}
}
// Parent is implemented by program.
type Parent interface {
type rtspServerParent interface {
Log(logger.Level, string, ...interface{})
}
// Server is a RTSP server.
type Server struct {
type rtspServer struct {
readTimeout time.Duration
isTLS bool
rtspAddress string
protocols map[conf.Protocol]struct{}
runOnConnect string
runOnConnectRestart bool
stats *stats.Stats
pathMan *pathman.PathManager
parent Parent
stats *stats
pathMan *pathManager
parent rtspServerParent
ctx context.Context
ctxCancel func()
wg sync.WaitGroup
srv *gortsplib.Server
mutex sync.RWMutex
conns map[*gortsplib.ServerConn]*rtspconn.Conn
sessions map[*gortsplib.ServerSession]*rtspsession.Session
conns map[*gortsplib.ServerConn]*rtspConn
sessions map[*gortsplib.ServerSession]*rtspSession
}
// New allocates a Server.
func New(
ctxParent context.Context,
func newRTSPServer(
parentCtx context.Context,
address string,
readTimeout time.Duration,
writeTimeout time.Duration,
@ -92,12 +85,12 @@ func New( @@ -92,12 +85,12 @@ func New(
protocols map[conf.Protocol]struct{},
runOnConnect string,
runOnConnectRestart bool,
stats *stats.Stats,
pathMan *pathman.PathManager,
parent Parent) (*Server, error) {
ctx, ctxCancel := context.WithCancel(ctxParent)
stats *stats,
pathMan *pathManager,
parent rtspServerParent) (*rtspServer, error) {
ctx, ctxCancel := context.WithCancel(parentCtx)
s := &Server{
s := &rtspServer{
readTimeout: readTimeout,
isTLS: isTLS,
rtspAddress: rtspAddress,
@ -107,8 +100,8 @@ func New( @@ -107,8 +100,8 @@ func New(
parent: parent,
ctx: ctx,
ctxCancel: ctxCancel,
conns: make(map[*gortsplib.ServerConn]*rtspconn.Conn),
sessions: make(map[*gortsplib.ServerSession]*rtspsession.Session),
conns: make(map[*gortsplib.ServerConn]*rtspConn),
sessions: make(map[*gortsplib.ServerSession]*rtspSession),
}
s.srv = &gortsplib.Server{
@ -160,8 +153,7 @@ func New( @@ -160,8 +153,7 @@ func New(
return s, nil
}
// Log is the main logging function.
func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
func (s *rtspServer) Log(level logger.Level, format string, args ...interface{}) {
label := func() string {
if s.isTLS {
return "RTSPS"
@ -171,13 +163,12 @@ func (s *Server) Log(level logger.Level, format string, args ...interface{}) { @@ -171,13 +163,12 @@ func (s *Server) Log(level logger.Level, format string, args ...interface{}) {
s.parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...)
}
// Close closes a Server.
func (s *Server) Close() {
func (s *rtspServer) close() {
s.ctxCancel()
s.wg.Wait()
}
func (s *Server) run() {
func (s *rtspServer) run() {
defer s.wg.Done()
s.wg.Add(1)
@ -209,8 +200,8 @@ outer: @@ -209,8 +200,8 @@ outer:
}
// OnConnOpen implements gortsplib.ServerHandlerOnConnOpen.
func (s *Server) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
c := rtspconn.New(
func (s *rtspServer) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
c := newRTSPConn(
s.rtspAddress,
s.readTimeout,
s.runOnConnect,
@ -226,7 +217,7 @@ func (s *Server) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) { @@ -226,7 +217,7 @@ func (s *Server) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
}
// OnConnClose implements gortsplib.ServerHandlerOnConnClose.
func (s *Server) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
func (s *rtspServer) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
s.mutex.Lock()
c := s.conns[ctx.Conn]
delete(s.conns, ctx.Conn)
@ -236,7 +227,7 @@ func (s *Server) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) { @@ -236,7 +227,7 @@ func (s *Server) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
}
// OnRequest implements gortsplib.ServerHandlerOnRequest.
func (s *Server) OnRequest(sc *gortsplib.ServerConn, req *base.Request) {
func (s *rtspServer) OnRequest(sc *gortsplib.ServerConn, req *base.Request) {
s.mutex.Lock()
c := s.conns[sc]
s.mutex.Unlock()
@ -245,7 +236,7 @@ func (s *Server) OnRequest(sc *gortsplib.ServerConn, req *base.Request) { @@ -245,7 +236,7 @@ func (s *Server) OnRequest(sc *gortsplib.ServerConn, req *base.Request) {
}
// OnResponse implements gortsplib.ServerHandlerOnResponse.
func (s *Server) OnResponse(sc *gortsplib.ServerConn, res *base.Response) {
func (s *rtspServer) OnResponse(sc *gortsplib.ServerConn, res *base.Response) {
s.mutex.Lock()
c := s.conns[sc]
s.mutex.Unlock()
@ -254,14 +245,14 @@ func (s *Server) OnResponse(sc *gortsplib.ServerConn, res *base.Response) { @@ -254,14 +245,14 @@ func (s *Server) OnResponse(sc *gortsplib.ServerConn, res *base.Response) {
}
// OnSessionOpen implements gortsplib.ServerHandlerOnSessionOpen.
func (s *Server) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
func (s *rtspServer) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
s.mutex.Lock()
// do not use ss.ID() in logs, since it allows to take ownership of a session
// use a new random ID
visualID, _ := newSessionVisualID(s.sessions)
se := rtspsession.New(
se := newRTSPSession(
s.rtspAddress,
s.protocols,
visualID,
@ -275,7 +266,7 @@ func (s *Server) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) { @@ -275,7 +266,7 @@ func (s *Server) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
}
// OnSessionClose implements gortsplib.ServerHandlerOnSessionClose.
func (s *Server) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
func (s *rtspServer) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
s.mutex.Lock()
se := s.sessions[ctx.Session]
delete(s.sessions, ctx.Session)
@ -285,7 +276,7 @@ func (s *Server) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) { @@ -285,7 +276,7 @@ func (s *Server) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
}
// OnDescribe implements gortsplib.ServerHandlerOnDescribe.
func (s *Server) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
func (s *rtspServer) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
s.mutex.RLock()
c := s.conns[ctx.Conn]
s.mutex.RUnlock()
@ -293,7 +284,7 @@ func (s *Server) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Re @@ -293,7 +284,7 @@ func (s *Server) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Re
}
// OnAnnounce implements gortsplib.ServerHandlerOnAnnounce.
func (s *Server) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
func (s *rtspServer) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
s.mutex.RLock()
c := s.conns[ctx.Conn]
se := s.sessions[ctx.Session]
@ -302,7 +293,7 @@ func (s *Server) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Re @@ -302,7 +293,7 @@ func (s *Server) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Re
}
// OnSetup implements gortsplib.ServerHandlerOnSetup.
func (s *Server) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
func (s *rtspServer) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
s.mutex.RLock()
c := s.conns[ctx.Conn]
se := s.sessions[ctx.Session]
@ -311,7 +302,7 @@ func (s *Server) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response @@ -311,7 +302,7 @@ func (s *Server) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response
}
// OnPlay implements gortsplib.ServerHandlerOnPlay.
func (s *Server) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
func (s *rtspServer) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
s.mutex.RLock()
se := s.sessions[ctx.Session]
s.mutex.RUnlock()
@ -319,7 +310,7 @@ func (s *Server) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, @@ -319,7 +310,7 @@ func (s *Server) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response,
}
// OnRecord implements gortsplib.ServerHandlerOnRecord.
func (s *Server) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
func (s *rtspServer) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
s.mutex.RLock()
se := s.sessions[ctx.Session]
s.mutex.RUnlock()
@ -327,7 +318,7 @@ func (s *Server) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Respon @@ -327,7 +318,7 @@ func (s *Server) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Respon
}
// OnPause implements gortsplib.ServerHandlerOnPause.
func (s *Server) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
func (s *rtspServer) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
s.mutex.RLock()
se := s.sessions[ctx.Session]
s.mutex.RUnlock()
@ -335,7 +326,7 @@ func (s *Server) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response @@ -335,7 +326,7 @@ func (s *Server) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response
}
// OnFrame implements gortsplib.ServerHandlerOnFrame.
func (s *Server) OnFrame(ctx *gortsplib.ServerHandlerOnFrameCtx) {
func (s *rtspServer) OnFrame(ctx *gortsplib.ServerHandlerOnFrameCtx) {
s.mutex.RLock()
se := s.sessions[ctx.Session]
s.mutex.RUnlock()

58
main_rtspreadpub_test.go → internal/core/rtsp_server_test.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package main
package core
import (
"bufio"
@ -24,7 +24,7 @@ func mustParseURL(s string) *base.URL { @@ -24,7 +24,7 @@ func mustParseURL(s string) *base.URL {
return u
}
func TestClientRTSPPublishRead(t *testing.T) {
func TestRTSPServerPublishRead(t *testing.T) {
for _, ca := range []struct {
publisherSoft string
publisherProto string
@ -54,7 +54,7 @@ func TestClientRTSPPublishRead(t *testing.T) { @@ -54,7 +54,7 @@ func TestClientRTSPPublishRead(t *testing.T) {
proto = "rtsp"
port = "8554"
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"readTimeout: 20s\n")
require.Equal(t, true, ok)
@ -72,7 +72,7 @@ func TestClientRTSPPublishRead(t *testing.T) { @@ -72,7 +72,7 @@ func TestClientRTSPPublishRead(t *testing.T) {
require.NoError(t, err)
defer os.Remove(serverKeyFpath)
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"readTimeout: 20s\n" +
"protocols: [tcp]\n" +
@ -197,9 +197,9 @@ func TestClientRTSPPublishRead(t *testing.T) { @@ -197,9 +197,9 @@ func TestClientRTSPPublishRead(t *testing.T) {
}
}
func TestClientRTSPAuth(t *testing.T) {
func TestRTSPServerAuth(t *testing.T) {
t.Run("publish", func(t *testing.T) {
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +
@ -240,7 +240,7 @@ func TestClientRTSPAuth(t *testing.T) { @@ -240,7 +240,7 @@ func TestClientRTSPAuth(t *testing.T) {
"vlc",
} {
t.Run("read_"+soft, func(t *testing.T) {
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +
@ -288,7 +288,7 @@ func TestClientRTSPAuth(t *testing.T) { @@ -288,7 +288,7 @@ func TestClientRTSPAuth(t *testing.T) {
}
t.Run("hashed", func(t *testing.T) {
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +
@ -322,7 +322,7 @@ func TestClientRTSPAuth(t *testing.T) { @@ -322,7 +322,7 @@ func TestClientRTSPAuth(t *testing.T) {
})
}
func TestClientRTSPAuthFail(t *testing.T) {
func TestRTSPServerAuthFail(t *testing.T) {
for _, ca := range []struct {
name string
user string
@ -345,7 +345,7 @@ func TestClientRTSPAuthFail(t *testing.T) { @@ -345,7 +345,7 @@ func TestClientRTSPAuthFail(t *testing.T) {
},
} {
t.Run("publish_"+ca.name, func(t *testing.T) {
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +
@ -387,7 +387,7 @@ func TestClientRTSPAuthFail(t *testing.T) { @@ -387,7 +387,7 @@ func TestClientRTSPAuthFail(t *testing.T) {
},
} {
t.Run("read_"+ca.name, func(t *testing.T) {
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +
@ -404,7 +404,7 @@ func TestClientRTSPAuthFail(t *testing.T) { @@ -404,7 +404,7 @@ func TestClientRTSPAuthFail(t *testing.T) {
}
t.Run("ip", func(t *testing.T) {
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" all:\n" +
@ -423,12 +423,12 @@ func TestClientRTSPAuthFail(t *testing.T) { @@ -423,12 +423,12 @@ func TestClientRTSPAuthFail(t *testing.T) {
})
}
func TestClientRTSPAutomaticProtocol(t *testing.T) {
func TestRTSPServerAutomaticProtocol(t *testing.T) {
for _, source := range []string{
"ffmpeg",
} {
t.Run(source, func(t *testing.T) {
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"protocols: [tcp]\n")
require.Equal(t, true, ok)
@ -460,7 +460,7 @@ func TestClientRTSPAutomaticProtocol(t *testing.T) { @@ -460,7 +460,7 @@ func TestClientRTSPAutomaticProtocol(t *testing.T) {
}
}
func TestClientRTSPPublisherOverride(t *testing.T) {
func TestRTSPServerPublisherOverride(t *testing.T) {
for _, ca := range []string{
"enabled",
"disabled",
@ -473,7 +473,7 @@ func TestClientRTSPPublisherOverride(t *testing.T) { @@ -473,7 +473,7 @@ func TestClientRTSPPublisherOverride(t *testing.T) {
" all:\n" +
" disablePublisherOverride: yes\n"
}
p, ok := testProgram(conf)
p, ok := newInstance(conf)
require.Equal(t, true, ok)
defer p.close()
@ -536,9 +536,9 @@ func TestClientRTSPPublisherOverride(t *testing.T) { @@ -536,9 +536,9 @@ func TestClientRTSPPublisherOverride(t *testing.T) {
}
}
func TestClientRTSPNonCompliantFrameSize(t *testing.T) {
func TestRTSPServerNonCompliantFrameSize(t *testing.T) {
t.Run("publish", func(t *testing.T) {
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"readBufferSize: 4500\n")
require.Equal(t, true, ok)
@ -587,7 +587,7 @@ func TestClientRTSPNonCompliantFrameSize(t *testing.T) { @@ -587,7 +587,7 @@ func TestClientRTSPNonCompliantFrameSize(t *testing.T) {
})
t.Run("proxy", func(t *testing.T) {
p1, ok := testProgram("rtmpDisable: yes\n" +
p1, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"protocols: [tcp]\n" +
"readBufferSize: 4500\n")
@ -610,7 +610,7 @@ func TestClientRTSPNonCompliantFrameSize(t *testing.T) { @@ -610,7 +610,7 @@ func TestClientRTSPNonCompliantFrameSize(t *testing.T) {
require.NoError(t, err)
defer source.Close()
p2, ok := testProgram("rtmpDisable: yes\n" +
p2, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"protocols: [tcp]\n" +
"readBufferSize: 4500\n" +
@ -651,8 +651,8 @@ func TestClientRTSPNonCompliantFrameSize(t *testing.T) { @@ -651,8 +651,8 @@ func TestClientRTSPNonCompliantFrameSize(t *testing.T) {
})
}
func TestClientRTSPRedirect(t *testing.T) {
p1, ok := testProgram("rtmpDisable: yes\n" +
func TestRTSPServerRedirect(t *testing.T) {
p1, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" path1:\n" +
@ -688,7 +688,7 @@ func TestClientRTSPRedirect(t *testing.T) { @@ -688,7 +688,7 @@ func TestClientRTSPRedirect(t *testing.T) {
require.Equal(t, 0, cnt2.wait())
}
func TestClientRTSPFallback(t *testing.T) {
func TestRTSPServerFallback(t *testing.T) {
for _, ca := range []string{
"absolute",
"relative",
@ -701,7 +701,7 @@ func TestClientRTSPFallback(t *testing.T) { @@ -701,7 +701,7 @@ func TestClientRTSPFallback(t *testing.T) {
return "/path2"
}()
p1, ok := testProgram("rtmpDisable: yes\n" +
p1, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" path1:\n" +
@ -738,11 +738,11 @@ func TestClientRTSPFallback(t *testing.T) { @@ -738,11 +738,11 @@ func TestClientRTSPFallback(t *testing.T) {
}
}
func TestClientRTSPRunOnDemand(t *testing.T) {
func TestRTSPServerRunOnDemand(t *testing.T) {
doneFile := filepath.Join(os.TempDir(), "ondemand_done")
onDemandFile, err := writeTempFile([]byte(fmt.Sprintf(`#!/bin/sh
trap 'touch %s; [ -z "$(jobs -p)" ] || kill $(jobs -p)' INT
ffmpeg -hide_banner -loglevel error -re -i testimages/ffmpeg/emptyvideo.mkv -c copy -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH &
ffmpeg -hide_banner -loglevel error -re -i ../../testimages/ffmpeg/emptyvideo.mkv -c copy -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH &
wait
`, doneFile)))
require.NoError(t, err)
@ -754,7 +754,7 @@ wait @@ -754,7 +754,7 @@ wait
t.Run("describe", func(t *testing.T) {
defer os.Remove(doneFile)
p1, ok := testProgram(fmt.Sprintf("rtmpDisable: yes\n"+
p1, ok := newInstance(fmt.Sprintf("rtmpDisable: yes\n"+
"hlsDisable: yes\n"+
"paths:\n"+
" all:\n"+
@ -796,7 +796,7 @@ wait @@ -796,7 +796,7 @@ wait
t.Run("describe and setup", func(t *testing.T) {
defer os.Remove(doneFile)
p1, ok := testProgram(fmt.Sprintf("rtmpDisable: yes\n"+
p1, ok := newInstance(fmt.Sprintf("rtmpDisable: yes\n"+
"hlsDisable: yes\n"+
"paths:\n"+
" all:\n"+
@ -863,7 +863,7 @@ wait @@ -863,7 +863,7 @@ wait
t.Run("setup", func(t *testing.T) {
defer os.Remove(doneFile)
p1, ok := testProgram(fmt.Sprintf("rtmpDisable: yes\n"+
p1, ok := newInstance(fmt.Sprintf("rtmpDisable: yes\n"+
"hlsDisable: yes\n"+
"paths:\n"+
" all:\n"+

98
internal/rtspsession/session.go → internal/core/rtsp_session.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package rtspsession
package core
import (
"errors"
@ -13,50 +13,44 @@ import ( @@ -13,50 +13,44 @@ import (
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/readpublisher"
"github.com/aler9/rtsp-simple-server/internal/rtspconn"
)
const (
pauseAfterAuthError = 2 * time.Second
)
// PathMan is implemented by pathman.PathMan.
type PathMan interface {
OnReadPublisherSetupPlay(readpublisher.SetupPlayReq)
OnReadPublisherAnnounce(readpublisher.AnnounceReq)
type rtspSessionPathMan interface {
OnReadPublisherSetupPlay(readPublisherSetupPlayReq)
OnReadPublisherAnnounce(readPublisherAnnounceReq)
}
// Parent is implemented by rtspserver.Server.
type Parent interface {
type rtspSessionParent interface {
Log(logger.Level, string, ...interface{})
}
// Session is a RTSP server-side session.
type Session struct {
type rtspSession struct {
rtspAddress string
protocols map[conf.Protocol]struct{}
visualID string
ss *gortsplib.ServerSession
pathMan PathMan
parent Parent
pathMan rtspSessionPathMan
parent rtspSessionParent
path readpublisher.Path
path readPublisherPath
setuppedTracks map[int]*gortsplib.Track // read
onReadCmd *externalcmd.Cmd // read
onPublishCmd *externalcmd.Cmd // publish
}
// New allocates a Session.
func New(
func newRTSPSession(
rtspAddress string,
protocols map[conf.Protocol]struct{},
visualID string,
ss *gortsplib.ServerSession,
sc *gortsplib.ServerConn,
pathMan PathMan,
parent Parent) *Session {
s := &Session{
pathMan rtspSessionPathMan,
parent rtspSessionParent) *rtspSession {
s := &rtspSession{
rtspAddress: rtspAddress,
protocols: protocols,
visualID: visualID,
@ -71,7 +65,7 @@ func New( @@ -71,7 +65,7 @@ func New(
}
// ParentClose closes a Session.
func (s *Session) ParentClose() {
func (s *rtspSession) ParentClose() {
switch s.ss.State() {
case gortsplib.ServerSessionStatePlay:
if s.onReadCmd != nil {
@ -86,7 +80,7 @@ func (s *Session) ParentClose() { @@ -86,7 +80,7 @@ func (s *Session) ParentClose() {
if s.path != nil {
res := make(chan struct{})
s.path.OnReadPublisherRemove(readpublisher.RemoveReq{s, res}) //nolint:govet
s.path.OnReadPublisherRemove(readPublisherRemoveReq{s, res}) //nolint:govet
<-res
s.path = nil
}
@ -95,39 +89,39 @@ func (s *Session) ParentClose() { @@ -95,39 +89,39 @@ func (s *Session) ParentClose() {
}
// Close closes a Session.
func (s *Session) Close() {
func (s *rtspSession) Close() {
s.ss.Close()
}
// IsReadPublisher implements readpublisher.ReadPublisher.
func (s *Session) IsReadPublisher() {}
// IsReadPublisher implements readPublisher.
func (s *rtspSession) IsReadPublisher() {}
// IsSource implements source.Source.
func (s *Session) IsSource() {}
// IsSource implements source.
func (s *rtspSession) IsSource() {}
// IsRTSPSession implements path.rtspSession.
func (s *Session) IsRTSPSession() {}
func (s *rtspSession) IsRTSPSession() {}
// VisualID returns the visual ID of the session.
func (s *Session) VisualID() string {
func (s *rtspSession) VisualID() string {
return s.visualID
}
func (s *Session) displayedProtocol() string {
func (s *rtspSession) displayedProtocol() string {
if *s.ss.SetuppedDelivery() == base.StreamDeliveryMulticast {
return "UDP-multicast"
}
return s.ss.SetuppedProtocol().String()
}
func (s *Session) log(level logger.Level, format string, args ...interface{}) {
func (s *rtspSession) log(level logger.Level, format string, args ...interface{}) {
s.parent.Log(level, "[session %s] "+format, append([]interface{}{s.visualID}, args...)...)
}
// OnAnnounce is called by rtspserver.Server.
func (s *Session) OnAnnounce(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
resc := make(chan readpublisher.AnnounceRes)
s.pathMan.OnReadPublisherAnnounce(readpublisher.AnnounceReq{
func (s *rtspSession) OnAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
resc := make(chan readPublisherAnnounceRes)
s.pathMan.OnReadPublisherAnnounce(readPublisherAnnounceReq{
Author: s,
PathName: ctx.Path,
Tracks: ctx.Tracks,
@ -141,10 +135,10 @@ func (s *Session) OnAnnounce(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnAnn @@ -141,10 +135,10 @@ func (s *Session) OnAnnounce(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnAnn
if res.Err != nil {
switch terr := res.Err.(type) {
case readpublisher.ErrAuthNotCritical:
case readPublisherErrAuthNotCritical:
return terr.Response, nil
case readpublisher.ErrAuthCritical:
case readPublisherErrAuthCritical:
// wait some seconds to stop brute force attacks
<-time.After(pauseAfterAuthError)
@ -165,7 +159,7 @@ func (s *Session) OnAnnounce(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnAnn @@ -165,7 +159,7 @@ func (s *Session) OnAnnounce(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnAnn
}
// OnSetup is called by rtspserver.Server.
func (s *Session) OnSetup(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
func (s *rtspSession) OnSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
if ctx.Transport.Protocol == base.StreamProtocolUDP {
if _, ok := s.protocols[conf.ProtocolUDP]; !ok {
return &base.Response{
@ -188,8 +182,8 @@ func (s *Session) OnSetup(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnSetupC @@ -188,8 +182,8 @@ func (s *Session) OnSetup(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnSetupC
switch s.ss.State() {
case gortsplib.ServerSessionStateInitial, gortsplib.ServerSessionStatePrePlay: // play
resc := make(chan readpublisher.SetupPlayRes)
s.pathMan.OnReadPublisherSetupPlay(readpublisher.SetupPlayReq{
resc := make(chan readPublisherSetupPlayRes)
s.pathMan.OnReadPublisherSetupPlay(readPublisherSetupPlayReq{
Author: s,
PathName: ctx.Path,
IP: ctx.Conn.NetConn().RemoteAddr().(*net.TCPAddr).IP,
@ -202,16 +196,16 @@ func (s *Session) OnSetup(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnSetupC @@ -202,16 +196,16 @@ func (s *Session) OnSetup(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnSetupC
if res.Err != nil {
switch terr := res.Err.(type) {
case readpublisher.ErrAuthNotCritical:
case readPublisherErrAuthNotCritical:
return terr.Response, nil, nil
case readpublisher.ErrAuthCritical:
case readPublisherErrAuthCritical:
// wait some seconds to stop brute force attacks
<-time.After(pauseAfterAuthError)
return terr.Response, nil, errors.New(terr.Message)
case readpublisher.ErrNoOnePublishing:
case readPublisherErrNoOnePublishing:
return &base.Response{
StatusCode: base.StatusNotFound,
}, nil, res.Err
@ -248,7 +242,7 @@ func (s *Session) OnSetup(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnSetupC @@ -248,7 +242,7 @@ func (s *Session) OnSetup(c *rtspconn.Conn, ctx *gortsplib.ServerHandlerOnSetupC
}
// OnPlay is called by rtspserver.Server.
func (s *Session) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
func (s *rtspSession) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
h := make(base.Header)
if s.ss.State() == gortsplib.ServerSessionStatePrePlay {
@ -258,8 +252,8 @@ func (s *Session) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, @@ -258,8 +252,8 @@ func (s *Session) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response,
}, fmt.Errorf("path has changed, was '%s', now is '%s'", s.path.Name(), ctx.Path)
}
resc := make(chan readpublisher.PlayRes)
s.path.OnReadPublisherPlay(readpublisher.PlayReq{s, resc}) //nolint:govet
resc := make(chan readPublisherPlayRes)
s.path.OnReadPublisherPlay(readPublisherPlayReq{s, resc}) //nolint:govet
<-resc
tracksLen := len(s.ss.SetuppedTracks())
@ -291,15 +285,15 @@ func (s *Session) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, @@ -291,15 +285,15 @@ func (s *Session) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response,
}
// OnRecord is called by rtspserver.Server.
func (s *Session) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
func (s *rtspSession) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
if ctx.Path != s.path.Name() {
return &base.Response{
StatusCode: base.StatusBadRequest,
}, fmt.Errorf("path has changed, was '%s', now is '%s'", s.path.Name(), ctx.Path)
}
resc := make(chan readpublisher.RecordRes)
s.path.OnReadPublisherRecord(readpublisher.RecordReq{Author: s, Res: resc})
resc := make(chan readPublisherRecordRes)
s.path.OnReadPublisherRecord(readPublisherRecordReq{Author: s, Res: resc})
res := <-resc
if res.Err != nil {
@ -335,7 +329,7 @@ func (s *Session) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Respo @@ -335,7 +329,7 @@ func (s *Session) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Respo
}
// OnPause is called by rtspserver.Server.
func (s *Session) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
func (s *rtspSession) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
switch s.ss.State() {
case gortsplib.ServerSessionStatePlay:
if s.onReadCmd != nil {
@ -343,7 +337,7 @@ func (s *Session) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Respons @@ -343,7 +337,7 @@ func (s *Session) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Respons
}
res := make(chan struct{})
s.path.OnReadPublisherPause(readpublisher.PauseReq{s, res}) //nolint:govet
s.path.OnReadPublisherPause(readPublisherPauseReq{s, res}) //nolint:govet
<-res
case gortsplib.ServerSessionStateRecord:
@ -352,7 +346,7 @@ func (s *Session) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Respons @@ -352,7 +346,7 @@ func (s *Session) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Respons
}
res := make(chan struct{})
s.path.OnReadPublisherPause(readpublisher.PauseReq{s, res}) //nolint:govet
s.path.OnReadPublisherPause(readPublisherPauseReq{s, res}) //nolint:govet
<-res
}
@ -362,12 +356,12 @@ func (s *Session) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Respons @@ -362,12 +356,12 @@ func (s *Session) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Respons
}
// OnFrame implements path.Reader.
func (s *Session) OnFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
func (s *rtspSession) OnFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
s.ss.WriteFrame(trackID, streamType, payload)
}
// OnIncomingFrame is called by rtspserver.Server.
func (s *Session) OnIncomingFrame(ctx *gortsplib.ServerHandlerOnFrameCtx) {
func (s *rtspSession) OnIncomingFrame(ctx *gortsplib.ServerHandlerOnFrameCtx) {
if s.ss.State() != gortsplib.ServerSessionStateRecord {
return
}

58
internal/rtspsource/source.go → internal/core/rtsp_source.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package rtspsource
package core
import (
"context"
@ -15,24 +15,20 @@ import ( @@ -15,24 +15,20 @@ import (
"github.com/aler9/gortsplib/pkg/base"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/source"
"github.com/aler9/rtsp-simple-server/internal/stats"
)
const (
retryPause = 5 * time.Second
rtspSourceRetryPause = 5 * time.Second
)
// Parent is implemented by path.Path.
type Parent interface {
type rtspSourceParent interface {
Log(logger.Level, string, ...interface{})
OnExtSourceSetReady(req source.ExtSetReadyReq)
OnExtSourceSetNotReady(req source.ExtSetNotReadyReq)
OnsourceExternalSetReady(req sourceExtSetReadyReq)
OnsourceExternalSetNotReady(req sourceExtSetNotReadyReq)
OnFrame(int, gortsplib.StreamType, []byte)
}
// Source is a RTSP external source.
type Source struct {
type rtspSource struct {
ur string
proto *gortsplib.ClientProtocol
anyPortEnable bool
@ -42,16 +38,15 @@ type Source struct { @@ -42,16 +38,15 @@ type Source struct {
readBufferCount int
readBufferSize int
wg *sync.WaitGroup
stats *stats.Stats
parent Parent
stats *stats
parent rtspSourceParent
ctx context.Context
ctxCancel func()
}
// New allocates a Source.
func New(
ctxParent context.Context,
func newRTSPSource(
parentCtx context.Context,
ur string,
proto *gortsplib.ClientProtocol,
anyPortEnable bool,
@ -61,11 +56,11 @@ func New( @@ -61,11 +56,11 @@ func New(
readBufferCount int,
readBufferSize int,
wg *sync.WaitGroup,
stats *stats.Stats,
parent Parent) *Source {
ctx, ctxCancel := context.WithCancel(ctxParent)
stats *stats,
parent rtspSourceParent) *rtspSource {
ctx, ctxCancel := context.WithCancel(parentCtx)
s := &Source{
s := &rtspSource{
ur: ur,
proto: proto,
anyPortEnable: anyPortEnable,
@ -90,24 +85,23 @@ func New( @@ -90,24 +85,23 @@ func New(
return s
}
// Close closes a Source.
func (s *Source) Close() {
func (s *rtspSource) Close() {
atomic.AddInt64(s.stats.CountSourcesRTSP, -1)
s.log(logger.Info, "stopped")
s.ctxCancel()
}
// IsSource implements source.Source.
func (s *Source) IsSource() {}
// IsSource implements source.
func (s *rtspSource) IsSource() {}
// IsExtSource implements source.ExtSource.
func (s *Source) IsExtSource() {}
// IsSourceExternal implements sourceExternal.
func (s *rtspSource) IsSourceExternal() {}
func (s *Source) log(level logger.Level, format string, args ...interface{}) {
func (s *rtspSource) log(level logger.Level, format string, args ...interface{}) {
s.parent.Log(level, "[rtsp source] "+format, args...)
}
func (s *Source) run() {
func (s *rtspSource) run() {
defer s.wg.Done()
for {
@ -118,7 +112,7 @@ func (s *Source) run() { @@ -118,7 +112,7 @@ func (s *Source) run() {
}
select {
case <-time.After(retryPause):
case <-time.After(rtspSourceRetryPause):
return true
case <-s.ctx.Done():
return false
@ -132,7 +126,7 @@ func (s *Source) run() { @@ -132,7 +126,7 @@ func (s *Source) run() {
s.ctxCancel()
}
func (s *Source) runInner() bool {
func (s *rtspSource) runInner() bool {
s.log(logger.Debug, "connecting")
client := &gortsplib.Client{
@ -193,8 +187,8 @@ func (s *Source) runInner() bool { @@ -193,8 +187,8 @@ func (s *Source) runInner() bool {
s.log(logger.Info, "ready")
cres := make(chan source.ExtSetReadyRes)
s.parent.OnExtSourceSetReady(source.ExtSetReadyReq{
cres := make(chan sourceExtSetReadyRes)
s.parent.OnsourceExternalSetReady(sourceExtSetReadyReq{
Tracks: conn.Tracks(),
Res: cres,
})
@ -202,7 +196,7 @@ func (s *Source) runInner() bool { @@ -202,7 +196,7 @@ func (s *Source) runInner() bool {
defer func() {
res := make(chan struct{})
s.parent.OnExtSourceSetNotReady(source.ExtSetNotReadyReq{
s.parent.OnsourceExternalSetNotReady(sourceExtSetNotReadyReq{
Res: res,
})
<-res

12
main_rtspsource_test.go → internal/core/rtsp_source_test.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package main
package core
import (
"crypto/tls"
@ -65,7 +65,7 @@ func (sh *testServer) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Respo @@ -65,7 +65,7 @@ func (sh *testServer) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Respo
}, nil
}
func TestSourceRTSP(t *testing.T) {
func TestRTSPSource(t *testing.T) {
for _, source := range []string{
"udp",
"tcp",
@ -101,7 +101,7 @@ func TestSourceRTSP(t *testing.T) { @@ -101,7 +101,7 @@ func TestSourceRTSP(t *testing.T) {
defer s.Close()
if source == "udp" || source == "tcp" {
p, ok := testProgram("paths:\n" +
p, ok := newInstance("paths:\n" +
" proxied:\n" +
" source: rtsp://testuser:testpass@localhost:8555/teststream\n" +
" sourceProtocol: " + source + "\n" +
@ -109,7 +109,7 @@ func TestSourceRTSP(t *testing.T) { @@ -109,7 +109,7 @@ func TestSourceRTSP(t *testing.T) {
require.Equal(t, true, ok)
defer p.close()
} else {
p, ok := testProgram("paths:\n" +
p, ok := newInstance("paths:\n" +
" proxied:\n" +
" source: rtsps://testuser:testpass@localhost:8555/teststream\n" +
" sourceFingerprint: 33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739\n" +
@ -142,14 +142,14 @@ func TestSourceRTSP(t *testing.T) { @@ -142,14 +142,14 @@ func TestSourceRTSP(t *testing.T) {
}
}
func TestSourceRTSPNoPassword(t *testing.T) {
func TestRTSPSourceNoPassword(t *testing.T) {
done := make(chan struct{})
s := gortsplib.Server{Handler: &testServer{user: "testuser", done: done}}
err := s.Start("127.0.0.1:8555")
require.NoError(t, err)
defer s.Close()
p, ok := testProgram("rtmpDisable: yes\n" +
p, ok := newInstance("rtmpDisable: yes\n" +
"hlsDisable: yes\n" +
"paths:\n" +
" proxied:\n" +

26
internal/core/source.go

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package core
import (
"github.com/aler9/gortsplib"
)
type source interface {
IsSource()
}
type sourceExternal interface {
IsSource()
IsSourceExternal()
Close()
}
type sourceExtSetReadyRes struct{}
type sourceExtSetReadyReq struct {
Tracks gortsplib.Tracks
Res chan sourceExtSetReadyRes
}
type sourceExtSetNotReadyReq struct {
Res chan struct{}
}

13
internal/stats/stats.go → internal/core/stats.go

@ -1,12 +1,11 @@ @@ -1,12 +1,11 @@
package stats
package core
func ptrInt64() *int64 {
v := int64(0)
return &v
}
// Stats contains statistics.
type Stats struct {
type stats struct {
// use pointers to avoid a crash on 32bit platforms
// https://github.com/golang/go/issues/9959
CountPublishers *int64
@ -17,9 +16,8 @@ type Stats struct { @@ -17,9 +16,8 @@ type Stats struct {
CountSourcesRTMPRunning *int64
}
// New allocates a Stats.
func New() *Stats {
return &Stats{
func newStats() *stats {
return &stats{
CountPublishers: ptrInt64(),
CountReaders: ptrInt64(),
CountSourcesRTSP: ptrInt64(),
@ -29,6 +27,5 @@ func New() *Stats { @@ -29,6 +27,5 @@ func New() *Stats {
}
}
// Close closes a stats.
func (s *Stats) Close() {
func (s *stats) close() {
}

2
internal/hlsconverter/multiaccessbuffer.go → internal/hls/multiaccessbuffer.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package hlsconverter
package hls
import (
"bytes"

2
internal/hlsconverter/multiaccessbuffer_test.go → internal/hls/multiaccessbuffer_test.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package hlsconverter
package hls
import (
"io"

39
internal/hlsconverter/tsfile.go → internal/hls/tsfile.go

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
package hlsconverter
package hls
import (
"context"
"io"
"strconv"
"time"
@ -12,7 +13,8 @@ import ( @@ -12,7 +13,8 @@ import (
"github.com/aler9/rtsp-simple-server/internal/h264"
)
type tsFile struct {
// TSFile is a MPEG-TS file.
type TSFile struct {
name string
buf *multiAccessBuffer
mux *astits.Muxer
@ -23,8 +25,9 @@ type tsFile struct { @@ -23,8 +25,9 @@ type tsFile struct {
maxPTS time.Duration
}
func newTSFile(videoTrack *gortsplib.Track, audioTrack *gortsplib.Track) *tsFile {
t := &tsFile{
// NewTSFile allocates a TSFile.
func NewTSFile(videoTrack *gortsplib.Track, audioTrack *gortsplib.Track) *TSFile {
t := &TSFile{
buf: newMultiAccessBuffer(),
name: strconv.FormatInt(time.Now().Unix(), 10),
}
@ -60,23 +63,38 @@ func newTSFile(videoTrack *gortsplib.Track, audioTrack *gortsplib.Track) *tsFile @@ -60,23 +63,38 @@ func newTSFile(videoTrack *gortsplib.Track, audioTrack *gortsplib.Track) *tsFile
return t
}
func (t *tsFile) Close() error {
// Close closes a TSFile.
func (t *TSFile) Close() error {
return t.buf.Close()
}
func (t *tsFile) Name() string {
// Name returns the file name.
func (t *TSFile) Name() string {
return t.name
}
func (t *tsFile) Duration() time.Duration {
// Duration returns the file duration.
func (t *TSFile) Duration() time.Duration {
return t.maxPTS - t.minPTS
}
func (t *tsFile) SetPCR(pcr time.Duration) {
// FirstPacketWritten returns whether a packet ha been written into the file.
func (t *TSFile) FirstPacketWritten() bool {
return t.firstPacketWritten
}
// SetPCR sets the PCR.
func (t *TSFile) SetPCR(pcr time.Duration) {
t.pcr = pcr
}
func (t *tsFile) WriteH264(dts time.Duration, pts time.Duration, isIDR bool, nalus [][]byte) error {
// NewReader allocates a reader to read the file.
func (t *TSFile) NewReader() io.Reader {
return t.buf.NewReader()
}
// WriteH264 writes H264 NALUs into the file.
func (t *TSFile) WriteH264(dts time.Duration, pts time.Duration, isIDR bool, nalus [][]byte) error {
if t.pcrTrackIsVideo {
if !t.firstPacketWritten {
t.firstPacketWritten = true
@ -125,7 +143,8 @@ func (t *tsFile) WriteH264(dts time.Duration, pts time.Duration, isIDR bool, nal @@ -125,7 +143,8 @@ func (t *tsFile) WriteH264(dts time.Duration, pts time.Duration, isIDR bool, nal
return err
}
func (t *tsFile) WriteAAC(sampleRate int, channelCount int, pts time.Duration, au []byte) error {
// WriteAAC writes AAC AUs into the file.
func (t *TSFile) WriteAAC(sampleRate int, channelCount int, pts time.Duration, au []byte) error {
if !t.pcrTrackIsVideo {
if !t.firstPacketWritten {
t.firstPacketWritten = true

45
internal/path/readersmap.go

@ -1,45 +0,0 @@ @@ -1,45 +0,0 @@
package path
import (
"sync"
"github.com/aler9/gortsplib"
)
type reader interface {
OnFrame(int, gortsplib.StreamType, []byte)
}
type readersMap struct {
mutex sync.RWMutex
ma map[reader]struct{}
}
func newReadersMap() *readersMap {
return &readersMap{
ma: make(map[reader]struct{}),
}
}
func (m *readersMap) add(reader reader) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.ma[reader] = struct{}{}
}
func (m *readersMap) remove(reader reader) {
m.mutex.Lock()
defer m.mutex.Unlock()
delete(m.ma, reader)
}
func (m *readersMap) forwardFrame(trackID int, streamType gortsplib.StreamType, payload []byte) {
m.mutex.RLock()
defer m.mutex.RUnlock()
for c := range m.ma {
c.OnFrame(trackID, streamType, payload)
}
}

142
internal/readpublisher/readpublisher.go

@ -1,142 +0,0 @@ @@ -1,142 +0,0 @@
package readpublisher
import (
"fmt"
"net"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/base"
"github.com/aler9/gortsplib/pkg/headers"
"github.com/aler9/rtsp-simple-server/internal/conf"
)
// Path is implemented by path.Path.
type Path interface {
Name() string
Conf() *conf.PathConf
OnReadPublisherRemove(RemoveReq)
OnReadPublisherPlay(PlayReq)
OnReadPublisherRecord(RecordReq)
OnReadPublisherPause(PauseReq)
OnFrame(int, gortsplib.StreamType, []byte)
}
// ErrNoOnePublishing is a "no one is publishing" error.
type ErrNoOnePublishing struct {
PathName string
}
// Error implements the error interface.
func (e ErrNoOnePublishing) Error() string {
return fmt.Sprintf("no one is publishing to path '%s'", e.PathName)
}
// ErrAuthNotCritical is a non-critical authentication error.
type ErrAuthNotCritical struct {
*base.Response
}
// Error implements the error interface.
func (ErrAuthNotCritical) Error() string {
return "non-critical authentication error"
}
// ErrAuthCritical is a critical authentication error.
type ErrAuthCritical struct {
Message string
Response *base.Response
}
// Error implements the error interface.
func (ErrAuthCritical) Error() string {
return "critical authentication error"
}
// ReadPublisher is an entity that can read/publish from/to a path.
type ReadPublisher interface {
IsReadPublisher()
IsSource()
Close()
OnFrame(int, gortsplib.StreamType, []byte)
}
// DescribeRes is a describe response.
type DescribeRes struct {
Stream *gortsplib.ServerStream
Redirect string
Err error
}
// DescribeReq is a describe request.
type DescribeReq struct {
PathName string
URL *base.URL
IP net.IP
ValidateCredentials func(authMethods []headers.AuthMethod, pathUser string, pathPass string) error
Res chan DescribeRes
}
// SetupPlayRes is a setup/play response.
type SetupPlayRes struct {
Path Path
Stream *gortsplib.ServerStream
Err error
}
// SetupPlayReq is a setup/play request.
type SetupPlayReq struct {
Author ReadPublisher
PathName string
IP net.IP
ValidateCredentials func(authMethods []headers.AuthMethod, pathUser string, pathPass string) error
Res chan SetupPlayRes
}
// AnnounceRes is a announce response.
type AnnounceRes struct {
Path Path
Err error
}
// AnnounceReq is a announce request.
type AnnounceReq struct {
Author ReadPublisher
PathName string
Tracks gortsplib.Tracks
IP net.IP
ValidateCredentials func(authMethods []headers.AuthMethod, pathUser string, pathPass string) error
Res chan AnnounceRes
}
// RemoveReq is a remove request.
type RemoveReq struct {
Author ReadPublisher
Res chan struct{}
}
// PlayRes is a play response.
type PlayRes struct{}
// PlayReq is a play request.
type PlayReq struct {
Author ReadPublisher
Res chan PlayRes
}
// RecordRes is a record response.
type RecordRes struct {
Err error
}
// RecordReq is a record request.
type RecordReq struct {
Author ReadPublisher
Res chan RecordRes
}
// PauseReq is a pause request.
type PauseReq struct {
Author ReadPublisher
Res chan struct{}
}

2
internal/rtmp/client.go

@ -11,7 +11,7 @@ import ( @@ -11,7 +11,7 @@ import (
// DialContext connects to a server in reading mode.
func DialContext(ctx context.Context, address string) (*Conn, error) {
// https://github.com/aler9/rtmp/blob/master/format/rtmp/readpublisher.go#L74
// https://github.com/aler9/rtmp/blob/3be4a55359274dcd88762e72aa0a702e2d8ba2fd/format/rtmp/client.go#L74
u, err := url.Parse(address)
if err != nil {

31
internal/source/source.go

@ -1,31 +0,0 @@ @@ -1,31 +0,0 @@
package source
import (
"github.com/aler9/gortsplib"
)
// Source is a source.
type Source interface {
IsSource()
}
// ExtSource is an external source.
type ExtSource interface {
IsSource()
IsExtSource()
Close()
}
// ExtSetReadyRes is a set ready response.
type ExtSetReadyRes struct{}
// ExtSetReadyReq is a set ready request.
type ExtSetReadyReq struct {
Tracks gortsplib.Tracks
Res chan ExtSetReadyRes
}
// ExtSetNotReadyReq is a set not ready request.
type ExtSetNotReadyReq struct {
Res chan struct{}
}

493
main.go

@ -1,502 +1,15 @@ @@ -1,502 +1,15 @@
package main
import (
"context"
"fmt"
"os"
"reflect"
"sync/atomic"
"gopkg.in/alecthomas/kingpin.v2"
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/confwatcher"
"github.com/aler9/rtsp-simple-server/internal/hlsserver"
"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/metrics"
"github.com/aler9/rtsp-simple-server/internal/pathman"
"github.com/aler9/rtsp-simple-server/internal/pprof"
"github.com/aler9/rtsp-simple-server/internal/rlimit"
"github.com/aler9/rtsp-simple-server/internal/rtmpserver"
"github.com/aler9/rtsp-simple-server/internal/rtspserver"
"github.com/aler9/rtsp-simple-server/internal/stats"
"github.com/aler9/rtsp-simple-server/internal/core"
)
var version = "v0.0.0"
type program struct {
ctx context.Context
ctxCancel func()
confPath string
conf *conf.Conf
confFound bool
stats *stats.Stats
logger *logger.Logger
metrics *metrics.Metrics
pprof *pprof.PPROF
pathMan *pathman.PathManager
rtspServerPlain *rtspserver.Server
rtspServerTLS *rtspserver.Server
rtmpServer *rtmpserver.Server
hlsServer *hlsserver.Server
confWatcher *confwatcher.ConfWatcher
// out
done chan struct{}
}
func newProgram(args []string) (*program, bool) {
k := kingpin.New("rtsp-simple-server",
"rtsp-simple-server "+version+"\n\nRTSP server.")
argVersion := k.Flag("version", "print version").Bool()
argConfPath := k.Arg("confpath", "path to a config file. The default is rtsp-simple-server.yml.").Default("rtsp-simple-server.yml").String()
kingpin.MustParse(k.Parse(args))
if *argVersion {
fmt.Println(version)
os.Exit(0)
}
// on Linux, try to raise the number of file descriptors that can be opened
// to allow the maximum possible number of clients
// do not check for errors
rlimit.Raise()
ctx, ctxCancel := context.WithCancel(context.Background())
p := &program{
ctx: ctx,
ctxCancel: ctxCancel,
confPath: *argConfPath,
done: make(chan struct{}),
}
var err error
p.conf, p.confFound, err = conf.Load(p.confPath)
if err != nil {
fmt.Printf("ERR: %s\n", err)
return nil, false
}
err = p.createResources(true)
if err != nil {
p.Log(logger.Info, "ERR: %s", err)
p.closeResources(nil)
return nil, false
}
if p.confFound {
p.confWatcher, err = confwatcher.New(p.confPath)
if err != nil {
p.Log(logger.Info, "ERR: %s", err)
p.closeResources(nil)
return nil, false
}
}
go p.run()
return p, true
}
func (p *program) close() {
p.ctxCancel()
<-p.done
}
func (p *program) Log(level logger.Level, format string, args ...interface{}) {
countPublishers := atomic.LoadInt64(p.stats.CountPublishers)
countReaders := atomic.LoadInt64(p.stats.CountReaders)
p.logger.Log(level, "[%d/%d] "+format, append([]interface{}{
countPublishers, countReaders,
}, args...)...)
}
func (p *program) run() {
defer close(p.done)
confChanged := func() chan struct{} {
if p.confWatcher != nil {
return p.confWatcher.Watch()
}
return make(chan struct{})
}()
outer:
for {
select {
case <-confChanged:
err := p.reloadConf()
if err != nil {
p.Log(logger.Info, "ERR: %s", err)
break outer
}
case <-p.ctx.Done():
break outer
}
}
p.ctxCancel()
p.closeResources(nil)
if p.confWatcher != nil {
p.confWatcher.Close()
}
}
func (p *program) createResources(initial bool) error {
var err error
if p.stats == nil {
p.stats = stats.New()
}
if p.logger == nil {
p.logger, err = logger.New(
p.conf.LogLevelParsed,
p.conf.LogDestinationsParsed,
p.conf.LogFile)
if err != nil {
return err
}
}
if initial {
p.Log(logger.Info, "rtsp-simple-server %s", version)
if !p.confFound {
p.Log(logger.Warn, "configuration file not found, using the default one")
}
}
if p.conf.Metrics {
if p.metrics == nil {
p.metrics, err = metrics.New(
p.conf.MetricsAddress,
p.stats,
p)
if err != nil {
return err
}
}
}
if p.conf.PPROF {
if p.pprof == nil {
p.pprof, err = pprof.New(
p.conf.PPROFAddress,
p)
if err != nil {
return err
}
}
}
if p.pathMan == nil {
p.pathMan = pathman.New(
p.ctx,
p.conf.RTSPAddress,
p.conf.ReadTimeout,
p.conf.WriteTimeout,
p.conf.ReadBufferCount,
p.conf.ReadBufferSize,
p.conf.AuthMethodsParsed,
p.conf.Paths,
p.stats,
p)
}
if !p.conf.RTSPDisable &&
(p.conf.EncryptionParsed == conf.EncryptionNo ||
p.conf.EncryptionParsed == conf.EncryptionOptional) {
if p.rtspServerPlain == nil {
_, useUDP := p.conf.ProtocolsParsed[conf.ProtocolUDP]
_, useMulticast := p.conf.ProtocolsParsed[conf.ProtocolMulticast]
p.rtspServerPlain, err = rtspserver.New(
p.ctx,
p.conf.RTSPAddress,
p.conf.ReadTimeout,
p.conf.WriteTimeout,
p.conf.ReadBufferCount,
p.conf.ReadBufferSize,
useUDP,
useMulticast,
p.conf.RTPAddress,
p.conf.RTCPAddress,
p.conf.MulticastIPRange,
p.conf.MulticastRTPPort,
p.conf.MulticastRTCPPort,
false,
"",
"",
p.conf.RTSPAddress,
p.conf.ProtocolsParsed,
p.conf.RunOnConnect,
p.conf.RunOnConnectRestart,
p.stats,
p.pathMan,
p)
if err != nil {
return err
}
}
}
if !p.conf.RTSPDisable &&
(p.conf.EncryptionParsed == conf.EncryptionStrict ||
p.conf.EncryptionParsed == conf.EncryptionOptional) {
if p.rtspServerTLS == nil {
p.rtspServerTLS, err = rtspserver.New(
p.ctx,
p.conf.RTSPSAddress,
p.conf.ReadTimeout,
p.conf.WriteTimeout,
p.conf.ReadBufferCount,
p.conf.ReadBufferSize,
false,
false,
"",
"",
"",
0,
0,
true,
p.conf.ServerCert,
p.conf.ServerKey,
p.conf.RTSPAddress,
p.conf.ProtocolsParsed,
p.conf.RunOnConnect,
p.conf.RunOnConnectRestart,
p.stats,
p.pathMan,
p)
if err != nil {
return err
}
}
}
if !p.conf.RTMPDisable {
if p.rtmpServer == nil {
p.rtmpServer, err = rtmpserver.New(
p.ctx,
p.conf.RTMPAddress,
p.conf.ReadTimeout,
p.conf.WriteTimeout,
p.conf.ReadBufferCount,
p.conf.RTSPAddress,
p.conf.RunOnConnect,
p.conf.RunOnConnectRestart,
p.stats,
p.pathMan,
p)
if err != nil {
return err
}
}
}
if !p.conf.HLSDisable {
if p.hlsServer == nil {
p.hlsServer, err = hlsserver.New(
p.ctx,
p.conf.HLSAddress,
p.conf.HLSSegmentCount,
p.conf.HLSSegmentDuration,
p.conf.HLSAllowOrigin,
p.conf.ReadBufferCount,
p.stats,
p.pathMan,
p)
if err != nil {
return err
}
}
}
return nil
}
func (p *program) closeResources(newConf *conf.Conf) {
closeStats := false
if newConf == nil {
closeStats = true
}
closeLogger := false
if newConf == nil ||
!reflect.DeepEqual(newConf.LogDestinationsParsed, p.conf.LogDestinationsParsed) ||
newConf.LogFile != p.conf.LogFile {
closeLogger = true
}
closeMetrics := false
if newConf == nil ||
newConf.Metrics != p.conf.Metrics ||
newConf.MetricsAddress != p.conf.MetricsAddress ||
closeStats {
closeMetrics = true
}
closePPROF := false
if newConf == nil ||
newConf.PPROF != p.conf.PPROF ||
newConf.PPROFAddress != p.conf.PPROFAddress ||
closeStats {
closePPROF = true
}
closePathMan := false
if newConf == nil ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteTimeout != p.conf.WriteTimeout ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
newConf.ReadBufferSize != p.conf.ReadBufferSize ||
!reflect.DeepEqual(newConf.AuthMethodsParsed, p.conf.AuthMethodsParsed) ||
closeStats {
closePathMan = true
} else if !reflect.DeepEqual(newConf.Paths, p.conf.Paths) {
p.pathMan.OnProgramConfReload(newConf.Paths)
}
closeServerPlain := false
if newConf == nil ||
newConf.RTSPDisable != p.conf.RTSPDisable ||
newConf.EncryptionParsed != p.conf.EncryptionParsed ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteTimeout != p.conf.WriteTimeout ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
!reflect.DeepEqual(newConf.ProtocolsParsed, p.conf.ProtocolsParsed) ||
newConf.RTPAddress != p.conf.RTPAddress ||
newConf.RTCPAddress != p.conf.RTCPAddress ||
newConf.MulticastIPRange != p.conf.MulticastIPRange ||
newConf.MulticastRTPPort != p.conf.MulticastRTPPort ||
newConf.MulticastRTCPPort != p.conf.MulticastRTCPPort ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
!reflect.DeepEqual(newConf.ProtocolsParsed, p.conf.ProtocolsParsed) ||
newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
closeStats ||
closePathMan {
closeServerPlain = true
}
closeServerTLS := false
if newConf == nil ||
newConf.RTSPDisable != p.conf.RTSPDisable ||
newConf.EncryptionParsed != p.conf.EncryptionParsed ||
newConf.RTSPSAddress != p.conf.RTSPSAddress ||
newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteTimeout != p.conf.WriteTimeout ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
newConf.ServerCert != p.conf.ServerCert ||
newConf.ServerKey != p.conf.ServerKey ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
!reflect.DeepEqual(newConf.ProtocolsParsed, p.conf.ProtocolsParsed) ||
newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
closeStats ||
closePathMan {
closeServerTLS = true
}
closeServerRTMP := false
if newConf == nil ||
newConf.RTMPDisable != p.conf.RTMPDisable ||
newConf.RTMPAddress != p.conf.RTMPAddress ||
newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteTimeout != p.conf.WriteTimeout ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
newConf.RTSPAddress != p.conf.RTSPAddress ||
newConf.RunOnConnect != p.conf.RunOnConnect ||
newConf.RunOnConnectRestart != p.conf.RunOnConnectRestart ||
closeStats ||
closePathMan {
closeServerRTMP = true
}
closeServerHLS := false
if newConf == nil ||
newConf.HLSDisable != p.conf.HLSDisable ||
newConf.HLSAddress != p.conf.HLSAddress ||
newConf.HLSSegmentCount != p.conf.HLSSegmentCount ||
newConf.HLSSegmentDuration != p.conf.HLSSegmentDuration ||
newConf.HLSAllowOrigin != p.conf.HLSAllowOrigin ||
newConf.ReadBufferCount != p.conf.ReadBufferCount ||
closeStats ||
closePathMan {
closeServerHLS = true
}
if closeServerTLS && p.rtspServerTLS != nil {
p.rtspServerTLS.Close()
p.rtspServerTLS = nil
}
if closeServerPlain && p.rtspServerPlain != nil {
p.rtspServerPlain.Close()
p.rtspServerPlain = nil
}
if closePathMan && p.pathMan != nil {
p.pathMan.Close()
p.pathMan = nil
}
if closeServerHLS && p.hlsServer != nil {
p.hlsServer.Close()
p.hlsServer = nil
}
if closeServerRTMP && p.rtmpServer != nil {
p.rtmpServer.Close()
p.rtmpServer = nil
}
if closePPROF && p.pprof != nil {
p.pprof.Close()
p.pprof = nil
}
if closeMetrics && p.metrics != nil {
p.metrics.Close()
p.metrics = nil
}
if closeLogger && p.logger != nil {
p.logger.Close()
p.logger = nil
}
if closeStats && p.stats != nil {
p.stats.Close()
}
}
func (p *program) reloadConf() error {
p.Log(logger.Info, "reloading configuration")
newConf, _, err := conf.Load(p.confPath)
if err != nil {
return err
}
p.closeResources(newConf)
p.conf = newConf
return p.createResources(false)
}
func main() {
p, ok := newProgram(os.Args[1:])
s, ok := core.New(os.Args[1:])
if !ok {
os.Exit(1)
}
<-p.done
s.Wait()
}

Loading…
Cancel
Save