mirror of
				https://github.com/langhuihui/monibuca.git
				synced 2025-10-31 14:06:21 +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,6 +325,34 @@ 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: | ||||||
|  | 		if ft.Kind() == reflect.Map { | ||||||
|  | 			target = reflect.MakeMap(ft) | ||||||
|  | 			tmpStruct := reflect.StructOf([]reflect.StructField{ | ||||||
|  | 				{ | ||||||
|  | 					Name: "Key", | ||||||
|  | 					Type: ft.Key(), | ||||||
|  | 				}, | ||||||
|  | 			}) | ||||||
|  | 			tmpValue := reflect.New(tmpStruct) | ||||||
|  | 			for k, v := range v.(map[string]any) { | ||||||
|  | 				_ = yaml.Unmarshal([]byte(fmt.Sprintf("key: %s", k)), tmpValue.Interface()) | ||||||
|  | 				var value reflect.Value | ||||||
|  | 				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) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
| 			tmpStruct := reflect.StructOf([]reflect.StructField{ | 			tmpStruct := reflect.StructOf([]reflect.StructField{ | ||||||
| 				{ | 				{ | ||||||
| 					Name: strings.ToUpper(k), | 					Name: strings.ToUpper(k), | ||||||
| @@ -342,6 +371,7 @@ func (config *Config) assign(k string, v any) (target reflect.Value) { | |||||||
| 			} | 			} | ||||||
| 			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 | ||||||
| 	} | 	} | ||||||
| @@ -58,6 +58,7 @@ type ( | |||||||
| 		Input  any | 		Input  any | ||||||
| 		Output []struct { | 		Output []struct { | ||||||
| 			Target     string `desc:"转码目标"` // 转码目标 | 			Target     string `desc:"转码目标"` // 转码目标 | ||||||
|  | 			StreamPath string | ||||||
| 			Conf       any | 			Conf       any | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -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) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -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 { | ||||||
|  | 			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)) | 				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,6 +263,7 @@ func (task *Task) start() (err error) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
|  | 	for { | ||||||
| 		task.StartTime = time.Now() | 		task.StartTime = time.Now() | ||||||
| 		if task.Logger != nil { | 		if task.Logger != nil { | ||||||
| 			task.Debug("task start", "taskId", task.ID, "taskType", task.GetTaskType(), "ownerType", task.GetOwnerType()) | 			task.Debug("task start", "taskId", task.ID, "taskType", task.GetTaskType(), "ownerType", task.GetOwnerType()) | ||||||
| @@ -267,45 +274,41 @@ func (task *Task) start() (err error) { | |||||||
| 		} | 		} | ||||||
| 		if err == nil { | 		if err == nil { | ||||||
| 			task.state = TASK_STATE_STARTED | 			task.state = TASK_STATE_STARTED | ||||||
|  | 			task.startup.Fulfill(err) | ||||||
|  | 			for _, listener := range task.afterStartListeners { | ||||||
|  | 				listener() | ||||||
|  | 			} | ||||||
| 			task.ResetRetryCount() | 			task.ResetRetryCount() | ||||||
| 			if runHandler, ok := task.handler.(TaskBlock); ok { | 			if runHandler, ok := task.handler.(TaskBlock); ok { | ||||||
| 				task.state = TASK_STATE_RUNNING | 				task.state = TASK_STATE_RUNNING | ||||||
| 				err = runHandler.Run() | 				err = runHandler.Run() | ||||||
| 				if err == nil { | 				if err == nil { | ||||||
| 				return ErrTaskComplete | 					err = ErrTaskComplete | ||||||
| 			} else { |  | ||||||
| 				task.needRetry, err = task.checkRetry(err) |  | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 	} else { |  | ||||||
| 		task.needRetry, err = task.checkRetry(err) |  | ||||||
| 		if task.needRetry { |  | ||||||
| 			defer task.reStart() |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	task.startup.Fulfill(err) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	for _, listener := range task.afterStartListeners { |  | ||||||
| 		listener() |  | ||||||
| 		} | 		} | ||||||
|  | 		if err == nil { | ||||||
| 			if goHandler, ok := task.handler.(TaskGo); ok { | 			if goHandler, ok := task.handler.(TaskGo); ok { | ||||||
| 				task.state = TASK_STATE_GOING | 				task.state = TASK_STATE_GOING | ||||||
| 				go task.run(goHandler.Go) | 				go task.run(goHandler.Go) | ||||||
| 			} | 			} | ||||||
| 	return | 			return true | ||||||
|  | 		} else { | ||||||
|  | 			task.Stop(err) | ||||||
|  | 			task.parent.onChildDispose(task.handler) | ||||||
|  | 			if task.checkRetry(err) { | ||||||
|  | 				task.reset() | ||||||
|  | 			} else { | ||||||
|  | 				return false | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 { |  | ||||||
| 		if task.needRetry, err = task.checkRetry(err); !task.needRetry { |  | ||||||
| 			task.Stop(errors.Join(err, ErrRetryRunOut)) |  | ||||||
| 	} else { | 	} else { | ||||||
| 		task.Stop(err) | 		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...) | ||||||
|   | |||||||
							
								
								
									
										49
									
								
								publisher.go
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								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
	 langhuihui
					langhuihui