Files
client/greyscale/README.md
T
2026-04-17 16:52:51 -04:00

251 lines
5.6 KiB
Markdown

# Greyscale
Greyscale is a JavaScript API/runtime project whose primary source lives in [`src`](src), while [`greyscale-app`](greyscale-app) is retained as a legacy demo.
## Project Scope
- Primary runtime source: [`src`](src)
- Primary API module: [`src/api.js`](src/api.js)
- Legacy demo (kept for reference): [`greyscale-app`](greyscale-app)
## Current Structure
```text
.
├── src/
│ ├── api.js
│ ├── app.js
│ ├── cache.js
│ ├── downloads.js
│ ├── lyrics.js
│ ├── player.js
│ └── utils.js
├── greyscale-app/ # legacy demo
├── monochrome/ # upstream reference submodule
└── server.js
```
## Run
```bash
node server.js
# open http://127.0.0.1:12345
```
---
## Testing
Run unit tests (offline/mocked only):
```bash
node tests/test.js
```
Run unit + live integration tests (real service calls):
```bash
node tests/test.js --live
```
Notes:
- The `--live` mode calls real uptime workers, real API instances, and `lrclib.net`.
- Some live checks may be skipped when upstream data is unavailable (for example missing IDs from search results).
- The summary at the end reports pass/fail/skip totals for unit and live suites separately.
---
## API Documentation
### `GreyscaleAPI` ([`src/api.js`](src/api.js))
Create an API instance:
```js
import { GreyscaleAPI } from './src/api.js';
const api = new GreyscaleAPI();
await api.initInstances();
```
#### Instance + transport
- `initInstances()`
- Loads healthy API and streaming instance pools from uptime workers.
- Falls back to static instance lists when workers fail.
- `fetchFromPool(poolType, path, options?)`
- Internal transport with failover across instance pool.
- `poolType`: `'api' | 'streaming'`
#### Search endpoints
- `searchTracks(query)`
- Endpoint: `/search/?s={query}`
- Returns paged object:
- `items: Track[]`
- `limit`, `offset`, `totalNumberOfItems`
- `searchAlbums(query)`
- Endpoint: `/search/?al={query}`
- Returns paged object with deduplicated album results.
- `searchArtists(query)`
- Endpoint: `/search/?a={query}`
- Returns paged object of artists.
#### Detail endpoints
- `getAlbum(id)`
- Endpoint: `/album/?id={id}`
- Returns:
- `{ album, tracks }`
- Handles album track pagination (`offset/limit`) for large albums.
- `getArtist(artistId, options?)`
- Endpoints:
- `/artist/?id={id}`
- `/artist/?f={id}&skip_tracks=true`
- Returns normalized artist profile including:
- `albums`
- `eps`
- `tracks` (top tracks)
- `getTrackMetadata(id)`
- Endpoint: `/info/?id={id}`
- Returns a normalized track object.
- `getTrackRecommendations(id)`
- Endpoint: `/recommendations/?id={id}`
- Returns: `Track[]`
#### Streaming + media
- `getTrackStream(id, quality = 'HI_RES_LOSSLESS')`
- Endpoint: `/track/?id={id}&quality={quality}`
- Returns:
- `{ streamUrl, isDash }`
- Handles direct URLs and manifest decoding.
- `downloadTrackBlob(id, quality = 'LOSSLESS', onProgress?)`
- Downloads stream into a `Blob`.
- Returns:
- `{ blob, filename, track }`
- Progress callback shape:
- `{ receivedBytes, totalBytes? }`
#### Artwork helpers
- `getCoverUrl(coverId, size = 320)`
- Builds Tidal image URL for album/track cover ids.
- `getArtistPictureUrl(id, size = 320)`
- Builds Tidal image URL for artist image ids.
#### Cache helpers
- `clearCache()`
- Clears API cache.
- `getCacheStats()`
- Returns cache stats object from `APICache`.
---
### `APICache` ([`src/cache.js`](src/cache.js))
```js
import { APICache } from './src/cache.js';
```
- Constructor:
- `new APICache({ maxSize = 250, ttl = 20min } = {})`
- Methods:
- `get(namespace, key)`
- `set(namespace, key, value, ttl?)`
- `clearExpired()`
- `clear()`
- `getCacheStats()`
Uses namespace-prefixed keys and timestamp-based TTL expiration.
---
### `DownloadManager` ([`src/downloads.js`](src/downloads.js))
```js
import { DownloadManager } from './src/downloads.js';
```
- Constructor:
- `new DownloadManager(api)`
- Methods:
- `setOnChange(callback)`
- `getTasks()`
- `clearFinished()`
- `cancel(taskId)`
- `downloadTrack(track, quality?)`
- `downloadAlbum(album, tracks, quality?)`
Task status lifecycle:
- `queued -> downloading -> done`
- error path: `error`
- cancel path: `cancelled`
---
### `LyricsManager` ([`src/lyrics.js`](src/lyrics.js))
```js
import { LyricsManager } from './src/lyrics.js';
```
- Constructor:
- `new LyricsManager()`
- Methods:
- `fetchLyrics(track)`
- Fetches from LRCLIB and caches by track key.
- `getCurrentLineIndex(currentTime, lines)`
- Binary search for active synced line.
- `renderLyrics(payload, activeIndex?)`
- Produces renderable HTML for synced/plain lyrics.
Internal helper:
- LRC parsing into `{ time, text }[]`.
---
### `SimplePlayer` ([`src/player.js`](src/player.js))
```js
import { SimplePlayer } from './src/player.js';
```
- Constructor:
- `new SimplePlayer({ audio, progressEl, currentTimeEl, durationEl })`
- Core methods:
- `setResolver(resolveStreamFn)`
- `setQueue(tracks, startIndex?)`
- `getCurrentTrack()`
- `playCurrent(resolverOverride?)`
- `playNext(resolverOverride?)`
- `playPrev(resolverOverride?)`
- `togglePlayPause()`
- Events/callbacks:
- `onTrackChanged(track)`
- `onTimeUpdate(current, duration, track)`
Supports direct audio URLs and DASH playback via `dash.js`.
---
## Notes
- [`src/app.js`](src/app.js) is the integration shell (routing + UI wiring) for runtime modules.
- [`greyscale-app`](greyscale-app) is intentionally preserved as demo history.