# How to Implement get_stream_details for Music Providers in Music Assistant

> Learn how to implement get_stream_details for music providers in Music Assistant. Resolve media items, build AudioFormat, and return StreamDetails with direct URLs and encryption keys.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: how-to-guide
- Published: 2026-06-13

---

**To implement `get_stream_details`, music providers must resolve the media item, build an `AudioFormat` object with codec metadata, and return a `StreamDetails` instance containing the direct URL and encryption keys if needed.**

In the Music Assistant server architecture, the `get_stream_details` method serves as the bridge between provider-specific APIs and the unified playback pipeline. This abstract method, defined in [`music_assistant/models/music_provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/music_provider.py) (lines 17-19), must be implemented by every music provider that uses custom streaming (when `stream_type` is set to `StreamType.CUSTOM`). When a player requests audio, the streaming controller calls this method to retrieve the actual stream URL and metadata required for playback.

## The get_stream_details Method Signature

According to the base class in [`music_assistant/models/music_provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/music_provider.py), every music provider must implement this asynchronous method:

```python
async def get_stream_details(
    self, item_id: str, media_type: MediaType
) -> StreamDetails:
    """Return StreamDetails for a given item."""

```

The framework calls this method whenever a player requests a stream for a specific item. The `item_id` must remain consistent throughout the playback lifecycle, as it is used later in `on_streamed` and `on_played` callbacks to update play statistics.

## Four-Step Implementation Pattern

Successful implementations follow a consistent pattern across all providers in the Music Assistant ecosystem.

### 1. Resolve the Media Item

First, fetch the complete media object using your provider's API. The type depends on the `media_type` parameter:

```python
if media_type is MediaType.TRACK:
    media = await self.get_track(item_id)
elif media_type is MediaType.RADIO:
    media = await self.get_radio(item_id)
elif media_type is MediaType.PODCAST_EPISODE:
    media = await self.get_podcast_episode(item_id)
else:
    raise NotImplementedError(f"Unsupported media type: {media_type}")

```

If the item cannot be resolved, raise `MediaNotFoundError` to signal the controller to handle the failure gracefully.

### 2. Collect Required Metadata

Gather technical specifications needed for the audio pipeline:

- **Duration**: Total playback time in seconds
- **MIME type**: Content type identifier
- **Codec**: Audio compression format (FLAC, MP3, AAC, etc.)
- **Sample rate**: Audio sampling frequency
- **Bit depth**: Bit depth for lossless formats
- **Channels**: Number of audio channels (2 for stereo, 1 for mono)

### 3. Create the StreamDetails Object

Instantiate `StreamDetails` with all necessary fields. The `path` field accepts either a direct URL or a local file path, and may be a signed URL with temporary tokens:

```python
from music_assistant_models.streamdetails import StreamDetails, StreamType
from music_assistant_models.audio_format import AudioFormat

audio_format = AudioFormat(
    codec=media.metadata.audio_codec,
    sample_rate=media.metadata.sample_rate,
    bit_depth=media.metadata.bit_depth,
    channels=media.metadata.channels,
)

return StreamDetails(
    item_id=item_id,
    provider=self.instance_id,
    media_type=media_type,
    path=media.uri,                    # Direct stream URL or file path

    duration=media.duration,
    audio_format=audio_format,
    stream_type=StreamType.CUSTOM,
    data={},                           # Optional: decryption keys, headers

)

```

### 4. Return the Object

Return the populated `StreamDetails` instance. The framework handles pre-fetching, buffering, and eventual playback. If you need to signal that the item is unavailable, return a `StreamDetails` with `path=None` to allow the controller to fall back to standard HTTP streaming.

## Handling Encrypted Streams and Caching

### Encryption Management

For DRM-protected or encrypted streams (common with FLAC files requiring per-track keys), store decryption parameters in the `data` dictionary:

```python
return StreamDetails(
    item_id=item_id,
    provider=self.instance_id,
    media_type=media_type,
    path=encrypted_url,
    duration=duration,
    audio_format=audio_format,
    stream_type=StreamType.CUSTOM,
    data={
        "decryption_key": media.key,
        "iv": media.initialization_vector,
    },
)

```

The playback layer reads `StreamDetails.data` transparently to decrypt audio chunks during streaming.

### Caching Strategies

Many providers cache generated `StreamDetails` to avoid repeated API calls. The Yandex Music Connect implementation (lines 666-680 in [`music_assistant/providers/yandex_ynison/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/yandex_ynison/provider.py)) demonstrates this pattern:

```python

# Check cache before generating new stream details

stream_details = self._cache.get(item_id)
if stream_details is None:
    stream_details = await self._build_stream_details(item_id)
    self._cache[item_id] = stream_details
return stream_details

```

This approach reduces latency for subsequent playback requests and minimizes API rate limit consumption.

## Complete Implementation Example

Here is a minimal template that demonstrates the full implementation pattern:

```python
from music_assistant_models.streamdetails import StreamDetails, StreamType
from music_assistant_models.audio_format import AudioFormat
from music_assistant.models.music_provider import MusicProvider
from music_assistant.models.enums import MediaType

class MyProvider(MusicProvider):
    async def get_stream_details(
        self, item_id: str, media_type: MediaType
    ) -> StreamDetails:
        """Return StreamDetails for a given item."""
        # Resolve the media item

        if media_type is MediaType.TRACK:
            media = await self.get_track(item_id)
        elif media_type is MediaType.RADIO:
            media = await self.get_radio(item_id)
        else:
            raise NotImplementedError(f"Unsupported type: {media_type}")

        # Build AudioFormat

        audio_fmt = AudioFormat(
            codec=media.metadata.audio_codec,
            sample_rate=media.metadata.sample_rate,
            bit_depth=media.metadata.bit_depth,
            channels=media.metadata.channels,
        )

        # Assemble StreamDetails

        return StreamDetails(
            item_id=item_id,
            provider=self.instance_id,
            media_type=media_type,
            path=media.uri,
            duration=media.duration,
            audio_format=audio_fmt,
            stream_type=StreamType.CUSTOM,
        )

```

## Real-World Provider Examples

### YouTube Music (YTMusic)

The YouTube Music provider (lines 656-667 in [`music_assistant/providers/ytmusic/__init__.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/ytmusic/__init__.py)) builds `StreamDetails` after resolving a YouTube video URL to a direct audio stream. It handles signature deciphering and quality selection before returning the final URL.

### Yandex Music

For encrypted FLAC streams, the Yandex Music provider (lines 180-210 in [`music_assistant/providers/yandex_music/streaming.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/yandex_music/streaming.py)) includes decryption keys in the `data` field. This allows the streaming pipeline to decrypt content on-the-fly while maintaining the integrity of the original encrypted source.

### ZVuk Music

A minimal implementation appears in [`music_assistant/providers/zvuk_music/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/zvuk_music/provider.py) (lines 709-724), where the provider returns a simple `StreamDetails` containing only the `path` and `duration` fields, relying on the framework to infer other audio parameters from the stream headers.

## Summary

- **Implement `get_stream_details`** in your `MusicProvider` subclass to enable custom streaming for your media items.
- **Resolve items** using provider-specific APIs like `get_track()` or `get_radio()` before building the response.
- **Populate `AudioFormat`** with accurate codec, sample rate, bit depth, and channel information.
- **Use `StreamType.CUSTOM`** when returning provider-specific URLs rather than standard HTTP streams.
- **Cache results** when possible to improve performance and reduce API load.
- **Store encryption keys** in the `data` dictionary for protected content.
- **Preserve `item_id` consistency** throughout the playback lifecycle for proper statistics tracking.

## Frequently Asked Questions

### What should I return if the stream URL is temporarily unavailable?

If the stream URL is unavailable but the item exists, return a `StreamDetails` object with `path=None`. The controller will attempt to fall back to standard HTTP streaming or handle the error gracefully. Alternatively, raise `MediaNotFoundError` if the item itself cannot be resolved.

### How do I handle different audio qualities or bitrates?

Resolve the highest available quality in your `get_stream_details` implementation, or implement logic to select based on user preferences before constructing the `AudioFormat` object. Store the selected quality parameters in the `audio_format` field so the player knows what to expect.

### Can I reuse StreamDetails objects for the same item?

Yes, caching is recommended. Store `StreamDetails` instances in a provider-specific cache keyed by `item_id`, as demonstrated in the Yandex Music Connect implementation (lines 666-680). This avoids repeated API calls and reduces latency for subsequent playback requests, though ensure cache expiration aligns with URL validity periods for signed URLs.

### What is the difference between StreamType.CUSTOM and other stream types?

`StreamType.CUSTOM` indicates that the provider supplies a direct URL or file path requiring custom handling, while other stream types might indicate standard HTTP streaming or local file serving. Set `StreamType.CUSTOM` in your `StreamDetails` when you provide a specific URL that the framework should use directly rather than constructing its own.