Compare commits
3 Commits
3005457c60
...
da9d1b0371
| Author | SHA1 | Date |
|---|---|---|
|
|
da9d1b0371 | 1 year ago |
|
|
61e36a3810 | 1 year ago |
|
|
2705ffcb1c | 1 year ago |
@ -0,0 +1,201 @@ |
||||
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 := os.Stat(task.OutputDir); os.IsNotExist(err) { |
||||
if err := os.MkdirAll(task.OutputDir, 0755); err != nil { |
||||
log.Printf("Failed to create output directory for %s: %v\n", task.URL, err) |
||||
return |
||||
} |
||||
} |
||||
|
||||
// 清理输出目录
|
||||
if err := clearOutputDir(task.OutputDir); err != nil { |
||||
log.Printf("Failed to clear output directory for %s: %v\n", task.URL, err) |
||||
// 不再立即返回,而是继续尝试启动任务
|
||||
} |
||||
|
||||
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() |
||||
} |
||||
} |
||||
Loading…
Reference in new issue