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

Greyscale

Greyscale is a JavaScript API/runtime project whose primary source lives in src, while greyscale-app is retained as a legacy demo.

Project Scope

Current Structure

.
├── 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

node server.js
# open http://127.0.0.1:12345

Testing

Run unit tests (offline/mocked only):

node tests/test.js

Run unit + live integration tests (real service calls):

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)

Create an API instance:

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)

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)

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)

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)

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 is the integration shell (routing + UI wiring) for runtime modules.
  • greyscale-app is intentionally preserved as demo history.