How to Implement get_stream_details for Music Providers in Music Assistant
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 (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, every music provider must implement this asynchronous method:
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:
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:
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:
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) demonstrates this pattern:
# 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:
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) 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) 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 (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_detailsin yourMusicProvidersubclass to enable custom streaming for your media items. - Resolve items using provider-specific APIs like
get_track()orget_radio()before building the response. - Populate
AudioFormatwith accurate codec, sample rate, bit depth, and channel information. - Use
StreamType.CUSTOMwhen 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
datadictionary for protected content. - Preserve
item_idconsistency 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →