This commit is contained in:
2025-12-09 16:33:44 +08:00
parent 345af5e2a3
commit 1d42f5ea50
49 changed files with 12015 additions and 1 deletions

410
index.go Executable file
View File

@@ -0,0 +1,410 @@
package main
import (
"fmt"
"net/http"
"os"
"path/filepath"
)
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")
if _, err := os.Stat(appPath); err == nil {
http.ServeFile(w, r, appPath)
fmt.Printf("[Web Access] Return full music app page\n")
return
}
}
// Test version available at /test
if r.URL.Path == "/test" {
testPath := filepath.Join("theme", "test-app.html")
if _, err := os.Stat(testPath); err == nil {
http.ServeFile(w, r, testPath)
fmt.Printf("[Web Access] Return test app page\n")
return
}
}
// Access classic interface via /classic
if r.URL.Path == "/classic" {
indexPath := filepath.Join("theme", "index.html")
if _, err := os.Stat(indexPath); err == nil {
http.ServeFile(w, r, indexPath)
fmt.Printf("[Web Access] Return classic index page\n")
return
}
defaultIndexPage(w)
return
}
if r.URL.Path != "/" {
fileHandler(w, r)
return
}
// Fallback to index.html
indexPath := filepath.Join("theme", "index.html")
// Check if index.html exists in theme directory
if _, err := os.Stat(indexPath); os.IsNotExist(err) {
defaultIndexPage(w)
} else if err != nil {
defaultIndexPage(w)
} else {
http.ServeFile(w, r, indexPath)
fmt.Printf("[Web Access] Return custom index pages\n")
}
}
func defaultIndexPage(w http.ResponseWriter) {
websiteVersion := "0.0.1-rc-1"
websiteNameCN := os.Getenv("WEBSITE_NAME_CN")
if websiteNameCN == "" {
websiteNameCN = "🎵 音乐搜索"
}
websiteNameEN := os.Getenv("WEBSITE_NAME_EN")
if websiteNameEN == "" {
websiteNameEN = "🎵 Music Search"
}
websiteTitleCN := os.Getenv("WEBSITE_TITLE_CN")
if websiteTitleCN == "" {
websiteTitleCN = "为嵌入式设备设计的音乐搜索服务器"
}
websiteTitleEN := os.Getenv("WEBSITE_TITLE_EN")
if websiteTitleEN == "" {
websiteTitleEN = "Music Search Server for Embedded Devices"
}
websiteDescCN := os.Getenv("WEBSITE_DESC_CN")
if websiteDescCN == "" {
websiteDescCN = "搜索并播放您喜爱的音乐"
}
websiteDescEN := os.Getenv("WEBSITE_DESC_EN")
if websiteDescEN == "" {
websiteDescEN = "Search and play your favorite music"
}
websiteKeywordsCN := os.Getenv("WEBSITE_KEYWORDS_CN")
if websiteKeywordsCN == "" {
websiteKeywordsCN = "音乐, 搜索, 嵌入式"
}
websiteKeywordsEN := os.Getenv("WEBSITE_KEYWORDS_EN")
if websiteKeywordsEN == "" {
websiteKeywordsEN = "music, search, embedded"
}
websiteFavicon := os.Getenv("WEBSITE_FAVICON")
if websiteFavicon == "" {
websiteFavicon = "/favicon.ico"
}
websiteBackground := os.Getenv("WEBSITE_BACKGROUND")
if websiteBackground == "" {
websiteBackground = "/background.webp"
}
fontawesomeCDN := os.Getenv("FONTAWESOME_CDN")
if fontawesomeCDN == "" {
fontawesomeCDN = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
}
// Build HTML
fmt.Fprintf(w, "<!DOCTYPE html><html>")
fmt.Fprintf(w, "<head>")
fmt.Fprintf(w, "<meta charset=\"UTF-8\">")
fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">")
fmt.Fprintf(w, "<meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">")
fmt.Fprintf(w, "<link rel=\"icon\" href=\"%s\">", websiteFavicon)
fmt.Fprintf(w, "<link rel=\"stylesheet\" href=\"%s\">", fontawesomeCDN)
fmt.Fprintf(w, "<title></title><style>")
// HTML style
fmt.Fprintf(w, "body {background-image: url('%s');background-size: cover;background-repeat: no-repeat;background-attachment: fixed;display: flex;justify-content: center;align-items: center;margin: 60px 0;}", websiteBackground)
fmt.Fprintf(w, ".container {background: rgba(255, 255, 255, 0.4);width: 65%%;border-radius: 20px;box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);backdrop-filter: blur(10px);display: flex;flex-direction: column;}")
fmt.Fprintf(w, ".title {font-size: 36px;font-weight: bold;margin: 25px auto 0px auto;text-align: center;}.description {font-size: 1.1rem;color: #4f596b;margin: 10px auto;text-align: center;}")
fmt.Fprintf(w, ".search-form {display: flex;justify-content: center;align-items: center;width: 100%%;margin-bottom: 20px;}.songContainer,.singerContainer,.searchContainer {display: flex;align-items: center;margin: 0 10px;}")
fmt.Fprintf(w, ".songInput {padding: 10px;border: 2px solid #ccc;border-radius: 20px;height: 45px;margin-left: -6%%;margin-right: 10px;font-size: 1.1rem;width: 110%%;background-color: rgba(255, 255, 255, 0.4);}")
fmt.Fprintf(w, ".artistInput {padding: 10px;border: 2px solid #ccc;border-radius: 20px;height: 45px;margin-left: 7%%;margin-right: 10px;font-size: 1.1rem;width: 80%%;background-color: rgba(255, 255, 255, 0.4);}")
fmt.Fprintf(w, ".searchBtn {padding: 10px 20px;border: none;background-image: linear-gradient(to right, pink, deeppink);color: white;margin-left: -20%%;font-size: 1.1rem;border-radius: 20px;width: 128%%;height: 60px;cursor: pointer;transition: all 0.3s ease;}")
fmt.Fprintf(w, "@media (max-width: 768px) {.search-form {flex-direction: column;align-items: flex-start;text-align: center;}.songContainer,.singerContainer,.searchContainer {display: block;margin: 4px 12%% 0 auto;width: 80%%;}.songInput {margin: 0;width: 100%%;}.artistInput {margin: 0;width: 100%%;}.searchBtn {margin: 0;width: 106%%;height: 40px;}}")
fmt.Fprintf(w, ".songInput:hover,.artistInput:hover,.songInput:focus,.artistInput:focus {outline: none;border: 2px solid deeppink;}.song-item:hover,.searchBtn:hover {box-shadow: 0 4px 8px rgba(255, 182, 193, 0.7);transform: translateY(-5px);}")
fmt.Fprintf(w, ".getError,.no-enter,.no-result {width: 80%%;margin: 4px auto;padding: 20px;background-color: rgba(255, 0, 38, 0.4);text-align: center;border: 1px solid rgb(255, 75, 75);border-radius: 15px;color: rgb(205, 0, 0);}")
fmt.Fprintf(w, ".loading {width: 80%%;margin: 4px auto;padding: 20px;text-align: center;color: deeppink;font-size: 45px;animation: spin 2s linear infinite;}@keyframes spin {from {transform: rotate(0deg);}to {transform: rotate(360deg);}}")
fmt.Fprintf(w, ".result {width: 85%%;margin: 4px auto;}.result-title {font-size: 24px;font-weight: bold;}.song-item {background-color: rgba(255, 255, 255, 0.4);border: 2px solid deeppink;border-radius: 15px;transition: all 0.3s ease;padding: 10px;}")
fmt.Fprintf(w, ".song-title-container {display: flex;align-items: center;}.song-name {font-size: 18px;font-weight: bold;}.cache {width: 45px;background-color: deepskyblue;color: #000;font-size: 14px;text-align: center;border-radius: 15px;}")
fmt.Fprintf(w, ".singer-name-icon,.lyric-icon {font-size: 18px;color: deeppink;}.singer-name,.lyric {font-size: 16px;color: #4f596b;}.playBtn,.pauseBtn {border: none;background-image: linear-gradient(to right, skyblue, deepskyblue);border-radius: 5px;padding: 5px 10px;font-size: 15px;transition: all 0.3s ease;}.playBtn:hover,.pauseBtn:hover {box-shadow: 0 4px 8px rgba(182, 232, 255, 0.7);transform: translateY(-5px);}")
fmt.Fprintf(w, ".audio-player-container {display: flex;align-items: center;}.audio {display: none;}.progress-bar {width: 70%%;margin: 4px auto;padding: 8px;background-color: rgba(255, 255, 255, 0.4);border: 1px solid deeppink;border-radius: 5px;display: flex;justify-content: space-between;align-items: center;}.progress {width: 0;height: 10px;background-color: deeppink;}.time {margin-left: auto;}")
fmt.Fprintf(w, ".stream_pcm {width: 80%%;margin: 4px auto;padding: 20px;background-color: rgba(135, 206, 235, 0.4);border: 1px solid skyblue;border-radius: 15px;}.stream_pcm_title {color: rgb(0, 100, 100);font-size: 16px;font-weight: bold;}.stream_pcm_content {margin-top: 10px;font-size: 14px;color: #555;}")
fmt.Fprintf(w, ".stream_pcm_type_title,.stream_pcm_content_num_title,.stream_pcm_content_time_title,.stream_pcm_response_title {font-weight: bold;}.stream_pcm_response_value {width: 100%%;background-color: rgba(255, 255, 255, 0.4);display: block;white-space: pre-wrap;overflow: auto;height: 200px;border-radius: 6px;padding: 10px;}")
fmt.Fprintf(w, ".info {width: 80%%;margin: 4px auto;padding: 20px;text-align: center;color: #4f596b;}.info strong {font-weight: bolder;color: #000;}.showStreamPcmBtnContainer,.hideStreamPcmBtnContainer {margin: 0 auto;width: 80%%;display: flex;justify-content: center;}")
fmt.Fprintf(w, ".showStreamPcmBtn {border: 1px solid deepskyblue;color: deepskyblue;}.showStreamPcmBtn:hover {background-color: deepskyblue;color: #000;}.hideStreamPcmBtn {border: 1px solid deeppink;color: deeppink;}.hideStreamPcmBtn:hover {background-color: deeppink;color: #000;}.showStreamPcmBtn,.hideStreamPcmBtn {background: none;padding: 2px 6px;}")
fmt.Fprintf(w, ".footer {text-align: center;margin: 10px auto;justify-content: center;align-items: center;width: 80%%;border-top: 1px solid #ccc;}.language-select {background-color: rgba(255, 255, 255, 0.4);border: 1px solid #ccc;text-align: center;width: 120px;height: 40px;border-radius: 10px;margin: 10px auto;}")
fmt.Fprintf(w, ".language-select:focus,.language-select:hover {outline: none;border: 1px solid deeppink;}.copyright {font-size: 14px;color: #4f596b;}")
fmt.Fprintf(w, "</style></head>")
// Build body
fmt.Fprintf(w, "<body><div class=\"container\"><div id=\"title\" class=\"title\"></div><div id=\"description\" class=\"description\"></div>")
fmt.Fprintf(w, "<div class=\"search-form\"><div class=\"songContainer\"><div class=\"song\"><input type=\"text\" id=\"songInput\" class=\"songInput\" autocomplete=\"off\"></div></div>")
fmt.Fprintf(w, "<div class=\"singerContainer\"><div class=\"singer\"><input type=\"text\" id=\"artistInput\" class=\"artistInput\" autocomplete=\"off\"></div></div><div class=\"searchContainer\"><div class=\"search\"><button type=\"button\" id=\"searchBtn\" class=\"searchBtn\"></button></div></div></div>")
fmt.Fprintf(w, "<div class=\"getError\" id=\"getError\"></div><div class=\"no-enter\" id=\"noEnter\"></div><div class=\"no-result\" id=\"noResult\"></div><div class=\"loading\" id=\"loading\"><i class=\"fa fa-circle-o-notch\"></i></div>")
fmt.Fprintf(w, "<div class=\"result\" id=\"result\"><div class=\"result-title\" id=\"resultTitle\"></div><div class=\"result-list\"><div class=\"song-item\"><div class=\"song-title-container\"><div class=\"song-name\" id=\"songName\"></div><div class=\"cache\" id=\"cache\"></div></div><div class=\"singer-name\"><span class=\"singer-name-icon\" id=\"singerNameIcon\"><i class=\"fa fa-user-o\"></i></span><span class=\"singer-name-value\" id=\"singerName\"></span></div><div class=\"lyric\"><span class=\"lyric-icon\" id=\"lyricIcon\"><i class=\"fa fa-file-text-o\"></i></span><span class=\"lyric-value\" id=\"noLyric\"></span><span class=\"lyric-value\" id=\"lyric\"></span></div><div class=\"audio-player-container\"><button type=\"button\" class=\"playBtn\" id=\"playBtn\"></button><button type=\"button\" class=\"pauseBtn\" id=\"pauseBtn\"></button><audio class=\"audio\" id=\"audio\"></audio><div class=\"progress-bar\"><div class=\"progress\" id=\"progress\"></div><div class=\"time\" id=\"time\"></div></div></div></div></div></div>")
fmt.Fprintf(w, "<div class=\"stream_pcm\" id=\"streamPcm\"><div class=\"stream_pcm_title\" id=\"streamPcmTitle\"></div><div class=\"stream_pcm_content\"><div class=\"stream_pcm_type\"><span class=\"stream_pcm_type_title\" id=\"streamPcmTypeTitle\"></span><span class=\"stream_pcm_type_value\" id=\"streamPcmTypeValue\"></span></div><div class=\"stream_pcm_content_num\"><span class=\"stream_pcm_content_num_title\" id=\"streamPcmContentNumTitle\"></span><span class=\"stream_pcm_content_num_value\">1</span></div><div class=\"stream_pcm_content_time\"><span class=\"stream_pcm_content_time_title\" id=\"streamPcmContentTimeTitle\"></span><span class=\"stream_pcm_content_time_value\" id=\"streamPcmContentTimeValue\"></span></div><div class=\"stream_pcm_response\"><span class=\"stream_pcm_response_title\" id=\"streamPcmResponseTitle\"></span><br><span class=\"stream_pcm_response_value\" id=\"streamPcmResponseValue\"></span></div></div></div>")
fmt.Fprintf(w, "<div class=\"info\" id=\"info\"></div><div class=\"showStreamPcmBtnContainer\" id=\"showStreamPcmBtnContainer\"><button type=\"button\" id=\"showStreamPcmBtn\" class=\"showStreamPcmBtn\"></button></div><div class=\"hideStreamPcmBtnContainer\" id=\"hideStreamPcmBtnContainer\"><button type=\"button\" id=\"hideStreamPcmBtn\" class=\"hideStreamPcmBtn\"></button></div><div class=\"footer\"><select id=\"languageSelect\" class=\"language-select\"><option value=\"zh-CN\">简体中文</option><option value=\"en\">English</option></select><div class=\"copyright\" id=\"copyright\"></div></div></div>")
fmt.Fprintf(w, "<script>")
// Set copyright year and read head meta tags
fmt.Fprintf(w, "const currentYear = new Date().getFullYear();var head = document.getElementsByTagName('head')[0];")
// language definition
fmt.Fprintf(w, "const titles = {'zh-CN': '%s','en': '%s'};", websiteNameCN, websiteNameEN)
fmt.Fprintf(w, "const titles2 = {'zh-CN': '%s','en': '%s'};", websiteTitleCN, websiteTitleEN)
fmt.Fprintf(w, "const descriptions = {'zh-CN': '%s','en': '%s'};", websiteDescCN, websiteDescEN)
fmt.Fprintf(w, "const keywords = {'zh-CN': '%s','en': '%s'};", websiteKeywordsCN, websiteKeywordsEN)
fmt.Fprintf(w, "const songInputs = {'zh-CN': '输入歌曲名称...','en': 'Enter song name...'};")
fmt.Fprintf(w, "const singerInputs = {'zh-CN': '歌手名称(可选)','en': 'Singer name(optional)'};")
fmt.Fprintf(w, "const searchBtns = {'zh-CN': '<i class=\"fa fa-search\"></i> 搜索','en': '<i class=\"fa fa-search\"></i> Search'};")
fmt.Fprintf(w, "const getErrors = {'zh-CN': '获取数据失败<br>可能是因为网络响应出错或其它原因<br>请检查您的网络并稍后再试','en': 'Failed to get data<br>It may be because of network response error or other reasons<br>Please check your network and try again later'};")
fmt.Fprintf(w, "const noEnters = {'zh-CN': '请输入歌曲名称','en': 'Please enter song name'};")
fmt.Fprintf(w, "const noResults = {'zh-CN': '没有找到相关歌曲','en': 'No related songs found'};")
fmt.Fprintf(w, "const resultTitles = {'zh-CN': '<i class=\"fa fa-list-ul\"></i> 搜索结果','en': '<i class=\"fa fa-list-ul\"></i> Search Result'};")
fmt.Fprintf(w, "const caches = {'zh-CN': '缓存','en': 'Cache'};")
fmt.Fprintf(w, "const noLyrics = {'zh-CN': '暂无歌词','en': 'No lyrics'};")
fmt.Fprintf(w, "const playBtns = {'zh-CN': '<i class=\"fa fa-play-circle-o\"></i> 播放','en': '<i class=\"fa fa-play-circle-o\"></i> Play'};")
fmt.Fprintf(w, "const pauseBtns = {'zh-CN': '<i class=\"fa fa-pause-circle-o\"></i> 暂停','en': '<i class=\"fa fa-pause-circle-o\"></i> Pause'};")
fmt.Fprintf(w, "const streamPcmTitle = {'zh-CN': '<i class=\"fa fa-info-circle\"></i> stream_pcm 响应讯息:','en': '<i class=\"fa fa-info-circle\"></i> stream_pcm response: '};")
fmt.Fprintf(w, "const streamPcmTypeTitle = {'zh-CN': '响应类型:','en': 'Response type: '};")
fmt.Fprintf(w, "const streamPcmTypeValue = {'zh-CN': '单曲播放讯息','en': 'Single song playback message'};")
fmt.Fprintf(w, "const streamPcmContentNumTitle = {'zh-CN': '响应数量:','en': 'Response number: '};")
fmt.Fprintf(w, "const streamPcmContentTimeTitle = {'zh-CN': '响应时间:','en': 'Response time: '};")
fmt.Fprintf(w, "const streamPcmResponseTitle = {'zh-CN': '完整响应:','en': 'Full response: '};")
fmt.Fprintf(w, "const info = {'zh-CN': '<strong><i class=\"fa fa-info-circle\"></i> 系统讯息</strong><br>嵌入式音乐搜索服务器 | Ver %s<br>支持云端/本地音乐搜索,支持多种音乐格式播放,支持多种语言<br>基于聚合API支持本地音乐缓存','en': '<strong><i class=\"fa fa-info-circle\"></i> System Information</strong><br>Embedded Music Search Server | Ver %s<br>Support cloud/local music search, support various music formats, support various languages<br>Based on aggregation API, support local music cache'};", websiteVersion, websiteVersion)
fmt.Fprintf(w, "const showStreamPcmBtns = {'zh-CN': '<i class=\"fa fa-eye\"></i> 显示 stream_pcm 响应','en': '<i class=\"fa fa-eye\"></i> Show stream_pcm response'};")
fmt.Fprintf(w, "const hideStreamPcmBtns = {'zh-CN': '<i class=\"fa fa-eye-slash\"></i> 隐藏 stream_pcm 响应','en': '<i class=\"fa fa-eye-slash\"></i> Hide stream_pcm response'};")
// Get browser language, set HTML lang attribute and Set default language
fmt.Fprintf(w, "const browserLang = navigator.language || 'en';document.documentElement.lang = browserLang || \"en\";document.getElementById('languageSelect').value = browserLang;")
// Initialize title
fmt.Fprintf(w, "document.title = (titles[browserLang] || '%s') + \" - \" + (titles2[browserLang] || '%s');", websiteNameEN, websiteTitleEN)
// Initialize meta description
fmt.Fprintf(w, "var existingMetaDescription = document.querySelector('meta[name=\"description\"]');if (existingMetaDescription) {existingMetaDescription.content = descriptions[browserLang] || '%s';} else {var metaDescription = document.createElement('meta');metaDescription.name = 'description';metaDescription.content = descriptions[browserLang] || '%s';head.appendChild(metaDescription);};", websiteDescEN, websiteDescEN)
// Initialize meta keywords
fmt.Fprintf(w, "var existingMetaKeywords = document.querySelector('meta[name=\"keywords\"]');if (existingMetaKeywords) {existingMetaKeywords.content = keywords[browserLang] || '%s';} else {var metaKeywords = document.createElement('meta');metaKeywords.name = 'keywords';metaKeywords.content = keywords[browserLang] || '%s';head.appendChild(metaKeywords);};", websiteKeywordsEN, websiteKeywordsEN)
// Set default language content
fmt.Fprintf(w, "document.getElementById('title').innerHTML = titles[browserLang] || '%s';", websiteNameEN)
fmt.Fprintf(w, "document.getElementById('copyright').innerHTML = \"&copy;\" + currentYear + \" \" + (titles[browserLang] || '%s');", websiteNameEN)
fmt.Fprintf(w, "document.getElementById('description').innerHTML = descriptions[browserLang] || '%s';", websiteDescEN)
fmt.Fprintf(w, "document.getElementById('songInput').placeholder = songInputs[browserLang] || 'Enter song name...';")
fmt.Fprintf(w, "document.getElementById('artistInput').placeholder = singerInputs[browserLang] || 'Singer name(optional)';")
fmt.Fprintf(w, "document.getElementById('searchBtn').innerHTML = searchBtns[browserLang] || '<i class=\"fa fa-search\"></i> Search';")
fmt.Fprintf(w, "document.getElementById('getError').innerHTML = getErrors[browserLang] || 'Failed to get data<br>It may be because of network response error or other reasons<br>Please check your network and try again later';")
fmt.Fprintf(w, "document.getElementById('noEnter').innerHTML = noEnters[browserLang] || 'Please enter song name';")
fmt.Fprintf(w, "document.getElementById('noResult').innerHTML = noResults[browserLang] || 'No related songs found';")
fmt.Fprintf(w, "document.getElementById('resultTitle').innerHTML = resultTitles[browserLang] || '<i class=\"fa fa-list-ul\"></i> Search Result';")
fmt.Fprintf(w, "document.getElementById('cache').innerHTML = caches[browserLang] || 'Cache';")
fmt.Fprintf(w, "document.getElementById('noLyric').innerHTML = noLyrics[browserLang] || 'No lyrics';")
fmt.Fprintf(w, "document.getElementById('playBtn').innerHTML = playBtns[browserLang] || '<i class=\"fa fa-play-circle-o\"></i> Play';")
fmt.Fprintf(w, "document.getElementById('pauseBtn').innerHTML = pauseBtns[browserLang] || '<i class=\"fa fa-pause-circle-o\"></i> Pause';")
fmt.Fprintf(w, "document.getElementById('streamPcmTitle').innerHTML = streamPcmTitle[browserLang] || '<i class=\"fa fa-info-circle\"></i> stream_pcm response: ';")
fmt.Fprintf(w, "document.getElementById('streamPcmTypeTitle').innerHTML = streamPcmTypeTitle[browserLang] || 'Response type: ';")
fmt.Fprintf(w, "document.getElementById('streamPcmTypeValue').innerHTML = streamPcmTypeValue[browserLang] || 'Single song playback message';")
fmt.Fprintf(w, "document.getElementById('streamPcmContentNumTitle').innerHTML = streamPcmContentNumTitle[browserLang] || 'Response number: ';")
fmt.Fprintf(w, "document.getElementById('streamPcmContentTimeTitle').innerHTML = streamPcmContentTimeTitle[browserLang] || 'Response time: ';")
fmt.Fprintf(w, "document.getElementById('streamPcmResponseTitle').innerHTML = streamPcmResponseTitle[browserLang] || 'Full response: ';")
fmt.Fprintf(w, "document.getElementById('info').innerHTML = info[browserLang] || '<strong><i class=\"fa fa-info-circle\"></i> System Information</strong><br>Embedded Music Search Server | Ver %s<br>Support cloud/local music search, support various music formats, support various languages<br>Based on aggregation API, support local music cache';", websiteVersion)
fmt.Fprintf(w, "document.getElementById('showStreamPcmBtn').innerHTML = showStreamPcmBtns[browserLang] || '<i class=\"fa fa-eye\"></i> Show stream_pcm response';")
fmt.Fprintf(w, "document.getElementById('hideStreamPcmBtn').innerHTML = hideStreamPcmBtns[browserLang] || '<i class=\"fa fa-eye-slash\"></i> Hide stream_pcm response';")
// Listen language selection change and update title
fmt.Fprintf(w, "document.getElementById('languageSelect').addEventListener('change', function () {")
fmt.Fprintf(w, "const selectedLang = this.value;")
// Set HTML lang attribute
fmt.Fprintf(w, "document.documentElement.lang = selectedLang || \"en\";")
// Set title
fmt.Fprintf(w, "document.title = (titles[selectedLang] || '%s') + \" - \" + (titles2[selectedLang] || '%s');", websiteNameEN, websiteTitleEN)
// Initialize meta description
fmt.Fprintf(w, "var existingMetaDescription = document.querySelector('meta[name=\"description\"]');if (existingMetaDescription) {existingMetaDescription.content = descriptions[selectedLang] || '%s';} else {var metaDescription = document.createElement('meta');metaDescription.name = 'description';metaDescription.content = descriptions[selectedLang] || '%s';head.appendChild(metaDescription);};", websiteDescEN, websiteDescEN)
// Initialize meta keywords
fmt.Fprintf(w, "var existingMetaKeywords = document.querySelector('meta[name=\"keywords\"]');if (existingMetaKeywords) {existingMetaKeywords.content = keywords[selectedLang] || '%s';} else {var metaKeywords = document.createElement('meta');metaKeywords.name = 'keywords';metaKeywords.content = keywords[selectedLang] || '%s';head.appendChild(metaKeywords);};", websiteKeywordsEN, websiteKeywordsEN)
// Set default language content
fmt.Fprintf(w, "document.getElementById('title').innerHTML = titles[selectedLang] || '%s';", websiteNameEN)
fmt.Fprintf(w, "document.getElementById('copyright').innerHTML = \"&copy;\" + currentYear + \" \" + (titles[selectedLang] || '%s');", websiteNameEN)
fmt.Fprintf(w, "document.getElementById('description').innerHTML = descriptions[selectedLang] || '%s';", websiteDescEN)
fmt.Fprintf(w, "document.getElementById('songInput').placeholder = songInputs[selectedLang] || 'Enter song name...';")
fmt.Fprintf(w, "document.getElementById('artistInput').placeholder = singerInputs[selectedLang] || 'Singer name(optional)';")
fmt.Fprintf(w, "document.getElementById('searchBtn').innerHTML = searchBtns[selectedLang] || '<i class=\"fa fa-search\"></i> Search';")
fmt.Fprintf(w, "document.getElementById('getError').innerHTML = getErrors[selectedLang] || 'Failed to get data<br>It may be because of network response error or other reasons<br>Please check your network and try again later';")
fmt.Fprintf(w, "document.getElementById('noEnter').innerHTML = noEnters[selectedLang] || 'Please enter song name';")
fmt.Fprintf(w, "document.getElementById('noResult').innerHTML = noResults[selectedLang] || 'No related songs found';")
fmt.Fprintf(w, "document.getElementById('resultTitle').innerHTML = resultTitles[selectedLang] || '<i class=\"fa fa-list-ul\"></i> Search Result';")
fmt.Fprintf(w, "document.getElementById('cache').innerHTML = caches[selectedLang] || 'Cache';")
fmt.Fprintf(w, "document.getElementById('noLyric').innerHTML = noLyrics[selectedLang] || 'No lyrics';")
fmt.Fprintf(w, "document.getElementById('playBtn').innerHTML = playBtns[selectedLang] || '<i class=\"fa fa-play-circle-o\"></i> Play';")
fmt.Fprintf(w, "document.getElementById('pauseBtn').innerHTML = pauseBtns[selectedLang] || '<i class=\"fa fa-pause-circle-o\"></i> Pause';")
fmt.Fprintf(w, "document.getElementById('streamPcmTitle').innerHTML = streamPcmTitle[selectedLang] || '<i class=\"fa fa-info-circle\"></i> stream_pcm response: ';")
fmt.Fprintf(w, "document.getElementById('streamPcmTypeTitle').innerHTML = streamPcmTypeTitle[selectedLang] || 'Response type: ';")
fmt.Fprintf(w, "document.getElementById('streamPcmTypeValue').innerHTML = streamPcmTypeValue[selectedLang] || 'Single song playback message';")
fmt.Fprintf(w, "document.getElementById('streamPcmContentNumTitle').innerHTML = streamPcmContentNumTitle[selectedLang] || 'Response number: ';")
fmt.Fprintf(w, "document.getElementById('streamPcmContentTimeTitle').innerHTML = streamPcmContentTimeTitle[selectedLang] || 'Response time: ';")
fmt.Fprintf(w, "document.getElementById('streamPcmResponseTitle').innerHTML = streamPcmResponseTitle[selectedLang] || 'Full response: ';")
fmt.Fprintf(w, "document.getElementById('info').innerHTML = info[selectedLang] || '<strong><i class=\"fa fa-info-circle\"></i> System Information</strong><br>Embedded Music Search Server | Ver %s<br>Support cloud/local music search, support various music formats, support various languages<br>Based on aggregation API, support local music cache';", websiteVersion)
fmt.Fprintf(w, "document.getElementById('showStreamPcmBtn').innerHTML = showStreamPcmBtns[selectedLang] || '<i class=\"fa fa-eye\"></i> Show stream_pcm response';")
fmt.Fprintf(w, "document.getElementById('hideStreamPcmBtn').innerHTML = hideStreamPcmBtns[selectedLang] || '<i class=\"fa fa-eye-slash\"></i> Hide stream_pcm response';")
fmt.Fprintf(w, "});")
// Getting Elements
fmt.Fprintf(w, "const songInput = document.getElementById('songInput');")
fmt.Fprintf(w, "const artistInput = document.getElementById('artistInput');")
fmt.Fprintf(w, "const searchBtn = document.getElementById('searchBtn');")
fmt.Fprintf(w, "const getError = document.getElementById('getError');")
fmt.Fprintf(w, "const noEnter = document.getElementById('noEnter');")
fmt.Fprintf(w, "const noResult = document.getElementById('noResult');")
fmt.Fprintf(w, "const loading = document.getElementById('loading');")
fmt.Fprintf(w, "const result = document.getElementById('result');")
fmt.Fprintf(w, "const songName = document.getElementById('songName');")
fmt.Fprintf(w, "const cache = document.getElementById('cache');")
fmt.Fprintf(w, "const singerName = document.getElementById('singerName');")
fmt.Fprintf(w, "const noLyric = document.getElementById('noLyric');")
fmt.Fprintf(w, "const playBtn = document.getElementById('playBtn');")
fmt.Fprintf(w, "const pauseBtn = document.getElementById('pauseBtn');")
fmt.Fprintf(w, "const lyric = document.getElementById('lyric');")
fmt.Fprintf(w, "const streamPcm = document.getElementById('streamPcm');")
fmt.Fprintf(w, "const streamPcmContentTimeValue = document.getElementById('streamPcmContentTimeValue');")
fmt.Fprintf(w, "const streamPcmResponseValue = document.getElementById('streamPcmResponseValue');")
fmt.Fprintf(w, "const showStreamPcmBtn = document.getElementById('showStreamPcmBtn');")
fmt.Fprintf(w, "const hideStreamPcmBtn = document.getElementById('hideStreamPcmBtn');")
// Hide content that should not be displayed before searching
fmt.Fprintf(w, "getError.style.display = 'none';")
fmt.Fprintf(w, "noEnter.style.display = 'none';")
fmt.Fprintf(w, "noResult.style.display = 'none';")
fmt.Fprintf(w, "loading.style.display = 'none';")
fmt.Fprintf(w, "result.style.display = 'none';")
fmt.Fprintf(w, "cache.style.display = 'none';")
fmt.Fprintf(w, "noLyric.style.display = 'none';")
fmt.Fprintf(w, "playBtn.style.display = 'none';")
fmt.Fprintf(w, "pauseBtn.style.display = 'none';")
fmt.Fprintf(w, "streamPcm.style.display = 'none';")
fmt.Fprintf(w, "showStreamPcmBtn.style.display = 'none';")
fmt.Fprintf(w, "hideStreamPcmBtn.style.display = 'none';")
// Empty song name processing
fmt.Fprintf(w, "searchBtn.addEventListener('click', function () {if (songInput.value.trim() === '') {noEnter.style.display = 'block';} else {noEnter.style.display = 'none';search();}});")
fmt.Fprintf(w, "songInput.addEventListener('keydown', function (event) {if (event.key === 'Enter') {if (songInput.value.trim() === '') {noEnter.style.display = 'block';} else {noEnter.style.display = 'none';search();}}});")
fmt.Fprintf(w, "artistInput.addEventListener('keydown', function (event) {if (event.key === 'Enter') {if (songInput.value.trim() === '') {noEnter.style.display = 'block';} else {noEnter.style.display = 'none';search();}}});")
// Searching for songs
fmt.Fprintf(w, "function search() {")
// Show loading
fmt.Fprintf(w, "loading.style.display = 'block';")
// Hide error
fmt.Fprintf(w, "getError.style.display = 'none';")
// Build request URL, urlencode song name and artist name
fmt.Fprintf(w, "const song = encodeURIComponent(songInput.value);")
fmt.Fprintf(w, "const artist = encodeURIComponent(artistInput.value);")
fmt.Fprintf(w, "const requestUrl = `/stream_pcm?song=${song}&artist=${artist}`;")
// Send request to server
fmt.Fprintf(w, "fetch(requestUrl)")
fmt.Fprintf(w, ".then(response => {if (!response.ok) {getError.style.display = 'block';throw new Error('Network response was not ok');}return response.json();})")
fmt.Fprintf(w, ".then(data => {")
// Fill in all the obtained content into streamPcmResponseValue
fmt.Fprintf(w, "streamPcmResponseValue.innerHTML = JSON.stringify(data, null, 2);")
// Get the current time and fill in streamPcmCntentTimeValue
fmt.Fprintf(w, "streamPcmContentTimeValue.innerHTML = new Date().toISOString();")
// Display result
fmt.Fprintf(w, "result.style.display = 'block';")
// Display Play button
fmt.Fprintf(w, "playBtn.style.display = 'block';")
// Display showStreamPcmBtn
fmt.Fprintf(w, "showStreamPcmBtn.style.display = 'block';")
// Hide hideStreamPcmBtn
fmt.Fprintf(w, "hideStreamPcmBtn.style.display = 'none';")
// Fill the title into the songName field
fmt.Fprintf(w, "if (data.title === \"\") {noResult.style.display = 'block';result.style.display = 'none';} else {noResult.style.display = 'none';songName.textContent = data.title;};")
// Fill the artist into the singerName field
fmt.Fprintf(w, "singerName.textContent = data.artist;")
// Set parsed lyrics to an empty array
fmt.Fprintf(w, "let parsedLyrics = [];")
// Check if the link 'lyric_url' is empty
fmt.Fprintf(w, "if (data.lyric_url) {")
// Visit lyric_url
fmt.Fprintf(w, "fetch(data.lyric_url)")
fmt.Fprintf(w, ".then(response => {if (!response.ok) {throw new Error('Lyrics request error');}return response.text();})")
fmt.Fprintf(w, ".then(lyricText => {")
// Show lyric
fmt.Fprintf(w, "lyric.style.display = 'block';")
// Parse lyrics
fmt.Fprintf(w, "parsedLyrics = parseLyrics(lyricText);")
fmt.Fprintf(w, "})")
fmt.Fprintf(w, ".catch(error => {")
// Show noLyric
fmt.Fprintf(w, "noLyric.style.display = 'block';")
// Hide lyric
fmt.Fprintf(w, "lyric.style.display = 'none';")
fmt.Fprintf(w, "});")
fmt.Fprintf(w, "} else {")
// If lyric_url is empty, display noLyric
fmt.Fprintf(w, "noLyric.style.display = 'block';")
// Hide lyric
fmt.Fprintf(w, "lyric.style.display = 'none';")
fmt.Fprintf(w, "};")
// Check cache
fmt.Fprintf(w, "if (data.from_cache === true) {")
// If the song is obtained from cache, display cache
fmt.Fprintf(w, "cache.style.display = 'block';")
fmt.Fprintf(w, "} else {")
// If the song is not obtained from cache, hide cache
fmt.Fprintf(w, "cache.style.display = 'none';")
fmt.Fprintf(w, "};")
// Create audio player
fmt.Fprintf(w, "const audioPlayer = document.getElementById('audio');")
fmt.Fprintf(w, "const customProgress = document.getElementById('progress');")
fmt.Fprintf(w, "const timeDisplay = document.getElementById('time');")
// Set audio source
fmt.Fprintf(w, "audioPlayer.src = data.audio_full_url;")
fmt.Fprintf(w, "audio.addEventListener('timeupdate', function () {")
fmt.Fprintf(w, "const currentTime = formatTime(audioPlayer.currentTime);")
fmt.Fprintf(w, "const duration = formatTime(audioPlayer.duration);")
fmt.Fprintf(w, "timeDisplay.textContent = `${currentTime}/${duration}`;")
fmt.Fprintf(w, "const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100;")
fmt.Fprintf(w, "customProgress.style.width = progress + '%%';")
// Find current lyric
fmt.Fprintf(w, "let currentLyric = parsedLyrics.find((lyric, index, arr) =>")
fmt.Fprintf(w, "lyric.timestamp > audioPlayer.currentTime && (index === 0 || arr[index - 1].timestamp <= audioPlayer.currentTime));")
fmt.Fprintf(w, "lyric.textContent = currentLyric ? currentLyric.lyricLine : '';")
fmt.Fprintf(w, "});")
// Save current time before playing audio
fmt.Fprintf(w, "let savedCurrentTime = 0;")
// PlayBtn click event
fmt.Fprintf(w, "playBtn.addEventListener('click', function () {")
// Save current time
fmt.Fprintf(w, "audioPlayer.currentTime = savedCurrentTime;")
// Play audio
fmt.Fprintf(w, "audioPlayer.play();")
// Hide playBtn
fmt.Fprintf(w, "playBtn.style.display = 'none';")
// Show pauseBtn
fmt.Fprintf(w, "pauseBtn.style.display = 'block';")
fmt.Fprintf(w, "});")
// PauseBtn click event
fmt.Fprintf(w, "pauseBtn.addEventListener('click', function () {")
// Save current time
fmt.Fprintf(w, "savedCurrentTime = audioPlayer.currentTime;")
// Pause audio
fmt.Fprintf(w, "audioPlayer.pause();")
// Hide pauseBtn
fmt.Fprintf(w, "pauseBtn.style.display = 'none';")
// Show playBtn
fmt.Fprintf(w, "playBtn.style.display = 'block';")
fmt.Fprintf(w, "});")
fmt.Fprintf(w, "})")
fmt.Fprintf(w, ".catch(error => {")
fmt.Fprintf(w, "console.error('Error requesting song information:', error);")
// When there is an error in the request, you can also consider displaying a prompt message or other content
fmt.Fprintf(w, "getError.style.display = 'block';")
fmt.Fprintf(w, "})")
fmt.Fprintf(w, ".finally(() => {")
// Regardless of the request result, the loading display should be turned off in the end
fmt.Fprintf(w, "loading.style.display = 'none';")
fmt.Fprintf(w, "});};")
// Format time
fmt.Fprintf(w, "function formatTime(seconds) {const minutes = Math.floor(seconds / 60);const secondsRemainder = Math.floor(seconds %% 60);return minutes.toString().padStart(2, '0') + ':' +secondsRemainder.toString().padStart(2, '0');};")
// Function to parse lyrics
fmt.Fprintf(w, "function parseLyrics(lyricText) {const lines = lyricText.split('\\n');const lyrics = [];for (let line of lines) {const match = line.match(/\\[(\\d{2}:\\d{2})(?:\\.\\d{2})?\\](.*)/);if (match) {const timestamp = match[1]; const lyricLine = match[2].trim();const [minutes, seconds] = timestamp.split(':');const timeInSeconds = (parseInt(minutes) * 60) + parseInt(seconds);lyrics.push({ timestamp: timeInSeconds, lyricLine });}}return lyrics;};")
// Show stream_pcm response
fmt.Fprintf(w, "showStreamPcmBtn.addEventListener('click', function () {streamPcm.style.display = 'block';showStreamPcmBtn.style.display = 'none';hideStreamPcmBtn.style.display = 'block';});")
// Hide stream_pcm response
fmt.Fprintf(w, "hideStreamPcmBtn.addEventListener('click', function () {streamPcm.style.display = 'none';showStreamPcmBtn.style.display = 'block';hideStreamPcmBtn.style.display = 'none';});")
fmt.Fprintf(w, "</script></body></html>")
fmt.Printf("[Web Access] Return default index pages\n")
}