diff --git a/Golang/liveurls/av3a.go b/Golang/liveurls/av3a.go new file mode 100644 index 0000000..2abd98b --- /dev/null +++ b/Golang/liveurls/av3a.go @@ -0,0 +1,192 @@ +package liveurls + +import ( + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "sync" + "syscall" + "time" + + "github.com/gin-gonic/gin" +) + +type FFMpegTask struct { + URL string + OutputDir string + M3U8File string + Cmd *exec.Cmd + Mutex sync.Mutex + LastCheck time.Time + Active bool +} + +var ( + tasks = make(map[string]*FFMpegTask) + tasksMutex sync.Mutex + checkPeriod = 10 * time.Second + timeout = 1 * time.Minute +) + +func clearOutputDir(dir string) error { + files, err := os.ReadDir(dir) + if err != nil { + return err + } + for _, file := range files { + filePath := filepath.Join(dir, file.Name()) + if err := os.RemoveAll(filePath); err != nil { + return err + } + } + return nil +} + +func StartFFMpeg(task *FFMpegTask) { + task.Mutex.Lock() + defer task.Mutex.Unlock() + + if task.Active { + return + } + + if err := clearOutputDir(task.OutputDir); err != nil { + log.Printf("Failed to clear output directory for %s: %v\n", task.URL, err) + return + } + + cmd := exec.Command("ffmpeg", "-fflags", "+genpts", "-analyzeduration", "1000000", + "-i", task.URL, + "-map", "0:v", "-map", "0:a", "-map", "0:a", + "-c:v", "copy", + "-c:a:0", "eac3", "-filter:a:0", "channelmap=0|1|2|3|4|5:FL+FR+FC+LFE+SL+SR", "-b:a:0", "384k", + "-c:a:1", "copy", + "-f", "hls", "-hls_time", "12", "-hls_list_size", "3", + "-hls_flags", "delete_segments+append_list", + "-hls_segment_filename", filepath.Join(task.OutputDir, "segment_%013d.ts"), + "-rtbufsize", "100M", "-max_delay", "1000000", + task.M3U8File, + ) + err := cmd.Start() + if err != nil { + log.Printf("Failed to start ffmpeg for %s: %v\n", task.URL, err) + return + } + + task.Cmd = cmd + task.LastCheck = time.Now() + task.Active = true + + log.Printf("Started ffmpeg for %s\n", task.URL) +} + +func CheckFFMpeg(task *FFMpegTask) { + task.Mutex.Lock() + defer task.Mutex.Unlock() + + if task.Cmd == nil || task.Cmd.Process == nil { + return + } + + // Use syscall.Signal(0) to check if the process is still running + err := task.Cmd.Process.Signal(syscall.Signal(0)) + if err != nil { + log.Printf("FFmpeg process for %s stopped. Restarting...\n", task.URL) + StartFFMpeg(task) + return + } + + currentTime := time.Now() + if currentTime.Sub(task.LastCheck) > timeout { + log.Printf("FFmpeg process for %s is stuck. Restarting...\n", task.URL) + StartFFMpeg(task) + return + } +} + +func HandleAV3ARequest(c *gin.Context) { + tasksMutex.Lock() + streamName := c.Param("rid") + task, exists := tasks[streamName] + if !exists { + tasksMutex.Unlock() + c.JSON(http.StatusNotFound, gin.H{"error": "Stream not found"}) + return + } + + if !task.Active { + StartFFMpeg(task) + } + tasksMutex.Unlock() + + c.JSON(http.StatusOK, gin.H{"message": "Stream is being prepared, please retry shortly"}) + + ticker := time.NewTicker(3 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if _, err := os.Stat(task.M3U8File); err == nil { + data, err := os.ReadFile(task.M3U8File) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read m3u8 file"}) + return + } + c.Header("Content-Type", "application/vnd.apple.mpegurl") + c.String(http.StatusOK, string(data)) + return + } + default: + time.Sleep(1 * time.Second) + } + } +} + +func ConfigureTasks() { + tasks = map[string]*FFMpegTask{ + "cctv4k16_10m.m3u8": { + URL: "http://192.168.10.1:35455/ysptp/cctv4k16_10m.m3u8", + OutputDir: "/home/cctv4k1610m", + M3U8File: "/home/cctv4k1610m/cctv4k16_10m.m3u8", + }, + "cctv4k16.m3u8": { + URL: "http://192.168.10.1:35455/ysptp/cctv4k16.m3u8", + OutputDir: "/home/cctv4k16", + M3U8File: "/home/cctv4k16/cctv4k16.m3u8", + }, + "cctv4k_10m.m3u8": { + URL: "http://192.168.10.1:35455/ysptp/cctv4k_10m.m3u8", + OutputDir: "/home/cctv4k10m", + M3U8File: "/home/cctv4k10m/cctv4k_10m.m3u8", + }, + "cctv4k.m3u8": { + URL: "http://192.168.10.1:35455/ysptp/cctv4k.m3u8", + OutputDir: "/home/cctv4k", + M3U8File: "/home/cctv4k/cctv4k.m3u8", + }, + "cctv8k_36m.m3u8": { + URL: "http://192.168.10.1:35455/ysptp/cctv8k_36m.m3u8", + OutputDir: "/home/cctv8k36m", + M3U8File: "/home/cctv8k36m/cctv8k_36m.m3u8", + }, + "cctv8k_120m.m3u8": { + URL: "http://192.168.10.1:35455/ysptp/cctv8k_120m.m3u8", + OutputDir: "/home/cctv8k120m", + M3U8File: "/home/cctv8k120m/cctv8k_120m.m3u8", + }, + } +} + +func MonitorTasks() { + for { + time.Sleep(checkPeriod) + tasksMutex.Lock() + for _, task := range tasks { + CheckFFMpeg(task) + } + tasksMutex.Unlock() + } +} diff --git a/Golang/main.go b/Golang/main.go index ef19e38..9349f06 100644 --- a/Golang/main.go +++ b/Golang/main.go @@ -183,6 +183,12 @@ func setupRouter(adurl string, enableTV bool) *gin.Engine { rid := c.Param("rid") ts := c.Query("ts") switch path { + case "av3atoeac3": + if enableTV { + liveurls.HandleAV3ARequest(c) + } else { + c.String(http.StatusForbidden, "公共服务不提供TV直播") + } case "itv": if enableTV { itvobj := &liveurls.Itv{} @@ -252,6 +258,11 @@ func setupRouter(adurl string, enableTV bool) *gin.Engine { func main() { tvEnabled := flag.Bool("tv", true, "Enable TV routes") flag.Parse() + + liveurls.ConfigureTasks() + + go liveurls.MonitorTasks() + key := []byte("6354127897263145") defstr, _ := base64.StdEncoding.DecodeString("NGrrC9lxtd9O7ezMt3Ux2WfX+HyCyepe9vDuhbSWVa8c+s7oFKbxuExfT4M/e4qvEgsUsvtceDWCYZ5+a7iKCEI/sps5jzGuWJNmsFnaFmQ=") defurl, _ := openssl.AesECBDecrypt(defstr, key, openssl.PKCS7_PADDING)