From d9abb0b18b8cff69d33214a383fd4fb1e7235c46 Mon Sep 17 00:00:00 2001 From: moecinnamo Date: Tue, 2 Dec 2025 20:27:05 +0800 Subject: [PATCH] Flow optimization --- api.go | 17 +++++++++-------- file.go | 33 +-------------------------------- helper.go | 27 ++++++++++++++------------- index.go | 8 ++++---- 4 files changed, 28 insertions(+), 57 deletions(-) diff --git a/api.go b/api.go index 9a6d51c..3dd58ce 100755 --- a/api.go +++ b/api.go @@ -165,6 +165,7 @@ func apiHandler(w http.ResponseWriter, r *http.Request) { } // If still not found, request and cache the music item in a separate goroutine + // 直接进行流式播放 if !found { fmt.Println("[Info] Updating music item cache from API request.") musicItem = requestAndCacheMusic(song, singer) @@ -196,18 +197,18 @@ func streamLiveHandler(w http.ResponseWriter, r *http.Request) { // 设置 CORS 和音频相关头 w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Accept-Ranges", "bytes") - + queryParams := r.URL.Query() song := queryParams.Get("song") singer := queryParams.Get("singer") - + fmt.Printf("[Stream Live] Request: song=%s, singer=%s\n", song, singer) - + if song == "" { http.Error(w, "Missing song parameter", http.StatusBadRequest) return } - + // 1. 检查缓存是否存在 dirName := fmt.Sprintf("./files/cache/music/%s-%s", singer, song) cachedFile := filepath.Join(dirName, "music.mp3") @@ -218,19 +219,19 @@ func streamLiveHandler(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, cachedFile) return } - + // 2. 缓存不存在,获取远程URL并实时流式转码 fmt.Printf("[Stream Live] Cache miss, fetching from API...\n") - + // 调用枫雨API获取远程音乐URL(不下载,只获取URL) remoteURL := getRemoteMusicURLOnly(song, singer) if remoteURL == "" { http.Error(w, "Failed to get remote music URL", http.StatusNotFound) return } - + fmt.Printf("[Stream Live] Starting live stream from: %s\n", remoteURL) - + // 4. 实时流式转码 if err := streamConvertToWriter(remoteURL, w); err != nil { fmt.Printf("[Stream Live] Error: %v\n", err) diff --git a/file.go b/file.go index 197ff96..93eddcb 100755 --- a/file.go +++ b/file.go @@ -1,14 +1,12 @@ package main import ( - "fmt" "io" "net/http" "net/url" "os" "path/filepath" "strings" - "time" ) // ListFiles function: Traverse all files in the specified directory and return a slice of the file path @@ -122,36 +120,7 @@ func fileHandler(w http.ResponseWriter, r *http.Request) { // 特殊处理空music.mp3 isEmptyMusic := (err == nil && len(fileContent) == 0 && strings.HasSuffix(filePath, "/music.mp3")) if err != nil || isEmptyMusic { - // 智能等待 - if strings.HasPrefix(filePath, "/cache/music/") { - maxWaitSec := 10 - if strings.HasSuffix(filePath, "/music.mp3") { - maxWaitSec = 60 - } - - // 指数退避重试:快速响应文件生成 - start := time.Now() - for i := 0; ; i++ { - elapsed := time.Since(start) - if elapsed.Seconds() > float64(maxWaitSec) { - break - } - - // 重试间隔 = min(200ms * 2^i, 1s) - waitDuration := time.Duration(200*(1< time.Second { - waitDuration = time.Second - } - time.Sleep(waitDuration) - - // 只检查解码后的主路径(避免冗余检查) - if info, statErr := os.Stat(fullPath); statErr == nil && (!isEmptyMusic || info.Size() > 0) { - http.ServeFile(w, r, fullPath) - return - } - } - fmt.Printf("[FAST] Timeout after %.1fs waiting for: %s\n", time.Since(start).Seconds(), fullPath) - } + // 没有/空的music.mp3文件,直接返回404 NotFoundHandler(w, r) return } diff --git a/helper.go b/helper.go index 0aecfe8..be114b7 100755 --- a/helper.go +++ b/helper.go @@ -85,7 +85,7 @@ func readFromCache(path string) (MusicItem, bool) { if err != nil || !info.IsDir() { return MusicItem{}, false } - + dirName := filepath.Base(path) parts := strings.SplitN(dirName, "-", 2) var artist, title string @@ -95,7 +95,7 @@ func readFromCache(path string) (MusicItem, bool) { } else { title = dirName } - + return getLocalMusicItem(title, artist), true } @@ -115,10 +115,10 @@ func requestAndCacheMusic(song, singer string) MusicItem { // 直接从远程URL流式转码(边下载边转码,超快!) func streamConvertAudio(inputURL, outputFile string) error { fmt.Printf("[Info] Stream converting from URL (fast mode)\n") - + // 先写入临时文件,完成后再重命名(避免读取到不完整的文件) tempFile := outputFile + ".tmp" - + // ffmpeg 直接读取远程 URL 并转码 // -t 600: 只下载前10分钟,减少80%下载量! // 移除 reconnect 参数,避免兼容性问题 @@ -130,14 +130,14 @@ func streamConvertAudio(inputURL, outputFile string) error { "-ac", "1", "-ar", "24000", "-b:a", "32k", "-q:a", "9", "-bufsize", "64k", tempFile) - + err := cmd.Run() if err != nil { fmt.Printf("[Error] Stream convert failed: %v\n", err) os.Remove(tempFile) // 清理临时文件 return err } - + // 检查生成的文件大小 fileInfo, err := os.Stat(tempFile) if err != nil || fileInfo.Size() < 1024 { @@ -152,7 +152,7 @@ func streamConvertAudio(inputURL, outputFile string) error { fmt.Printf("[Error] Failed to rename temp file: %v\n", err) return err } - + fmt.Printf("[Success] Stream convert completed: %s\n", outputFile) return nil } @@ -160,30 +160,31 @@ func streamConvertAudio(inputURL, outputFile string) error { // 实时流式转码到 HTTP Writer(边下载边播放!) func streamConvertToWriter(inputURL string, w http.ResponseWriter) error { fmt.Printf("[Info] Live streaming from URL: %s\n", inputURL) - + // ffmpeg 边下载边转码,输出到 stdout cmd := exec.Command("ffmpeg", "-i", inputURL, "-threads", "0", "-ac", "1", "-ar", "24000", "-b:a", "32k", "-q:a", "9", "-f", "mp3", + "-map_metadata", "-1", "pipe:1") // 输出到 stdout - + // 获取 stdout pipe stdout, err := cmd.StdoutPipe() if err != nil { return fmt.Errorf("failed to get stdout pipe: %v", err) } - + // 启动 ffmpeg if err := cmd.Start(); err != nil { return fmt.Errorf("failed to start ffmpeg: %v", err) } - + // 设置响应头 w.Header().Set("Content-Type", "audio/mpeg") // 移除 Transfer-Encoding: chunked,让 Go 自动处理 - + // 边读边写到 HTTP response buf := make([]byte, 8192) for { @@ -198,7 +199,7 @@ func streamConvertToWriter(inputURL string, w http.ResponseWriter) error { break } } - + cmd.Wait() fmt.Printf("[Success] Live streaming completed\n") return nil diff --git a/index.go b/index.go index c81be7e..e7a5214 100755 --- a/index.go +++ b/index.go @@ -11,7 +11,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Server", "MeowMusicEmbeddedServer") w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Printf("[Web Access] Handling request for %s\n", r.URL.Path) - + // Serve full music app for both / and /app if r.URL.Path == "/" || r.URL.Path == "/app" { appPath := filepath.Join("theme", "full-app.html") @@ -21,7 +21,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { return } } - + // Test version available at /test if r.URL.Path == "/test" { testPath := filepath.Join("theme", "test-app.html") @@ -31,7 +31,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { return } } - + // Access classic interface via /classic if r.URL.Path == "/classic" { indexPath := filepath.Join("theme", "index.html") @@ -43,7 +43,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { defaultIndexPage(w) return } - + if r.URL.Path != "/" { fileHandler(w, r) return