|
| 1 | +--- |
| 2 | +description: Resolve audio streams for tracks in Nuclear's queue. |
| 3 | +--- |
| 4 | + |
| 5 | +# Streaming |
| 6 | + |
| 7 | +## Streaming API for Plugins |
| 8 | + |
| 9 | +The Streaming API resolves playable audio URLs for tracks. When a user plays a track, Nuclear searches for stream candidates (e.g., YouTube videos) and then resolves the actual audio URL just-in-time. |
| 10 | + |
| 11 | +{% hint style="info" %} |
| 12 | +Access streaming via `NuclearAPI.Streaming.*` in your plugin's lifecycle hooks. |
| 13 | +{% endhint %} |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Core concepts |
| 18 | + |
| 19 | +### Two-phase resolution |
| 20 | + |
| 21 | +Stream resolution happens in two phases: |
| 22 | + |
| 23 | +1. **Candidate discovery** — Search for potential sources (e.g., YouTube videos matching the track) |
| 24 | +2. **Stream resolution** — Extract the actual audio URL from the top candidate |
| 25 | + |
| 26 | +### Stream candidates |
| 27 | + |
| 28 | +A candidate represents a potential audio source before the URL is resolved. Key fields: |
| 29 | + |
| 30 | +| Field | Type | Description | |
| 31 | +|-------|------|-------------| |
| 32 | +| `id` | `string` | Unique identifier for this candidate | |
| 33 | +| `title` | `string` | Display title (e.g., video title) | |
| 34 | +| `durationMs` | `number?` | Duration in milliseconds | |
| 35 | +| `thumbnail` | `string?` | Preview image URL | |
| 36 | +| `source` | `ProviderRef` | Which streaming provider found this | |
| 37 | +| `stream` | `Stream?` | Resolved audio URL (populated after resolution) | |
| 38 | +| `lastResolvedAtIso` | `string?` | When the stream was last resolved, so we know when to refresh it | |
| 39 | +| `failed` | `boolean` | True if resolution failed permanently | |
| 40 | + |
| 41 | +{% hint style="info" %} |
| 42 | +See `StreamCandidate` in `@nuclearplayer/model` for the full type definition. |
| 43 | +{% endhint %} |
| 44 | + |
| 45 | +### Streams |
| 46 | + |
| 47 | +A resolved stream contains the actual playable URL. Key fields: |
| 48 | + |
| 49 | +| Field | Type | Description | |
| 50 | +|-------|------|-------------| |
| 51 | +| `url` | `string` | The audio URL | |
| 52 | +| `protocol` | `'file' \| 'http' \| 'https' \| 'hls'` | Stream protocol | |
| 53 | +| `mimeType` | `string?` | e.g., `'audio/webm'` | |
| 54 | +| `bitrateKbps` | `number?` | Audio quality | |
| 55 | +| `codec` | `string?` | e.g., `'opus'`, `'aac'` | |
| 56 | +| `qualityLabel` | `string?` | e.g., `'320kbps'`, `'FLAC'` | |
| 57 | + |
| 58 | +{% hint style="info" %} |
| 59 | +See `Stream` in `@nuclearplayer/model` for the full type definition. |
| 60 | +{% endhint %} |
| 61 | + |
| 62 | +### Stream expiry |
| 63 | + |
| 64 | +Audio URLs from services like YouTube typically expire after a period (often as short as a few hours). Nuclear automatically re-resolves expired streams when needed. The expiry window is configurable via the `playback.streamExpiryMs` setting. |
| 65 | + |
| 66 | +--- |
| 67 | + |
| 68 | +## Usage |
| 69 | + |
| 70 | +### Finding candidates |
| 71 | + |
| 72 | +Call `resolveCandidatesForTrack(track)` with a `Track` object containing at least a title and artist. The method returns a result object with `success`, `candidates` (on success), or `error` (on failure). |
| 73 | + |
| 74 | +### Resolving a stream |
| 75 | + |
| 76 | +Once we have a candidate, Nuclear calls `resolveStreamForCandidate(candidate)` to get the actual audio URL. The method returns an updated candidate with the `stream` field populated, or the same candidate if already resolved/failed. This happens when we try to play the track. |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +## Reference |
| 81 | + |
| 82 | +| Method | Description | |
| 83 | +|--------|-------------| |
| 84 | +| `resolveCandidatesForTrack(track)` | Search for stream candidates matching a track. Returns `StreamResolutionResult`. | |
| 85 | +| `resolveStreamForCandidate(candidate)` | Resolve the audio URL for a candidate. Returns updated `StreamCandidate` or `undefined`. The candidate is not mutated, a fresh copy is returned. | |
| 86 | + |
| 87 | +### Resolution behavior |
| 88 | + |
| 89 | +| Input state | Output | |
| 90 | +|-------------|--------| |
| 91 | +| Candidate with `failed: true` | Returns candidate unchanged (no retry) | |
| 92 | +| Candidate with valid, non-expired `stream` | Returns candidate unchanged (cached) | |
| 93 | +| Candidate with expired or missing `stream` | Attempts resolution, returns updated candidate | |
| 94 | +| Resolution succeeds | Returns candidate with `stream` populated | |
| 95 | +| Resolution fails after retries | Returns candidate with `failed: true` | |
| 96 | +| No streaming provider registered | Returns `undefined` | |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## Related settings |
| 101 | + |
| 102 | +These core settings affect stream resolution: |
| 103 | + |
| 104 | +| Setting | Description | Default | |
| 105 | +|---------|-------------|---------| |
| 106 | +| `playback.streamExpiryMs` | How long before a resolved stream is considered expired | 1 hour | |
| 107 | +| `playback.streamResolutionRetries` | How many times to retry before marking as failed | 3 | |
0 commit comments