package record import ( _ "embed" "errors" "crypto/md5" "encoding/hex" "io" "sync" "time" "fmt" "strconv" "strings" "go.uber.org/zap" "gorm.io/gorm" . "m7s.live/engine/v4" "m7s.live/engine/v4/codec" "m7s.live/engine/v4/config" "m7s.live/engine/v4/util" "m7s.live/engine/v4/db" ) type RecordConfig struct { config.Subscribe Ffmpeg string `json:"ffmpeg"` Flv Record `desc:"flv录制配置"` Mp4 Record `desc:"mp4录制配置"` Fmp4 Record `desc:"fmp4录制配置"` Hls Record `desc:"hls录制配置"` Raw Record `desc:"视频裸流录制配置"` RawAudio Record `desc:"音频裸流录制配置"` recordings sync.Map } func getIsRecordByChannelId(channelId string) int { database := db.DB() isRecord := 0 err := database.Raw("select is_record from t_sdp_delivery where channel_id=?",channelId).Find(&isRecord).Error if err != nil{ if err == gorm.ErrRecordNotFound{ return 0 } return 1 } return isRecord } //go:embed default.yaml var defaultYaml DefaultYaml var ErrRecordExist = errors.New("recorder exist") var RecordPluginConfig = &RecordConfig{ Ffmpeg:"ffmpeg", Flv: Record{ Path: "record/flv", Ext: ".flv", GetDurationFn: getFLVDuration, }, Fmp4: Record{ Path: "record/fmp4", Ext: ".mp4", }, Mp4: Record{ Path: "record/mp4", Ext: ".mp4", }, Hls: Record{ Path: "record/hls", Ext: ".m3u8", }, Raw: Record{ Path: "record/raw", Ext: ".", // 默认h264扩展名为.h264,h265扩展名为.h265 }, RawAudio: Record{ Path: "record/raw", Ext: ".", // 默认aac扩展名为.aac,pcma扩展名为.pcma,pcmu扩展名为.pcmu }, } var plugin = InstallPlugin(RecordPluginConfig, defaultYaml) func (conf *RecordConfig) OnEvent(event any) { switch v := event.(type) { case FirstConfig, config.Config: conf.Flv.Init() conf.Mp4.Init() conf.Fmp4.Init() conf.Hls.Init() conf.Raw.Init() conf.RawAudio.Init() case SEpublish: streamPath := v.Target.Path deviceId := v.Target.AppName if deviceId != "record"{ // 获取的是录像,不录 isRecord := getIsRecordByChannelId(strings.TrimSuffix(v.Target.StreamName, "/rtsp")) // 对steam path自动加上签名 if EngineConfig.Subscribe.Key != ""{ nowTime := time.Now().Unix() expireTime := nowTime + 315360000 expireTime16 := strconv.FormatInt(expireTime, 16) m := md5.New() m.Write([]byte(EngineConfig.Subscribe.Key +streamPath + expireTime16)) streamPathList := strings.Split(streamPath,"?") if len(streamPathList) > 0 { streamPath = fmt.Sprintf("%s?%s=%s&%s=%s",streamPathList[0],EngineConfig.Subscribe.SecretArgName,hex.EncodeToString(m.Sum(nil)),EngineConfig.Subscribe.ExpireArgName,expireTime16) } } plugin.Info("是否需要开启录像",zap.String("streamPath",streamPath),zap.Int("是否录像",isRecord)) //fmt.Println("发布流:",streamPath," 是否需要录像:",isRecord) if isRecord == 1 { if conf.Flv.NeedRecord(streamPath) { go NewFLVRecorder().Start(streamPath) } //fmt.Println("是否需要mp4录像:",conf.Mp4.NeedRecord(streamPath)) if conf.Mp4.NeedRecord(streamPath) { go NewMP4Recorder().Start(streamPath) } if conf.Fmp4.NeedRecord(streamPath) { go NewFMP4Recorder().Start(streamPath) } if conf.Hls.NeedRecord(streamPath) { go NewHLSRecorder().Start(streamPath) } if conf.Raw.NeedRecord(streamPath) { go NewRawRecorder().Start(streamPath) } if conf.RawAudio.NeedRecord(streamPath) { go NewRawAudioRecorder().Start(streamPath) } } } } } func (conf *RecordConfig) getRecorderConfigByType(t string) (recorder *Record) { switch t { case "flv": recorder = &conf.Flv case "mp4": recorder = &conf.Mp4 case "fmp4": recorder = &conf.Fmp4 case "hls": recorder = &conf.Hls case "raw": recorder = &conf.Raw case "raw_audio": recorder = &conf.RawAudio } return } func getFLVDuration(file io.ReadSeeker) uint32 { _, err := file.Seek(-4, io.SeekEnd) if err == nil { var tagSize uint32 if tagSize, err = util.ReadByteToUint32(file, true); err == nil { _, err = file.Seek(-int64(tagSize)-4, io.SeekEnd) if err == nil { _, timestamp, _, err := codec.ReadFLVTag(file) if err == nil { return timestamp } } } } return 0 }