Music Assistant Flow Mode vs Gapless Playback: How the Server Handles Continuous Audio Streaming

Music Assistant uses native gapless playback when your player supports track stitching, but falls back to Flow Mode—where the server concatenates tracks into a single PCM stream—when hardware lacks gapless capabilities or when crossfade is required.

The music-assistant/server repository distinguishes between two approaches for continuous audio playback. Gapless playback delegates track transitions to the player hardware, while Flow Mode processes the entire queue server-side into one continuous stream. Understanding when each activates helps optimize audio quality and resource usage in your multi-room setup.

What Is Gapless Playback in Music Assistant?

Gapless playback occurs when the player device itself stitches the next audio frame to the previous one without silence. In the Music Assistant architecture, this happens when a player reports supports_gapless == True through the Player model defined in music_assistant/models/player.py.

When gapless playback is active:

  • Music Assistant sends each track individually to the player
  • The device handles all transition logic natively
  • No server-side audio processing or resampling occurs
  • Crossfade capabilities depend entirely on player hardware

This approach minimizes server CPU usage and preserves bit-perfect audio when the player supports it.

What Is Flow Mode and When Does It Activate?

Flow Mode is a server-side fallback that creates a single continuous audio stream from the entire playback queue. The system reads tracks sequentially, optionally resamples them, and concatenates them into one PCM stream sent to the player.

Flow Mode automatically activates in three scenarios:

  1. The player returns True from requires_flow_mode() (indicating no native gapless support)
  2. Crossfade is requested but the player cannot handle different sample rates between tracks
  3. The user explicitly forces Flow Mode via configuration

The Decision Logic in the Stream Controller

The core determination happens in music_assistant/controllers/streams/controller.py. The controller evaluates multiple conditions to set the flow_mode boolean:


# music_assistant/controllers/streams/controller.py

crossfade_needs_flow_mode = (
    not self.config.get_value(CONF_CROSSFADE_DIFFERENT_SAMPLE_RATES, False)
    and not protocol_player.supports_gapless
)
flow_mode = (
    force_flow_mode
    or (protocol_player is not None and protocol_player.flow_mode)
    or crossfade_needs_flow_mode
)

When flow_mode evaluates to True, the controller passes the queue to controllers/streams/audio.py to build the continuous stream. The system also maintains a debug log of individual tracks in queue.flow_mode_stream_log for troubleshooting.

Sample Rate Handling Strategies

Flow Mode requires the entire queue to share a single sample rate. The behavior is controlled by the flow_mode_sample_rate configuration entry defined in music_assistant/constants.py.

The available strategies include:

  • Smart (default): Starts with the first track's sample rate, upsamples lower-rate tracks, and restarts the flow when encountering a higher-rate track
  • Bit-perfect: Never resamples; playback restarts whenever sample rates differ (disables gapless and crossfade)
  • Fixed rates (48 kHz / 96 kHz): Forces all audio to the specified rate using high-quality resamplers
  • Highest: Resamples to the maximum sample rate the player supports

The configuration is read in controllers/streams/audio.py (lines 1206-1238):


# music_assistant/controllers/streams/audio.py

flow_mode_conf = player.config.get_value(
    CONF_FLOW_MODE_SAMPLE_RATE, FLOW_MODE_SAMPLE_RATE_SMART
)

How Player Capabilities Determine the Mode

Each player implementation in music_assistant/providers/*/player.py defines a requires_flow_mode() method thatsignals whether the hardware needs server-side stream concatenation.

The requires_flow_mode Property

Player providers override this method to indicate their gapless capabilities:

Amplipi (lacks native gapless support):


# music_assistant/providers/amplipi/player.py

def requires_flow_mode(self) -> bool:
    return True

MSX Bridge (handles per-track playback natively):


# music_assistant/providers/msx_bridge/player.py

def requires_flow_mode(self) -> bool:
    return False

The stream controller queries this property via protocol_player.flow_mode to determine whether to enable Flow Mode automatically.

Configuring Flow Mode Behavior

End users control Flow Mode through the "Enable queue flow mode" toggle in the UI. This setting corresponds to translation keys in music_assistant/translations/en.json under common.config_entries.flow_mode.

When enabled, this setting forces Flow Mode regardless of the player's native capabilities, bypassing the automatic supports_gapless detection.

Practical Implementation Examples

Enabling Flow Mode via API

You can programmatically enable Flow Mode for a specific player using the configuration API:


# Example: turn on flow mode for player "living_room_speaker"

await mass.players.set_config_value(
    player_id="living_room_speaker",
    key="flow_mode",
    value=True,
)

This updates the per-player configuration file, which the stream controller reads during playback initialization.

Forcing Flow Mode for Single Requests

For custom services or one-off streams, you can bypass the capability check entirely:


# Example: play a URL, forcing flow mode

await mass.streams.queue_uri(
    uri="https://example.com/track.mp3",
    player_id="kitchen_speaker",
    force_flow_mode=True,
)

The force_flow_mode=True parameter (see line 1028 in controller.py) overrides automatic detection and forces the controller to build a continuous stream.

Debugging with Flow Mode Logs

When troubleshooting continuous playback issues, inspect the internal track log:


# After playback, retrieve the per-track log

log = await mass.player_queues.get_queue("living_room_speaker")
print(log.flow_mode_stream_log)   # list of dicts with track metadata

This flow_mode_stream_log property is populated in controllers/streams/audio.py (line 2064) whenever Flow Mode is active, showing exactly which tracks were concatenated and their metadata.

Summary

  • Gapless playback delegates track stitching to the player hardware when supports_gapless is True, minimizing server processing
  • Flow Mode concatenates tracks server-side into a single PCM stream when requires_flow_mode() returns True or when crossfade is needed
  • The stream controller in music_assistant/controllers/streams/controller.py automatically selects the appropriate mode based on player capabilities and configuration
  • Sample rate handling in Flow Mode offers five strategies from bit-perfect to fixed 96 kHz resampling
  • You can force Flow Mode via the UI toggle or API calls when specific use cases require server-side stream processing

Frequently Asked Questions

What is the difference between Flow Mode and gapless playback in Music Assistant?

Gapless playback relies on the player device to stitch tracks together without silence, while Flow Mode has the Music Assistant server concatenate tracks into a single continuous stream before sending them to the player. Flow Mode is automatically used when the player lacks native gapless support or when crossfade is required between tracks with different sample rates.

How do I know if my player is using Flow Mode or native gapless playback?

Check the player's requires_flow_mode() implementation in its provider file under music_assistant/providers/. If this method returns True, Music Assistant automatically enables Flow Mode. You can also inspect the flow_mode_stream_log property of the queue after playback to see if the server was concatenating tracks.

Why does Flow Mode require all tracks to use the same sample rate?

Because Flow Mode creates a single continuous PCM stream, the audio format must remain consistent throughout the entire queue. The server handles this through the flow_mode_sample_rate configuration, which can either resample all tracks to a common rate (Smart, 48kHz, 96kHz, or Highest) or restart the stream when rates differ (Bit-perfect).

Can I force Flow Mode even if my player supports gapless playback?

Yes. Enable the "Enable queue flow mode" toggle in the player configuration UI, or use the API call mass.players.set_config_value(player_id="your_player", key="flow_mode", value=True). You can also pass force_flow_mode=True to individual stream requests like queue_uri() for one-time enforcement.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →