|
@ -4,10 +4,12 @@ import ( |
|
|
"errors" |
|
|
"errors" |
|
|
"fmt" |
|
|
"fmt" |
|
|
"os/exec" |
|
|
"os/exec" |
|
|
|
|
|
"path" |
|
|
"path/filepath" |
|
|
"path/filepath" |
|
|
"sort" |
|
|
"sort" |
|
|
"strings" |
|
|
"strings" |
|
|
"sync" |
|
|
"sync" |
|
|
|
|
|
"ycmediakit/internal/pkg/unit/keepalive" |
|
|
"ycmediakit/internal/pkg/util" |
|
|
"ycmediakit/internal/pkg/util" |
|
|
|
|
|
|
|
|
ffmpegCmd "github.com/u2takey/ffmpeg-go" |
|
|
ffmpegCmd "github.com/u2takey/ffmpeg-go" |
|
@ -23,9 +25,10 @@ func init() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
type ToHlsServer struct { |
|
|
type ToHlsServer struct { |
|
|
cfg *ToHlsConfig |
|
|
Cfg *ToHlsConfig |
|
|
m sync.Mutex |
|
|
m sync.Mutex |
|
|
cmdMap map[string]*ToHlsInfo |
|
|
cmdMap map[string]*ToHlsInfo |
|
|
|
|
|
Supervisor *keepalive.Supervisor |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
type ToHlsInfo struct { |
|
|
type ToHlsInfo struct { |
|
@ -35,92 +38,98 @@ type ToHlsInfo struct { |
|
|
cmd *exec.Cmd |
|
|
cmd *exec.Cmd |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func NewToHlsServer(hlsCfg *ToHlsConfig) *ToHlsServer { |
|
|
func NewToHlsServer(cfg *ToHlsConfig) *ToHlsServer { |
|
|
return &ToHlsServer{ |
|
|
s := &ToHlsServer{ |
|
|
cfg: hlsCfg, |
|
|
Cfg: cfg, |
|
|
cmdMap: make(map[string]*ToHlsInfo), |
|
|
cmdMap: make(map[string]*ToHlsInfo), |
|
|
} |
|
|
} |
|
|
|
|
|
if s.Cfg.Keepalive.Enable { |
|
|
|
|
|
s.Supervisor = keepalive.NewSupervisor(&s.Cfg.Keepalive) |
|
|
|
|
|
handlePath = "/" + cfg.HlsRoot |
|
|
|
|
|
s.Supervisor.Register(handlePath, s) |
|
|
|
|
|
} |
|
|
|
|
|
return s |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func (server *ToHlsServer) Add(url string, streamPath string, timeoutMs int, curHlsCfg *ToHlsConfig) bool { |
|
|
func (s *ToHlsServer) Add(url string, streamPath string, timeoutMs int, curHlsCfg *ToHlsConfig) bool { |
|
|
server.m.Lock() |
|
|
s.m.Lock() |
|
|
defer server.m.Unlock() |
|
|
defer s.m.Unlock() |
|
|
_, ok := server.cmdMap[streamPath] |
|
|
_, ok := s.cmdMap[streamPath] |
|
|
if ok { |
|
|
if ok { |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
// merge curHlsCfg, cfg
|
|
|
// merge curHlsCfg, Cfg
|
|
|
cmd := server.BuildToHlsCmd(url, streamPath, timeoutMs, curHlsCfg) |
|
|
cmd := s.BuildToHlsCmd(url, streamPath, timeoutMs, curHlsCfg) |
|
|
info := &ToHlsInfo{ |
|
|
info := &ToHlsInfo{ |
|
|
ToHlsConfig: *curHlsCfg, |
|
|
ToHlsConfig: *curHlsCfg, |
|
|
url: url, |
|
|
url: url, |
|
|
streamPath: streamPath, |
|
|
streamPath: streamPath, |
|
|
cmd: cmd, |
|
|
cmd: cmd, |
|
|
} |
|
|
} |
|
|
server.cmdMap[streamPath] = info |
|
|
s.cmdMap[streamPath] = info |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func (server *ToHlsServer) Exists(streamPath string) bool { |
|
|
func (s *ToHlsServer) Exists(streamPath string) bool { |
|
|
server.m.Lock() |
|
|
s.m.Lock() |
|
|
defer server.m.Unlock() |
|
|
defer s.m.Unlock() |
|
|
_, ok := server.cmdMap[streamPath] |
|
|
_, ok := s.cmdMap[streamPath] |
|
|
return ok |
|
|
return ok |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func (server *ToHlsServer) Delete(streamPath string) bool { |
|
|
func (s *ToHlsServer) Delete(streamPath string) bool { |
|
|
server.m.Lock() |
|
|
s.m.Lock() |
|
|
defer server.m.Unlock() |
|
|
defer s.m.Unlock() |
|
|
delete(server.cmdMap, streamPath) |
|
|
delete(s.cmdMap, streamPath) |
|
|
path := filepath.Join(util.GetWorkPath(), server.cfg.HlsRoot, streamPath, "..") |
|
|
dir := filepath.Join(util.GetWorkPath(), s.Cfg.HlsRoot, BuildStreamFilePath(streamPath), "..") |
|
|
err := util.RemoveDir(path) |
|
|
err := util.RemoveDir(dir) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
log.Error(err.Error()) |
|
|
log.Error(err.Error()) |
|
|
} |
|
|
} |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func (server *ToHlsServer) GetList() []string { |
|
|
func (s *ToHlsServer) GetList() []string { |
|
|
server.m.Lock() |
|
|
s.m.Lock() |
|
|
defer server.m.Unlock() |
|
|
defer s.m.Unlock() |
|
|
var slice []string |
|
|
var slice []string |
|
|
for _, elm := range server.cmdMap { |
|
|
for _, elm := range s.cmdMap { |
|
|
slice = append(slice, elm.streamPath) |
|
|
slice = append(slice, elm.streamPath) |
|
|
} |
|
|
} |
|
|
sort.Strings(slice) |
|
|
sort.Strings(slice) |
|
|
return slice |
|
|
return slice |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func (server *ToHlsServer) DeleteAndStop(streamPath string) error { |
|
|
func (s *ToHlsServer) DeleteAndStop(streamPath string) error { |
|
|
server.m.Lock() |
|
|
s.m.Lock() |
|
|
defer server.m.Unlock() |
|
|
defer s.m.Unlock() |
|
|
delete(server.cmdMap, streamPath) |
|
|
delete(s.cmdMap, streamPath) |
|
|
_, err := server.StopToHlsCmd(streamPath) |
|
|
_, err := s.StopToHlsCmd(streamPath) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
return err |
|
|
return err |
|
|
} |
|
|
} |
|
|
path := filepath.Join(util.GetWorkPath(), server.cfg.HlsRoot, streamPath) |
|
|
dir := filepath.Join(util.GetWorkPath(), s.Cfg.HlsRoot, BuildStreamFilePath(streamPath)) |
|
|
err = util.RemoveDir(path) |
|
|
err = util.RemoveDir(dir) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
log.Error(err.Error()) |
|
|
log.Error(err.Error()) |
|
|
} |
|
|
} |
|
|
return nil |
|
|
return nil |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func (server *ToHlsServer) BuildToHlsCmd(url string, streamPath string, timeoutMs int, hlsCfg *ToHlsConfig) *exec.Cmd { |
|
|
func (s *ToHlsServer) BuildToHlsCmd(url string, streamPath string, timeoutMs int, hlsCfg *ToHlsConfig) *exec.Cmd { |
|
|
path := filepath.Join(util.GetWorkPath(), server.cfg.HlsRoot, streamPath) |
|
|
dir := filepath.Join(util.GetWorkPath(), s.Cfg.HlsRoot, BuildStreamFilePath(streamPath)) |
|
|
_, err := util.CheckDir(path) |
|
|
_, err := util.CheckDir(dir) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
log.Error(err.Error()) |
|
|
log.Error(err.Error()) |
|
|
} |
|
|
} |
|
|
path = filepath.Join(path, hlsCfg.HlsName+".m3u8") |
|
|
p := filepath.Join(dir, hlsCfg.HlsName+".m3u8") |
|
|
buffer := new(strings.Builder) |
|
|
buffer := new(strings.Builder) |
|
|
cmd := ffmpegCmd. |
|
|
cmd := ffmpegCmd. |
|
|
Input(url, ffmpegCmd.KwArgs{ |
|
|
Input(url, ffmpegCmd.KwArgs{ |
|
|
"stimeout": timeoutMs * 1000, |
|
|
"stimeout": timeoutMs * 1000, |
|
|
"loglevel": "error", |
|
|
"loglevel": "error", |
|
|
}). |
|
|
}). |
|
|
Output(path, ffmpegCmd.KwArgs{ |
|
|
Output(p, ffmpegCmd.KwArgs{ |
|
|
"c:v": hlsCfg.EncodeCodec, |
|
|
"c:v": hlsCfg.EncodeCodec, |
|
|
"profile:v": hlsCfg.Profile, |
|
|
"profile:v": hlsCfg.Profile, |
|
|
"vf": util.Merge("scale=", hlsCfg.VfScale), |
|
|
"vf": util.Merge("scale=", hlsCfg.VfScale), |
|
@ -134,11 +143,13 @@ func (server *ToHlsServer) BuildToHlsCmd(url string, streamPath string, timeoutM |
|
|
return cmd |
|
|
return cmd |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func (server *ToHlsServer) RunToHlsCmd(streamPath string) (bool, error) { |
|
|
func (s *ToHlsServer) RunToHlsCmd(streamPath string) (bool, error) { |
|
|
info, ok := server.cmdMap[streamPath] |
|
|
info, ok := s.cmdMap[streamPath] |
|
|
if !ok { |
|
|
if !ok { |
|
|
return false, nil |
|
|
return false, nil |
|
|
} |
|
|
} |
|
|
|
|
|
s.Supervisor.Activate(streamPath) |
|
|
|
|
|
defer s.Supervisor.DeActivate(streamPath) |
|
|
err := info.cmd.Run() |
|
|
err := info.cmd.Run() |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
errStr := strings.TrimSpace(fmt.Sprint(info.cmd.Stderr)) |
|
|
errStr := strings.TrimSpace(fmt.Sprint(info.cmd.Stderr)) |
|
@ -147,8 +158,8 @@ func (server *ToHlsServer) RunToHlsCmd(streamPath string) (bool, error) { |
|
|
return true, nil |
|
|
return true, nil |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func (server *ToHlsServer) StopToHlsCmd(streamPath string) (bool, error) { |
|
|
func (s *ToHlsServer) StopToHlsCmd(streamPath string) (bool, error) { |
|
|
info, ok := server.cmdMap[streamPath] |
|
|
info, ok := s.cmdMap[streamPath] |
|
|
if !ok { |
|
|
if !ok { |
|
|
return true, nil |
|
|
return true, nil |
|
|
} |
|
|
} |
|
@ -159,6 +170,29 @@ func (server *ToHlsServer) StopToHlsCmd(streamPath string) (bool, error) { |
|
|
return true, nil |
|
|
return true, nil |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func (server *ToHlsServer) BuildHlsPath(streamPath string) string { |
|
|
func (s *ToHlsServer) BuildHlsPath(streamPath string) string { |
|
|
return filepath.Join(server.cfg.HlsRoot, streamPath, server.cfg.HlsName+".m3u8") |
|
|
return path.Join("/"+s.Cfg.HlsRoot, streamPath, s.Cfg.HlsName+".m3u8") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
|
handlePath string |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
func (s *ToHlsServer) CreateKey(oriUrl string) (string, bool) { |
|
|
|
|
|
url := oriUrl |
|
|
|
|
|
paramArr := strings.Split(url, "/") |
|
|
|
|
|
if len(paramArr) != 5 || paramArr[1] != s.Cfg.HlsRoot { |
|
|
|
|
|
return "", false |
|
|
|
|
|
} |
|
|
|
|
|
streamPath := paramArr[2] + "/" + paramArr[3] |
|
|
|
|
|
return streamPath, true |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *ToHlsServer) Expire(key string) bool { |
|
|
|
|
|
_, ok := s.cmdMap[key] |
|
|
|
|
|
if !ok { |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
_ = s.DeleteAndStop(key) |
|
|
|
|
|
return true |
|
|
} |
|
|
} |
|
|