@ -8,6 +8,7 @@ import (
@@ -8,6 +8,7 @@ import (
"net/http"
_ "net/http/pprof"
"os"
"time"
"github.com/aler9/gortsplib"
"github.com/aler9/sdp/v3"
@ -28,29 +29,31 @@ func (programEventClientNew) isProgramEvent() {}
@@ -28,29 +29,31 @@ func (programEventClientNew) isProgramEvent() {}
type programEventClientClose struct {
done chan struct { }
client * serverC lient
client * c lient
}
func ( programEventClientClose ) isProgramEvent ( ) { }
type programEventClientDescribe struct {
path string
res chan [ ] byte
client * client
path string
}
func ( programEventClientDescribe ) isProgramEvent ( ) { }
type programEventClientAnnounce struct {
res chan error
client * serverClient
path string
res chan error
client * client
path string
sdpText [ ] byte
sdpParsed * sdp . SessionDescription
}
func ( programEventClientAnnounce ) isProgramEvent ( ) { }
type programEventClientSetupPlay struct {
res chan error
client * serverC lient
client * c lient
path string
protocol gortsplib . StreamProtocol
rtpPort int
@ -61,7 +64,7 @@ func (programEventClientSetupPlay) isProgramEvent() {}
@@ -61,7 +64,7 @@ func (programEventClientSetupPlay) isProgramEvent() {}
type programEventClientSetupRecord struct {
res chan error
client * serverC lient
client * c lient
protocol gortsplib . StreamProtocol
rtpPort int
rtcpPort int
@ -71,35 +74,35 @@ func (programEventClientSetupRecord) isProgramEvent() {}
@@ -71,35 +74,35 @@ func (programEventClientSetupRecord) isProgramEvent() {}
type programEventClientPlay1 struct {
res chan error
client * serverC lient
client * c lient
}
func ( programEventClientPlay1 ) isProgramEvent ( ) { }
type programEventClientPlay2 struct {
done chan struct { }
client * serverC lient
client * c lient
}
func ( programEventClientPlay2 ) isProgramEvent ( ) { }
type programEventClientPlayStop struct {
done chan struct { }
client * serverC lient
client * c lient
}
func ( programEventClientPlayStop ) isProgramEvent ( ) { }
type programEventClientRecord struct {
done chan struct { }
client * serverC lient
client * c lient
}
func ( programEventClientRecord ) isProgramEvent ( ) { }
type programEventClientRecordStop struct {
done chan struct { }
client * serverC lient
client * c lient
}
func ( programEventClientRecordStop ) isProgramEvent ( ) { }
@ -121,46 +124,45 @@ type programEventClientFrameTcp struct {
@@ -121,46 +124,45 @@ type programEventClientFrameTcp struct {
func ( programEventClientFrameTcp ) isProgramEvent ( ) { }
type programEventStreamer Ready struct {
type programEventSource Ready struct {
source * source
}
func ( programEventStreamer Ready ) isProgramEvent ( ) { }
func ( programEventSource Ready ) isProgramEvent ( ) { }
type programEventStreamer NotReady struct {
type programEventSource NotReady struct {
source * source
}
func ( programEventStreamer NotReady ) isProgramEvent ( ) { }
func ( programEventSource NotReady ) isProgramEvent ( ) { }
type programEventStreamer Frame struct {
type programEventSource Frame struct {
source * source
trackId int
streamType gortsplib . StreamType
buf [ ] byte
}
func ( programEventStreamerFrame ) isProgramEvent ( ) { }
func ( programEventSourceFrame ) isProgramEvent ( ) { }
type programEventSourceReset struct {
source * source
}
func ( programEventSourceReset ) isProgramEvent ( ) { }
type programEventTerminate struct { }
func ( programEventTerminate ) isProgramEvent ( ) { }
// a publisher can be either a serverClient or a source
type publisher interface {
publisherIsReady ( ) bool
publisherSdpText ( ) [ ] byte
publisherSdpParsed ( ) * sdp . SessionDescription
}
type program struct {
conf * conf
rtspl * serverTcpListener
rtpl * serverUdpListener
rtcpl * serverUdpListener
clients map [ * serverClient ] struct { }
rtspl * serverTcp
rtpl * serverUdp
rtcpl * serverUdp
sources [ ] * source
publishers map [ string ] publisher
clients map [ * client ] struct { }
paths map [ string ] * path
publisherCount int
readerCount int
@ -188,18 +190,18 @@ func newProgram(args []string, stdin io.Reader) (*program, error) {
@@ -188,18 +190,18 @@ func newProgram(args []string, stdin io.Reader) (*program, error) {
}
p := & program {
conf : conf ,
clients : make ( map [ * serverC lient] struct { } ) ,
publishers : make ( map [ string ] publisher ) ,
events : make ( chan programEvent ) ,
done : make ( chan struct { } ) ,
conf : conf ,
clients : make ( map [ * c lient] struct { } ) ,
paths : make ( map [ string ] * path ) ,
events : make ( chan programEvent ) ,
done : make ( chan struct { } ) ,
}
for path , pconf := range conf . Paths {
if pconf . Source != "record" {
s := newSource ( p , path , pconf . sourceUrl , pconf . sourceProtocolParsed )
s := newSource ( p , path , pconf )
p . sources = append ( p . sources , s )
p . publisher s [ path ] = s
p . path s [ path ] = newPath ( p , path , s )
}
}
@ -217,17 +219,17 @@ func newProgram(args []string, stdin io.Reader) (*program, error) {
@@ -217,17 +219,17 @@ func newProgram(args []string, stdin io.Reader) (*program, error) {
http . DefaultServeMux = http . NewServeMux ( )
}
p . rtpl , err = newServerUdpListener ( p , conf . RtpPort , gortsplib . StreamTypeRtp )
p . rtpl , err = newServerUdp ( p , conf . RtpPort , gortsplib . StreamTypeRtp )
if err != nil {
return nil , err
}
p . rtcpl , err = newServerUdpListener ( p , conf . RtcpPort , gortsplib . StreamTypeRtcp )
p . rtcpl , err = newServerUdp ( p , conf . RtcpPort , gortsplib . StreamTypeRtcp )
if err != nil {
return nil , err
}
p . rtspl , err = newServerTcpListener ( p )
p . rtspl , err = newServerTcp ( p )
if err != nil {
return nil , err
}
@ -249,163 +251,170 @@ func (p *program) log(format string, args ...interface{}) {
@@ -249,163 +251,170 @@ func (p *program) log(format string, args ...interface{}) {
}
func ( p * program ) run ( ) {
checkPathsTicker := time . NewTicker ( 5 * time . Second )
defer checkPathsTicker . Stop ( )
outer :
for rawEvt := range p . events {
switch evt := rawEvt . ( type ) {
case programEventClientNew :
c := newServerClient ( p , evt . nconn )
p . clients [ c ] = struct { } { }
c . log ( "connected" )
case programEventClientClose :
// already deleted
if _ , ok := p . clients [ evt . client ] ; ! ok {
close ( evt . done )
continue
for {
select {
case <- checkPathsTicker . C :
for _ , path := range p . paths {
path . check ( )
}
delete ( p . clients , evt . client )
case rawEvt := <- p . events :
switch evt := rawEvt . ( type ) {
case programEventClientNew :
c := newServerClient ( p , evt . nconn )
p . clients [ c ] = struct { } { }
c . log ( "connected" )
if evt . client . path != "" {
if pub , ok := p . publishers [ evt . client . path ] ; ok && pub == evt . client {
delete ( p . publishers , evt . client . path )
case programEventClientClose :
delete ( p . clients , evt . client )
if evt . client . path != "" {
if path , ok := p . paths [ evt . client . path ] ; ok {
// if this is a publisher
if path . publisher == evt . client {
path . publisherReset ( )
// delete the path
delete ( p . paths , evt . client . path )
}
}
}
}
evt . client . log ( "disconnected" )
close ( evt . done )
evt . client . log ( "disconnected" )
close ( evt . done )
case programEventClientDescribe :
pub , ok := p . publishers [ evt . path ]
if ! ok || ! pub . publisherIsReady ( ) {
evt . res <- nil
continue
}
case programEventClientDescribe :
path , ok := p . paths [ evt . path ]
evt . res <- pub . publisherSdpText ( )
// no path: return 404
if ! ok {
evt . client . describeRes <- nil
continue
}
case programEventClientAnnounce :
_ , ok := p . publishers [ evt . path ]
if ok {
evt . res <- fmt . Errorf ( "someone is already publishing on path '%s'" , evt . path )
continue
}
sdpText , wait := path . describe ( )
evt . client . path = evt . path
evt . client . state = clientStateAnnounce
p . publishers [ evt . path ] = evt . client
evt . res <- nil
if wait {
evt . client . path = evt . path
evt . client . state = clientStateWaitingDescription
continue
}
case programEventClientSetupPlay :
pub , ok := p . publishers [ evt . path ]
if ! ok || ! pub . publisherIsReady ( ) {
evt . res <- fmt . Errorf ( "no one is streaming on path '%s'" , evt . path )
continue
}
evt . client . describeRes <- sdpText
sdpParsed := pub . publisherSdpParsed ( )
case programEventClientAnnounce :
_ , ok := p . paths [ evt . path ]
if ok {
evt . res <- fmt . Errorf ( "someone is already publishing on path '%s'" , evt . path )
continue
}
if len ( evt . client . streamTracks ) >= len ( sdpParsed . MediaDescriptions ) {
evt . res <- fmt . Errorf ( "all the tracks have already been setup" )
continue
}
evt . client . path = evt . path
evt . client . state = clientStateAnnounce
p . paths [ evt . path ] = newPath ( p , evt . path , evt . client )
p . paths [ evt . path ] . publisherSdpText = evt . sdpText
p . paths [ evt . path ] . publisherSdpParsed = evt . sdpParsed
evt . res <- nil
evt . client . path = evt . path
evt . client . streamProtocol = evt . protocol
evt . client . streamTracks = append ( evt . client . streamTracks , & serverClientTrack {
rtpPort : evt . rtpPort ,
rtcpPort : evt . rtcpPort ,
} )
evt . client . state = clientStatePrePlay
evt . res <- nil
case programEventClientSetupRecord :
evt . client . streamProtocol = evt . protocol
evt . client . streamTracks = append ( evt . client . streamTracks , & serverClientTrack {
rtpPort : evt . rtpPort ,
rtcpPort : evt . rtcpPort ,
} )
evt . client . state = clientStatePreRecord
evt . res <- nil
case programEventClientPlay1 :
pub , ok := p . publishers [ evt . client . path ]
if ! ok || ! pub . publisherIsReady ( ) {
evt . res <- fmt . Errorf ( "no one is streaming on path '%s'" , evt . client . path )
continue
}
case programEventClientSetupPlay :
path , ok := p . paths [ evt . path ]
if ! ok || ! path . publisherReady {
evt . res <- fmt . Errorf ( "no one is publishing on path '%s'" , evt . path )
continue
}
sdpParsed := pub . publisherSdpParsed ( )
if len ( evt . client . streamTracks ) >= len ( path . publisherSdpParsed . MediaDescriptions ) {
evt . res <- fmt . Errorf ( "all the tracks have already been setup" )
continue
}
if len ( evt . client . streamTracks ) != len ( sdpParsed . MediaDescriptions ) {
evt . res <- fmt . Errorf ( "not all tracks have been setup" )
continue
}
evt . client . path = evt . path
evt . client . streamProtocol = evt . protocol
evt . client . streamTracks = append ( evt . client . streamTracks , & clientTrack {
rtpPort : evt . rtpPort ,
rtcpPort : evt . rtcpPort ,
} )
evt . client . state = clientStatePrePlay
evt . res <- nil
evt . res <- nil
case programEventClientSetupRecord :
evt . client . streamProtocol = evt . protocol
evt . client . streamTracks = append ( evt . client . streamTracks , & clientTrack {
rtpPort : evt . rtpPort ,
rtcpPort : evt . rtcpPort ,
} )
evt . client . state = clientStatePreRecord
evt . res <- nil
case programEventClientPlay2 :
p . readerCount += 1
evt . client . state = clientStatePlay
close ( evt . done )
case programEventClientPlay1 :
path , ok := p . paths [ evt . client . path ]
if ! ok || ! path . publisherReady {
evt . res <- fmt . Errorf ( "no one is publishing on path '%s'" , evt . client . path )
continue
}
case programEventClientPlayStop :
p . readerCount -= 1
evt . client . state = clientStatePrePlay
close ( evt . done )
if len ( evt . client . streamTracks ) != len ( path . publisherSdpParsed . MediaDescriptions ) {
evt . res <- fmt . Errorf ( "not all tracks have been setup" )
continue
}
case programEventClientRecord :
p . publisherCount += 1
evt . client . state = clientStateRecord
close ( evt . done )
evt . res <- nil
case programEventClientRecordStop :
p . publisherCount -= 1
evt . client . state = clientStatePreRecord
case programEventClientPlay2 :
p . readerCount += 1
evt . client . state = clientStatePlay
close ( evt . done )
// close all other clients that share the same path
for oc := range p . clients {
if oc != evt . client && oc . path == evt . client . path {
go oc . close ( )
}
}
case programEventClientPlayStop :
p . readerCount -= 1
evt . client . state = clientStatePrePlay
close ( evt . done )
case programEventClientRecord :
p . publisherCount += 1
evt . client . state = clientStateRecord
p . paths [ evt . client . path ] . publisherSetReady ( )
close ( evt . done )
close ( evt . done )
case programEventClientRecordStop :
p . publisherCount -= 1
evt . client . state = clientStatePreRecord
p . paths [ evt . client . path ] . publisherSetNotReady ( )
close ( evt . done )
case programEventClientFrameUdp :
client , trackId := p . findPublisher ( evt . addr , evt . streamType )
if client == nil {
continue
}
case programEventClientFrameUdp :
client , trackId := p . findClient Publisher ( evt . addr , evt . streamType )
if client == nil {
continue
}
client . rtcpReceivers [ trackId ] . OnFrame ( evt . streamType , evt . buf )
p . forwardFrame ( client . path , trackId , evt . streamType , evt . buf )
client . rtcpReceivers [ trackId ] . OnFrame ( evt . streamType , evt . buf )
p . forwardFrame ( client . path , trackId , evt . streamType , evt . buf )
case programEventClientFrameTcp :
p . forwardFrame ( evt . path , evt . trackId , evt . streamType , evt . buf )
case programEventClientFrameTcp :
p . forwardFrame ( evt . path , evt . trackId , evt . streamType , evt . buf )
case programEventStreamerReady :
evt . source . ready = true
p . publisherCount += 1
evt . source . log ( "ready" )
case programEventSourceReady :
evt . source . log ( "ready" )
p . paths [ evt . source . path ] . publisherSetReady ( )
case programEventStreamerNotReady :
evt . source . ready = false
p . publisherCount -= 1
evt . source . log ( "not ready" )
case programEventSourceNotReady :
evt . source . log ( "not ready" )
p . paths [ evt . source . path ] . publisherSetNotReady ( )
// close all clients that share the same path
for oc := range p . clients {
if oc . path == evt . source . path {
go oc . close ( )
}
}
case programEventSourceFrame :
p . forwardFrame ( evt . source . path , evt . trackId , evt . streamType , evt . buf )
case programEventStreamerFrame :
p . forwardFrame ( evt . source . path , evt . trackId , evt . streamType , evt . buf )
case programEventSourceReset :
p . paths [ evt . source . path ] . publisherReset ( )
case programEventTerminate :
break outer
case programEventTerminate :
break outer
}
}
}
@ -416,7 +425,7 @@ outer:
@@ -416,7 +425,7 @@ outer:
close ( evt . done )
case programEventClientDescribe :
evt . res <- nil
evt . client . desc ribeR es <- nil
case programEventClientAnnounce :
evt . res <- fmt . Errorf ( "terminated" )
@ -446,7 +455,8 @@ outer:
@@ -446,7 +455,8 @@ outer:
} ( )
for _ , s := range p . sources {
s . close ( )
s . events <- sourceEventTerminate { }
<- s . done
}
p . rtspl . close ( )
@ -454,7 +464,8 @@ outer:
@@ -454,7 +464,8 @@ outer:
p . rtpl . close ( )
for c := range p . clients {
c . close ( )
c . conn . NetConn ( ) . Close ( )
<- c . done
}
close ( p . events )
@ -466,9 +477,21 @@ func (p *program) close() {
@@ -466,9 +477,21 @@ func (p *program) close() {
<- p . done
}
func ( p * program ) findPublisher ( addr * net . UDPAddr , streamType gortsplib . StreamType ) ( * serverClient , int ) {
for _ , pub := range p . publishers {
cl , ok := pub . ( * serverClient )
func ( p * program ) findConfForPath ( path string ) * confPath {
if pconf , ok := p . conf . Paths [ path ] ; ok {
return pconf
}
if pconf , ok := p . conf . Paths [ "all" ] ; ok {
return pconf
}
return nil
}
func ( p * program ) findClientPublisher ( addr * net . UDPAddr , streamType gortsplib . StreamType ) ( * client , int ) {
for _ , path := range p . paths {
cl , ok := path . publisher . ( * client )
if ! ok {
continue
}
@ -523,7 +546,7 @@ func (p *program) forwardFrame(path string, trackId int, streamType gortsplib.St
@@ -523,7 +546,7 @@ func (p *program) forwardFrame(path string, trackId int, streamType gortsplib.St
buf = buf [ : len ( frame ) ]
copy ( buf , frame )
client . events <- serverC lientEventFrameTcp{
client . events <- c lientEventFrameTcp{
frame : & gortsplib . InterleavedFrame {
TrackId : trackId ,
StreamType : streamType ,