rebase
This commit is contained in:
245
api.go
Executable file
245
api.go
Executable file
@@ -0,0 +1,245 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// APIHandler handles API requests.
|
||||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Server", "MeowMusicEmbeddedServer")
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
queryParams := r.URL.Query()
|
||||
fmt.Printf("[Web Access] Handling request for %s?%s\n", r.URL.Path, queryParams.Encode())
|
||||
song := queryParams.Get("song")
|
||||
singer := queryParams.Get("singer")
|
||||
|
||||
ip, err := IPhandler(r)
|
||||
if err != nil {
|
||||
ip = "0.0.0.0"
|
||||
}
|
||||
|
||||
if song == "" {
|
||||
musicItem := MusicItem{
|
||||
FromCache: false,
|
||||
IP: ip,
|
||||
}
|
||||
json.NewEncoder(w).Encode(musicItem)
|
||||
return
|
||||
}
|
||||
|
||||
// Attempt to retrieve music items from sources.json
|
||||
sources := readSources()
|
||||
|
||||
var musicItem MusicItem
|
||||
var found bool = false
|
||||
|
||||
// Build request scheme
|
||||
var scheme string
|
||||
if r.TLS == nil {
|
||||
scheme = "http"
|
||||
} else {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
if source.Title == song {
|
||||
if singer == "" || source.Artist == singer {
|
||||
// Determine the protocol for each URL and build accordingly
|
||||
var audioURL, audioFullURL, m3u8URL, lyricURL, coverURL string
|
||||
if strings.HasPrefix(source.AudioURL, "http://") {
|
||||
audioURL = scheme + "://" + r.Host + "/url/http/" + url.QueryEscape(strings.TrimPrefix(source.AudioURL, "http://"))
|
||||
} else if strings.HasPrefix(source.AudioURL, "https://") {
|
||||
audioURL = scheme + "://" + r.Host + "/url/https/" + url.QueryEscape(strings.TrimPrefix(source.AudioURL, "https://"))
|
||||
} else {
|
||||
audioURL = scheme + "://" + r.Host + "/" + url.QueryEscape(source.AudioURL)
|
||||
}
|
||||
if strings.HasPrefix(source.AudioFullURL, "http://") {
|
||||
audioFullURL = scheme + "://" + r.Host + "/url/http/" + url.QueryEscape(strings.TrimPrefix(source.AudioFullURL, "http://"))
|
||||
} else if strings.HasPrefix(source.AudioFullURL, "https://") {
|
||||
audioFullURL = scheme + "://" + r.Host + "/url/https/" + url.QueryEscape(strings.TrimPrefix(source.AudioFullURL, "https://"))
|
||||
} else {
|
||||
audioFullURL = scheme + "://" + r.Host + "/" + url.QueryEscape(source.AudioFullURL)
|
||||
}
|
||||
if strings.HasPrefix(source.M3U8URL, "http://") {
|
||||
m3u8URL = scheme + "://" + r.Host + "/url/http/" + url.QueryEscape(strings.TrimPrefix(source.M3U8URL, "http://"))
|
||||
} else if strings.HasPrefix(source.M3U8URL, "https://") {
|
||||
m3u8URL = scheme + "://" + r.Host + "/url/https/" + url.QueryEscape(strings.TrimPrefix(source.M3U8URL, "https://"))
|
||||
} else {
|
||||
m3u8URL = scheme + "://" + r.Host + "/" + url.QueryEscape(source.M3U8URL)
|
||||
}
|
||||
if strings.HasPrefix(source.LyricURL, "http://") {
|
||||
lyricURL = scheme + "://" + r.Host + "/url/http/" + url.QueryEscape(strings.TrimPrefix(source.LyricURL, "http://"))
|
||||
} else if strings.HasPrefix(source.LyricURL, "https://") {
|
||||
lyricURL = scheme + "://" + r.Host + "/url/https/" + url.QueryEscape(strings.TrimPrefix(source.LyricURL, "https://"))
|
||||
} else {
|
||||
lyricURL = scheme + "://" + r.Host + "/" + url.QueryEscape(source.LyricURL)
|
||||
}
|
||||
if strings.HasPrefix(source.CoverURL, "http://") {
|
||||
coverURL = scheme + "://" + r.Host + "/url/http/" + url.QueryEscape(strings.TrimPrefix(source.CoverURL, "http://"))
|
||||
} else if strings.HasPrefix(source.CoverURL, "https://") {
|
||||
coverURL = scheme + "://" + r.Host + "/url/https/" + url.QueryEscape(strings.TrimPrefix(source.CoverURL, "https://"))
|
||||
} else {
|
||||
coverURL = scheme + "://" + r.Host + "/" + url.QueryEscape(source.CoverURL)
|
||||
}
|
||||
musicItem = MusicItem{
|
||||
Title: source.Title,
|
||||
Artist: source.Artist,
|
||||
AudioURL: audioURL,
|
||||
AudioFullURL: audioFullURL,
|
||||
M3U8URL: m3u8URL,
|
||||
LyricURL: lyricURL,
|
||||
CoverURL: coverURL,
|
||||
Duration: source.Duration,
|
||||
FromCache: false,
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in sources.json, attempt to retrieve from local folder
|
||||
if !found {
|
||||
musicItem = getLocalMusicItem(song, singer)
|
||||
musicItem.FromCache = false
|
||||
if musicItem.Title != "" {
|
||||
if musicItem.AudioURL != "" {
|
||||
musicItem.AudioURL = scheme + "://" + r.Host + musicItem.AudioURL
|
||||
}
|
||||
if musicItem.AudioFullURL != "" {
|
||||
musicItem.AudioFullURL = scheme + "://" + r.Host + musicItem.AudioFullURL
|
||||
}
|
||||
if musicItem.M3U8URL != "" {
|
||||
musicItem.M3U8URL = scheme + "://" + r.Host + musicItem.M3U8URL
|
||||
}
|
||||
if musicItem.LyricURL != "" {
|
||||
musicItem.LyricURL = scheme + "://" + r.Host + musicItem.LyricURL
|
||||
}
|
||||
if musicItem.CoverURL != "" {
|
||||
musicItem.CoverURL = scheme + "://" + r.Host + musicItem.CoverURL
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found, attempt to retrieve from cache file
|
||||
if !found {
|
||||
fmt.Println("[Info] Reading music from cache.")
|
||||
// Fuzzy matching for singer and song
|
||||
files, err := filepath.Glob("./cache/*.json")
|
||||
if err != nil {
|
||||
fmt.Println("[Error] Error reading cache directory:", err)
|
||||
return
|
||||
}
|
||||
for _, file := range files {
|
||||
if strings.Contains(filepath.Base(file), song) && (singer == "" || strings.Contains(filepath.Base(file), singer)) {
|
||||
musicItem, found = readFromCache(file)
|
||||
if found {
|
||||
if musicItem.AudioURL != "" {
|
||||
musicItem.AudioURL = scheme + "://" + r.Host + musicItem.AudioURL
|
||||
}
|
||||
if musicItem.AudioFullURL != "" {
|
||||
musicItem.AudioFullURL = scheme + "://" + r.Host + musicItem.AudioFullURL
|
||||
}
|
||||
if musicItem.M3U8URL != "" {
|
||||
musicItem.M3U8URL = scheme + "://" + r.Host + musicItem.M3U8URL
|
||||
}
|
||||
if musicItem.LyricURL != "" {
|
||||
musicItem.LyricURL = scheme + "://" + r.Host + musicItem.LyricURL
|
||||
}
|
||||
if musicItem.CoverURL != "" {
|
||||
musicItem.CoverURL = scheme + "://" + r.Host + musicItem.CoverURL
|
||||
}
|
||||
musicItem.FromCache = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found, request and cache the music item in a separate goroutine
|
||||
// 直接进行流式播放
|
||||
if !found {
|
||||
encodedSong := url.QueryEscape(song)
|
||||
encodedSinger := url.QueryEscape(singer)
|
||||
streamURL := scheme + "://" + r.Host + "/stream_live?song=" + encodedSong + "&singer=" + encodedSinger
|
||||
fmt.Println("[Info] Updating music item cache from API request.")
|
||||
musicItem = requestAndCacheMusic(song, singer)
|
||||
fmt.Println("[Info] Music item cache updated.")
|
||||
musicItem.FromCache = false
|
||||
musicItem.AudioURL = streamURL
|
||||
musicItem.AudioFullURL = streamURL
|
||||
musicItem.M3U8URL = scheme + "://" + r.Host + musicItem.M3U8URL
|
||||
musicItem.LyricURL = scheme + "://" + r.Host + musicItem.LyricURL
|
||||
musicItem.CoverURL = scheme + "://" + r.Host + musicItem.CoverURL
|
||||
found = true
|
||||
}
|
||||
|
||||
// If still not found, return an empty MusicItem
|
||||
if !found {
|
||||
musicItem = MusicItem{
|
||||
FromCache: false,
|
||||
IP: ip,
|
||||
}
|
||||
} else {
|
||||
musicItem.IP = ip
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.Encode(musicItem)
|
||||
}
|
||||
|
||||
// streamLiveHandler 实时流式转码接口 - 边下载边播放,无需等待!
|
||||
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")
|
||||
if _, err := os.Stat(cachedFile); err == nil {
|
||||
// 缓存存在,直接返回文件
|
||||
fmt.Printf("[Stream Live] Serving from cache: %s\n", cachedFile)
|
||||
w.Header().Set("Content-Type", "audio/mpeg")
|
||||
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)
|
||||
// 错误可能已经发送了部分数据,无法再发送错误响应
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user