package record import ( "encoding/json" "io" "net/http" "os" "path" "path/filepath" "strings" "sync" . "github.com/Monibuca/engine/v3" . "github.com/Monibuca/utils/v3" ) var config struct { Path string AutoRecord bool AppendFlv bool } var recordings sync.Map type FlvFileInfo struct { Path string Size int64 Duration uint32 } type FileWr interface { io.Reader io.Writer io.Seeker io.Closer } var ExtraConfig struct { CreateFileFn func(filename string) (FileWr, error) AutoRecordFilter func(stream string) bool } func init() { pc := PluginConfig{ Name: "Record", Config: &config, HotConfig: map[string]func(interface{}){ "AutoRecord": func(v interface{}) { config.AutoRecord = v.(bool) }, "AppendFlv": func(v interface{}) { config.AppendFlv = v.(bool) }, }, } pc.Install(run) } func run() { go AddHook(HOOK_PUBLISH, onPublish) os.MkdirAll(config.Path, 0755) http.HandleFunc("/vod/", VodHandler) http.HandleFunc("/api/record/flv/list", func(w http.ResponseWriter, r *http.Request) { CORS(w, r) if files, err := tree(config.Path, 0); err == nil { var bytes []byte if bytes, err = json.Marshal(files); err == nil { w.Write(bytes) } else { w.Write([]byte("{\"err\":\"" + err.Error() + "\"}")) } } else { w.Write([]byte("{\"err\":\"" + err.Error() + "\"}")) } }) http.HandleFunc("/api/record/flv", func(w http.ResponseWriter, r *http.Request) { CORS(w, r) if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" { if err := SaveFlv(streamPath, r.URL.Query().Get("append") == "true"); err != nil { w.Write([]byte(err.Error())) } else { w.Write([]byte("success")) } } else { w.Write([]byte("no streamPath")) } }) http.HandleFunc("/api/record/flv/stop", func(w http.ResponseWriter, r *http.Request) { CORS(w, r) if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" { filePath := filepath.Join(config.Path, streamPath+".flv") if stream, ok := recordings.Load(filePath); ok { output := stream.(*Subscriber) output.Close() w.Write([]byte("success")) } else { w.Write([]byte("no query stream")) } } else { w.Write([]byte("no such stream")) } }) http.HandleFunc("/api/record/flv/play", func(w http.ResponseWriter, r *http.Request) { CORS(w, r) if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" { if err := PublishFlvFile(streamPath); err != nil { w.Write([]byte(err.Error())) } else { w.Write([]byte("success")) } } else { w.Write([]byte("no streamPath")) } }) http.HandleFunc("/api/record/flv/delete", func(w http.ResponseWriter, r *http.Request) { CORS(w, r) if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" { filePath := filepath.Join(config.Path, streamPath+".flv") if Exist(filePath) { if err := os.Remove(filePath); err != nil { w.Write([]byte(err.Error())) } else { w.Write([]byte("success")) } } else { w.Write([]byte("no such file")) } } else { w.Write([]byte("no streamPath")) } }) } func onPublish(p *Stream) { if config.AutoRecord || (ExtraConfig.AutoRecordFilter != nil && ExtraConfig.AutoRecordFilter(p.StreamPath)) { SaveFlv(p.StreamPath, config.AppendFlv) } } func tree(dstPath string, level int) (files []*FlvFileInfo, err error) { var dstF *os.File dstF, err = os.Open(dstPath) if err != nil { return } defer dstF.Close() fileInfo, err := dstF.Stat() if err != nil { return } if !fileInfo.IsDir() { //如果dstF是文件 if path.Ext(fileInfo.Name()) == ".flv" { p := strings.TrimPrefix(dstPath, config.Path) p = strings.ReplaceAll(p, "\\", "/") files = append(files, &FlvFileInfo{ Path: strings.TrimPrefix(p, "/"), Size: fileInfo.Size(), Duration: getDuration(dstF), }) } return } else { //如果dstF是文件夹 var dir []os.FileInfo dir, err = dstF.Readdir(0) //获取文件夹下各个文件或文件夹的fileInfo if err != nil { return } for _, fileInfo = range dir { var _files []*FlvFileInfo _files, err = tree(filepath.Join(dstPath, fileInfo.Name()), level+1) if err != nil { return } files = append(files, _files...) } return } }