Fixed issues such as slow/unable to play music through stream_pcm.

This commit is contained in:
2025-12-02 19:46:41 +08:00
parent a8028bdc28
commit 0c097d63a6
2 changed files with 80 additions and 146 deletions

205
file.go
View File

@@ -54,10 +54,17 @@ func GetFileContent(filePath string) ([]byte, error) {
// fileHandler function: Handle file requests // fileHandler function: Handle file requests
func fileHandler(w http.ResponseWriter, r *http.Request) { func fileHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "MeowMusicEmbeddedServer") w.Header().Set("Server", "MeowMusicEmbeddedServer")
// Obtain the path of the request
filePath := r.URL.Path filePath := r.URL.Path
// Check if the request path starts with "/url/" // 提前URL解码
decodedPath, decodeErr := url.QueryUnescape(filePath)
if decodeErr == nil {
// 兼容历史数据:将+替换为空格(仅当解码成功时)
decodedPath = strings.ReplaceAll(decodedPath, "+", " ")
filePath = decodedPath // 后续统一使用解码后路径
}
// 处理 /url/ 远程请求(保持不变)
if strings.HasPrefix(filePath, "/url/") { if strings.HasPrefix(filePath, "/url/") {
// Extract the URL after "/url/" // Extract the URL after "/url/"
urlPath := filePath[len("/url/"):] urlPath := filePath[len("/url/"):]
@@ -102,161 +109,83 @@ func fileHandler(w http.ResponseWriter, r *http.Request) {
NotFoundHandler(w, r) NotFoundHandler(w, r)
return return
} }
// Set appropriate Content-Type based on file extension setContentType(w, decodedURL)
ext := filepath.Ext(decodedURL)
switch ext {
case ".mp3":
w.Header().Set("Content-Type", "audio/mpeg")
case ".wav":
w.Header().Set("Content-Type", "audio/wav")
case ".flac":
w.Header().Set("Content-Type", "audio/flac")
case ".aac":
w.Header().Set("Content-Type", "audio/aac")
case ".ogg":
w.Header().Set("Content-Type", "audio/ogg")
case ".m4a":
w.Header().Set("Content-Type", "audio/mp4")
case ".amr":
w.Header().Set("Content-Type", "audio/amr")
case ".jpg", ".jpeg":
w.Header().Set("Content-Type", "image/jpeg")
case ".png":
w.Header().Set("Content-Type", "image/png")
case ".gif":
w.Header().Set("Content-Type", "image/gif")
case ".bmp":
w.Header().Set("Content-Type", "image/bmp")
case ".svg":
w.Header().Set("Content-Type", "image/svg+xml")
case ".webp":
w.Header().Set("Content-Type", "image/webp")
case ".txt":
w.Header().Set("Content-Type", "text/plain")
case ".lrc":
w.Header().Set("Content-Type", "text/plain")
case ".mrc":
w.Header().Set("Content-Type", "text/plain")
case ".json":
w.Header().Set("Content-Type", "application/json")
default:
w.Header().Set("Content-Type", "application/octet-stream")
}
// Write file content to response // Write file content to response
w.Write(fileContent) w.Write(fileContent)
return return
} }
// Construct the complete file path // 统一使用解码后路径
fullFilePath := filepath.Join("./files", filePath) fullPath := filepath.Join("./files", filePath)
fileContent, err := GetFileContent(fullPath)
// Try replacing '+' with ' ' and check if the file exists // 特殊处理空music.mp3
tempFilePath := strings.ReplaceAll(fullFilePath, "+", " ") isEmptyMusic := (err == nil && len(fileContent) == 0 && strings.HasSuffix(filePath, "/music.mp3"))
if _, err := os.Stat(tempFilePath); err == nil { if err != nil || isEmptyMusic {
fullFilePath = tempFilePath // 智能等待
}
// Get file content
fileContent, err := GetFileContent(fullFilePath)
// 检查是否为空文件(特别是 music.mp3如果是空的也视为不存在需要等待转码
if err == nil && len(fileContent) == 0 && strings.HasSuffix(filePath, "/music.mp3") {
err = fmt.Errorf("file is empty")
}
if err != nil {
// If file not found, try replacing ' ' with '+' and check again
tempFilePath = strings.ReplaceAll(fullFilePath, " ", "+")
fileContent, err = GetFileContent(tempFilePath)
// 同样检查带 + 的路径是否为空
if err == nil && len(fileContent) == 0 && strings.HasSuffix(filePath, "/music.mp3") {
err = fmt.Errorf("file is empty")
}
if err != nil {
// 特殊处理:如果请求的是缓存中的文件,等待后台处理完成
fmt.Printf("[Web Access] File not found, checking path prefix: %s\n", filePath)
if strings.HasPrefix(filePath, "/cache/music/") { if strings.HasPrefix(filePath, "/cache/music/") {
// music.mp3 等待最多 60 秒,歌词等待最多 10 秒 maxWaitSec := 10
maxWait := 10
if strings.HasSuffix(filePath, "/music.mp3") { if strings.HasSuffix(filePath, "/music.mp3") {
maxWait = 60 maxWaitSec = 60
} }
// URL 解码路径(处理中文文件名) // 指数退避重试:快速响应文件生成
decodedPath, _ := url.QueryUnescape(filePath) start := time.Now()
decodedPath = strings.ReplaceAll(decodedPath, "+", " ") // + 转空格 for i := 0; ; i++ {
decodedFullPath := filepath.Join("./files", decodedPath) elapsed := time.Since(start)
if elapsed.Seconds() > float64(maxWaitSec) {
break
}
fmt.Printf("[Web Access] Waiting for file: %s (max %d seconds)\n", decodedFullPath, maxWait) // 重试间隔 = min(200ms * 2^i, 1s)
for i := 0; i < maxWait; i++ { waitDuration := time.Duration(200*(1<<uint(i))) * time.Millisecond
time.Sleep(1 * time.Second) if waitDuration > time.Second {
// 检查 URL 解码后的路径 waitDuration = time.Second
if _, err := os.Stat(decodedFullPath); err == nil {
fmt.Printf("[Web Access] File ready after %d seconds: %s\n", i+1, decodedFullPath)
http.ServeFile(w, r, decodedFullPath)
return
} }
// 检查原始路径 time.Sleep(waitDuration)
if _, err := os.Stat(fullFilePath); err == nil {
http.ServeFile(w, r, fullFilePath) // 只检查解码后的主路径(避免冗余检查)
return if info, statErr := os.Stat(fullPath); statErr == nil && (!isEmptyMusic || info.Size() > 0) {
} http.ServeFile(w, r, fullPath)
// 检查带 + 的路径
tempPath := strings.ReplaceAll(fullFilePath, " ", "+")
if _, err := os.Stat(tempPath); err == nil {
http.ServeFile(w, r, tempPath)
return return
} }
} }
fmt.Printf("[FAST] Timeout after %.1fs waiting for: %s\n", time.Since(start).Seconds(), fullPath)
} }
NotFoundHandler(w, r) NotFoundHandler(w, r)
return return
} }
}
// Set appropriate Content-Type based on file extension // 避免重复Content-Type设置
ext := filepath.Ext(filePath) setContentType(w, filePath)
switch ext {
case ".mp3":
w.Header().Set("Content-Type", "audio/mpeg")
case ".wav":
w.Header().Set("Content-Type", "audio/wav")
case ".flac":
w.Header().Set("Content-Type", "audio/flac")
case ".aac":
w.Header().Set("Content-Type", "audio/aac")
case ".ogg":
w.Header().Set("Content-Type", "audio/ogg")
case ".m4a":
w.Header().Set("Content-Type", "audio/mp4")
case ".amr":
w.Header().Set("Content-Type", "audio/amr")
case ".jpg", ".jpeg":
w.Header().Set("Content-Type", "image/jpeg")
case ".png":
w.Header().Set("Content-Type", "image/png")
case ".gif":
w.Header().Set("Content-Type", "image/gif")
case ".bmp":
w.Header().Set("Content-Type", "image/bmp")
case ".svg":
w.Header().Set("Content-Type", "image/svg+xml")
case ".webp":
w.Header().Set("Content-Type", "image/webp")
case ".txt":
w.Header().Set("Content-Type", "text/plain")
case ".lrc":
w.Header().Set("Content-Type", "text/plain")
case ".mrc":
w.Header().Set("Content-Type", "text/plain")
case ".json":
w.Header().Set("Content-Type", "application/json")
default:
w.Header().Set("Content-Type", "application/octet-stream")
}
// Write file content to response
w.Write(fileContent) w.Write(fileContent)
} }
func setContentType(w http.ResponseWriter, path string) {
ext := strings.ToLower(filepath.Ext(path))
contentTypes := map[string]string{
".mp3": "audio/mpeg",
".wav": "audio/wav",
".flac": "audio/flac",
".aac": "audio/aac",
".ogg": "audio/ogg",
".m4a": "audio/mp4",
".amr": "audio/amr",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".bmp": "image/bmp",
".svg": "image/svg+xml",
".webp": "image/webp",
".txt": "text/plain",
".lrc": "text/plain",
".mrc": "text/plain",
".json": "application/json",
}
if ct, ok := contentTypes[ext]; ok {
w.Header().Set("Content-Type", ct)
} else {
w.Header().Set("Content-Type", "application/octet-stream")
}
}

View File

@@ -1 +1,6 @@
{} {
"favorite": {
"name": "我喜欢",
"songs": []
}
}