# How the PlayerQueuesController Manages Playback Queues in Music Assistant

> Discover how the PlayerQueuesController manages music playback queues in music-assistant/server. Learn about lifecycle persistence, API manipulation, and real-time processing.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: internals
- Published: 2026-06-17

---

**The PlayerQueuesController in the music-assistant/server repository handles every aspect of playback queue management, from lifecycle persistence and API manipulation to real-time playback processing and error recovery.**

The `PlayerQueuesController` is the central orchestrator for all playback queues in Music Assistant. Located in [`music_assistant/controllers/player_queues/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/player_queues/controller.py), this component creates and maintains `PlayerQueue` instances for every registered player, exposes a comprehensive API for queue manipulation, and drives the actual playback flow by translating high-level commands into low-level player instructions.

## Queue Lifecycle and Persistence

The controller automatically manages the creation and destruction of queue instances. When a player registers with the system, `on_player_register` (lines 10-30) instantiates a new `PlayerQueue` and attempts to restore its previous state from the cache.

The restoration process reads from two cache categories: `CACHE_CATEGORY_PLAYER_QUEUE_STATE` and `CACHE_CATEGORY_PLAYER_QUEUE_ITEMS`. Using `PlayerQueue.from_dict` and `QueueItem.from_cache`, the controller rebuilds the queue exactly as it existed before the player disconnected. When a player disappears, `on_player_remove` (lines 32-57) executes a complete cleanup, cancelling pending timers, removing cache entries, and purging internal dictionaries to prevent memory leaks.

## Queue Manipulation API

All public queue operations are exposed through `@api_command` decorators, providing a consistent interface for enqueueing, reordering, and configuring playback behavior.

**Key manipulation methods include:**

- **`play_media`** – Resolves media into `QueueItem` instances and initiates playback (lines 24-48)
- **`move_item`** – Repositions items within the queue using absolute or relative shifts (lines 57-90)
- **`clear`** – Removes all items from the queue while preserving settings (lines 37-51)
- **`set_shuffle`** – Toggles shuffle mode and triggers re-shuffling of existing items (lines 95-122)
- **`set_repeat`** – Configures repeat mode (off, one, all) for the queue (lines 124-136)
- **`set_playback_speed`** – Adjusts playback speed with time-base correction (lines 75-84)
- **`save_as_playlist`** – Exports the current queue to a permanent playlist (lines 54-73)

## Playback Processing Engine

The core playback flow follows a strict pipeline: `play_media` → `_handle_play_media` → `play_index`. The `_handle_play_media` method is decorated with `@handle_play_action` to serialize concurrent play requests and prevent race conditions.

When `play_index` executes (lines 84-94), it sets `queue.index_in_buffer`, generates a fresh `session_id`, and attempts to load the target item via `_load_item`. If the item fails to load (e.g., `MediaNotFoundError`), the controller automatically selects the next playable index using `_get_next_index` and retries up to five times. Upon successful loading, the controller calls `self.mass.players.play_media` to dispatch the actual command to the underlying player, then updates `queue.current_index`, `queue.current_item`, and emits a UI update via `signal_update`.

Navigation commands (`next`, `previous`, `skip`, `seek`) update the queue state immediately, then schedule a debounced `play_index` call to handle the actual transition (lines 73-81, 93-101). For pause, stop, and resume operations, the controller cancels pending timers, adjusts `queue.resume_pos` and `queue.elapsed_time`, and invokes internal handlers (`_handle_cmd_pause`, `_handle_cmd_stop`) to avoid circular redirects between the queue and player controllers (lines 23-33, 84-92).

## Handling Dynamic Queues and Radio Sources

The controller supports two advanced playback modes that require continuous queue management.

**Radio Mode:** When `dont_stop_the_music_enabled` is active and the queue approaches depletion, the controller treats the current track list as a radio source. The `_fill_radio_tracks` method periodically queries the music provider for similar tracks and appends them to the queue, ensuring uninterrupted playback.

**Flow Mode:** For live streaming providers, `on_player_elapsed_time_corrected` (lines 97-125) reconciles the difference between media time and stream time. Using the `get_current_playback_speed` helper from [`music_assistant/controllers/player_queues/helpers.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/player_queues/helpers.py), it keeps `queue.elapsed_time` accurate even when the player reports stream-relative positions, essential for variable-speed playback and precise seeking.

## Concurrency and Safety Mechanisms

The controller implements several safeguards to prevent race conditions during state transitions.

The `@handle_play_action` decorator maintains a reference count (`_play_action_refcount`) that prevents overlapping play actions on the same queue. Before executing any state-changing command, the controller cancels pending timers to avoid stale `play_index` calls.

During track transitions, the controller tracks transitioning players in `_transitioning_players` and temporarily ignores player-reported updates to prevent state corruption. This ensures that `queue.current_index` remains synchronized with the actual audio being heard, even when network latency or provider delays occur.

## Practical Implementation Examples

The following examples demonstrate common queue operations using the Music Assistant API:

```python

# Enqueue a single track and start playback immediately

await mass.player_queues.play_media(
    queue_id="myplayer",
    media="spotify:track:6rqhFgbbKwnb9MLmUQDhG6",
    option=QueueOption.PLAY,
)

# Replace the current queue with an album, shuffle it, and start playing

await mass.player_queues.play_media(
    queue_id="myplayer",
    media="spotify:album:1ATL5GLyefJaxhQzSPVrLX",
    option=QueueOption.REPLACE,
    radio_mode=False,
    sort_by="random",  # Controller handles shuffling internally

)

# Skip forward 30 seconds in the current track

await mass.player_queues.skip(queue_id="myplayer", seconds=30)

# Change repeat mode to repeat a single track

await mass.player_queues.set_repeat(
    queue_id="myplayer", 
    repeat_mode=RepeatMode.ONE
)

# Pause playback, then resume with a fade-in after 2 seconds

await mass.player_queues.pause(queue_id="myplayer")
await asyncio.sleep(2)
await mass.player_queues.resume(queue_id="myplayer", fade_in=True)

# Move the third item up by one position

await mass.player_queues.move_item(
    queue_id="myplayer", 
    queue_item_id="item-3", 
    pos_shift=-1
)

# Transfer the entire queue from one player to another

await mass.player_queues.transfer_queue(
    source_queue_id="livingroom",
    target_queue_id="bedroom",
    auto_play=True,
)

```

## Summary

- **Centralized Management:** The `PlayerQueuesController` in [`music_assistant/controllers/player_queues/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/player_queues/controller.py) serves as the single source of truth for all playback queue state, handling everything from player registration to queue persistence via `CACHE_CATEGORY_PLAYER_QUEUE_STATE`.
- **Robust Playback Flow:** Playback processing uses a serialized pipeline (`play_media` → `_handle_play_media` → `play_index`) with automatic retry logic for failed tracks and strict concurrency controls via `@handle_play_action`.
- **Dynamic Content Support:** The controller automatically refills queues using radio sources when `dont_stop_the_music_enabled` is active, and accurately tracks elapsed time for variable-speed playback using `on_player_elapsed_time_corrected`.
- **Safety Mechanisms:** Race conditions are prevented through reference counting (`_play_action_refcount`), timer cancellation, and transition tracking (`_transitioning_players`), ensuring the queue state remains synchronized with actual player behavior.

## Frequently Asked Questions

### How does the PlayerQueuesController handle corrupted or missing media files?

When `play_index` encounters a `MediaNotFoundError` or other loading failure, it automatically calls `_get_next_index` to select the next playable item and retries up to five times. This ensures that a single missing track does not halt playback, allowing the queue to continue with available content while updating `queue.current_index` accordingly.

### What happens to my queue when a player disconnects and reconnects?

The controller persists queue state using `CACHE_CATEGORY_PLAYER_QUEUE_STATE` and `CACHE_CATEGORY_PLAYER_QUEUE_ITEMS`. When the player re-registers via `on_player_register`, it reconstructs the `PlayerQueue` using `PlayerQueue.from_dict` and `QueueItem.from_cache`, restoring your exact position, shuffle state, and track list from the [`music_assistant/models/player_queue.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player_queue.py) data model.

### Can I transfer an active queue between different players?

Yes, the `transfer_queue` method allows you to move the entire queue state—including the current index, playback position, and all items—from one player to another. When `auto_play=True` is specified, playback resumes immediately on the target player without interruption, making it seamless to move music from one room to another.

### How does the controller prevent conflicts when multiple commands are issued simultaneously?

The `@handle_play_action` decorator serializes concurrent play actions using an internal reference count (`_play_action_refcount`). This ensures that `play_media`, `next`, `previous`, and other state-changing operations execute atomically, preventing race conditions that could corrupt the queue state or cause playback errors during rapid successive commands.