commit 751272017c9e579f763b8c07d63f3f617449ec92
parent d79e1e7e6b8cc5ce593c3133beec5723a6e596d9
Author: Pablo Murad <pblmrd@gmail.com>
Date: Fri, 1 May 2026 12:10:12 -0300
metadata
Diffstat:
| M | README.md | | | 30 | ++++++++++++++++++++++++++++++ |
| M | src/App.css | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
| M | src/App.tsx | | | 46 | ++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 115 insertions(+), 0 deletions(-)
diff --git a/README.md b/README.md
@@ -77,6 +77,36 @@ server {
No extra `vite.config` base URL is needed when the site is served at the domain root.
+## Troubleshooting
+
+### HTTP 503 on `/api/track/random` (“No tracks available”)
+
+The API returns **503** only when the in-memory track pool is **empty** (`trackCount: 0`). The browser request is reaching Node; fix **metadata path and env** on the server.
+
+1. **On the VPS (SSH)**, call health locally (replace `38471` if you use another `PORT`):
+
+ ```bash
+ curl -sS http://127.0.0.1:38471/api/health
+ ```
+
+ Check `metadataTsv`, `metadataExists`, `metadataSizeBytes`, `trackCount`, `tracksReady`, and `hint`.
+
+2. **Align `METADATA_TSV`** with the real file (e.g. `/opt/mymusics/data/metadata.tsv`). Wrong paths such as `/opt/data/metadata.tsv` look “almost right” but **fail** if the file lives under the app directory. Alternatively **remove** `METADATA_TSV` from `.env` / PM2 so the app uses the default `data/metadata.tsv` next to the project.
+
+3. **Restart the process** after editing env so variables reload:
+
+ ```bash
+ pm2 restart mymusics --update-env
+ ```
+
+ (Use your PM2 app name if different.)
+
+4. Re-run `curl` until `tracksReady` is `true` and `trackCount` > 0.
+
+### Console: `content.js` and TensorFlow / WebGL kernel messages
+
+Messages like **“The kernel '…' for backend 'webgl' is already registered”** in **`content.js`** come from a **browser extension** (not from MyMusics). To verify the site without that noise, use a **private/incognito window** with extensions disabled for that window, or temporarily disable extensions.
+
## Playback notes
- The browser loads audio directly from `https://archive.org/download/...` URLs. First play may be slow while the Archive serves the file from inside large ZIPs.
diff --git a/src/App.css b/src/App.css
@@ -160,6 +160,45 @@
font-weight: 500;
}
+.health-banner {
+ margin-bottom: 1.25rem;
+ padding: 1rem 1.1rem;
+ border-radius: 12px;
+ border: 2px solid color-mix(in srgb, var(--orange) 55%, transparent);
+ background: color-mix(in srgb, var(--orange) 12%, var(--panel));
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.3);
+}
+
+.health-banner strong {
+ display: block;
+ margin-bottom: 0.5rem;
+ color: var(--yellow);
+ font-size: 0.95rem;
+ letter-spacing: 0.03em;
+ text-transform: uppercase;
+}
+
+.health-banner p {
+ margin: 0 0 0.65rem;
+ font-size: 0.9rem;
+ line-height: 1.45;
+ word-break: break-word;
+}
+
+.health-banner p:last-child {
+ margin-bottom: 0;
+}
+
+.health-banner-hint {
+ color: var(--muted);
+ font-size: 0.82rem !important;
+}
+
+.health-banner code {
+ font-size: 0.8em;
+ color: var(--sky);
+}
+
.footer {
margin-top: 2.5rem;
text-align: center;
diff --git a/src/App.tsx b/src/App.tsx
@@ -14,12 +14,21 @@ type RandomResponse = {
type ErrBody = { error?: string };
+type HealthBody = {
+ tracksReady?: boolean;
+ hint?: string;
+ metadataTsv?: string;
+ metadataExists?: boolean;
+ trackCount?: number;
+};
+
export default function App() {
const audioRef = useRef<HTMLAudioElement>(null);
const [track, setTrack] = useState<TrackInfo | null>(null);
const [status, setStatus] = useState<string>("");
const [history, setHistory] = useState<TrackInfo[]>([]);
const [autoPlay, setAutoPlay] = useState(true);
+ const [healthWarn, setHealthWarn] = useState<string | null>(null);
const playUrl = useCallback((url: string) => {
const a = audioRef.current;
@@ -61,6 +70,32 @@ export default function App() {
}, [playUrl]);
useEffect(() => {
+ void (async () => {
+ try {
+ const res = await fetch("/api/health");
+ const h = (await res.json()) as HealthBody;
+ if (!h.tracksReady) {
+ const parts = [
+ h.hint,
+ h.metadataTsv && `Path: ${h.metadataTsv}`,
+ h.metadataExists === false && "File not found at configured path.",
+ typeof h.trackCount === "number" && `Tracks loaded: ${h.trackCount}.`,
+ ].filter(Boolean);
+ setHealthWarn(
+ parts.length > 0
+ ? parts.join(" ")
+ : "No tracks loaded. Check server metadata and /api/health.",
+ );
+ } else {
+ setHealthWarn(null);
+ }
+ } catch {
+ setHealthWarn(null);
+ }
+ })();
+ }, []);
+
+ useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect -- intentional mount bootstrap
void loadNext();
}, [loadNext]);
@@ -71,6 +106,17 @@ export default function App() {
return (
<div className="page">
+ {healthWarn ? (
+ <div className="health-banner" role="alert">
+ <strong>Server metadata</strong>
+ <p>{healthWarn}</p>
+ <p className="health-banner-hint">
+ On the host, run <code>curl -sS http://127.0.0.1:38471/api/health</code> (adjust
+ port) and fix <code>METADATA_TSV</code> or remove it to use the default{" "}
+ <code>data/metadata.tsv</code>.
+ </p>
+ </div>
+ ) : null}
<header className="header">
<img
className="logo"