README.md (7532B)
1 # MyMusics 2 3 Retro-styled web player that picks random tracks from [`data/metadata.tsv`](data/metadata.tsv) and streams MP3s from the Internet Archive item [**The Myspace Dragon Hoard (2008–2010)**](https://archive.org/details/myspace_dragon_hoard_2010). URLs follow the same pattern as the official IA “Hobbit” player (ZIP member paths). 4 5 The API uses a **SQLite index** (`data/tracks.db`) built from the TSV for fast random selection, search, and lookup by id — without loading hundreds of thousands of rows into RAM. 6 7 ## Requirements 8 9 - Node.js 20+ 10 - `data/metadata.tsv` (Dragon Hoard export). No local MP3 mirror is required. 11 - After clone/deploy: run **`npm run index-metadata`** once (or on each TSV update) to create `data/tracks.db`. 12 13 ## Configuration 14 15 1. Copy `.env.example` to `.env` and adjust: 16 17 - `METADATA_TSV` — path to `metadata.tsv` (default: `data/metadata.tsv`). 18 - `TRACKS_DB` — SQLite index (default: `data/tracks.db`). 19 - **Ports** — [`config/ports.ts`](config/ports.ts): `PORT_INDEX` (0–3) or explicit `PORT` / `VITE_DEV_PORT`. Defaults: API `38471`, Vite `38472`. 20 - `IA_ITEM_ID` (optional) — Internet Archive item id (default `myspace_dragon_hoard_2010`). 21 - `SERVE_STATIC` — serve SPA from `dist/` on the API port when built. 22 - `CORS_ORIGINS` — comma-separated API CORS origins (production defaults to the public site). 23 - `PUBLIC_SITE_URL` / `VITE_PUBLIC_SITE_URL` — canonical URL for share links, oEmbed, embed snippet. 24 - `VITE_EMBED_PARENT_ORIGIN` — restrict embed `postMessage` target (optional). 25 26 2. Install dependencies: 27 28 ```bash 29 npm install 30 ``` 31 32 3. Build the track index (required before first API start): 33 34 ```bash 35 npm run index-metadata 36 ``` 37 38 Use `npm run index-metadata -- --if-stale` to skip when the DB is newer than the TSV (used by `npm run build`). Use `--force` to rebuild always. 39 40 ## Development 41 42 Fast dev with a small TSV sample (optional): 43 44 ```bash 45 npm run sample-metadata 46 # set METADATA_TSV=data/metadata.sample.tsv in .env, then: 47 npm run index-metadata -- --force 48 ``` 49 50 Start API + Vite: 51 52 ```bash 53 npm run dev 54 ``` 55 56 Open the URL Vite prints (default `http://localhost:38472`). 57 58 ## Production (static build + API) 59 60 ```bash 61 npm run build 62 SERVE_STATIC=true npm run start 63 ``` 64 65 `npm run build` runs `index-metadata --if-stale`, compiles the server, and builds the SPA. 66 67 **PM2** (after `npm run build`): 68 69 ```bash 70 npm run pm2:prod 71 pm2 save 72 ``` 73 74 Set `METADATA_TSV` / `TRACKS_DB` in `ecosystem.config.cjs` → `env_production` on the VPS. Re-run **`npm run index-metadata`** when `metadata.tsv` changes. 75 76 ### Docker 77 78 ```bash 79 docker compose build 80 docker compose up 81 ``` 82 83 Ensure `data/metadata.tsv` is present under `./data` (volume). The image runs `index-metadata --if-stale` during build when the TSV is copied in. 84 85 ### VPS: “No tracks available” / `trackCount: 0` 86 87 1. Ensure **`data/metadata.tsv`** exists and run **`npm run index-metadata`**. 88 2. Check **`GET /api/health`**: `tracksReady: true`, `tracksDbExists: true`, `trackCount` > 0, `ftsReady: true`, and `hint` if something failed. 89 3. Restart PM2 after env changes: `pm2 restart mymusics --update-env`. 90 91 ### Reverse proxy (e.g. `mymusics.murad.gg`) 92 93 Point HTTPS at the Node port (default `38471`). Example **nginx**: 94 95 ```nginx 96 server { 97 server_name mymusics.murad.gg; 98 location / { 99 proxy_pass http://127.0.0.1:38471; 100 proxy_http_version 1.1; 101 proxy_set_header Host $host; 102 proxy_set_header X-Real-IP $remote_addr; 103 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 104 proxy_set_header X-Forwarded-Proto $scheme; 105 } 106 } 107 ``` 108 109 **Embed on third-party sites:** HTML responses use `Content-Security-Policy: frame-ancestors *`. Ensure the proxy does not add `X-Frame-Options: DENY`. 110 111 ## Embed (`/embed`) — priority 112 113 Iframe URL: `https://mymusics.murad.gg/embed` with optional query params: 114 115 | Param | Default | Effect | 116 |-------|---------|--------| 117 | `autoplay` | `1` | `0` disables auto-advance and skips random load on mount | 118 | `theme` | `default` | `compact` — smaller layout | 119 | `start` | — | Track id — loads `GET /api/track/:id` | 120 | `brand` | `1` | `0` hides footer logo | 121 | `muted` | `0` | `1` starts **audio** muted | 122 | `preset` | `default` | Color preset: `light`, `dark`, `midnight`, `minimal` | 123 | `accent` | — | Accent hex (with/without `#`) | 124 | `bg` | — | Page background hex | 125 | `panel` | — | Panel/card background hex | 126 | `text` | — | Primary text hex | 127 | `fgMuted` | — | Secondary text hex (not audio mute) | 128 | `radius` | preset | Border radius 0–24 (px) | 129 | `font` | `sans` | `system`, `sans`, or `serif` | 130 131 Full customization guide (presets, CSS tokens, postMessage theme): **[docs/EMBED-CUSTOMIZATION.md](docs/EMBED-CUSTOMIZATION.md)**. 132 133 **postMessage** (iframe → parent), payload `{ source: "mymusics", type, ... }`: 134 135 - `mymusics:ready` — `{ trackCount }` 136 - `mymusics:track` — `{ id, title, artist, streamUrl }` 137 - `mymusics:state` — `{ state: "playing" \| "paused" \| "buffering" \| "error" }` 138 - `mymusics:error` — `{ code, message }` 139 - `mymusics:theme-applied` — `{ tokens }` after a theme change 140 141 Parent → iframe: `{ source: "mymusics-host", type: "mymusics:command", command: "play" \| "pause" \| "next" }`. 142 143 Parent → iframe (theme): `{ source: "mymusics-host", type: "mymusics:theme", theme: { preset, accent, ... } }`. 144 145 **oEmbed:** `GET /api/oembed?url=https://mymusics.murad.gg/embed` 146 147 The Home and About pages include a snippet generator with preset, accent, and live preview. 148 149 ## API 150 151 | Method | Path | Description | 152 |--------|------|-------------| 153 | GET | `/api/health` | Diagnostics (`tracksDb`, `ftsReady`, `blockedCount`, …) | 154 | GET | `/api/track/random` | Random track + `streamUrl` | 155 | GET | `/api/track/up-next?exclude=` | Next track (prefers different id) | 156 | GET | `/api/track/:id` | Track by id | 157 | GET | `/api/track/search?q=&limit=` | Search by title/artist (FTS) | 158 | POST | `/api/reload` | Reload DB / paths from env | 159 | POST | `/api/events` | Client metrics (`stream_error`, `time_to_play`) | 160 | GET | `/api/oembed?url=` | oEmbed JSON for `/embed` | 161 162 ## Share links 163 164 - `https://mymusics.murad.gg/?track=TRACK_ID` 165 - `https://mymusics.murad.gg/t/TRACK_ID` (redirects to `/?track=`) 166 167 ## Maintenance 168 169 Weekly cron (optional) — sample Archive URLs and block failures: 170 171 ```bash 172 npm run verify-tracks 173 ``` 174 175 Env: `VERIFY_SAMPLE_SIZE` (default `50`). 176 177 ## Troubleshooting 178 179 ### HTTP 503 on `/api/track/random` 180 181 The pool is empty. Run `npm run index-metadata` and verify `/api/health`. 182 183 ### Console: TensorFlow / `content.js` messages 184 185 These come from **browser extensions**, not MyMusics. 186 187 ## Playback notes 188 189 - Audio streams from `https://archive.org/download/...`. First play can be slow (ZIP member extraction). 190 - Archive **503** errors are retried automatically a few times; use **Next** if needed. 191 - Volume is remembered in `localStorage`. Shortcuts on Home: Space, N, M. 192 193 ## Scripts 194 195 | Command | Description | 196 |---------|-------------| 197 | `npm run dev` | Vite + API | 198 | `npm run index-metadata` | Build `data/tracks.db` from TSV | 199 | `npm run index-metadata:force` | Force rebuild index | 200 | `npm run sample-metadata` | Write `data/metadata.sample.tsv` (500 lines) | 201 | `npm run verify-tracks` | HEAD sample URLs → `blocked_ids` | 202 | `npm run build` | Index (if stale) + server + SPA | 203 | `npm run start` | Production Node server | 204 | `npm run test` | Vitest (metadata + track store) | 205 | `npm run pm2:prod` | Build + PM2 | 206 207 ## Logo 208 209 `public/mymusics.png` is the MyMusics logo asset.