> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sdk.anghami.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming & Billing

> Stream acquisition is the billable event. No separate playback reporting. Here is the contract.

The SDK has one billable event type: **stream acquisition**. Browsing, searching, library reads, and metadata fetches do not bill. The two RPCs that matter are:

* `StreamingService.AcquireMusicStream(song_id, max_quality)` — billed as a music stream.
* `StreamingService.AcquireVideoStream(movie_id | episode_id, max_quality)` — billed as a video stream. The request carries a `oneof content { MovieID movie_id; EpisodeID episode_id }`.

Both require an OAuth access token with the `stream` scope. **API keys cannot acquire streams.**

## What you get back

Each acquire returns a `StreamInfo` payload with:

* `manifest_url` — the **time-limited stream URL** (HLS m3u8 or DASH mpd).
* `expires_in_seconds` — seconds remaining until the URL expires (relative, not an absolute timestamp).
* `drm_scheme`, `license_url`, `drm_params` — **DRM metadata** when the stream is protected. `drm_scheme` is `DRM_SCHEME_UNSPECIFIED` for clear streams. `drm_params` is a string→string map of scheme-specific values (e.g. `certificate_url` for FairPlay).
* `available_qualities` — the quality tiers the server actually exposes for this stream (each entry is a `QualityOption { label, bitrate_kbps, codec }`). The server may pick a tier below the requested `max_quality` if the user is not entitled to it.

The URL is valid for `expires_in_seconds`. Re-acquire to get a fresh URL. **Re-acquiring within the lifetime of an existing URL does not re-bill** — it returns a fresh signed URL for the same logical stream.

## What counts as a billable event

| Event                                                                   | Billable?                |
| ----------------------------------------------------------------------- | ------------------------ |
| First `AcquireMusicStream` for `(user, song)` in a session              | Yes                      |
| Re-acquiring the same `(user, song)` while the prior URL is still valid | No (same logical stream) |
| Re-acquiring after URL expiry                                           | Yes (new logical stream) |
| Acquiring for a different song                                          | Yes                      |
| Acquiring at a different quality for the same song                      | Yes                      |
| `GetSong`, `Search`, `BrowseCharts`, …                                  | Never                    |

The same rules apply to video.

## No playback reporting

There is **no** `ReportPlaybackStart` / `ReportPlaybackEnd` / `ReportPlaybackProgress` RPC, by design. The billable event is the acquire, not the play. This keeps the SDK simple and the billing contract unambiguous: if your client acquires a stream, that's the event, regardless of whether the user actually pressed play.

## Concurrency

Stream acquisition has its own concurrency cap separate from request-rate limits — the cap is per OAuth user, not per API key. Exceeding it returns `ERROR_CODE_PERMISSION_DENIED` with a message indicating concurrent stream limits. This is enforced at the entitlement layer, not the rate-limit layer.

## DRM

When `drm_scheme` is anything other than `DRM_SCHEME_UNSPECIFIED`, the client must:

1. Read `drm_scheme` — one of `DRM_SCHEME_FAIRPLAY` (Apple FairPlay), `DRM_SCHEME_WIDEVINE` (Google Widevine), `DRM_SCHEME_PLAYREADY` (Microsoft PlayReady), or `DRM_SCHEME_CLEARKEY` (W3C Clear Key, test scenarios only).
2. POST a license request to `license_url` with the platform-specific key request body. `license_url` is empty for `CLEARKEY` and unprotected streams.
3. Pass any scheme-specific values from `drm_params` (e.g. `certificate_url` for FairPlay) into the platform DRM module before playback.

Generated TypeScript and Swift clients expose typed wrappers; raw HTTP callers should forward `drm_scheme`, `license_url`, and `drm_params` opaquely to the platform layer.

## Best practices

* **Acquire just-in-time.** Don't pre-acquire entire playlists; you'll bill for streams the user never plays.
* **Refresh on expiry, not eagerly.** Honor `expires_in_seconds` and only re-acquire when the URL is about to expire or has expired.
* **Surface acquire failures cleanly.** A failed acquire is the right time to show DRM errors, region-restriction errors, or entitlement errors to the user.
* **Use `max_quality` deliberately.** Acquiring `AUDIO_QUALITY_HIGH` for a user only entitled to `AUDIO_QUALITY_STANDARD` is downgraded server-side — no separate negotiation step needed. Audio tiers: `AUDIO_QUALITY_LOW`, `AUDIO_QUALITY_STANDARD`, `AUDIO_QUALITY_HIGH`, `AUDIO_QUALITY_LOSSLESS`. Video tiers: `VIDEO_QUALITY_SD`, `VIDEO_QUALITY_HD`, `VIDEO_QUALITY_FULL_HD`, `VIDEO_QUALITY_UHD`.
