mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-10-30 14:16:19 +08:00
feat: transcode success
This commit is contained in:
@@ -1,42 +1,15 @@
|
|||||||
global:
|
global:
|
||||||
loglevel: debug
|
loglevel: debug
|
||||||
http:
|
|
||||||
listenaddr: :8082
|
|
||||||
onpublish:
|
|
||||||
record:
|
|
||||||
.* :
|
|
||||||
filepath: $0
|
|
||||||
# enableauth: true
|
|
||||||
# tcp:
|
|
||||||
# listenaddr: :50051
|
|
||||||
# ringsize: 20-250
|
|
||||||
# buffertime: 10s
|
|
||||||
# speed: 1
|
|
||||||
#console:
|
|
||||||
# secret: de2c0bb9fd47684adc07a426e139239b
|
|
||||||
logrotate:
|
|
||||||
level: debug
|
|
||||||
rtsp:
|
|
||||||
rtmp:
|
rtmp:
|
||||||
# tcp:
|
|
||||||
# listenaddr: :11935
|
|
||||||
publish:
|
|
||||||
# idletimeout: 10s
|
|
||||||
# closedelaytimeout: 4s
|
|
||||||
subscribe:
|
|
||||||
# submode: 1
|
|
||||||
# subaudio: false
|
|
||||||
onsub:
|
onsub:
|
||||||
pull:
|
pull:
|
||||||
live/.*: rtmp://localhost/$0
|
live/.*: rtmp://localhost/$0
|
||||||
#flv:
|
|
||||||
# pull:
|
|
||||||
# pullonstart:
|
|
||||||
# live/test: /Users/dexter/project/v5/monibuca/example/default/record/live/test.flv
|
|
||||||
gb28181:
|
gb28181:
|
||||||
sip:
|
sip:
|
||||||
listenaddr:
|
listenaddr:
|
||||||
- udp::5060
|
- udp::5060
|
||||||
onsubscribe:
|
onsub:
|
||||||
pull:
|
pull:
|
||||||
.* : $0
|
.* : $0
|
||||||
@@ -2,5 +2,4 @@ global:
|
|||||||
loglevel: trace
|
loglevel: trace
|
||||||
flv:
|
flv:
|
||||||
pull:
|
pull:
|
||||||
pullonstart:
|
live/test: dump.flv
|
||||||
live/test: /Users/dexter/Movies/jb-demo.flv
|
|
||||||
@@ -6,4 +6,4 @@ transcode:
|
|||||||
.+:
|
.+:
|
||||||
output:
|
output:
|
||||||
- target: rtmp://localhost/$0/h265
|
- target: rtmp://localhost/$0/h265
|
||||||
conf: -loglevel trace -c:v h265
|
conf: -loglevel debug -c:a aac -c:v hevc_videotoolbox
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
global:
|
global:
|
||||||
|
loglevel: debug
|
||||||
tcp: :50050
|
tcp: :50050
|
||||||
http: :8081
|
http: :8081
|
||||||
rtsp:
|
rtsp:
|
||||||
@@ -7,4 +8,4 @@ rtsp:
|
|||||||
live/test:
|
live/test:
|
||||||
url: rtsp://localhost/live/test
|
url: rtsp://localhost/live/test
|
||||||
maxretry: -1
|
maxretry: -1
|
||||||
retryinterval: 5s
|
retryinterval: 10s
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package config
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/mcuadros/go-defaults"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -324,23 +325,52 @@ func (config *Config) assign(k string, v any) (target reflect.Value) {
|
|||||||
regexpStr := source.String()
|
regexpStr := source.String()
|
||||||
target.Set(reflect.ValueOf(Regexp{regexp.MustCompile(regexpStr)}))
|
target.Set(reflect.ValueOf(Regexp{regexp.MustCompile(regexpStr)}))
|
||||||
default:
|
default:
|
||||||
tmpStruct := reflect.StructOf([]reflect.StructField{
|
if ft.Kind() == reflect.Map {
|
||||||
{
|
target = reflect.MakeMap(ft)
|
||||||
Name: strings.ToUpper(k),
|
tmpStruct := reflect.StructOf([]reflect.StructField{
|
||||||
Type: ft,
|
{
|
||||||
},
|
Name: "Key",
|
||||||
})
|
Type: ft.Key(),
|
||||||
tmpValue := reflect.New(tmpStruct)
|
},
|
||||||
if v != nil {
|
})
|
||||||
var out []byte
|
tmpValue := reflect.New(tmpStruct)
|
||||||
if vv, ok := v.(string); ok {
|
for k, v := range v.(map[string]any) {
|
||||||
out = []byte(fmt.Sprintf("%s: %s", k, vv))
|
_ = yaml.Unmarshal([]byte(fmt.Sprintf("key: %s", k)), tmpValue.Interface())
|
||||||
} else {
|
var value reflect.Value
|
||||||
out, _ = yaml.Marshal(map[string]any{k: v})
|
if ft.Elem().Kind() == reflect.Struct {
|
||||||
|
value = reflect.New(ft.Elem())
|
||||||
|
defaults.SetDefaults(value.Interface())
|
||||||
|
if reflect.TypeOf(v).Kind() != reflect.Map {
|
||||||
|
value.Elem().Field(0).Set(reflect.ValueOf(v))
|
||||||
|
} else {
|
||||||
|
out, _ := yaml.Marshal(v)
|
||||||
|
_ = yaml.Unmarshal(out, value.Interface())
|
||||||
|
}
|
||||||
|
value = value.Elem()
|
||||||
|
} else {
|
||||||
|
value = reflect.ValueOf(v)
|
||||||
|
}
|
||||||
|
target.SetMapIndex(tmpValue.Elem().Field(0), value)
|
||||||
}
|
}
|
||||||
_ = yaml.Unmarshal(out, tmpValue.Interface())
|
} else {
|
||||||
|
tmpStruct := reflect.StructOf([]reflect.StructField{
|
||||||
|
{
|
||||||
|
Name: strings.ToUpper(k),
|
||||||
|
Type: ft,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
tmpValue := reflect.New(tmpStruct)
|
||||||
|
if v != nil {
|
||||||
|
var out []byte
|
||||||
|
if vv, ok := v.(string); ok {
|
||||||
|
out = []byte(fmt.Sprintf("%s: %s", k, vv))
|
||||||
|
} else {
|
||||||
|
out, _ = yaml.Marshal(map[string]any{k: v})
|
||||||
|
}
|
||||||
|
_ = yaml.Unmarshal(out, tmpValue.Interface())
|
||||||
|
}
|
||||||
|
target = tmpValue.Elem().Field(0)
|
||||||
}
|
}
|
||||||
target = tmpValue.Elem().Field(0)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ type (
|
|||||||
Pull struct {
|
Pull struct {
|
||||||
URL string `desc:"拉流地址"`
|
URL string `desc:"拉流地址"`
|
||||||
MaxRetry int `desc:"断开后自动重试次数,0:不重试,-1:无限重试"` // 断开后自动重拉,0 表示不自动重拉,-1 表示无限重拉,高于0 的数代表最大重拉次数
|
MaxRetry int `desc:"断开后自动重试次数,0:不重试,-1:无限重试"` // 断开后自动重拉,0 表示不自动重拉,-1 表示无限重拉,高于0 的数代表最大重拉次数
|
||||||
RetryInterval time.Duration `desc:"重试间隔" default:"5s"` // 重试间隔
|
RetryInterval time.Duration `default:"5s" desc:"重试间隔"` // 重试间隔
|
||||||
Proxy string `desc:"代理地址"` // 代理地址
|
Proxy string `desc:"代理地址"` // 代理地址
|
||||||
Header map[string][]string
|
Header map[string][]string
|
||||||
}
|
}
|
||||||
Push struct {
|
Push struct {
|
||||||
URL string `desc:"推送地址"` // 推送地址
|
URL string `desc:"推送地址"` // 推送地址
|
||||||
MaxRetry int `desc:"断开后自动重试次数,0:不重试,-1:无限重试"` // 断开后自动重推,0 表示不自动重推,-1 表示无限重推,高于0 的数代表最大重推次数
|
MaxRetry int `desc:"断开后自动重试次数,0:不重试,-1:无限重试"` // 断开后自动重推,0 表示不自动重推,-1 表示无限重推,高于0 的数代表最大重推次数
|
||||||
RetryInterval time.Duration `desc:"重试间隔" default:"5s"` // 重试间隔
|
RetryInterval time.Duration `default:"5s" desc:"重试间隔"` // 重试间隔
|
||||||
Proxy string `desc:"代理地址"` // 代理地址
|
Proxy string `desc:"代理地址"` // 代理地址
|
||||||
Header map[string][]string
|
Header map[string][]string
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,9 @@ type (
|
|||||||
Transform struct {
|
Transform struct {
|
||||||
Input any
|
Input any
|
||||||
Output []struct {
|
Output []struct {
|
||||||
Target string `desc:"转码目标"` // 转码目标
|
Target string `desc:"转码目标"` // 转码目标
|
||||||
Conf any
|
StreamPath string
|
||||||
|
Conf any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OnPublish struct {
|
OnPublish struct {
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ var (
|
|||||||
ErrLost = errors.New("lost")
|
ErrLost = errors.New("lost")
|
||||||
|
|
||||||
ErrRecordSamePath = errors.New("record same path")
|
ErrRecordSamePath = errors.New("record same path")
|
||||||
|
ErrTransformSame = errors.New("transform same")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -147,11 +147,6 @@ func (mt *Job) addChild(task ITask) int {
|
|||||||
return len(mt.children) - 1
|
return len(mt.children) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mt *Job) removeChild(index int) {
|
|
||||||
defer mt.onChildDispose(mt.children[index])
|
|
||||||
mt.children = slices.Delete(mt.children, index, index+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mt *Job) run() {
|
func (mt *Job) run() {
|
||||||
cases := []reflect.SelectCase{{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(mt.addSub)}}
|
cases := []reflect.SelectCase{{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(mt.addSub)}}
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -177,27 +172,25 @@ func (mt *Job) run() {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if task := rev.Interface().(ITask); task.getParent() == mt {
|
if child := rev.Interface().(ITask); child.getParent() != mt || child.start() {
|
||||||
index := mt.addChild(task)
|
mt.children = append(mt.children, child)
|
||||||
if err := task.start(); err == nil {
|
cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(child.GetSignal())})
|
||||||
cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(task.GetSignal())})
|
|
||||||
} else {
|
|
||||||
task.Stop(err)
|
|
||||||
mt.removeChild(index)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mt.addChild(task)
|
|
||||||
cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(task.GetSignal())})
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
taskIndex := chosen - 1
|
taskIndex := chosen - 1
|
||||||
task := mt.children[taskIndex]
|
child := mt.children[taskIndex]
|
||||||
switch tt := task.(type) {
|
switch tt := child.(type) {
|
||||||
case IChannelTask:
|
case IChannelTask:
|
||||||
tt.Tick(rev.Interface())
|
tt.Tick(rev.Interface())
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
mt.removeChild(taskIndex)
|
if mt.onChildDispose(child); child.checkRetry(child.StopReason()) {
|
||||||
|
if child.reset(); child.start() {
|
||||||
|
cases[chosen].Chan = reflect.ValueOf(child.GetSignal())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mt.children = slices.Delete(mt.children, taskIndex, taskIndex+1)
|
||||||
cases = slices.Delete(cases, chosen, chosen+1)
|
cases = slices.Delete(cases, chosen, chosen+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
119
pkg/task/task.go
119
pkg/task/task.go
@@ -54,8 +54,10 @@ type (
|
|||||||
GetSignal() any
|
GetSignal() any
|
||||||
Stop(error)
|
Stop(error)
|
||||||
StopReason() error
|
StopReason() error
|
||||||
start() error
|
start() bool
|
||||||
dispose()
|
dispose()
|
||||||
|
checkRetry(error) bool
|
||||||
|
reset()
|
||||||
IsStopped() bool
|
IsStopped() bool
|
||||||
GetTaskType() TaskType
|
GetTaskType() TaskType
|
||||||
GetOwnerType() string
|
GetOwnerType() string
|
||||||
@@ -110,7 +112,6 @@ type (
|
|||||||
startup, shutdown *util.Promise
|
startup, shutdown *util.Promise
|
||||||
parent *Job
|
parent *Job
|
||||||
parentCtx context.Context
|
parentCtx context.Context
|
||||||
needRetry bool
|
|
||||||
state TaskState
|
state TaskState
|
||||||
level byte
|
level byte
|
||||||
}
|
}
|
||||||
@@ -224,31 +225,36 @@ func (task *Task) GetSignal() any {
|
|||||||
return task.Done()
|
return task.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *Task) checkRetry(err error) (bool, error) {
|
func (task *Task) checkRetry(err error) bool {
|
||||||
if errors.Is(err, ErrTaskComplete) {
|
if errors.Is(err, ErrTaskComplete) {
|
||||||
return false, err
|
return false
|
||||||
}
|
}
|
||||||
if task.retry.MaxRetry < 0 || task.retry.RetryCount < task.retry.MaxRetry {
|
if task.retry.MaxRetry < 0 || task.retry.RetryCount < task.retry.MaxRetry {
|
||||||
task.retry.RetryCount++
|
task.retry.RetryCount++
|
||||||
if task.Logger != nil {
|
if task.Logger != nil {
|
||||||
task.Warn(fmt.Sprintf("retry %d/%d", task.retry.RetryCount, task.retry.MaxRetry))
|
if task.retry.MaxRetry < 0 {
|
||||||
|
task.Warn(fmt.Sprintf("retry %d/∞", task.retry.RetryCount))
|
||||||
|
} else {
|
||||||
|
task.Warn(fmt.Sprintf("retry %d/%d", task.retry.RetryCount, task.retry.MaxRetry))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if delta := time.Since(task.StartTime); delta < task.retry.RetryInterval {
|
if delta := time.Since(task.StartTime); delta < task.retry.RetryInterval {
|
||||||
time.Sleep(task.retry.RetryInterval - delta)
|
time.Sleep(task.retry.RetryInterval - delta)
|
||||||
}
|
}
|
||||||
return true, err
|
return true
|
||||||
} else {
|
} else {
|
||||||
if task.retry.MaxRetry > 0 {
|
if task.retry.MaxRetry > 0 {
|
||||||
if task.Logger != nil {
|
if task.Logger != nil {
|
||||||
task.Warn(fmt.Sprintf("max retry %d failed", task.retry.MaxRetry))
|
task.Warn(fmt.Sprintf("max retry %d failed", task.retry.MaxRetry))
|
||||||
}
|
}
|
||||||
return false, errors.Join(err, ErrRetryRunOut)
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, err
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *Task) start() (err error) {
|
func (task *Task) start() bool {
|
||||||
|
var err error
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = errors.New(fmt.Sprint(r))
|
err = errors.New(fmt.Sprint(r))
|
||||||
@@ -257,55 +263,52 @@ func (task *Task) start() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
task.StartTime = time.Now()
|
for {
|
||||||
if task.Logger != nil {
|
task.StartTime = time.Now()
|
||||||
task.Debug("task start", "taskId", task.ID, "taskType", task.GetTaskType(), "ownerType", task.GetOwnerType())
|
if task.Logger != nil {
|
||||||
}
|
task.Debug("task start", "taskId", task.ID, "taskType", task.GetTaskType(), "ownerType", task.GetOwnerType())
|
||||||
task.state = TASK_STATE_STARTING
|
}
|
||||||
if v, ok := task.handler.(TaskStarter); ok {
|
task.state = TASK_STATE_STARTING
|
||||||
err = v.Start()
|
if v, ok := task.handler.(TaskStarter); ok {
|
||||||
}
|
err = v.Start()
|
||||||
if err == nil {
|
}
|
||||||
task.state = TASK_STATE_STARTED
|
if err == nil {
|
||||||
task.ResetRetryCount()
|
task.state = TASK_STATE_STARTED
|
||||||
if runHandler, ok := task.handler.(TaskBlock); ok {
|
task.startup.Fulfill(err)
|
||||||
task.state = TASK_STATE_RUNNING
|
for _, listener := range task.afterStartListeners {
|
||||||
err = runHandler.Run()
|
listener()
|
||||||
if err == nil {
|
}
|
||||||
return ErrTaskComplete
|
task.ResetRetryCount()
|
||||||
} else {
|
if runHandler, ok := task.handler.(TaskBlock); ok {
|
||||||
task.needRetry, err = task.checkRetry(err)
|
task.state = TASK_STATE_RUNNING
|
||||||
|
err = runHandler.Run()
|
||||||
|
if err == nil {
|
||||||
|
err = ErrTaskComplete
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
if err == nil {
|
||||||
task.needRetry, err = task.checkRetry(err)
|
if goHandler, ok := task.handler.(TaskGo); ok {
|
||||||
if task.needRetry {
|
task.state = TASK_STATE_GOING
|
||||||
defer task.reStart()
|
go task.run(goHandler.Go)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
task.Stop(err)
|
||||||
|
task.parent.onChildDispose(task.handler)
|
||||||
|
if task.checkRetry(err) {
|
||||||
|
task.reset()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
task.startup.Fulfill(err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, listener := range task.afterStartListeners {
|
|
||||||
listener()
|
|
||||||
}
|
|
||||||
if goHandler, ok := task.handler.(TaskGo); ok {
|
|
||||||
task.state = TASK_STATE_GOING
|
|
||||||
go task.run(goHandler.Go)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *Task) reStart() {
|
func (task *Task) reset() {
|
||||||
if task.IsStopped() {
|
task.Context, task.CancelCauseFunc = context.WithCancelCause(task.parentCtx)
|
||||||
task.Context, task.CancelCauseFunc = context.WithCancelCause(task.parentCtx)
|
task.shutdown = util.NewPromise(context.Background())
|
||||||
task.shutdown = util.NewPromise(context.Background())
|
|
||||||
}
|
|
||||||
task.startup = util.NewPromise(task.Context)
|
task.startup = util.NewPromise(task.Context)
|
||||||
parent := task.parent
|
|
||||||
task.parent = nil
|
|
||||||
parent.AddTask(task.handler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *Task) dispose() {
|
func (task *Task) dispose() {
|
||||||
@@ -331,9 +334,6 @@ func (task *Task) dispose() {
|
|||||||
listener()
|
listener()
|
||||||
}
|
}
|
||||||
task.state = TASK_STATE_DISPOSED
|
task.state = TASK_STATE_DISPOSED
|
||||||
if !errors.Is(reason, ErrTaskComplete) && task.needRetry {
|
|
||||||
task.reStart()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *Task) ResetRetryCount() {
|
func (task *Task) ResetRetryCount() {
|
||||||
@@ -341,16 +341,9 @@ func (task *Task) ResetRetryCount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (task *Task) run(handler func() error) {
|
func (task *Task) run(handler func() error) {
|
||||||
var err error
|
if err := handler(); err == nil {
|
||||||
err = handler()
|
|
||||||
if err == nil {
|
|
||||||
task.needRetry = false
|
|
||||||
task.Stop(ErrTaskComplete)
|
task.Stop(ErrTaskComplete)
|
||||||
} else {
|
} else {
|
||||||
if task.needRetry, err = task.checkRetry(err); !task.needRetry {
|
task.Stop(err)
|
||||||
task.Stop(errors.Join(err, ErrRetryRunOut))
|
|
||||||
} else {
|
|
||||||
task.Stop(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ type BufReader struct {
|
|||||||
buf MemoryReader
|
buf MemoryReader
|
||||||
BufLen int
|
BufLen int
|
||||||
feedData func() error
|
feedData func() error
|
||||||
|
Dump *os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBufReaderWithBufLen(reader io.Reader, bufLen int) (r *BufReader) {
|
func NewBufReaderWithBufLen(reader io.Reader, bufLen int) (r *BufReader) {
|
||||||
@@ -172,6 +174,9 @@ func (r *BufReader) ReadRange(n int, yield func([]byte)) (err error) {
|
|||||||
func (r *BufReader) Read(to []byte) (n int, err error) {
|
func (r *BufReader) Read(to []byte) (n int, err error) {
|
||||||
n = len(to)
|
n = len(to)
|
||||||
err = r.ReadNto(n, to)
|
err = r.ReadNto(n, to)
|
||||||
|
if r.Dump != nil {
|
||||||
|
r.Dump.Write(to)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +23,29 @@ func (l *limitReader) Read(p []byte) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBufReader_Buffered(t *testing.T) {
|
||||||
|
var feeder = make(chan net.Buffers, 100)
|
||||||
|
testReader := NewBufReaderBuffersChan(feeder)
|
||||||
|
testReader.BufLen = 4
|
||||||
|
t.Run("feed", func(t *testing.T) {
|
||||||
|
feeder <- net.Buffers{[]byte{1, 2, 3, 4, 5}, []byte{6, 7, 8, 9, 10}}
|
||||||
|
feeder <- net.Buffers{[]byte{11, 12, 13, 14, 15}, []byte{16, 17, 18, 19, 20}}
|
||||||
|
})
|
||||||
|
t.Run("read", func(t *testing.T) {
|
||||||
|
var b = make([]byte, 10)
|
||||||
|
testReader.Read(b)
|
||||||
|
if !bytes.Equal(b, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
|
||||||
|
t.Error("read error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
testReader.Read(b)
|
||||||
|
if !bytes.Equal(b, []byte{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}) {
|
||||||
|
t.Error("read error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestBufRead(t *testing.T) {
|
func TestBufRead(t *testing.T) {
|
||||||
t.Run(t.Name(), func(t *testing.T) {
|
t.Run(t.Name(), func(t *testing.T) {
|
||||||
var testData = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
|
var testData = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
|
||||||
|
|||||||
56
plugin.go
56
plugin.go
@@ -2,9 +2,11 @@ package m7s
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -58,6 +60,7 @@ type (
|
|||||||
OnStop()
|
OnStop()
|
||||||
Pull(string, config.Pull)
|
Pull(string, config.Pull)
|
||||||
Transform(string, config.Transform)
|
Transform(string, config.Transform)
|
||||||
|
OnPublish(*Publisher)
|
||||||
}
|
}
|
||||||
|
|
||||||
IRegisterHandler interface {
|
IRegisterHandler interface {
|
||||||
@@ -79,10 +82,6 @@ type (
|
|||||||
IQUICPlugin interface {
|
IQUICPlugin interface {
|
||||||
OnQUICConnect(quic.Connection) task.ITask
|
OnQUICConnect(quic.Connection) task.ITask
|
||||||
}
|
}
|
||||||
|
|
||||||
IListenPublishPlugin interface {
|
|
||||||
OnPublish(*Publisher) task.ITask
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var plugins []PluginMeta
|
var plugins []PluginMeta
|
||||||
@@ -364,7 +363,46 @@ func (p *Plugin) OnInit() error {
|
|||||||
func (p *Plugin) OnStop() {
|
func (p *Plugin) OnStop() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func (p *Plugin) OnPublish(pub *Publisher) {
|
||||||
|
onPublish := p.config.OnPub
|
||||||
|
if p.Meta.Pusher != nil {
|
||||||
|
for r, pushConf := range onPublish.Push {
|
||||||
|
if group := r.FindStringSubmatch(pub.StreamPath); group != nil {
|
||||||
|
for i, g := range group {
|
||||||
|
pushConf.URL = strings.Replace(pushConf.URL, fmt.Sprintf("$%d", i), g, -1)
|
||||||
|
}
|
||||||
|
p.Push(pub.StreamPath, pushConf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Meta.Recorder != nil {
|
||||||
|
for r, recConf := range onPublish.Record {
|
||||||
|
if group := r.FindStringSubmatch(pub.StreamPath); group != nil {
|
||||||
|
for i, g := range group {
|
||||||
|
recConf.FilePath = strings.Replace(recConf.FilePath, fmt.Sprintf("$%d", i), g, -1)
|
||||||
|
}
|
||||||
|
p.Record(pub.StreamPath, recConf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Meta.Transformer != nil {
|
||||||
|
for r, tranConf := range onPublish.Transform {
|
||||||
|
if group := r.FindStringSubmatch(pub.StreamPath); group != nil {
|
||||||
|
for j, to := range tranConf.Output {
|
||||||
|
for i, g := range group {
|
||||||
|
to.Target = strings.Replace(to.Target, fmt.Sprintf("$%d", i), g, -1)
|
||||||
|
}
|
||||||
|
targetUrl, err := url.Parse(to.Target)
|
||||||
|
if err == nil {
|
||||||
|
to.StreamPath = strings.TrimPrefix(targetUrl.Path, "/")
|
||||||
|
}
|
||||||
|
tranConf.Output[j] = to
|
||||||
|
}
|
||||||
|
p.Transform(pub.StreamPath, tranConf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
func (p *Plugin) PublishWithConfig(ctx context.Context, streamPath string, conf config.Publish) (publisher *Publisher, err error) {
|
func (p *Plugin) PublishWithConfig(ctx context.Context, streamPath string, conf config.Publish) (publisher *Publisher, err error) {
|
||||||
publisher = createPublisher(p, streamPath, conf)
|
publisher = createPublisher(p, streamPath, conf)
|
||||||
if p.config.EnableAuth {
|
if p.config.EnableAuth {
|
||||||
@@ -402,6 +440,14 @@ func (p *Plugin) SubscribeWithConfig(ctx context.Context, streamPath string, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = p.Server.Streams.AddTask(subscriber, ctx).WaitStarted()
|
err = p.Server.Streams.AddTask(subscriber, ctx).WaitStarted()
|
||||||
|
if err == nil {
|
||||||
|
select {
|
||||||
|
case <-subscriber.waitPublishDone:
|
||||||
|
err = subscriber.Publisher.WaitTrack()
|
||||||
|
case <-subscriber.Done():
|
||||||
|
err = subscriber.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
90
plugin/flv/pkg/echo.go
Normal file
90
plugin/flv/pkg/echo.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package flv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"m7s.live/m7s/v5/pkg/util"
|
||||||
|
rtmp "m7s.live/m7s/v5/plugin/rtmp/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Echo(r io.Reader) (err error) {
|
||||||
|
reader := util.NewBufReader(r)
|
||||||
|
var hasAudio, hasVideo bool
|
||||||
|
var absTS uint32
|
||||||
|
var head util.Memory
|
||||||
|
head, err = reader.ReadBytes(13)
|
||||||
|
if err == nil {
|
||||||
|
var flvHead [3]byte
|
||||||
|
var version, flag byte
|
||||||
|
err = head.NewReader().ReadByteTo(&flvHead[0], &flvHead[1], &flvHead[2], &version, &flag)
|
||||||
|
if flvHead != [...]byte{'F', 'L', 'V'} {
|
||||||
|
err = errors.New("not flv file")
|
||||||
|
} else {
|
||||||
|
hasAudio = flag&0x04 != 0
|
||||||
|
hasVideo = flag&0x01 != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var startTs uint32
|
||||||
|
fmt.Println(hasAudio, hasVideo)
|
||||||
|
allocator := util.NewScalableMemoryAllocator(1 << 10)
|
||||||
|
var tagSize int
|
||||||
|
for offsetTs := absTS; err == nil; tagSize, err = reader.ReadBE(4) {
|
||||||
|
fmt.Println(tagSize)
|
||||||
|
t, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataSize, err := reader.ReadBE32(3)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timestamp, err := reader.ReadBE32(3)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timestamp = timestamp | uint32(h)<<24
|
||||||
|
if startTs == 0 {
|
||||||
|
startTs = timestamp
|
||||||
|
}
|
||||||
|
if _, err = reader.ReadBE(3); err != nil { // stream id always 0
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var frame rtmp.RTMPData
|
||||||
|
ds := int(dataSize)
|
||||||
|
frame.SetAllocator(allocator)
|
||||||
|
err = reader.ReadNto(ds, frame.NextN(ds))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
absTS = offsetTs + (timestamp - startTs)
|
||||||
|
frame.Timestamp = absTS
|
||||||
|
fmt.Println(t, offsetTs, timestamp, startTs, absTS)
|
||||||
|
switch t {
|
||||||
|
case FLV_TAG_TYPE_AUDIO:
|
||||||
|
frame.Recycle()
|
||||||
|
case FLV_TAG_TYPE_VIDEO:
|
||||||
|
frame.Recycle()
|
||||||
|
case FLV_TAG_TYPE_SCRIPT:
|
||||||
|
r := frame.NewReader()
|
||||||
|
amf := &rtmp.AMF{
|
||||||
|
Buffer: util.Buffer(r.ToBytes()),
|
||||||
|
}
|
||||||
|
var obj any
|
||||||
|
obj, err = amf.Unmarshal()
|
||||||
|
name := obj
|
||||||
|
obj, err = amf.Unmarshal()
|
||||||
|
metaData := obj
|
||||||
|
frame.Recycle()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("script", name, metaData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
37
plugin/flv/pkg/echo_test.go
Normal file
37
plugin/flv/pkg/echo_test.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package flv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"m7s.live/m7s/v5/pkg/util"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRead(t *testing.T) {
|
||||||
|
var feeder = make(chan net.Buffers, 100)
|
||||||
|
reader := util.NewBufReaderBuffersChan(feeder)
|
||||||
|
|
||||||
|
t.Run("feed", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
file, _ := os.Open("/Users/dexter/Downloads/ps.flv")
|
||||||
|
for {
|
||||||
|
var buf = make([]byte, 1024)
|
||||||
|
n, err := file.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
close(feeder)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
feeder <- net.Buffers{buf[:n]}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("read", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
err := Echo(reader)
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
t.Error(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -5,10 +5,7 @@ import (
|
|||||||
transcode "m7s.live/m7s/v5/plugin/transcode/pkg"
|
transcode "m7s.live/m7s/v5/plugin/transcode/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var _ = m7s.InstallPlugin[TranscodePlugin](transcode.NewTransform)
|
||||||
//_ m7s.IListenPublishPlugin = (*TranscodePlugin)(nil)
|
|
||||||
_ = m7s.InstallPlugin[TranscodePlugin](transcode.NewTransform)
|
|
||||||
)
|
|
||||||
|
|
||||||
type TranscodePlugin struct {
|
type TranscodePlugin struct {
|
||||||
m7s.Plugin
|
m7s.Plugin
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package transcode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"m7s.live/m7s/v5/pkg/task"
|
|
||||||
"m7s.live/m7s/v5/pkg/util"
|
|
||||||
flv "m7s.live/m7s/v5/plugin/flv/pkg"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PipeInput struct {
|
|
||||||
task.Task
|
|
||||||
rBuf chan net.Buffers
|
|
||||||
*util.BufReader
|
|
||||||
flv.Live
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PipeInput) Start() (err error) {
|
|
||||||
p.rBuf = make(chan net.Buffers, 100)
|
|
||||||
p.BufReader = util.NewBufReaderBuffersChan(p.rBuf)
|
|
||||||
p.rBuf <- net.Buffers{flv.FLVHead}
|
|
||||||
p.WriteFlvTag = func(flv net.Buffers) (err error) {
|
|
||||||
select {
|
|
||||||
case p.rBuf <- flv:
|
|
||||||
default:
|
|
||||||
p.Warn("pipe input buffer full")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PipeInput) Dispose() {
|
|
||||||
close(p.rBuf)
|
|
||||||
p.BufReader.Recycle()
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"m7s.live/m7s/v5/pkg/util"
|
"m7s.live/m7s/v5/pkg/util"
|
||||||
flv "m7s.live/m7s/v5/plugin/flv/pkg"
|
flv "m7s.live/m7s/v5/plugin/flv/pkg"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -46,8 +47,12 @@ type (
|
|||||||
func NewTransform() m7s.ITransformer {
|
func NewTransform() m7s.ITransformer {
|
||||||
ret := &Transformer{}
|
ret := &Transformer{}
|
||||||
ret.WriteFlvTag = func(flv net.Buffers) (err error) {
|
ret.WriteFlvTag = func(flv net.Buffers) (err error) {
|
||||||
|
var buffer []byte
|
||||||
|
for _, b := range flv {
|
||||||
|
buffer = append(buffer, b...)
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case ret.rBuf <- flv:
|
case ret.rBuf <- buffer:
|
||||||
default:
|
default:
|
||||||
ret.Warn("pipe input buffer full")
|
ret.Warn("pipe input buffer full")
|
||||||
}
|
}
|
||||||
@@ -59,7 +64,7 @@ func NewTransform() m7s.ITransformer {
|
|||||||
type Transformer struct {
|
type Transformer struct {
|
||||||
m7s.DefaultTransformer
|
m7s.DefaultTransformer
|
||||||
TransRule
|
TransRule
|
||||||
rBuf chan net.Buffers
|
rBuf chan []byte
|
||||||
*util.BufReader
|
*util.BufReader
|
||||||
flv.Live
|
flv.Live
|
||||||
}
|
}
|
||||||
@@ -95,11 +100,17 @@ func (t *Transformer) Start() (err error) {
|
|||||||
}
|
}
|
||||||
t.To[i] = enc
|
t.To[i] = enc
|
||||||
args = append(args, strings.Fields(enc.Args)...)
|
args = append(args, strings.Fields(enc.Args)...)
|
||||||
if strings.HasPrefix(to.Target, "rtmp://") {
|
var targetUrl *url.URL
|
||||||
|
targetUrl, err = url.Parse(to.Target)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch targetUrl.Scheme {
|
||||||
|
case "rtmp":
|
||||||
args = append(args, "-f", "flv", to.Target)
|
args = append(args, "-f", "flv", to.Target)
|
||||||
} else if strings.HasPrefix(to.Target, "rtsp://") {
|
case "rtsp":
|
||||||
args = append(args, "-f", "rtsp", to.Target)
|
args = append(args, "-f", "rtsp", to.Target)
|
||||||
} else {
|
default:
|
||||||
args = append(args, to.Target)
|
args = append(args, to.Target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,10 +118,10 @@ func (t *Transformer) Start() (err error) {
|
|||||||
"cmd": args,
|
"cmd": args,
|
||||||
"config": t.TransRule,
|
"config": t.TransRule,
|
||||||
}
|
}
|
||||||
t.rBuf = make(chan net.Buffers, 100)
|
t.rBuf = make(chan []byte, 100)
|
||||||
t.BufReader = util.NewBufReaderBuffersChan(t.rBuf)
|
t.BufReader = util.NewBufReaderChan(t.rBuf)
|
||||||
t.Subscriber = t.TransformJob.Subscriber
|
t.Subscriber = t.TransformJob.Subscriber
|
||||||
|
//t.BufReader.Dump, err = os.OpenFile("dump.flv", os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
var cmdTask CommandTask
|
var cmdTask CommandTask
|
||||||
cmdTask.logFileName = fmt.Sprintf("logs/transcode_%s_%s.log", strings.ReplaceAll(t.TransformJob.StreamPath, "/", "_"), time.Now().Format("20060102150405"))
|
cmdTask.logFileName = fmt.Sprintf("logs/transcode_%s_%s.log", strings.ReplaceAll(t.TransformJob.StreamPath, "/", "_"), time.Now().Format("20060102150405"))
|
||||||
cmdTask.Cmd = exec.CommandContext(t, "ffmpeg", args...)
|
cmdTask.Cmd = exec.CommandContext(t, "ffmpeg", args...)
|
||||||
|
|||||||
51
publisher.go
51
publisher.go
@@ -2,14 +2,12 @@ package m7s
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"m7s.live/m7s/v5/pkg/task"
|
"m7s.live/m7s/v5/pkg/task"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -171,48 +169,14 @@ func (p *Publisher) Start() (err error) {
|
|||||||
s.Waiting.Remove(waiting)
|
s.Waiting.Remove(waiting)
|
||||||
}
|
}
|
||||||
for plugin := range s.Plugins.Range {
|
for plugin := range s.Plugins.Range {
|
||||||
if plugin.Disabled {
|
plugin.OnPublish(p)
|
||||||
continue
|
|
||||||
}
|
|
||||||
onPublish := plugin.GetCommonConf().OnPub
|
|
||||||
if plugin.Meta.Pusher != nil {
|
|
||||||
for r, pushConf := range onPublish.Push {
|
|
||||||
if group := r.FindStringSubmatch(p.StreamPath); group != nil {
|
|
||||||
for i, g := range group {
|
|
||||||
pushConf.URL = strings.Replace(pushConf.URL, fmt.Sprintf("$%d", i), g, -1)
|
|
||||||
}
|
|
||||||
plugin.Push(p.StreamPath, pushConf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if plugin.Meta.Recorder != nil {
|
|
||||||
for r, recConf := range onPublish.Record {
|
|
||||||
if group := r.FindStringSubmatch(p.StreamPath); group != nil {
|
|
||||||
for i, g := range group {
|
|
||||||
recConf.FilePath = strings.Replace(recConf.FilePath, fmt.Sprintf("$%d", i), g, -1)
|
|
||||||
}
|
|
||||||
plugin.Record(p.StreamPath, recConf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if plugin.Meta.Transformer != nil {
|
|
||||||
for r, tranConf := range onPublish.Transform {
|
|
||||||
if group := r.FindStringSubmatch(p.StreamPath); group != nil {
|
|
||||||
for j, to := range tranConf.Output {
|
|
||||||
for i, g := range group {
|
|
||||||
to.Target = strings.Replace(to.Target, fmt.Sprintf("$%d", i), g, -1)
|
|
||||||
}
|
|
||||||
tranConf.Output[j] = to
|
|
||||||
}
|
|
||||||
plugin.Transform(p.StreamPath, tranConf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := plugin.handler.(IListenPublishPlugin); ok {
|
|
||||||
v.OnPublish(p)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
s.Transforms.Post(func() error {
|
||||||
|
if m, ok := s.Transforms.Transformed.Get(p.StreamPath); ok {
|
||||||
|
m.TransformJob.TransformPublished(p)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
p.AddTask(&PublishTimeout{Publisher: p})
|
p.AddTask(&PublishTimeout{Publisher: p})
|
||||||
if p.PublishTimeout > 0 {
|
if p.PublishTimeout > 0 {
|
||||||
p.AddTask(&PublishNoDataTimeout{Publisher: p})
|
p.AddTask(&PublishNoDataTimeout{Publisher: p})
|
||||||
@@ -297,6 +261,7 @@ func (p *Publisher) RemoveSubscriber(subscriber *Subscriber) {
|
|||||||
|
|
||||||
func (p *Publisher) AddSubscriber(subscriber *Subscriber) {
|
func (p *Publisher) AddSubscriber(subscriber *Subscriber) {
|
||||||
subscriber.Publisher = p
|
subscriber.Publisher = p
|
||||||
|
close(subscriber.waitPublishDone)
|
||||||
if p.Subscribers.AddUnique(subscriber) {
|
if p.Subscribers.AddUnique(subscriber) {
|
||||||
p.Info("subscriber +1", "count", p.Subscribers.Length)
|
p.Info("subscriber +1", "count", p.Subscribers.Length)
|
||||||
if subscriber.BufferTime > p.BufferTime {
|
if subscriber.BufferTime > p.BufferTime {
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ func (p *HTTPFilePuller) Start() (err error) {
|
|||||||
if res, err = os.Open(remoteURL); err == nil {
|
if res, err = os.Open(remoteURL); err == nil {
|
||||||
p.ReadCloser = res
|
p.ReadCloser = res
|
||||||
}
|
}
|
||||||
|
//p.PullJob.Publisher.Publish.Speed = 1
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ type Server struct {
|
|||||||
Pulls task.Manager[string, *PullJob]
|
Pulls task.Manager[string, *PullJob]
|
||||||
Pushs task.Manager[string, *PushJob]
|
Pushs task.Manager[string, *PushJob]
|
||||||
Records task.Manager[string, *RecordJob]
|
Records task.Manager[string, *RecordJob]
|
||||||
Transforms task.Manager[string, *TransformJob]
|
Transforms Transforms
|
||||||
Subscribers SubscriberCollection
|
Subscribers SubscriberCollection
|
||||||
LogHandler MultiLogHandler
|
LogHandler MultiLogHandler
|
||||||
apiList []string
|
apiList []string
|
||||||
|
|||||||
@@ -60,12 +60,13 @@ type Subscriber struct {
|
|||||||
PubSubBase
|
PubSubBase
|
||||||
config.Subscribe
|
config.Subscribe
|
||||||
Publisher *Publisher
|
Publisher *Publisher
|
||||||
|
waitPublishDone chan struct{}
|
||||||
AudioReader, VideoReader *AVRingReader
|
AudioReader, VideoReader *AVRingReader
|
||||||
StartAudioTS, StartVideoTS time.Duration
|
StartAudioTS, StartVideoTS time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSubscriber(p *Plugin, streamPath string, conf config.Subscribe) *Subscriber {
|
func createSubscriber(p *Plugin, streamPath string, conf config.Subscribe) *Subscriber {
|
||||||
subscriber := &Subscriber{Subscribe: conf}
|
subscriber := &Subscriber{Subscribe: conf, waitPublishDone: make(chan struct{})}
|
||||||
subscriber.ID = task.GetNextTaskID()
|
subscriber.ID = task.GetNextTaskID()
|
||||||
subscriber.Plugin = p
|
subscriber.Plugin = p
|
||||||
subscriber.TimeoutTimer = time.NewTimer(subscriber.WaitTimeout)
|
subscriber.TimeoutTimer = time.NewTimer(subscriber.WaitTimeout)
|
||||||
@@ -83,11 +84,21 @@ func (s *Subscriber) Start() (err error) {
|
|||||||
s.Info("subscribe")
|
s.Info("subscribe")
|
||||||
if publisher, ok := server.Streams.Get(s.StreamPath); ok {
|
if publisher, ok := server.Streams.Get(s.StreamPath); ok {
|
||||||
publisher.AddSubscriber(s)
|
publisher.AddSubscriber(s)
|
||||||
err = publisher.WaitTrack()
|
return publisher.WaitTrack()
|
||||||
} else if waitStream, ok := server.Waiting.Get(s.StreamPath); ok {
|
} else if waitStream, ok := server.Waiting.Get(s.StreamPath); ok {
|
||||||
waitStream.Add(s)
|
waitStream.Add(s)
|
||||||
} else {
|
} else {
|
||||||
server.createWait(s.StreamPath).Add(s)
|
server.createWait(s.StreamPath).Add(s)
|
||||||
|
// var avoidTrans bool
|
||||||
|
//AVOID:
|
||||||
|
// for trans := range server.Transforms.Range {
|
||||||
|
// for _, output := range trans.Config.Output {
|
||||||
|
// if output.StreamPath == s.StreamPath {
|
||||||
|
// avoidTrans = true
|
||||||
|
// break AVOID
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
for plugin := range server.Plugins.Range {
|
for plugin := range server.Plugins.Range {
|
||||||
for reg, conf := range plugin.GetCommonConf().OnSub.Pull {
|
for reg, conf := range plugin.GetCommonConf().OnSub.Pull {
|
||||||
if plugin.Meta.Puller != nil {
|
if plugin.Meta.Puller != nil {
|
||||||
@@ -99,21 +110,23 @@ func (s *Subscriber) Start() (err error) {
|
|||||||
plugin.handler.Pull(s.StreamPath, conf)
|
plugin.handler.Pull(s.StreamPath, conf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for reg, conf := range plugin.GetCommonConf().OnSub.Transform {
|
//if !avoidTrans {
|
||||||
if plugin.Meta.Transformer != nil {
|
// for reg, conf := range plugin.GetCommonConf().OnSub.Transform {
|
||||||
if reg.MatchString(s.StreamPath) {
|
// if plugin.Meta.Transformer != nil {
|
||||||
if group := reg.FindStringSubmatch(s.StreamPath); group != nil {
|
// if reg.MatchString(s.StreamPath) {
|
||||||
for j, c := range conf.Output {
|
// if group := reg.FindStringSubmatch(s.StreamPath); group != nil {
|
||||||
for i, value := range group {
|
// for j, c := range conf.Output {
|
||||||
c.Target = strings.Replace(c.Target, fmt.Sprintf("$%d", i), value, -1)
|
// for i, value := range group {
|
||||||
}
|
// c.Target = strings.Replace(c.Target, fmt.Sprintf("$%d", i), value, -1)
|
||||||
conf.Output[j] = c
|
// }
|
||||||
}
|
// conf.Output[j] = c
|
||||||
}
|
// }
|
||||||
plugin.handler.Transform(s.StreamPath, conf)
|
// }
|
||||||
}
|
// plugin.handler.Transform(s.StreamPath, conf)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"m7s.live/m7s/v5/pkg"
|
"m7s.live/m7s/v5/pkg"
|
||||||
"m7s.live/m7s/v5/pkg/config"
|
"m7s.live/m7s/v5/pkg/config"
|
||||||
"m7s.live/m7s/v5/pkg/task"
|
"m7s.live/m7s/v5/pkg/task"
|
||||||
|
"m7s.live/m7s/v5/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -25,8 +26,20 @@ type (
|
|||||||
task.Job
|
task.Job
|
||||||
TransformJob TransformJob
|
TransformJob TransformJob
|
||||||
}
|
}
|
||||||
|
TransformedMap struct {
|
||||||
|
StreamPath string
|
||||||
|
TransformJob *TransformJob
|
||||||
|
}
|
||||||
|
Transforms struct {
|
||||||
|
Transformed util.Collection[string, *TransformedMap]
|
||||||
|
task.Manager[string, *TransformJob]
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (t *TransformedMap) GetKey() string {
|
||||||
|
return t.StreamPath
|
||||||
|
}
|
||||||
|
|
||||||
func (r *DefaultTransformer) GetTransformJob() *TransformJob {
|
func (r *DefaultTransformer) GetTransformJob() *TransformJob {
|
||||||
return &r.TransformJob
|
return &r.TransformJob
|
||||||
}
|
}
|
||||||
@@ -61,8 +74,36 @@ func (p *TransformJob) Init(transformer ITransformer, plugin *Plugin, streamPath
|
|||||||
func (p *TransformJob) Start() (err error) {
|
func (p *TransformJob) Start() (err error) {
|
||||||
s := p.Plugin.Server
|
s := p.Plugin.Server
|
||||||
if _, ok := s.Transforms.Get(p.GetKey()); ok {
|
if _, ok := s.Transforms.Get(p.GetKey()); ok {
|
||||||
return pkg.ErrRecordSamePath
|
return pkg.ErrTransformSame
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := s.Transforms.Transformed.Get(p.GetKey()); ok {
|
||||||
|
return pkg.ErrStreamExist
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, to := range p.Config.Output {
|
||||||
|
if to.StreamPath != "" {
|
||||||
|
s.Transforms.Transformed.Set(&TransformedMap{
|
||||||
|
StreamPath: to.StreamPath,
|
||||||
|
TransformJob: p,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
p.AddTask(p.transformer, p.Logger)
|
p.AddTask(p.transformer, p.Logger)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *TransformJob) TransformPublished(pub *Publisher) {
|
||||||
|
p.Publisher = pub
|
||||||
|
pub.OnDispose(func() {
|
||||||
|
p.Stop(pub.StopReason())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TransformJob) Dispose() {
|
||||||
|
for _, to := range p.Config.Output {
|
||||||
|
if to.StreamPath != "" {
|
||||||
|
p.Plugin.Server.Transforms.Transformed.RemoveByKey(to.StreamPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user