mirror of
https://github.com/Monibuca/plugin-record.git
synced 2025-10-30 11:26:18 +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
|
||||
}
|
||||
}
|
||||
|
||||
85
flv.go
85
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,47 +121,35 @@ 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
|
||||
} else {
|
||||
filename = filepath.Join(r.Stream.Path, filename)
|
||||
}
|
||||
if file, err := r.CreateFileFn(filename, r.append); err == nil {
|
||||
r.SetIO(file)
|
||||
case FileWr:
|
||||
// 写入文件头
|
||||
if !r.append {
|
||||
r.Write(codec.FLVHeader)
|
||||
v.Write(codec.FLVHeader)
|
||||
} else {
|
||||
if _, err = file.Seek(-4, io.SeekEnd); err != nil {
|
||||
if _, err := v.Seek(-4, io.SeekEnd); err != nil {
|
||||
r.Error("seek file failed", zap.Error(err))
|
||||
r.Write(codec.FLVHeader)
|
||||
v.Write(codec.FLVHeader)
|
||||
} else {
|
||||
tmp := make(util.Buffer, 4)
|
||||
tmp2 := tmp
|
||||
file.Read(tmp)
|
||||
v.Read(tmp)
|
||||
tagSize := tmp.ReadUint32()
|
||||
tmp = tmp2
|
||||
file.Seek(int64(tagSize), io.SeekEnd)
|
||||
file.Read(tmp2)
|
||||
v.Seek(int64(tagSize), io.SeekEnd)
|
||||
v.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))
|
||||
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
|
||||
}
|
||||
file.Seek(0, io.SeekEnd)
|
||||
v.Seek(0, io.SeekEnd)
|
||||
}
|
||||
}
|
||||
go r.start()
|
||||
} else {
|
||||
r.Error("create file failed", zap.Error(err))
|
||||
r.Stop()
|
||||
}
|
||||
case FLVFrame:
|
||||
check := false
|
||||
var absTime uint32
|
||||
@@ -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=
|
||||
|
||||
57
hls.go
57
hls.go
@@ -22,27 +22,31 @@ type HLSRecorder struct {
|
||||
MemoryTs
|
||||
}
|
||||
|
||||
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
|
||||
func NewHLSRecorder() (r *HLSRecorder) {
|
||||
r = &HLSRecorder{}
|
||||
r.Record = RecordPluginConfig.Hls
|
||||
return r
|
||||
}
|
||||
h.BytesPool = make(util.BytesPool, 17)
|
||||
return plugin.Subscribe(streamPath, h)
|
||||
|
||||
func (h *HLSRecorder) Start(streamPath string) error {
|
||||
h.ID = streamPath + "/hls"
|
||||
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
|
||||
err = r.File.Close()
|
||||
}
|
||||
r.Closer.Close()
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 (r *RawRecorder) Start(streamPath string) error {
|
||||
if r.IsAudio {
|
||||
r.Record = &RecordPluginConfig.RawAudio
|
||||
} else {
|
||||
r.Record = &RecordPluginConfig.Raw
|
||||
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 {
|
||||
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) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package record
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -9,41 +10,100 @@ import (
|
||||
. "m7s.live/engine/v4"
|
||||
)
|
||||
|
||||
type IRecorder interface {
|
||||
ISubscriber
|
||||
GetRecorder() *Recorder
|
||||
Start(streamPath string) error
|
||||
io.Closer
|
||||
CreateFile() (FileWr, error)
|
||||
}
|
||||
|
||||
type Recorder struct {
|
||||
Subscriber
|
||||
SkipTS uint32
|
||||
*Record `json:"-" yaml:"-"`
|
||||
newFile bool // 创建了新的文件
|
||||
Record `json:"-" yaml:"-"`
|
||||
File FileWr `json:"-" yaml:"-"`
|
||||
FileName string // 自定义文件名,分段录像无效
|
||||
append bool // 是否追加模式
|
||||
}
|
||||
|
||||
func (r *Recorder) start() {
|
||||
RecordPluginConfig.recordings.Store(r.ID, r)
|
||||
r.PlayRaw()
|
||||
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