2020-07-29 21:30:42 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-07-30 11:31:18 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2020-07-29 21:30:42 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
const (
|
|
|
|
describeTimeout = 5 * time.Second
|
|
|
|
sourceStopAfterDescribeSecs = 10 * time.Second
|
|
|
|
onDemandCmdStopAfterDescribeSecs = 10 * time.Second
|
|
|
|
)
|
|
|
|
|
2020-07-29 21:30:42 +00:00
|
|
|
// a publisher is either a client or a source
|
|
|
|
type publisher interface {
|
|
|
|
isPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
type path struct {
|
2020-08-30 13:27:03 +00:00
|
|
|
p *program
|
|
|
|
name string
|
|
|
|
confp *confPath
|
|
|
|
permanent bool
|
|
|
|
source *source
|
|
|
|
publisher publisher
|
|
|
|
publisherReady bool
|
2020-09-05 12:51:36 +00:00
|
|
|
publisherTrackCount int
|
|
|
|
publisherSdp []byte
|
2020-08-30 13:27:03 +00:00
|
|
|
lastDescribeReq time.Time
|
|
|
|
lastDescribeActivation time.Time
|
|
|
|
onInitCmd *exec.Cmd
|
|
|
|
onDemandCmd *exec.Cmd
|
2020-07-29 21:30:42 +00:00
|
|
|
}
|
|
|
|
|
2020-08-05 09:49:36 +00:00
|
|
|
func newPath(p *program, name string, confp *confPath, permanent bool) *path {
|
2020-07-30 11:31:18 +00:00
|
|
|
pa := &path{
|
2020-07-29 21:30:42 +00:00
|
|
|
p: p,
|
2020-08-05 09:49:36 +00:00
|
|
|
name: name,
|
2020-07-30 11:31:18 +00:00
|
|
|
confp: confp,
|
|
|
|
permanent: permanent,
|
2020-07-29 21:30:42 +00:00
|
|
|
}
|
2020-07-30 11:31:18 +00:00
|
|
|
|
2020-08-30 12:15:00 +00:00
|
|
|
if confp.Source != "record" {
|
2020-08-30 13:51:28 +00:00
|
|
|
s := newSource(p, pa, confp)
|
2020-08-30 12:15:00 +00:00
|
|
|
pa.source = s
|
|
|
|
pa.publisher = s
|
|
|
|
}
|
|
|
|
|
2020-07-30 15:30:50 +00:00
|
|
|
return pa
|
2020-07-29 21:30:42 +00:00
|
|
|
}
|
|
|
|
|
2020-08-31 22:13:27 +00:00
|
|
|
func (pa *path) log(format string, args ...interface{}) {
|
|
|
|
pa.p.log("[path "+pa.name+"] "+format, args...)
|
|
|
|
}
|
|
|
|
|
2020-08-30 11:18:43 +00:00
|
|
|
func (pa *path) onInit() {
|
2020-08-30 12:15:00 +00:00
|
|
|
if pa.source != nil {
|
2020-09-03 14:31:52 +00:00
|
|
|
go pa.source.run(pa.source.state)
|
2020-08-30 12:15:00 +00:00
|
|
|
}
|
|
|
|
|
2020-08-30 11:18:43 +00:00
|
|
|
if pa.confp.RunOnInit != "" {
|
2020-08-31 22:13:27 +00:00
|
|
|
pa.log("starting on init command")
|
2020-09-09 17:32:51 +00:00
|
|
|
|
|
|
|
var err error
|
|
|
|
pa.onInitCmd, err = startExternalCommand(pa.confp.RunOnInit, pa.name)
|
2020-08-30 11:18:43 +00:00
|
|
|
if err != nil {
|
2020-08-31 22:13:27 +00:00
|
|
|
pa.log("ERR: %s", err)
|
2020-08-30 11:18:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 14:02:58 +00:00
|
|
|
func (pa *path) onClose(wait bool) {
|
2020-08-30 12:15:00 +00:00
|
|
|
if pa.source != nil {
|
2020-08-31 13:31:37 +00:00
|
|
|
close(pa.source.terminate)
|
2020-08-30 12:15:00 +00:00
|
|
|
<-pa.source.done
|
|
|
|
}
|
|
|
|
|
2020-08-30 11:18:43 +00:00
|
|
|
if pa.onInitCmd != nil {
|
2020-08-31 22:13:27 +00:00
|
|
|
pa.log("stopping on init command (closing)")
|
2020-08-30 11:18:43 +00:00
|
|
|
pa.onInitCmd.Process.Signal(os.Interrupt)
|
|
|
|
pa.onInitCmd.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
if pa.onDemandCmd != nil {
|
2020-08-31 22:13:27 +00:00
|
|
|
pa.log("stopping on demand command (closing)")
|
2020-08-30 11:18:43 +00:00
|
|
|
pa.onDemandCmd.Process.Signal(os.Interrupt)
|
|
|
|
pa.onDemandCmd.Wait()
|
|
|
|
}
|
2020-08-31 13:31:37 +00:00
|
|
|
|
|
|
|
for c := range pa.p.clients {
|
|
|
|
if c.path == pa {
|
|
|
|
if c.state == clientStateWaitDescription {
|
|
|
|
c.path = nil
|
|
|
|
c.state = clientStateInitial
|
|
|
|
c.describe <- describeRes{nil, fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
|
|
|
|
} else {
|
2020-08-31 20:29:30 +00:00
|
|
|
c.close()
|
2020-09-03 14:02:58 +00:00
|
|
|
|
|
|
|
if wait {
|
2020-09-03 14:24:39 +00:00
|
|
|
<-c.done
|
2020-09-03 14:02:58 +00:00
|
|
|
}
|
2020-08-31 13:31:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-30 11:18:43 +00:00
|
|
|
}
|
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
func (pa *path) hasClients() bool {
|
|
|
|
for c := range pa.p.clients {
|
2020-08-30 13:51:28 +00:00
|
|
|
if c.path == pa {
|
2020-08-30 13:27:03 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *path) hasClientsWaitingDescribe() bool {
|
|
|
|
for c := range pa.p.clients {
|
2020-08-31 13:31:37 +00:00
|
|
|
if c.state == clientStateWaitDescription && c.path == pa {
|
2020-08-30 13:27:03 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *path) hasClientReaders() bool {
|
|
|
|
for c := range pa.p.clients {
|
2020-08-30 13:51:28 +00:00
|
|
|
if c.path == pa && c != pa.publisher {
|
2020-08-30 13:27:03 +00:00
|
|
|
return true
|
2020-07-29 21:30:42 +00:00
|
|
|
}
|
2020-08-30 13:27:03 +00:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2020-07-30 11:31:18 +00:00
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
func (pa *path) onCheck() {
|
2020-07-30 11:31:18 +00:00
|
|
|
// reply to DESCRIBE requests if they are in timeout
|
2020-08-30 13:27:03 +00:00
|
|
|
if pa.hasClientsWaitingDescribe() &&
|
|
|
|
time.Since(pa.lastDescribeActivation) >= describeTimeout {
|
2020-07-30 11:31:18 +00:00
|
|
|
for c := range pa.p.clients {
|
2020-08-31 13:31:37 +00:00
|
|
|
if c.state == clientStateWaitDescription &&
|
2020-08-30 13:51:28 +00:00
|
|
|
c.path == pa {
|
|
|
|
c.path = nil
|
2020-07-30 11:31:18 +00:00
|
|
|
c.state = clientStateInitial
|
2020-08-31 13:31:37 +00:00
|
|
|
c.describe <- describeRes{nil, fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
|
2020-07-30 11:31:18 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-30 13:27:03 +00:00
|
|
|
}
|
2020-07-30 11:31:18 +00:00
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
// stop on demand source if needed
|
|
|
|
if pa.source != nil &&
|
|
|
|
pa.confp.SourceOnDemand &&
|
|
|
|
pa.source.state == sourceStateRunning &&
|
|
|
|
!pa.hasClients() &&
|
|
|
|
time.Since(pa.lastDescribeReq) >= sourceStopAfterDescribeSecs {
|
2020-08-31 22:13:27 +00:00
|
|
|
pa.log("stopping on demand source since (not requested anymore)")
|
2020-08-30 13:27:03 +00:00
|
|
|
pa.source.state = sourceStateStopped
|
2020-08-31 13:31:37 +00:00
|
|
|
pa.source.setState <- pa.source.state
|
2020-07-30 11:31:18 +00:00
|
|
|
}
|
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
// stop on demand command if needed
|
|
|
|
if pa.onDemandCmd != nil &&
|
|
|
|
!pa.hasClientReaders() &&
|
|
|
|
time.Since(pa.lastDescribeReq) >= onDemandCmdStopAfterDescribeSecs {
|
2020-08-31 22:13:27 +00:00
|
|
|
pa.log("stopping on demand command (not requested anymore)")
|
2020-08-30 13:27:03 +00:00
|
|
|
pa.onDemandCmd.Process.Signal(os.Interrupt)
|
|
|
|
pa.onDemandCmd.Wait()
|
|
|
|
pa.onDemandCmd = nil
|
|
|
|
}
|
2020-07-31 15:46:40 +00:00
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
// remove non-permanent paths
|
|
|
|
if !pa.permanent &&
|
|
|
|
pa.publisher == nil &&
|
|
|
|
!pa.hasClients() {
|
2020-09-03 14:02:58 +00:00
|
|
|
pa.onClose(false)
|
2020-08-30 13:27:03 +00:00
|
|
|
delete(pa.p.paths, pa.name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *path) onPublisherRemove() {
|
|
|
|
pa.publisher = nil
|
2020-08-31 13:31:37 +00:00
|
|
|
|
|
|
|
// close all clients that are reading or waiting for reading
|
|
|
|
for c := range pa.p.clients {
|
|
|
|
if c.path == pa &&
|
|
|
|
c.state != clientStateWaitDescription &&
|
|
|
|
c != pa.publisher {
|
2020-08-31 20:29:30 +00:00
|
|
|
c.close()
|
2020-08-31 13:31:37 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-30 13:27:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pa *path) onPublisherSetReady() {
|
|
|
|
pa.publisherReady = true
|
|
|
|
|
|
|
|
// reply to all clients that are waiting for a description
|
|
|
|
for c := range pa.p.clients {
|
2020-08-31 13:31:37 +00:00
|
|
|
if c.state == clientStateWaitDescription &&
|
2020-08-30 13:51:28 +00:00
|
|
|
c.path == pa {
|
|
|
|
c.path = nil
|
2020-08-30 13:27:03 +00:00
|
|
|
c.state = clientStateInitial
|
2020-09-05 12:51:36 +00:00
|
|
|
c.describe <- describeRes{pa.publisherSdp, nil}
|
2020-07-30 11:31:18 +00:00
|
|
|
}
|
2020-08-30 13:27:03 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-30 11:31:18 +00:00
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
func (pa *path) onPublisherSetNotReady() {
|
|
|
|
pa.publisherReady = false
|
|
|
|
|
2020-08-31 13:31:37 +00:00
|
|
|
// close all clients that are reading or waiting for reading
|
2020-08-30 13:27:03 +00:00
|
|
|
for c := range pa.p.clients {
|
2020-08-31 13:31:37 +00:00
|
|
|
if c.path == pa &&
|
|
|
|
c.state != clientStateWaitDescription &&
|
|
|
|
c != pa.publisher {
|
2020-08-31 20:29:30 +00:00
|
|
|
c.close()
|
2020-07-30 11:31:18 +00:00
|
|
|
}
|
2020-07-29 21:30:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
func (pa *path) onDescribe(client *client) {
|
|
|
|
pa.lastDescribeReq = time.Now()
|
2020-07-30 11:31:18 +00:00
|
|
|
|
|
|
|
// publisher not found
|
|
|
|
if pa.publisher == nil {
|
2020-07-31 15:49:48 +00:00
|
|
|
// on demand command is available: put the client on hold
|
2020-07-30 11:31:18 +00:00
|
|
|
if pa.confp.RunOnDemand != "" {
|
2020-08-30 13:27:03 +00:00
|
|
|
if pa.onDemandCmd == nil { // start if needed
|
2020-08-31 22:13:27 +00:00
|
|
|
pa.log("starting on demand command")
|
2020-08-30 13:27:03 +00:00
|
|
|
pa.lastDescribeActivation = time.Now()
|
2020-09-09 17:32:51 +00:00
|
|
|
|
|
|
|
var err error
|
2020-09-19 15:15:33 +00:00
|
|
|
pa.onDemandCmd, err = startExternalCommand(pa.confp.RunOnDemand, pa.name)
|
2020-07-30 11:31:18 +00:00
|
|
|
if err != nil {
|
2020-08-31 22:13:27 +00:00
|
|
|
pa.log("ERR: %s", err)
|
2020-07-30 11:31:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-30 13:51:28 +00:00
|
|
|
client.path = pa
|
2020-08-31 13:31:37 +00:00
|
|
|
client.state = clientStateWaitDescription
|
2020-07-31 15:46:40 +00:00
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
// no on-demand: reply with 404
|
|
|
|
} else {
|
2020-08-31 13:31:37 +00:00
|
|
|
client.describe <- describeRes{nil, fmt.Errorf("no one is publishing on path '%s'", pa.name)}
|
2020-08-30 13:27:03 +00:00
|
|
|
}
|
2020-07-30 11:31:18 +00:00
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
// publisher was found but is not ready: put the client on hold
|
|
|
|
} else if !pa.publisherReady {
|
|
|
|
if pa.source != nil && pa.source.state == sourceStateStopped { // start if needed
|
2020-08-31 22:13:27 +00:00
|
|
|
pa.log("starting on demand source")
|
2020-08-30 13:27:03 +00:00
|
|
|
pa.lastDescribeActivation = time.Now()
|
|
|
|
pa.source.state = sourceStateRunning
|
2020-08-31 13:31:37 +00:00
|
|
|
pa.source.setState <- pa.source.state
|
2020-07-29 21:30:42 +00:00
|
|
|
}
|
|
|
|
|
2020-08-30 13:51:28 +00:00
|
|
|
client.path = pa
|
2020-08-31 13:31:37 +00:00
|
|
|
client.state = clientStateWaitDescription
|
2020-07-29 21:30:42 +00:00
|
|
|
|
2020-08-30 13:27:03 +00:00
|
|
|
// publisher was found and is ready
|
|
|
|
} else {
|
2020-09-05 12:51:36 +00:00
|
|
|
client.describe <- describeRes{pa.publisherSdp, nil}
|
2020-07-29 21:30:42 +00:00
|
|
|
}
|
|
|
|
}
|