251 lines
5.6 KiB
Markdown
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.
|