diff --git a/.gitignore b/.gitignore index a547bf3..d0d8372 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +# Data directories +/data diff --git a/App.tsx b/App.tsx index 260b4f3..342666e 100644 --- a/App.tsx +++ b/App.tsx @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 057b263..0937ee9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/nginx.conf b/nginx.conf index 341f508..122bbff 100644 --- a/nginx.conf +++ b/nginx.conf @@ -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; diff --git a/utils/parsers.ts b/utils/parsers.ts index 32d9e0e..02cf5c8 100644 --- a/utils/parsers.ts +++ b/utils/parsers.ts @@ -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 => { 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')) { diff --git a/vite.config.ts b/vite.config.ts index d171d81..3ec78f4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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)