mirror of
				https://github.com/Monibuca/plugin-record.git
				synced 2025-11-01 04:12:49 +08:00 
			
		
		
		
	feat: support mp4 (not fmp4) format, start record can override fragment config,add fileName args
This commit is contained in:
		| @@ -61,7 +61,7 @@ record: | ||||
|  | ||||
| - `/record/api/list/recording` 罗列所有正在录制中的流的信息 | ||||
| - `/record/api/list?type=flv` 罗列所有录制的flv文件 | ||||
| - `/record/api/start?type=flv&streamPath=live/rtc` 开始录制某个流,返回一个字符串用于停止录制用的id | ||||
| - `/record/api/start?type=flv&streamPath=live/rtc&fileName=xxx` 开始录制某个流,返回一个字符串用于停止录制用的id(fileName是可选的,且只用于非切片情况) | ||||
| - `/record/api/stop?id=xxx` 停止录制某个流 | ||||
|  | ||||
| 其中将type值改为mp4则录制成fmp4格式。 | ||||
|   | ||||
							
								
								
									
										17
									
								
								config.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								config.go
									
									
									
									
									
								
							| @@ -10,7 +10,7 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"m7s.live/engine/v4" | ||||
| 	"m7s.live/engine/v4/util" | ||||
| ) | ||||
|  | ||||
| type FileWr interface { | ||||
| @@ -35,7 +35,7 @@ type Record struct { | ||||
| 	fs            http.Handler | ||||
| 	CreateFileFn  func(filename string, append bool) (FileWr, error) `json:"-" yaml:"-"` | ||||
| 	GetDurationFn func(file io.ReadSeeker) uint32                    `json:"-" yaml:"-"` | ||||
| 	recording     map[string]engine.ISubscriber | ||||
| 	recording     map[string]IRecorder | ||||
| } | ||||
|  | ||||
| func (r *Record) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
| @@ -43,14 +43,11 @@ func (r *Record) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
| } | ||||
|  | ||||
| func (r *Record) NeedRecord(streamPath string) bool { | ||||
| 	if _, ok := r.recording[streamPath]; ok { | ||||
| 		return false | ||||
| 	} | ||||
| 	return r.AutoRecord && (r.filterReg == nil || r.filterReg.MatchString(streamPath)) | ||||
| } | ||||
|  | ||||
| func (r *Record) Init() { | ||||
| 	r.recording = make(map[string]engine.ISubscriber) | ||||
| 	r.recording = make(map[string]IRecorder) | ||||
| 	os.MkdirAll(r.Path, 0766) | ||||
| 	if r.Filter != "" { | ||||
| 		r.filterReg = regexp.MustCompile(r.Filter) | ||||
| @@ -58,16 +55,10 @@ func (r *Record) Init() { | ||||
| 	r.fs = http.FileServer(http.Dir(r.Path)) | ||||
| 	r.CreateFileFn = func(filename string, append bool) (file FileWr, err error) { | ||||
| 		filePath := filepath.Join(r.Path, filename) | ||||
| 		flag := os.O_CREATE | ||||
| 		if append { | ||||
| 			flag = flag | os.O_RDWR | os.O_APPEND | ||||
| 		} else { | ||||
| 			flag = flag | os.O_RDWR | os.O_TRUNC | ||||
| 		} | ||||
| 		if err = os.MkdirAll(filepath.Dir(filePath), 0766); err != nil { | ||||
| 			return file, err | ||||
| 		} | ||||
| 		file, err = os.OpenFile(filePath, flag, 0766) | ||||
| 		file, err = os.OpenFile(filePath, os.O_CREATE | os.O_RDWR | util.Conditoinal(append, os.O_APPEND, os.O_TRUNC), 0766) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										111
									
								
								flv.go
									
									
									
									
									
								
							
							
						
						
									
										111
									
								
								flv.go
									
									
									
									
									
								
							| @@ -4,8 +4,6 @@ import ( | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"go.uber.org/zap" | ||||
| @@ -22,22 +20,18 @@ type FLVRecorder struct { | ||||
| 	duration      int64 | ||||
| } | ||||
|  | ||||
| func NewFLVRecorder() (r *FLVRecorder) { | ||||
| 	r = &FLVRecorder{} | ||||
| 	r.Record = RecordPluginConfig.Flv | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *FLVRecorder) Start(streamPath string) (err error) { | ||||
| 	r.Record = &RecordPluginConfig.Flv | ||||
| 	r.ID = streamPath + "/flv" | ||||
| 	if _, ok := RecordPluginConfig.recordings.Load(r.ID); ok { | ||||
| 		return ErrRecordExist | ||||
| 	} | ||||
| 	return plugin.Subscribe(streamPath, r) | ||||
| 	return r.start(r, streamPath, SUBTYPE_FLV) | ||||
| } | ||||
|  | ||||
| func (r *FLVRecorder) start() { | ||||
| 	RecordPluginConfig.recordings.Store(r.ID, r) | ||||
| 	r.PlayFLV() | ||||
| 	RecordPluginConfig.recordings.Delete(r.ID) | ||||
| } | ||||
|  | ||||
| func (r *FLVRecorder) writeMetaData(file *os.File, duration int64) { | ||||
| func (r *FLVRecorder) writeMetaData(file FileWr, duration int64) { | ||||
| 	defer file.Close() | ||||
| 	at, vt := r.Audio, r.Video | ||||
| 	hasAudio, hasVideo := at != nil, vt != nil | ||||
| @@ -127,46 +121,34 @@ func (r *FLVRecorder) writeMetaData(file *os.File, duration int64) { | ||||
| } | ||||
|  | ||||
| func (r *FLVRecorder) OnEvent(event any) { | ||||
| 	r.Recorder.OnEvent(event) | ||||
| 	switch v := event.(type) { | ||||
| 	case ISubscriber: | ||||
| 		filename := strconv.FormatInt(time.Now().Unix(), 10) + r.Ext | ||||
| 		if r.Fragment == 0 { | ||||
| 			filename = r.Stream.Path + r.Ext | ||||
| 	case FileWr: | ||||
| 		// 写入文件头 | ||||
| 		if !r.append { | ||||
| 			v.Write(codec.FLVHeader) | ||||
| 		} else { | ||||
| 			filename = filepath.Join(r.Stream.Path, filename) | ||||
| 		} | ||||
| 		if file, err := r.CreateFileFn(filename, r.append); err == nil { | ||||
| 			r.SetIO(file) | ||||
| 			// 写入文件头 | ||||
| 			if !r.append { | ||||
| 				r.Write(codec.FLVHeader) | ||||
| 			if _, err := v.Seek(-4, io.SeekEnd); err != nil { | ||||
| 				r.Error("seek file failed", zap.Error(err)) | ||||
| 				v.Write(codec.FLVHeader) | ||||
| 			} else { | ||||
| 				if _, err = file.Seek(-4, io.SeekEnd); err != nil { | ||||
| 					r.Error("seek file failed", zap.Error(err)) | ||||
| 					r.Write(codec.FLVHeader) | ||||
| 				} else { | ||||
| 					tmp := make(util.Buffer, 4) | ||||
| 					tmp2 := tmp | ||||
| 					file.Read(tmp) | ||||
| 					tagSize := tmp.ReadUint32() | ||||
| 					tmp = tmp2 | ||||
| 					file.Seek(int64(tagSize), io.SeekEnd) | ||||
| 					file.Read(tmp2) | ||||
| 					ts := tmp2.ReadUint24() | (uint32(tmp[3]) << 24) | ||||
| 					r.Info("append flv", zap.String("filename", filename), zap.Uint32("last tagSize", tagSize), zap.Uint32("last ts", ts)) | ||||
| 					if r.VideoReader != nil { | ||||
| 						r.VideoReader.StartTs = time.Duration(ts) * time.Millisecond | ||||
| 					} | ||||
| 					if r.AudioReader != nil { | ||||
| 						r.AudioReader.StartTs = time.Duration(ts) * time.Millisecond | ||||
| 					} | ||||
| 					file.Seek(0, io.SeekEnd) | ||||
| 				tmp := make(util.Buffer, 4) | ||||
| 				tmp2 := tmp | ||||
| 				v.Read(tmp) | ||||
| 				tagSize := tmp.ReadUint32() | ||||
| 				tmp = tmp2 | ||||
| 				v.Seek(int64(tagSize), io.SeekEnd) | ||||
| 				v.Read(tmp2) | ||||
| 				ts := tmp2.ReadUint24() | (uint32(tmp[3]) << 24) | ||||
| 				r.Info("append flv", zap.Uint32("last tagSize", tagSize), zap.Uint32("last ts", ts)) | ||||
| 				if r.VideoReader != nil { | ||||
| 					r.VideoReader.StartTs = time.Duration(ts) * time.Millisecond | ||||
| 				} | ||||
| 				if r.AudioReader != nil { | ||||
| 					r.AudioReader.StartTs = time.Duration(ts) * time.Millisecond | ||||
| 				} | ||||
| 				v.Seek(0, io.SeekEnd) | ||||
| 			} | ||||
| 			go r.start() | ||||
| 		} else { | ||||
| 			r.Error("create file failed", zap.Error(err)) | ||||
| 			r.Stop() | ||||
| 		} | ||||
| 	case FLVFrame: | ||||
| 		check := false | ||||
| @@ -186,15 +168,15 @@ func (r *FLVRecorder) OnEvent(event any) { | ||||
| 		if r.duration = int64(absTime); r.Fragment > 0 && check && time.Duration(r.duration)*time.Millisecond >= r.Fragment { | ||||
| 			r.Close() | ||||
| 			r.Offset = 0 | ||||
| 			if file, err := r.CreateFileFn(filepath.Join(r.Stream.Path, strconv.FormatInt(time.Now().Unix(), 10)+r.Ext), false); err == nil { | ||||
| 				r.SetIO(file) | ||||
| 				r.Write(codec.FLVHeader) | ||||
| 			if file, err := r.createFile(); err == nil { | ||||
| 				r.File = file | ||||
| 				file.Write(codec.FLVHeader) | ||||
| 				var dcflv net.Buffers | ||||
| 				if r.VideoReader != nil { | ||||
| 					r.VideoReader.ResetAbsTime() | ||||
| 					dcflv = codec.VideoAVCC2FLV(0, r.VideoReader.Track.SequenceHead) | ||||
| 					flv := append(dcflv, codec.VideoAVCC2FLV(0, r.VideoReader.Value.AVCC.ToBuffers()...)...) | ||||
| 					flv.WriteTo(r) | ||||
| 					flv.WriteTo(file) | ||||
| 				} | ||||
| 				if r.AudioReader != nil { | ||||
| 					r.AudioReader.ResetAbsTime() | ||||
| @@ -202,32 +184,27 @@ func (r *FLVRecorder) OnEvent(event any) { | ||||
| 						dcflv = codec.AudioAVCC2FLV(0, r.AudioReader.Track.SequenceHead) | ||||
| 					} | ||||
| 					flv := append(dcflv, codec.AudioAVCC2FLV(0, r.AudioReader.Value.AVCC.ToBuffers()...)...) | ||||
| 					flv.WriteTo(r) | ||||
| 					flv.WriteTo(file) | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		if n, err := v.WriteTo(r); err != nil { | ||||
| 		if n, err := v.WriteTo(r.File); err != nil { | ||||
| 			r.Error("write file failed", zap.Error(err)) | ||||
| 			r.Stop() | ||||
| 			r.Stop(zap.Error(err)) | ||||
| 		} else { | ||||
| 			r.Offset += n | ||||
| 		} | ||||
| 	default: | ||||
| 		r.Subscriber.OnEvent(event) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *FLVRecorder) SetIO(file any) { | ||||
| 	r.Subscriber.SetIO(file) | ||||
| 	r.Closer = r | ||||
| } | ||||
|  | ||||
| func (r *FLVRecorder) Close() error { | ||||
| 	if file, ok := r.Writer.(*os.File); ok && !r.append { | ||||
| 		go r.writeMetaData(file, r.duration) | ||||
| 	} else if closer, ok := r.Writer.(io.Closer); ok { | ||||
| 		return closer.Close() | ||||
| 	if r.File != nil { | ||||
| 		if !r.append { | ||||
| 			go r.writeMetaData(r.File, r.duration) | ||||
| 		} else { | ||||
| 			return r.File.Close() | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										149
									
								
								fmp4.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								fmp4.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| package record | ||||
|  | ||||
| import ( | ||||
| 	"github.com/edgeware/mp4ff/aac" | ||||
| 	"github.com/edgeware/mp4ff/mp4" | ||||
| 	. "m7s.live/engine/v4" | ||||
| 	"m7s.live/engine/v4/codec" | ||||
| ) | ||||
|  | ||||
| type mediaContext struct { | ||||
| 	trackId  uint32 | ||||
| 	fragment *mp4.Fragment | ||||
| 	ts       uint32 // 每个小片段起始时间戳 | ||||
| } | ||||
|  | ||||
| func (m *mediaContext) push(recoder *FMP4Recorder, dt uint32, dur uint32, data []byte, flags uint32) { | ||||
| 	if m.fragment != nil && dt-m.ts > 1000 { | ||||
| 		m.fragment.Encode(recoder) | ||||
| 		m.fragment = nil | ||||
| 	} | ||||
| 	if m.fragment == nil { | ||||
| 		recoder.seqNumber++ | ||||
| 		m.fragment, _ = mp4.CreateFragment(recoder.seqNumber, m.trackId) | ||||
| 		m.ts = dt | ||||
| 	} | ||||
| 	m.fragment.AddFullSample(mp4.FullSample{ | ||||
| 		Data:       data, | ||||
| 		DecodeTime: uint64(dt), | ||||
| 		Sample: mp4.Sample{ | ||||
| 			Flags: flags, | ||||
| 			Dur:   dur, | ||||
| 			Size:  uint32(len(data)), | ||||
| 		}, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| type FMP4Recorder struct { | ||||
| 	Recorder | ||||
| 	*mp4.InitSegment `json:"-" yaml:"-"` | ||||
| 	video            mediaContext | ||||
| 	audio            mediaContext | ||||
| 	seqNumber        uint32 | ||||
| 	ftyp             *mp4.FtypBox | ||||
| } | ||||
|  | ||||
| func NewFMP4Recorder() *FMP4Recorder { | ||||
| 	r := &FMP4Recorder{} | ||||
| 	r.Record = RecordPluginConfig.Fmp4 | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *FMP4Recorder) Start(streamPath string) (err error) { | ||||
| 	r.ID = streamPath + "/fmp4" | ||||
| 	return r.start(r, streamPath, SUBTYPE_RAW) | ||||
| } | ||||
|  | ||||
| func (r *FMP4Recorder) Close() error { | ||||
| 	if r.File != nil { | ||||
| 		if r.video.fragment != nil { | ||||
| 			r.video.fragment.Encode(r.File) | ||||
| 			r.video.fragment = nil | ||||
| 		} | ||||
| 		if r.audio.fragment != nil { | ||||
| 			r.audio.fragment.Encode(r.File) | ||||
| 			r.audio.fragment = nil | ||||
| 		} | ||||
| 		r.File.Close() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (r *FMP4Recorder) OnEvent(event any) { | ||||
| 	r.Recorder.OnEvent(event) | ||||
| 	switch v := event.(type) { | ||||
| 	case FileWr: | ||||
| 		r.InitSegment = mp4.CreateEmptyInit() | ||||
| 		r.Moov.Mvhd.NextTrackID = 1 | ||||
| 		if r.VideoReader != nil { | ||||
| 			moov := r.Moov | ||||
| 			trackID := moov.Mvhd.NextTrackID | ||||
| 			moov.Mvhd.NextTrackID++ | ||||
| 			newTrak := mp4.CreateEmptyTrak(trackID, 1000, "video", "chi") | ||||
| 			moov.AddChild(newTrak) | ||||
| 			moov.Mvex.AddChild(mp4.CreateTrex(trackID)) | ||||
| 			r.video.trackId = trackID | ||||
| 			switch r.Video.CodecID { | ||||
| 			case codec.CodecID_H264: | ||||
| 				r.ftyp = mp4.NewFtyp("isom", 0x200, []string{ | ||||
| 					"isom", "iso2", "avc1", "mp41", | ||||
| 				}) | ||||
| 				newTrak.SetAVCDescriptor("avc1", r.Video.ParamaterSets[0:1], r.Video.ParamaterSets[1:2], true) | ||||
| 			case codec.CodecID_H265: | ||||
| 				r.ftyp = mp4.NewFtyp("isom", 0x200, []string{ | ||||
| 					"isom", "iso2", "hvc1", "mp41", | ||||
| 				}) | ||||
| 				newTrak.SetHEVCDescriptor("hvc1", r.Video.ParamaterSets[0:1], r.Video.ParamaterSets[1:2], r.Video.ParamaterSets[2:3], true) | ||||
| 			} | ||||
| 		} | ||||
| 		if r.AudioReader != nil { | ||||
| 			moov := r.Moov | ||||
| 			trackID := moov.Mvhd.NextTrackID | ||||
| 			moov.Mvhd.NextTrackID++ | ||||
| 			newTrak := mp4.CreateEmptyTrak(trackID, 1000, "audio", "chi") | ||||
| 			moov.AddChild(newTrak) | ||||
| 			moov.Mvex.AddChild(mp4.CreateTrex(trackID)) | ||||
| 			r.audio.trackId = trackID | ||||
| 			switch r.Audio.CodecID { | ||||
| 			case codec.CodecID_AAC: | ||||
| 				switch r.Audio.AudioObjectType { | ||||
| 				case 1: | ||||
| 					newTrak.SetAACDescriptor(aac.HEAACv1, int(r.Audio.SampleRate)) | ||||
| 				case 2: | ||||
| 					newTrak.SetAACDescriptor(aac.AAClc, int(r.Audio.SampleRate)) | ||||
| 				case 3: | ||||
| 					newTrak.SetAACDescriptor(aac.HEAACv2, int(r.Audio.SampleRate)) | ||||
| 				} | ||||
| 			case codec.CodecID_PCMA: | ||||
| 				stsd := newTrak.Mdia.Minf.Stbl.Stsd | ||||
| 				pcma := mp4.CreateAudioSampleEntryBox("pcma", | ||||
| 					uint16(r.Audio.Channels), | ||||
| 					uint16(r.Audio.SampleSize), uint16(r.Audio.SampleRate), nil) | ||||
| 				stsd.AddChild(pcma) | ||||
| 			case codec.CodecID_PCMU: | ||||
| 				stsd := newTrak.Mdia.Minf.Stbl.Stsd | ||||
| 				pcmu := mp4.CreateAudioSampleEntryBox("pcmu", | ||||
| 					uint16(r.Audio.Channels), | ||||
| 					uint16(r.Audio.SampleSize), uint16(r.Audio.SampleRate), nil) | ||||
| 				stsd.AddChild(pcmu) | ||||
| 			} | ||||
| 		} | ||||
| 		r.ftyp.Encode(r) | ||||
| 		r.Moov.Encode(r) | ||||
| 		r.seqNumber = 0 | ||||
| 	case AudioFrame: | ||||
| 		if r.audio.trackId != 0 { | ||||
| 			r.audio.push(r, v.AbsTime, v.DeltaTime, v.AUList.ToBytes(), mp4.SyncSampleFlags) | ||||
| 		} | ||||
| 	case VideoFrame: | ||||
| 		if r.video.trackId != 0 { | ||||
| 			flag := mp4.NonSyncSampleFlags | ||||
| 			if v.IFrame { | ||||
| 				flag = mp4.SyncSampleFlags | ||||
| 			} | ||||
| 			if data := v.AVCC.ToBytes(); len(data) > 5 { | ||||
| 				r.video.push(r, v.AbsTime, v.DeltaTime, data[5:], flag) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										27
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								go.mod
									
									
									
									
									
								
							| @@ -4,13 +4,19 @@ go 1.19 | ||||
|  | ||||
| require ( | ||||
| 	github.com/edgeware/mp4ff v0.28.0 | ||||
| 	go.uber.org/zap v1.23.0 | ||||
| 	m7s.live/engine/v4 v4.12.6 | ||||
| 	m7s.live/plugin/hls/v4 v4.0.0-20220619163635-447976e65ab9 | ||||
| 	github.com/yapingcat/gomedia v0.0.0-20230727105416-c491e66c9d2a | ||||
| 	go.uber.org/zap v1.24.0 | ||||
| 	m7s.live/engine/v4 v4.13.8 | ||||
| 	m7s.live/plugin/hls/v4 v4.3.2 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	github.com/aler9/gortsplib/v2 v2.2.2 // indirect | ||||
| 	github.com/abema/go-mp4 v0.10.1 // indirect | ||||
| 	github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82 // indirect | ||||
| 	github.com/asticode/go-astikit v0.30.0 // indirect | ||||
| 	github.com/asticode/go-astits v1.11.0 // indirect | ||||
| 	github.com/bluenviron/gohlslib v0.2.5 // indirect | ||||
| 	github.com/bluenviron/mediacommon v0.7.0 // indirect | ||||
| 	github.com/cnotch/ipchub v1.1.0 // indirect | ||||
| 	github.com/denisbrodbeck/machineid v1.0.1 // indirect | ||||
| 	github.com/go-ole/go-ole v1.2.6 // indirect | ||||
| @@ -23,8 +29,8 @@ require ( | ||||
| 	github.com/mcuadros/go-defaults v1.2.0 // indirect | ||||
| 	github.com/onsi/ginkgo/v2 v2.2.0 // indirect | ||||
| 	github.com/pion/randutil v0.1.0 // indirect | ||||
| 	github.com/pion/rtp v1.7.13 // indirect | ||||
| 	github.com/pion/webrtc/v3 v3.1.49 // indirect | ||||
| 	github.com/pion/rtp v1.8.0 // indirect | ||||
| 	github.com/pion/webrtc/v3 v3.1.56 // indirect | ||||
| 	github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect | ||||
| 	github.com/q191201771/naza v0.30.8 // indirect | ||||
| 	github.com/quangngotan95/go-m3u8 v0.1.0 // indirect | ||||
| @@ -32,19 +38,18 @@ require ( | ||||
| 	github.com/quic-go/qtls-go1-19 v0.2.0 // indirect | ||||
| 	github.com/quic-go/qtls-go1-20 v0.1.0 // indirect | ||||
| 	github.com/quic-go/quic-go v0.32.0 // indirect | ||||
| 	github.com/shirou/gopsutil/v3 v3.22.10 // indirect | ||||
| 	github.com/shirou/gopsutil/v3 v3.22.11 // indirect | ||||
| 	github.com/tklauser/go-sysconf v0.3.11 // indirect | ||||
| 	github.com/tklauser/numcpus v0.6.0 // indirect | ||||
| 	github.com/yapingcat/gomedia v0.0.0-20230426092936-387031404274 // indirect | ||||
| 	github.com/yusufpapurcu/wmi v1.2.2 // indirect | ||||
| 	go.uber.org/atomic v1.10.0 // indirect | ||||
| 	go.uber.org/multierr v1.8.0 // indirect | ||||
| 	golang.org/x/crypto v0.4.0 // indirect | ||||
| 	golang.org/x/crypto v0.11.0 // indirect | ||||
| 	golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect | ||||
| 	golang.org/x/mod v0.7.0 // indirect | ||||
| 	golang.org/x/net v0.8.0 // indirect | ||||
| 	golang.org/x/net v0.12.0 // indirect | ||||
| 	golang.org/x/sync v0.1.0 // indirect | ||||
| 	golang.org/x/sys v0.6.0 // indirect | ||||
| 	golang.org/x/sys v0.10.0 // indirect | ||||
| 	golang.org/x/tools v0.3.0 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										145
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,7 +1,19 @@ | ||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||
| github.com/aler9/gortsplib/v2 v2.2.2 h1:tTw8pdKSOEjlZjjE1S4ftXPHJkYOqjNNv3hjQ0Nto9M= | ||||
| github.com/aler9/gortsplib/v2 v2.2.2/go.mod h1:k6uBVHGwsIc/0L5SLLqWwi6bSJUb4VR0HfvncyHlKQI= | ||||
| github.com/abema/go-mp4 v0.10.1 h1:wOhZgNxjduc8r4FJdwPa5x/gdBSSX+8MTnfNj/xkJaE= | ||||
| github.com/abema/go-mp4 v0.10.1/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws= | ||||
| github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82 h1:9WgSzBLo3a9ToSVV7sRTBYZ1GGOZUpq4+5H3SN0UZq4= | ||||
| github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82/go.mod h1:qsMrZCbeBf/mCLOeF16KDkPu4gktn/pOWyaq1aYQE7U= | ||||
| github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflxkRsZA= | ||||
| github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= | ||||
| github.com/asticode/go-astits v1.11.0 h1:GTHUXht0ZXAJXsVbsLIcyfHr1Bchi4QQwMARw2ZWAng= | ||||
| github.com/asticode/go-astits v1.11.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= | ||||
| github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= | ||||
| github.com/bluenviron/gohlslib v0.2.5 h1:M+uWOLjOa7hirUITLndf9bYarA8F7HR1m7+5dG/3WVI= | ||||
| github.com/bluenviron/gohlslib v0.2.5/go.mod h1:IvhV5h+92FljHy/xHlcOZm09rDeqg1RR88MjCTBx67Y= | ||||
| github.com/bluenviron/mediacommon v0.7.0 h1:dJWLLL9oDbAqfK8KuNfnDUQwNbeMAtGeRjZc9Vo95js= | ||||
| github.com/bluenviron/mediacommon v0.7.0/go.mod h1:wuLJdxcITiSPgY1MvQqrX+qPlKmNfeV9wNvXth5M98I= | ||||
| github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= | ||||
| github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= | ||||
| github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= | ||||
| github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||||
| github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | ||||
| @@ -13,6 +25,7 @@ github.com/cnotch/queue v0.0.0-20200326024423-6e88bdbf2ad4/go.mod h1:zOssjAlNusO | ||||
| github.com/cnotch/queue v0.0.0-20201224060551-4191569ce8f6/go.mod h1:zOssjAlNusOxvtaqT+EMA+Iyi8rrtKr4/XfzN1Fgoeg= | ||||
| github.com/cnotch/scheduler v0.0.0-20200522024700-1d2da93eefc5/go.mod h1:F4GE3SZkJZ8an1Y0ZCqvSM3jeozNuKzoC67erG1PhIo= | ||||
| github.com/cnotch/xlog v0.0.0-20201208005456-cfda439cd3a0/go.mod h1:RW9oHsR79ffl3sR3yMGgxYupMn2btzdtJUwoxFPUE5E= | ||||
| github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| @@ -23,12 +36,18 @@ github.com/edgeware/mp4ff v0.28.0/go.mod h1:GNUeA6tEFksH2CrjJF2FSGdJolba8yPGmo16 | ||||
| github.com/emitter-io/address v1.0.0/go.mod h1:GfZb5+S/o8694B1GMGK2imUYQyn2skszMvGNA5D84Ug= | ||||
| github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||||
| github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= | ||||
| github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= | ||||
| github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= | ||||
| github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= | ||||
| github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= | ||||
| github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= | ||||
| github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= | ||||
| github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= | ||||
| github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= | ||||
| github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= | ||||
| github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= | ||||
| github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= | ||||
| github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= | ||||
| github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= | ||||
| github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= | ||||
| github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| @@ -50,25 +69,33 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | ||||
| github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||
| github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= | ||||
| github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||||
| github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | ||||
| github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||
| github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||||
| github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||
| github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | ||||
| github.com/kelindar/process v0.0.0-20170730150328-69a29e249ec3/go.mod h1:+lTCLnZFXOkqwD8sLPl6u4erAc0cP8wFegQHfipz7KE= | ||||
| github.com/kelindar/rate v1.0.0/go.mod h1:AjT4G+hTItNwt30lucEGZIz8y7Uk5zPho6vurIZ+1Es= | ||||
| github.com/kelindar/tcp v1.0.0/go.mod h1:JB5hj1cshLU60XrLij2BBxW3JQ4hOye8vqbyvuKb52k= | ||||
| github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= | ||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||
| github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= | ||||
| github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= | ||||
| github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= | ||||
| github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= | ||||
| github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY= | ||||
| github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= | ||||
| github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= | ||||
| github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc= | ||||
| github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY= | ||||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= | ||||
| github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= | ||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= | ||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||
| github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= | ||||
| @@ -85,34 +112,37 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J | ||||
| github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= | ||||
| github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= | ||||
| github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= | ||||
| github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= | ||||
| github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY= | ||||
| github.com/pion/ice/v2 v2.2.12/go.mod h1:z2KXVFyRkmjetRlaVRgjO9U3ShKwzhlUylvxKfHfd5A= | ||||
| github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8= | ||||
| github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw= | ||||
| github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0= | ||||
| github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= | ||||
| github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0= | ||||
| github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY= | ||||
| github.com/pion/ice/v2 v2.3.1/go.mod h1:aq2kc6MtYNcn4XmMhobAv6hTNJiHzvD0yXRz80+bnP8= | ||||
| github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA= | ||||
| github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= | ||||
| github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= | ||||
| github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8= | ||||
| github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= | ||||
| github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= | ||||
| github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= | ||||
| github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= | ||||
| github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= | ||||
| github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= | ||||
| github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= | ||||
| github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= | ||||
| github.com/pion/sctp v1.8.3/go.mod h1:OHbDjdk7kg+L+7TJim9q/qGVefdEJohuA2SZyihccgI= | ||||
| github.com/pion/rtp v1.8.0 h1:SYD7040IR+NqrGBOc2GDU5iDjAR+0m5rnX/EWCUMNhw= | ||||
| github.com/pion/rtp v1.8.0/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= | ||||
| github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= | ||||
| github.com/pion/sctp v1.8.6/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= | ||||
| github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= | ||||
| github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA= | ||||
| github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= | ||||
| github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= | ||||
| github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= | ||||
| github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= | ||||
| github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg= | ||||
| github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= | ||||
| github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= | ||||
| github.com/pion/webrtc/v3 v3.1.49 h1:rbsNGxK9jMYts+xE6zYAJMUQHnGwmk/JYze8yttW+to= | ||||
| github.com/pion/webrtc/v3 v3.1.49/go.mod h1:kHf/o47QW4No1rgpsFux/h7lUhtUnwFnSFDZOXeLapw= | ||||
| github.com/pion/srtp/v2 v2.0.12/go.mod h1:C3Ep44hlOo2qEYaq4ddsmK5dL63eLehXFbHaZ9F5V9Y= | ||||
| github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw= | ||||
| github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= | ||||
| github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc= | ||||
| github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0= | ||||
| github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs= | ||||
| github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8= | ||||
| github.com/pion/webrtc/v3 v3.1.56 h1:ScaiqKQN3liQwT+kJwOBaYP6TwSfixzdUnZmzHAo0a0= | ||||
| github.com/pion/webrtc/v3 v3.1.56/go.mod h1:7VhbA6ihqJlz6R/INHjyh1b8HpiV9Ct4UQvE1OB/xoM= | ||||
| github.com/pixelbender/go-sdp v1.1.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs= | ||||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||
| github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= | ||||
| @@ -131,28 +161,30 @@ github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8u | ||||
| github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA= | ||||
| github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= | ||||
| github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= | ||||
| github.com/shirou/gopsutil/v3 v3.22.10 h1:4KMHdfBRYXGF9skjDWiL4RA2N+E8dRdodU/bOZpPoVg= | ||||
| github.com/shirou/gopsutil/v3 v3.22.10/go.mod h1:QNza6r4YQoydyCfo6rH0blGfKahgibh4dQmV5xdFkQk= | ||||
| github.com/shirou/gopsutil/v3 v3.22.11 h1:kxsPKS+Eeo+VnEQ2XCaGJepeP6KY53QoRTETx3+1ndM= | ||||
| github.com/shirou/gopsutil/v3 v3.22.11/go.mod h1:xl0EeL4vXJ+hQMAGN8B9VFpxukEMA0XdevQOe5MZ1oY= | ||||
| github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||
| github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||
| github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= | ||||
| github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= | ||||
| github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||||
| github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I= | ||||
| github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= | ||||
| github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= | ||||
| github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= | ||||
| github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= | ||||
| github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= | ||||
| github.com/yapingcat/gomedia v0.0.0-20230426092936-387031404274 h1:cj4I+bvWX9I+Hg6tnZ7DAiOVxzhyLhdvYVKp+WpM/2c= | ||||
| github.com/yapingcat/gomedia v0.0.0-20230426092936-387031404274/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc= | ||||
| github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= | ||||
| github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= | ||||
| github.com/yapingcat/gomedia v0.0.0-20230727105416-c491e66c9d2a h1:x60q0A7QmoUTzixNz7zVTdEA9JC0oYqm8S51PdbTWgs= | ||||
| github.com/yapingcat/gomedia v0.0.0-20230727105416-c491e66c9d2a/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc= | ||||
| github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= | ||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||
| @@ -164,17 +196,18 @@ go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 | ||||
| go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= | ||||
| go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= | ||||
| go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= | ||||
| go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= | ||||
| go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= | ||||
| go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= | ||||
| go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= | ||||
| golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= | ||||
| golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= | ||||
| golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= | ||||
| golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= | ||||
| golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= | ||||
| golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= | ||||
| golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= | ||||
| golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= | ||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| @@ -187,19 +220,16 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn | ||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= | ||||
| golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= | ||||
| golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= | ||||
| golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= | ||||
| golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= | ||||
| golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= | ||||
| golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= | ||||
| golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||||
| golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||||
| golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= | ||||
| golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= | ||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| @@ -210,6 +240,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| @@ -225,26 +256,28 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w | ||||
| golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= | ||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= | ||||
| golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= | ||||
| golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= | ||||
| golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
| @@ -264,24 +297,28 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi | ||||
| google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= | ||||
| google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= | ||||
| gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | ||||
| gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= | ||||
| gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= | ||||
| gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= | ||||
| gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | ||||
| gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| m7s.live/engine/v4 v4.12.6 h1:JWUwxOVHzL9wh8JbWUtZaDsRBXzyAzPI+4UEMGftsU0= | ||||
| m7s.live/engine/v4 v4.12.6/go.mod h1:LoALBfV5rmsz5TJQr6cmLxM33mfUE5BKBq/sMtXOVlc= | ||||
| m7s.live/plugin/hls/v4 v4.0.0-20220619163635-447976e65ab9 h1:EcB8awppfwza+s4ECjUr3xLTtl9BgJcZ12EgfE/L2YA= | ||||
| m7s.live/plugin/hls/v4 v4.0.0-20220619163635-447976e65ab9/go.mod h1:Qn4dDz5xlyBJwO+eZ3w8CUQ8Hl6KN1nmv0a3IsOjJvw= | ||||
| m7s.live/engine/v4 v4.13.8 h1:pDl8YWxip5aTidw2Q4NuU+8A6irBraLRfoeBi42S6iQ= | ||||
| m7s.live/engine/v4 v4.13.8/go.mod h1:k/6iFSuJxmhJL8VO45NAga8BbgZHLLfRXOwCcCzk2s8= | ||||
| m7s.live/plugin/hls/v4 v4.3.1 h1:FXcQShWnNTE0Guxg6YMzb5pbvMpn6MKiwcZ05N6nzhs= | ||||
| m7s.live/plugin/hls/v4 v4.3.1/go.mod h1:HSV/yO9NMvmNkompn+faM/0en0t3q0axp4zEA2mdyWw= | ||||
|   | ||||
							
								
								
									
										55
									
								
								hls.go
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								hls.go
									
									
									
									
									
								
							| @@ -22,27 +22,31 @@ type HLSRecorder struct { | ||||
| 	MemoryTs | ||||
| } | ||||
|  | ||||
| func NewHLSRecorder() (r *HLSRecorder) { | ||||
| 	r = &HLSRecorder{} | ||||
| 	r.Record = RecordPluginConfig.Hls | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (h *HLSRecorder) Start(streamPath string) error { | ||||
| 	h.Record = &RecordPluginConfig.Hls | ||||
| 	h.ID = streamPath + "/hls" | ||||
| 	if _, ok := RecordPluginConfig.recordings.Load(h.ID); ok { | ||||
| 		return ErrRecordExist | ||||
| 	} | ||||
| 	h.BytesPool = make(util.BytesPool, 17) | ||||
| 	return plugin.Subscribe(streamPath, h) | ||||
| 	return h.start(h, streamPath, SUBTYPE_RAW) | ||||
| } | ||||
|  | ||||
| func (h *HLSRecorder) OnEvent(event any) { | ||||
| 	var err error | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			h.Error("HLSRecorder Stop", zap.Error(err)) | ||||
| 			h.Stop() | ||||
| 			h.Stop(zap.Error(err)) | ||||
| 		} | ||||
| 	}() | ||||
| 	h.Recorder.OnEvent(event) | ||||
| 	switch v := event.(type) { | ||||
| 	case *HLSRecorder: | ||||
| 		h.BytesPool = make(util.BytesPool, 17) | ||||
| 		if h.Writer, err = h.createFile(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		h.SetIO(h.Writer) | ||||
| 		h.playlist = hls.Playlist{ | ||||
| 			Writer:         h.Writer, | ||||
| 			Version:        3, | ||||
| @@ -52,12 +56,11 @@ func (h *HLSRecorder) OnEvent(event any) { | ||||
| 		if err = h.playlist.Init(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		if err = h.createHlsTsSegmentFile(); err != nil { | ||||
| 			h.Stop() | ||||
| 		if h.File, err = h.CreateFile(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		go h.start() | ||||
| 	case AudioFrame: | ||||
| 		h.Recorder.OnEvent(event) | ||||
| 		pes := &mpegts.MpegtsPESFrame{ | ||||
| 			Pid:                       mpegts.PID_AUDIO, | ||||
| 			IsKeyFrame:                false, | ||||
| @@ -65,18 +68,12 @@ func (h *HLSRecorder) OnEvent(event any) { | ||||
| 			ProgramClockReferenceBase: uint64(v.DTS), | ||||
| 		} | ||||
| 		h.WriteAudioFrame(v, pes) | ||||
| 		h.BLL.WriteTo(h) | ||||
| 		h.BLL.WriteTo(h.File) | ||||
| 		h.Recycle() | ||||
| 		h.Clear() | ||||
| 		h.audio_cc = pes.ContinuityCounter | ||||
| 	case VideoFrame: | ||||
| 		if h.Fragment != 0 && h.newFile { | ||||
| 			h.newFile = false | ||||
| 			h.Close() | ||||
| 			if err = h.createHlsTsSegmentFile(); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		h.Recorder.OnEvent(event) | ||||
| 		pes := &mpegts.MpegtsPESFrame{ | ||||
| 			Pid:                       mpegts.PID_VIDEO, | ||||
| 			IsKeyFrame:                v.IFrame, | ||||
| @@ -86,21 +83,25 @@ func (h *HLSRecorder) OnEvent(event any) { | ||||
| 		if err = h.WriteVideoFrame(v, pes); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		h.BLL.WriteTo(h) | ||||
| 		h.BLL.WriteTo(h.File) | ||||
| 		h.Recycle() | ||||
| 		h.Clear() | ||||
| 		h.video_cc = pes.ContinuityCounter | ||||
| 	default: | ||||
| 		h.Recorder.OnEvent(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 创建一个新的ts文件 | ||||
| func (h *HLSRecorder) createHlsTsSegmentFile() (err error) { | ||||
| func (h *HLSRecorder) CreateFile() (fw FileWr, err error) { | ||||
| 	tsFilename := strconv.FormatInt(time.Now().Unix(), 10) + ".ts" | ||||
| 	fw, err := h.CreateFileFn(filepath.Join(h.Stream.Path, tsFilename), false) | ||||
| 	filePath := filepath.Join(h.Stream.Path, tsFilename) | ||||
| 	fw, err = h.CreateFileFn(filePath, false) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		h.Error("create file", zap.String("path", filePath), zap.Error(err)) | ||||
| 		return | ||||
| 	} | ||||
| 	h.SetIO(fw) | ||||
| 	h.Info("create file", zap.String("path", filePath)) | ||||
| 	inf := hls.PlaylistInf{ | ||||
| 		Duration: h.Fragment.Seconds(), | ||||
| 		Title:    tsFilename, | ||||
| @@ -109,7 +110,7 @@ func (h *HLSRecorder) createHlsTsSegmentFile() (err error) { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = mpegts.WriteDefaultPATPacket(fw); err != nil { | ||||
| 		return err | ||||
| 		return | ||||
| 	} | ||||
| 	var vcodec codec.VideoCodecID = 0 | ||||
| 	var acodec codec.AudioCodecID = 0 | ||||
| @@ -120,5 +121,5 @@ func (h *HLSRecorder) createHlsTsSegmentFile() (err error) { | ||||
| 		acodec = h.Audio.CodecID | ||||
| 	} | ||||
| 	mpegts.WritePMTPacket(fw, vcodec, acodec) | ||||
| 	return err | ||||
| 	return | ||||
| } | ||||
|   | ||||
							
								
								
									
										39
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								main.go
									
									
									
									
									
								
							| @@ -17,6 +17,7 @@ type RecordConfig struct { | ||||
| 	config.Subscribe | ||||
| 	Flv        Record | ||||
| 	Mp4        Record | ||||
| 	Fmp4       Record | ||||
| 	Hls        Record | ||||
| 	Raw        Record | ||||
| 	RawAudio   Record | ||||
| @@ -33,6 +34,10 @@ var RecordPluginConfig = &RecordConfig{ | ||||
| 		Ext:           ".flv", | ||||
| 		GetDurationFn: getFLVDuration, | ||||
| 	}, | ||||
| 	Fmp4: Record{ | ||||
| 		Path: "record/fmp4", | ||||
| 		Ext:  ".mp4", | ||||
| 	}, | ||||
| 	Mp4: Record{ | ||||
| 		Path: "record/mp4", | ||||
| 		Ext:  ".mp4", | ||||
| @@ -58,43 +63,29 @@ func (conf *RecordConfig) OnEvent(event any) { | ||||
| 	case FirstConfig, config.Config: | ||||
| 		conf.Flv.Init() | ||||
| 		conf.Mp4.Init() | ||||
| 		conf.Fmp4.Init() | ||||
| 		conf.Hls.Init() | ||||
| 		conf.Raw.Init() | ||||
| 		conf.RawAudio.Init() | ||||
| 	case SEclose: | ||||
| 		streamPath := v.Target.Path | ||||
| 		delete(conf.Flv.recording, streamPath) | ||||
| 		delete(conf.Mp4.recording, streamPath) | ||||
| 		delete(conf.Hls.recording, streamPath) | ||||
| 		delete(conf.Raw.recording, streamPath) | ||||
| 		delete(conf.RawAudio.recording, streamPath) | ||||
| 	case SEpublish: | ||||
| 		streamPath := v.Target.Path | ||||
| 		if conf.Flv.NeedRecord(streamPath) { | ||||
| 			var flv FLVRecorder | ||||
| 			conf.Flv.recording[streamPath] = &flv | ||||
| 			go flv.Start(streamPath) | ||||
| 			go NewFLVRecorder().Start(streamPath) | ||||
| 		} | ||||
| 		if conf.Mp4.NeedRecord(streamPath) { | ||||
| 			recoder := NewMP4Recorder() | ||||
| 			conf.Mp4.recording[streamPath] = recoder | ||||
| 			go recoder.Start(streamPath) | ||||
| 			go NewMP4Recorder().Start(streamPath) | ||||
| 		} | ||||
| 		if conf.Fmp4.NeedRecord(streamPath) { | ||||
| 			go NewFMP4Recorder().Start(streamPath) | ||||
| 		} | ||||
| 		if conf.Hls.NeedRecord(streamPath) { | ||||
| 			var hls HLSRecorder | ||||
| 			conf.Hls.recording[streamPath] = &hls | ||||
| 			go hls.Start(streamPath) | ||||
| 			go NewHLSRecorder().Start(streamPath) | ||||
| 		} | ||||
| 		if conf.Raw.NeedRecord(streamPath) { | ||||
| 			var raw RawRecorder | ||||
| 			conf.Raw.recording[streamPath] = &raw | ||||
| 			go raw.Start(streamPath) | ||||
| 			go NewRawRecorder().Start(streamPath) | ||||
| 		} | ||||
| 		if conf.RawAudio.NeedRecord(streamPath) { | ||||
| 			var raw RawRecorder | ||||
| 			raw.IsAudio = true | ||||
| 			conf.RawAudio.recording[streamPath] = &raw | ||||
| 			go raw.Start(streamPath) | ||||
| 			go NewRawAudioRecorder().Start(streamPath) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -104,6 +95,8 @@ func (conf *RecordConfig) getRecorderConfigByType(t string) (recorder *Record) { | ||||
| 		recorder = &conf.Flv | ||||
| 	case "mp4": | ||||
| 		recorder = &conf.Mp4 | ||||
| 	case "fmp4": | ||||
| 		recorder = &conf.Fmp4 | ||||
| 	case "hls": | ||||
| 		recorder = &conf.Hls | ||||
| 	case "raw": | ||||
|   | ||||
							
								
								
									
										194
									
								
								mp4.go
									
									
									
									
									
								
							
							
						
						
									
										194
									
								
								mp4.go
									
									
									
									
									
								
							| @@ -1,178 +1,82 @@ | ||||
| package record | ||||
|  | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/edgeware/mp4ff/aac" | ||||
| 	"github.com/edgeware/mp4ff/mp4" | ||||
| 	"github.com/yapingcat/gomedia/go-mp4" | ||||
| 	"go.uber.org/zap" | ||||
| 	. "m7s.live/engine/v4" | ||||
| 	"m7s.live/engine/v4/codec" | ||||
| 	"m7s.live/engine/v4/track" | ||||
| 	"m7s.live/engine/v4/util" | ||||
| ) | ||||
|  | ||||
| type mediaContext struct { | ||||
| 	trackId  uint32 | ||||
| 	fragment *mp4.Fragment | ||||
| 	ts       uint32 // 每个小片段起始时间戳 | ||||
| } | ||||
|  | ||||
| func (m *mediaContext) push(recoder *MP4Recorder, dt uint32, dur uint32, data []byte, flags uint32) { | ||||
| 	if m.fragment != nil && dt-m.ts > 1000 { | ||||
| 		m.fragment.Encode(recoder) | ||||
| 		m.fragment = nil | ||||
| 	} | ||||
| 	if m.fragment == nil { | ||||
| 		recoder.seqNumber++ | ||||
| 		m.fragment, _ = mp4.CreateFragment(recoder.seqNumber, m.trackId) | ||||
| 		m.ts = dt | ||||
| 	} | ||||
| 	m.fragment.AddFullSample(mp4.FullSample{ | ||||
| 		Data:       data, | ||||
| 		DecodeTime: uint64(dt), | ||||
| 		Sample: mp4.Sample{ | ||||
| 			Flags: flags, | ||||
| 			Dur:   dur, | ||||
| 			Size:  uint32(len(data)), | ||||
| 		}, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| type MP4Recorder struct { | ||||
| 	Recorder | ||||
| 	*mp4.InitSegment `json:"-" yaml:"-"` | ||||
| 	video            mediaContext | ||||
| 	audio            mediaContext | ||||
| 	seqNumber        uint32 | ||||
| 	ftyp             *mp4.FtypBox | ||||
| 	*mp4.Movmuxer `json:"-" yaml:"-"` | ||||
| 	videoId       uint32 | ||||
| 	audioId       uint32 | ||||
| } | ||||
|  | ||||
| func NewMP4Recorder() *MP4Recorder { | ||||
| 	r := &MP4Recorder{ | ||||
| 		InitSegment: mp4.CreateEmptyInit(), | ||||
| 	} | ||||
| 	r.Moov.Mvhd.NextTrackID = 1 | ||||
| 	r := &MP4Recorder{} | ||||
| 	r.Record = RecordPluginConfig.Mp4 | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *MP4Recorder) Start(streamPath string) (err error) { | ||||
| 	r.Record = &RecordPluginConfig.Mp4 | ||||
| 	r.ID = streamPath + "/mp4" | ||||
| 	if _, ok := RecordPluginConfig.recordings.Load(r.ID); ok { | ||||
| 		return ErrRecordExist | ||||
| 	} | ||||
| 	return plugin.Subscribe(streamPath, r) | ||||
| 	return r.start(r, streamPath, SUBTYPE_RAW) | ||||
| } | ||||
|  | ||||
| func (r *MP4Recorder) Close() error { | ||||
| 	if r.Writer != nil { | ||||
| 		if r.video.fragment != nil { | ||||
| 			r.video.fragment.Encode(r.Writer) | ||||
| 			r.video.fragment = nil | ||||
| func (r *MP4Recorder) Close() (err error) { | ||||
| 	if r.File != nil { | ||||
| 		err = r.Movmuxer.WriteTrailer() | ||||
| 		if err != nil { | ||||
| 			r.Error("mp4 write trailer", zap.Error(err)) | ||||
| 		} else { | ||||
| 			// _, err = r.file.Write(r.cache.buf) | ||||
| 			r.Info("mp4 write trailer", zap.Error(err)) | ||||
| 		} | ||||
| 		if r.audio.fragment != nil { | ||||
| 			r.audio.fragment.Encode(r.Writer) | ||||
| 			r.audio.fragment = nil | ||||
| 		} | ||||
| 		r.Closer.Close() | ||||
| 		err = r.File.Close() | ||||
| 	} | ||||
| 	return nil | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (r *MP4Recorder) OnEvent(event any) { | ||||
| 	r.Recorder.OnEvent(event) | ||||
| 	if r.newFile { | ||||
| 		r.newFile = false | ||||
| 		r.Close() | ||||
| 		if file, err := r.CreateFileFn(filepath.Join(r.Stream.Path, strconv.FormatInt(time.Now().Unix(), 10)+r.Ext), false); err == nil { | ||||
| 			r.SetIO(file) | ||||
| 			r.InitSegment = mp4.CreateEmptyInit() | ||||
| 			r.Moov.Mvhd.NextTrackID = 1 | ||||
| 			if r.VideoReader != nil { | ||||
| 				r.OnEvent(r.Video) | ||||
| 			} | ||||
| 			if r.AudioReader != nil { | ||||
| 				r.OnEvent(r.Audio) | ||||
| 			} | ||||
| 			r.ftyp.Encode(r) | ||||
| 			r.Moov.Encode(r) | ||||
| 			r.seqNumber = 0 | ||||
| 		} | ||||
| 	} | ||||
| 	switch v := event.(type) { | ||||
| 	case *track.Video: | ||||
| 		moov := r.Moov | ||||
| 		trackID := moov.Mvhd.NextTrackID | ||||
| 		moov.Mvhd.NextTrackID++ | ||||
| 		newTrak := mp4.CreateEmptyTrak(trackID, 1000, "video", "chi") | ||||
| 		moov.AddChild(newTrak) | ||||
| 		moov.Mvex.AddChild(mp4.CreateTrex(trackID)) | ||||
| 		r.video.trackId = trackID | ||||
| 		switch v.CodecID { | ||||
| 		case codec.CodecID_H264: | ||||
| 			r.ftyp = mp4.NewFtyp("isom", 0x200, []string{ | ||||
| 				"isom", "iso2", "avc1", "mp41", | ||||
| 			}) | ||||
| 			newTrak.SetAVCDescriptor("avc1", v.ParamaterSets[0:1], v.ParamaterSets[1:2], true) | ||||
| 		case codec.CodecID_H265: | ||||
| 			r.ftyp = mp4.NewFtyp("isom", 0x200, []string{ | ||||
| 				"isom", "iso2", "hvc1", "mp41", | ||||
| 			}) | ||||
| 			newTrak.SetHEVCDescriptor("hvc1", v.ParamaterSets[0:1], v.ParamaterSets[1:2], v.ParamaterSets[2:3], true) | ||||
| 		} | ||||
| 		r.AddTrack(v) | ||||
| 	case *track.Audio: | ||||
| 		moov := r.Moov | ||||
| 		trackID := moov.Mvhd.NextTrackID | ||||
| 		moov.Mvhd.NextTrackID++ | ||||
| 		newTrak := mp4.CreateEmptyTrak(trackID, 1000, "audio", "chi") | ||||
| 		moov.AddChild(newTrak) | ||||
| 		moov.Mvex.AddChild(mp4.CreateTrex(trackID)) | ||||
| 		r.audio.trackId = trackID | ||||
| 		switch v.CodecID { | ||||
| func (r *MP4Recorder) setTracks() { | ||||
| 	if r.Audio != nil { | ||||
| 		switch r.Audio.CodecID { | ||||
| 		case codec.CodecID_AAC: | ||||
| 			switch v.AudioObjectType { | ||||
| 			case 1: | ||||
| 				newTrak.SetAACDescriptor(aac.HEAACv1, int(v.SampleRate)) | ||||
| 			case 2: | ||||
| 				newTrak.SetAACDescriptor(aac.AAClc, int(v.SampleRate)) | ||||
| 			case 3: | ||||
| 				newTrak.SetAACDescriptor(aac.HEAACv2, int(v.SampleRate)) | ||||
| 			} | ||||
| 			r.audioId = r.AddAudioTrack(mp4.MP4_CODEC_AAC) | ||||
| 		case codec.CodecID_PCMA: | ||||
| 			stsd := newTrak.Mdia.Minf.Stbl.Stsd | ||||
| 			pcma := mp4.CreateAudioSampleEntryBox("pcma", | ||||
| 				uint16(v.Channels), | ||||
| 				uint16(v.SampleSize), uint16(v.SampleRate), nil) | ||||
| 			stsd.AddChild(pcma) | ||||
| 			r.audioId = r.AddAudioTrack(mp4.MP4_CODEC_G711A) | ||||
| 		case codec.CodecID_PCMU: | ||||
| 			stsd := newTrak.Mdia.Minf.Stbl.Stsd | ||||
| 			pcmu := mp4.CreateAudioSampleEntryBox("pcmu", | ||||
| 				uint16(v.Channels), | ||||
| 				uint16(v.SampleSize), uint16(v.SampleRate), nil) | ||||
| 			stsd.AddChild(pcmu) | ||||
| 			r.audioId = r.AddAudioTrack(mp4.MP4_CODEC_G711U) | ||||
| 		} | ||||
| 		r.AddTrack(v) | ||||
| 	case ISubscriber: | ||||
| 		if r.ftyp != nil && r.Writer != nil { | ||||
| 			r.ftyp.Encode(r) | ||||
| 			r.Moov.Encode(r) | ||||
| 			go r.start() | ||||
| 	} | ||||
| 	if r.Video != nil { | ||||
| 		switch r.Video.CodecID { | ||||
| 		case codec.CodecID_H264: | ||||
| 			r.videoId = r.AddVideoTrack(mp4.MP4_CODEC_H264) | ||||
| 		case codec.CodecID_H265: | ||||
| 			r.videoId = r.AddVideoTrack(mp4.MP4_CODEC_H265) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| func (r *MP4Recorder) OnEvent(event any) { | ||||
| 	var err error | ||||
| 	r.Recorder.OnEvent(event) | ||||
| 	switch v := event.(type) { | ||||
| 	case FileWr: | ||||
| 		r.Movmuxer, err = mp4.CreateMp4Muxer(v) | ||||
| 		if err != nil { | ||||
| 			r.Error("mp4 create muxer", zap.Error(err)) | ||||
| 		} else { | ||||
| 			r.setTracks() | ||||
| 		} | ||||
| 	case AudioFrame: | ||||
| 		if r.audio.trackId != 0 { | ||||
| 			r.audio.push(r, v.AbsTime, v.DeltaTime, v.AUList.ToBytes(), mp4.SyncSampleFlags) | ||||
| 		if r.audioId != 0 { | ||||
| 			r.Write(r.audioId, util.ConcatBuffers(v.GetADTS()), uint64(v.AbsTime+(v.PTS-v.DTS)/90), uint64(v.AbsTime)) | ||||
| 		} | ||||
| 	case VideoFrame: | ||||
| 		if r.video.trackId != 0 { | ||||
| 			flag := mp4.NonSyncSampleFlags | ||||
| 			if v.IFrame { | ||||
| 				flag = mp4.SyncSampleFlags | ||||
| 			} | ||||
| 			if data := v.AVCC.ToBytes(); len(data) > 5 { | ||||
| 				r.video.push(r, v.AbsTime, v.DeltaTime, data[5:], flag) | ||||
| 			} | ||||
| 		if r.videoId != 0 { | ||||
| 			r.Write(r.videoId, util.ConcatBuffers(v.GetAnnexB()), uint64(v.AbsTime+(v.PTS-v.DTS)/90), uint64(v.AbsTime)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										63
									
								
								raw.go
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								raw.go
									
									
									
									
									
								
							| @@ -1,11 +1,6 @@ | ||||
| package record | ||||
|  | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"go.uber.org/zap" | ||||
| 	. "m7s.live/engine/v4" | ||||
| 	"m7s.live/engine/v4/codec" | ||||
| 	"m7s.live/engine/v4/track" | ||||
| @@ -16,38 +11,32 @@ type RawRecorder struct { | ||||
| 	IsAudio bool | ||||
| } | ||||
|  | ||||
| func NewRawRecorder() (r *RawRecorder) { | ||||
| 	r = &RawRecorder{} | ||||
| 	r.Record = RecordPluginConfig.Raw | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func NewRawAudioRecorder() (r *RawRecorder) { | ||||
| 	r = &RawRecorder{IsAudio: true} | ||||
| 	r.Record = RecordPluginConfig.RawAudio | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *RawRecorder) Start(streamPath string) error { | ||||
| 	if r.IsAudio { | ||||
| 		r.Record = &RecordPluginConfig.RawAudio | ||||
| 	} else { | ||||
| 		r.Record = &RecordPluginConfig.Raw | ||||
| 	} | ||||
| 	r.ID = streamPath + "/raw" | ||||
| 	if r.IsAudio { | ||||
| 		r.ID += "_audio" | ||||
| 	} | ||||
| 	if _, ok := RecordPluginConfig.recordings.Load(r.ID); ok { | ||||
| 		return ErrRecordExist | ||||
| 	} | ||||
| 	return plugin.Subscribe(streamPath, r) | ||||
| 	return r.start(r, streamPath, SUBTYPE_RAW) | ||||
| } | ||||
|  | ||||
| func (r *RawRecorder) OnEvent(event any) { | ||||
| 	switch v := event.(type) { | ||||
| 	case FileWr: | ||||
| 		r.SetIO(v) | ||||
| 	case *RawRecorder: | ||||
| 		filename := strconv.FormatInt(time.Now().Unix(), 10) + r.Ext | ||||
| 		if r.Fragment == 0 { | ||||
| 			filename = r.Stream.Path + r.Ext | ||||
| 		} else { | ||||
| 			filename = filepath.Join(r.Stream.Path, filename) | ||||
| 		} | ||||
| 		if file, err := r.CreateFileFn(filename, r.append); err == nil { | ||||
| 			r.SetIO(file) | ||||
| 		} else { | ||||
| 			r.Error("create file failed", zap.Error(err)) | ||||
| 			r.Stop() | ||||
| 		} | ||||
| 		go r.start() | ||||
| 		r.Recorder.OnEvent(event) | ||||
| 	case *track.Video: | ||||
| 		if r.IsAudio { | ||||
| 			break | ||||
| @@ -76,26 +65,10 @@ func (r *RawRecorder) OnEvent(event any) { | ||||
| 		} | ||||
| 		r.AddTrack(v) | ||||
| 	case AudioFrame: | ||||
| 		if r.Fragment > 0 { | ||||
| 			if r.cut(v.AbsTime); r.newFile { | ||||
| 				r.newFile = false | ||||
| 				r.Close() | ||||
| 				if file, err := r.CreateFileFn(filepath.Join(r.Stream.Path, strconv.FormatInt(time.Now().Unix(), 10)+r.Ext), false); err == nil { | ||||
| 					r.SetIO(file) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		r.Recorder.OnEvent(event) | ||||
| 		v.WriteRawTo(r) | ||||
| 	case VideoFrame: | ||||
| 		if r.Fragment > 0 && v.IFrame { | ||||
| 			if r.cut(v.AbsTime); r.newFile { | ||||
| 				r.newFile = false | ||||
| 				r.Close() | ||||
| 				if file, err := r.CreateFileFn(filepath.Join(r.Stream.Path, strconv.FormatInt(time.Now().Unix(), 10)+r.Ext), false); err == nil { | ||||
| 					r.SetIO(file) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		r.Recorder.OnEvent(event) | ||||
| 		v.WriteAnnexBTo(r) | ||||
| 	default: | ||||
| 		r.IO.OnEvent(v) | ||||
|   | ||||
							
								
								
									
										42
									
								
								restful.go
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								restful.go
									
									
									
									
									
								
							| @@ -2,6 +2,7 @@ package record | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| @@ -16,7 +17,7 @@ func (conf *RecordConfig) API_list(w http.ResponseWriter, r *http.Request) { | ||||
| 	var err error | ||||
| 	recorder := conf.getRecorderConfigByType(t) | ||||
| 	if recorder == nil { | ||||
| 		for _, t = range []string{"flv", "mp4", "hls", "raw", "raw_audio"} { | ||||
| 		for _, t = range []string{"flv", "mp4", "fmp4", "hls", "raw", "raw_audio"} { | ||||
| 			recorder = conf.getRecorderConfigByType(t) | ||||
| 			var fs []*VideoFileInfo | ||||
| 			if fs, err = recorder.Tree(recorder.Path, 0); err == nil { | ||||
| @@ -41,6 +42,8 @@ func (conf *RecordConfig) API_list(w http.ResponseWriter, r *http.Request) { | ||||
| func (conf *RecordConfig) API_start(w http.ResponseWriter, r *http.Request) { | ||||
| 	query := r.URL.Query() | ||||
| 	streamPath := query.Get("streamPath") | ||||
| 	fileName := query.Get("fileName") | ||||
| 	fragment := query.Get("fragment") | ||||
| 	if streamPath == "" { | ||||
| 		http.Error(w, "no streamPath", http.StatusBadRequest) | ||||
| 		return | ||||
| @@ -48,43 +51,40 @@ func (conf *RecordConfig) API_start(w http.ResponseWriter, r *http.Request) { | ||||
| 	t := query.Get("type") | ||||
| 	var id string | ||||
| 	var err error | ||||
| 	var irecorder IRecorder | ||||
| 	switch t { | ||||
| 	case "": | ||||
| 		t = "flv" | ||||
| 		fallthrough | ||||
| 	case "flv": | ||||
| 		var flvRecoder FLVRecorder | ||||
| 		flvRecoder.append = query.Get("append") != "" | ||||
| 		err = flvRecoder.Start(streamPath) | ||||
| 		id = flvRecoder.ID | ||||
| 		irecorder = NewFLVRecorder() | ||||
| 	case "mp4": | ||||
| 		recorder := NewMP4Recorder() | ||||
| 		err = recorder.Start(streamPath) | ||||
| 		id = recorder.ID | ||||
| 		irecorder = NewMP4Recorder() | ||||
| 	case "fmp4": | ||||
| 		irecorder = NewFMP4Recorder() | ||||
| 	case "hls": | ||||
| 		var recorder HLSRecorder | ||||
| 		err = recorder.Start(streamPath) | ||||
| 		id = recorder.ID | ||||
| 		irecorder = NewHLSRecorder() | ||||
| 	case "raw": | ||||
| 		var recorder RawRecorder | ||||
| 		recorder.append = query.Get("append") != "" | ||||
| 		err = recorder.Start(streamPath) | ||||
| 		id = recorder.ID | ||||
| 		irecorder = NewRawRecorder() | ||||
| 	case "raw_audio": | ||||
| 		var recorder RawRecorder | ||||
| 		recorder.IsAudio = true | ||||
| 		recorder.append = query.Get("append") != "" | ||||
| 		err = recorder.Start(streamPath) | ||||
| 		id = recorder.ID | ||||
| 		irecorder = NewRawAudioRecorder() | ||||
| 	default: | ||||
| 		http.Error(w, "type not supported", http.StatusBadRequest) | ||||
| 		return | ||||
| 	} | ||||
| 	recorder := irecorder.GetRecorder() | ||||
| 	if fragment != "" { | ||||
| 		recorder.Fragment, err = time.ParseDuration(fragment) | ||||
| 	} | ||||
| 	recorder.FileName = fileName | ||||
| 	recorder.append = query.Get("append") != "" | ||||
| 	err = irecorder.Start(streamPath) | ||||
| 	id = recorder.ID | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), http.StatusInternalServerError) | ||||
| 		return | ||||
| 	} | ||||
| 	w.Write([]byte(id)) | ||||
| 	fmt.Fprintf(w, id) | ||||
| } | ||||
|  | ||||
| func (conf *RecordConfig) API_list_recording(w http.ResponseWriter, r *http.Request) { | ||||
|   | ||||
							
								
								
									
										104
									
								
								subscriber.go
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								subscriber.go
									
									
									
									
									
								
							| @@ -1,6 +1,7 @@ | ||||
| package record | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
| @@ -9,41 +10,100 @@ import ( | ||||
| 	. "m7s.live/engine/v4" | ||||
| ) | ||||
|  | ||||
| type Recorder struct { | ||||
| 	Subscriber | ||||
| 	SkipTS  uint32 | ||||
| 	*Record `json:"-" yaml:"-"` | ||||
| 	newFile bool // 创建了新的文件 | ||||
| 	append  bool // 是否追加模式 | ||||
| type IRecorder interface { | ||||
| 	ISubscriber | ||||
| 	GetRecorder() *Recorder | ||||
| 	Start(streamPath string) error | ||||
| 	io.Closer | ||||
| 	CreateFile() (FileWr, error) | ||||
| } | ||||
|  | ||||
| func (r *Recorder) start() { | ||||
| 	RecordPluginConfig.recordings.Store(r.ID, r) | ||||
| 	r.PlayRaw() | ||||
| 	RecordPluginConfig.recordings.Delete(r.ID) | ||||
| type Recorder struct { | ||||
| 	Subscriber | ||||
| 	SkipTS   uint32 | ||||
| 	Record   `json:"-" yaml:"-"` | ||||
| 	File     FileWr `json:"-" yaml:"-"` | ||||
| 	FileName string // 自定义文件名,分段录像无效 | ||||
| 	append   bool   // 是否追加模式 | ||||
| } | ||||
|  | ||||
| func (r *Recorder) GetRecorder() *Recorder { | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (r *Recorder) CreateFile() (FileWr, error) { | ||||
| 	return r.createFile() | ||||
| } | ||||
|  | ||||
| func (r *Recorder) Close() error { | ||||
| 	if r.File != nil { | ||||
| 		return r.File.Close() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (r *Recorder) createFile() (f FileWr, err error) { | ||||
| 	filePath := r.getFileName(r.Stream.Path) + r.Ext | ||||
| 	f, err = r.CreateFileFn(filePath, r.append) | ||||
| 	if err == nil { | ||||
| 		r.Info("create file", zap.String("path", filePath)) | ||||
| 	} else { | ||||
| 		r.Error("create file", zap.String("path", filePath), zap.Error(err)) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (r *Recorder) getFileName(streamPath string) (filename string) { | ||||
| 	filename = streamPath | ||||
| 	if r.Fragment == 0 { | ||||
| 		if r.FileName != "" { | ||||
| 			filename = filepath.Join(filename, r.FileName) | ||||
| 		} | ||||
| 	} else { | ||||
| 		filename = filepath.Join(filename, strconv.FormatInt(time.Now().Unix(), 10)) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (r *Recorder) start(re IRecorder, streamPath string, subType byte) (err error) { | ||||
| 	err = plugin.Subscribe(streamPath, re) | ||||
| 	if err == nil { | ||||
| 		if _, loaded := RecordPluginConfig.recordings.LoadOrStore(r.ID, re); loaded { | ||||
| 			return ErrRecordExist | ||||
| 		} | ||||
| 		r.recording[streamPath] = re | ||||
| 		r.Closer = re | ||||
| 		go func() { | ||||
| 			r.PlayBlock(subType) | ||||
| 			RecordPluginConfig.recordings.Delete(r.ID) | ||||
| 			delete(r.recording, streamPath) | ||||
| 			re.Close() | ||||
| 		}() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (r *Recorder) cut(absTime uint32) { | ||||
| 	if ts := absTime - r.SkipTS; time.Duration(ts)*time.Millisecond >= r.Fragment { | ||||
| 		r.SkipTS = absTime | ||||
| 		r.newFile = true | ||||
| 		r.Close() | ||||
| 		if file, err := r.Spesific.(IRecorder).CreateFile(); err == nil { | ||||
| 			r.File = file | ||||
| 			r.Spesific.OnEvent(file) | ||||
| 		} else { | ||||
| 			r.Stop(zap.Error(err)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *Recorder) OnEvent(event any) { | ||||
| 	switch v := event.(type) { | ||||
| 	case ISubscriber: | ||||
| 		filename := strconv.FormatInt(time.Now().Unix(), 10) + r.Ext | ||||
| 		if r.Fragment == 0 { | ||||
| 			filename = r.Stream.Path + r.Ext | ||||
| 	case IRecorder: | ||||
| 		if file, err := r.Spesific.(IRecorder).CreateFile(); err == nil { | ||||
| 			r.File = file | ||||
| 			r.Spesific.OnEvent(file) | ||||
| 		} else { | ||||
| 			filename = filepath.Join(r.Stream.Path, filename) | ||||
| 		} | ||||
| 		if file, err := r.CreateFileFn(filename, r.append); err == nil { | ||||
| 			r.SetIO(file) | ||||
| 		} else { | ||||
| 			r.Error("create file failed", zap.Error(err)) | ||||
| 			r.Stop() | ||||
| 			r.Stop(zap.Error(err)) | ||||
| 		} | ||||
| 	case AudioFrame: | ||||
| 		// 纯音频流的情况下需要切割文件 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 langhuihui
					langhuihui