|
| 1 | +# Contributing |
| 2 | + |
| 3 | +## What this library is |
| 4 | + |
| 5 | +- A headless Spotify client, allowing you to **authenticate and retrieve a decrypted audio stream for any track**. |
| 6 | +- *Not* a standalone audio player: the **provided stream must be piped to another application** (like `ffplay`) or handled by a server to be played. |
| 7 | + |
| 8 | +## Environment setup |
| 9 | + |
| 10 | +### Prerequisites |
| 11 | +- Python 3.10+ |
| 12 | + |
| 13 | +### Install runtime packages |
| 14 | + |
| 15 | +```sh |
| 16 | +pip install -r requirements.txt |
| 17 | +``` |
| 18 | + |
| 19 | +### Install protoc |
| 20 | + |
| 21 | +> This step is **only needed if you're changing any `.proto` serialization schema files**, |
| 22 | +> which will subsequently require using the protoc compiler to generate updated versions of |
| 23 | +> the `*_pb2.py` Python stubs that implement serialization/deserialization for those schemas. |
| 24 | +
|
| 25 | +- Go to the [protobuf release matching the version pinned in `requirements.txt`](https://github.com/protocolbuffers/protobuf/releases/tag/v3.20.1). |
| 26 | +- Download and install the `protoc-*.zip` file meant for your platform. |
| 27 | + |
| 28 | +After modifying the `.proto` files you need to, **make sure to follow [these steps](#protocol-buffer-generation) to regenerate the Python stubs**. |
| 29 | + |
| 30 | +## Protocol buffer generation |
| 31 | + |
| 32 | +> These steps are only necessary after changing `.proto` files. |
| 33 | +
|
| 34 | +- From the repository root, conveniently recompile all `.proto` schema files with this command: |
| 35 | + |
| 36 | + ```bash |
| 37 | + find proto -name "*.proto" | xargs protoc -I=proto --python_out=librespot/proto |
| 38 | + ``` |
| 39 | + |
| 40 | +- Alternatively, to recompile a single file (e.g. `proto/metadata.proto`), run: |
| 41 | + |
| 42 | + ```bash |
| 43 | + protoc -I=proto --python_out=librespot/proto proto/metadata.proto |
| 44 | + ``` |
| 45 | + |
| 46 | +- Commit both the source `.proto` and the regenerated Python output **together** so they can |
| 47 | +be compared easily. |
| 48 | + |
| 49 | +## Architecture |
| 50 | + |
| 51 | +The main components are: |
| 52 | + |
| 53 | +- **`Session` class** *(entrypoint)* |
| 54 | + |
| 55 | + - `Session.Builder` is used to configure and create a session, via one of: |
| 56 | + |
| 57 | + - username/password |
| 58 | + - stored credentials |
| 59 | + - OAuth |
| 60 | + |
| 61 | + - An active session is **required** for all other operations. |
| 62 | + |
| 63 | +- **`ApiClient` class** |
| 64 | + |
| 65 | + - A high-level client for making standard HTTPS requests to Spotify's Web API endpoints (e.g., `https://spclient.wg.spotify.com`). |
| 66 | + - Accessed via `session.api()`, it provides convenient methods like `get_metadata_4_track()` and handles client tokens automatically. |
| 67 | +
|
| 68 | +- **`MercuryClient` class** |
| 69 | +
|
| 70 | + - The low-level client for Spotify's proprietary `mercury` protocol, which uses `hm://` URIs. |
| 71 | + - Accessed via `session.mercury()`, it handles sending and receiving messages over the main session connection for metadata lookups and subscriptions that are not available via the standard Web API. |
| 72 | +
|
| 73 | +- **`DealerClient` class** |
| 74 | +
|
| 75 | + - Manages the persistent WebSocket (`wss://`) connection to Spotify's `dealer` service. |
| 76 | + - Accessed via `session.dealer()`, it listens for and dispatches real-time, asynchronous JSON-based events, such as remote player state changes or notifications from other connected devices. |
| 77 | +
|
| 78 | +- **`Session.Receiver` thread** |
| 79 | +
|
| 80 | + - Spawned after authentication to read every encrypted packet coming from the access point. |
| 81 | + - Routes decoded commands to subsystems (`MercuryClient`, `AudioKeyManager`, `ChannelManager`, etc.) and responds to keep-alive pings to hold the session open. |
| 82 | +
|
| 83 | +- **Metadata types** |
| 84 | +
|
| 85 | + - The `librespot.metadata` module provides typed identifiers (`TrackId`, `AlbumId`, `PlaylistId`, `EpisodeId`, etc.) used to reference Spotify content throughout the API. |
| 86 | + - They are constructed from Spotify identifiers, typically using one of the following methods: |
| 87 | +
|
| 88 | + - `from_uri()`: For all ID types. |
| 89 | + - `from_base62()`: For most ID types (e.g., tracks, albums, artists). |
| 90 | +
|
| 91 | +- **`PlayableContentFeeder` class** |
| 92 | +
|
| 93 | + - Retrieves audio streams; is accessed via `session.content_feeder()`. |
| 94 | + - `load(playable_id, audio_quality_picker, preload, halt_listener)`: |
| 95 | +
|
| 96 | + - Accepts: |
| 97 | +
|
| 98 | + - a `TrackId` or `EpisodeId` (any `PlayableId`) |
| 99 | + - an `AudioQualityPicker` |
| 100 | + - a `preload` flag |
| 101 | + - an optional `HaltListener` callback (pass `None` if unneeded). |
| 102 | +
|
| 103 | + - Returns a `LoadedStream` that contains the decrypted stream together with: |
| 104 | +
|
| 105 | + - track/episode metadata |
| 106 | + - normalization data |
| 107 | + - transfer metrics |
| 108 | +
|
| 109 | +- **`audio` module** |
| 110 | + |
| 111 | + - Contains tools for format selection, quality management, streaming, and decryption. |
| 112 | + - `VorbisOnlyAudioQuality` and `LosslessOnlyAudioQuality` choose the best matching `Metadata.AudioFile` for a preferred container/quality combination. |
| 113 | + - `CdnManager` acquires and refreshes signed CDN URLs, feeding a `Streamer` that decrypts chunks on the fly while staying seekable. |
| 114 | +
|
| 115 | +- **`AudioKeyManager` and `ChannelManager`** |
| 116 | +
|
| 117 | + - Handle the low-level transport for protected audio: `AudioKeyManager` requests AES keys, and `ChannelManager` can stream encrypted chunks directly from the access point when CDN delivery is unavailable. |
| 118 | + - Both are driven transparently by `PlayableContentFeeder`/`CdnManager`, so callers only interact with the decrypted `LoadedStream`. |
| 119 | +
|
| 120 | +- **`EventService` class** |
| 121 | +
|
| 122 | + - Asynchronous publisher that emits telemetry (e.g., fetch metrics, playback events) to `hm://event-service/v1/events` via Mercury. |
| 123 | + - Accessible through `session.event_service()` for consumers that need to forward custom events. |
| 124 | +
|
| 125 | +- **`TokenProvider` class** |
| 126 | +
|
| 127 | + - Caches Login5 access tokens per scope, refreshing them proactively as they near expiry. |
| 128 | + - Used by `ApiClient` to supply the correct `Authorization` headers for Spotify Web API calls. |
| 129 | +
|
| 130 | +- **`SearchManager` class** |
| 131 | +
|
| 132 | + - High-level wrapper around `hm://searchview/km/v4/search/...` requests sent over Mercury. |
| 133 | + - Fills in username, locale, and country defaults from the current session before dispatching the call. |
| 134 | +
|
| 135 | +- **OAuth tokens for Spotify Web API** |
| 136 | +
|
| 137 | + - Can be obtained via `session.tokens().get(scope)` |
| 138 | + - Enable authenticated API calls for operations like search, playlist management, and user data access |
0 commit comments