支持本地数据

main
mula.liu 2026-01-26 20:02:00 +08:00
parent 45242a1fa1
commit 479c7fca43
6 changed files with 47 additions and 14 deletions

3
.gitignore vendored
View File

@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# Data directories
/data

17
App.tsx
View File

@ -78,18 +78,23 @@ const App: React.FC = () => {
if (track.lyrics.length === 0 && track.lyricsSource) {
try {
let lrcContent = "";
// Check if it's a URL
if (track.lyricsSource.startsWith('http')) {
// Check if it's a URL (http or relative/absolute local path)
// We treat anything starting with http, /, or ./ as a URL to fetch
if (track.lyricsSource.startsWith('http') || track.lyricsSource.startsWith('/') || track.lyricsSource.startsWith('./')) {
// Try to fetch lyrics, might need proxy if CORS fails
try {
const res = await fetch(track.lyricsSource);
if (!res.ok) throw new Error("Direct lyrics fetch failed");
lrcContent = await res.text();
} catch (e) {
// Simple proxy fallback for lyrics text
const proxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(track.lyricsSource)}`;
const res = await fetch(proxyUrl);
lrcContent = await res.text();
// Only try proxy if it's a remote HTTP URL
if (track.lyricsSource.startsWith('http')) {
const proxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(track.lyricsSource)}`;
const res = await fetch(proxyUrl);
lrcContent = await res.text();
} else {
throw e;
}
}
} else {
// Assume it's raw text

View File

@ -5,7 +5,9 @@ services:
build: .
container_name: nex-music-web
ports:
- "8081:80"
- "80:80"
volumes:
- ./data:/usr/share/nginx/html/data:ro
restart: always
# If you want to mount the .env file dynamically or valid it locally
# env_file:

View File

@ -13,6 +13,13 @@ server {
try_files $uri $uri/ /index.html;
}
# Serve static music data
location /data/ {
alias /usr/share/nginx/html/data/;
autoindex on;
add_header Access-Control-Allow-Origin *;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;

View File

@ -47,8 +47,18 @@ const getValue = (item: any, keys: string[]) => {
const resolveUrl = (url: string, baseUrl: string) => {
if (!url) return "";
try {
// If url is absolute, this ignores baseUrl
return new URL(url, baseUrl).href;
// Handle case where baseUrl is relative (e.g. "/data/playlist.json")
// We construct a full URL for the base if it's not already absolute
let fullBaseUrl = baseUrl;
if (baseUrl && !baseUrl.match(/^https?:\/\//)) {
// If running in browser context
if (typeof window !== 'undefined') {
fullBaseUrl = new URL(baseUrl, window.location.origin).href;
}
}
// If url is absolute, this ignores fullBaseUrl
return new URL(url, fullBaseUrl).href;
} catch (e) {
return url;
}
@ -122,13 +132,22 @@ export const fetchPlaylist = async (url: string): Promise<TrackData[]> => {
audioUrl = resolveUrl(audioUrl, url);
lyricsSource = resolveUrl(lyricsSource, url);
// Check if URL is local/same-origin
const isLocal = (u: string) => {
if (typeof window === 'undefined') return false;
try {
return new URL(u).origin === window.location.origin;
} catch(e) { return false; }
};
// Fix Mixed Content for Cover: Route HTTP images through Weserv proxy
if (coverUrl && coverUrl.startsWith('http:')) {
// ONLY if it's external. Local/same-origin HTTP is fine.
if (coverUrl && coverUrl.startsWith('http:') && !isLocal(coverUrl)) {
coverUrl = `https://images.weserv.nl/?url=${encodeURIComponent(coverUrl)}`;
}
// Fix Mixed Content for Audio
if (audioUrl && audioUrl.startsWith('http:')) {
if (audioUrl && audioUrl.startsWith('http:') && !isLocal(audioUrl)) {
// Strategy: Prefer native HTTPS upgrade if possible, otherwise proxy.
// Qiniu (clouddn.com) and other major CDNs usually support HTTPS.
if (audioUrl.includes('.clouddn.com') || audioUrl.includes('aliyuncs.com')) {

View File

@ -11,9 +11,6 @@ export default defineConfig(({ mode }) => {
hmr: true,
},
plugins: [react()],
build: {
target: 'es2015',
},
define: {
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)