Files
plugin-record/main.go
2022-01-30 22:18:03 +08:00

179 lines
4.2 KiB
Go

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
}
}