# How to Subscribe to MassEvent Callbacks in Music Assistant Server

> Subscribe to MassEvent callbacks in Music Assistant Server using the Mass.subscribe() method to receive real-time player updates and playback changes directly from the server.

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

---

**Use the `Mass.subscribe()` method (or its alias `register_event()`) in [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py) to register callbacks that receive `MassEvent` objects whenever the Music Assistant server emits events like player updates or media playback changes.**

The music-assistant/server repository implements a robust event-driven architecture centered around the `Mass` core class. When you need to react to system changes—such as player state updates or media item modifications—you subscribe to MassEvent callbacks using the built-in event bus. This mechanism allows both synchronous functions and asynchronous coroutines to handle real-time events emitted throughout the Music Assistant ecosystem.

## Understanding the MassEvent Architecture

### The EventBus Implementation in mass.py

The subscription logic resides in [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py), where the `Mass` class maintains an internal event registry. The framework defines `EventCallBackType` as a union of callable types:

```python
EventCallBackType = Callable[[MassEvent], None] | Callable[[MassEvent], Coroutine[Any, Any, None]]

```

This type alias accepts both standard functions returning `None` and coroutine functions returning `Coroutine[Any, Any, None]`.

### MassEvent Data Structure

As defined in [`music_assistant_models/event.py`](https://github.com/music-assistant/server/blob/main/music_assistant_models/event.py), each `MassEvent` instance contains three critical attributes:

- **event**: The `EventType` enum value (e.g., `EventType.PLAYER_UPDATED`, `EventType.MEDIA_ITEM_PLAYED`)
- **object_id**: String identifier for the affected entity (player ID, media item ID, queue ID)
- **data**: Optional payload containing event-specific information such as `PlayerState` or `MediaItem` objects

## How to Subscribe to MassEvent Callbacks

### Using the subscribe() Method

The primary API for registering listeners is `Mass.subscribe(event_type, callback)`. The method accepts an `EventType` enum member and a callback conforming to `EventCallBackType`, returning a remover callable that unregisters the listener when invoked.

### Synchronous vs Asynchronous Handlers

The event bus automatically detects whether your callback is a coroutine function using `inspect.iscoroutinefunction()`. Async callbacks are awaited in the event loop, while synchronous callbacks are executed directly. This design ensures thread-safe operations within the `Mass` instance's event loop.

## Practical Code Examples

### Basic Event Subscription

```python
from music_assistant_models.event import EventType, MassEvent
from music_assistant import Mass

def handle_player_update(event: MassEvent) -> None:
    """React to player state changes."""
    player_id = event.object_id
    state = event.data
    print(f"Player {player_id} volume level: {state.volume}")

# Assuming mass is your initialized Mass instance

mass.subscribe(EventType.PLAYER_UPDATED, handle_player_update)

```

### Async Callback Implementation

```python
import asyncio
from music_assistant_models.event import EventType, MassEvent

async def on_media_played(event: MassEvent) -> None:
    """Handle async operations when media finishes playing."""
    report = event.data
    await asyncio.sleep(0)  # Safe to await within the callback

    print(f"Playback completed for: {report.title}")

mass.subscribe(EventType.MEDIA_ITEM_PLAYED, on_media_played)

```

### Managing Listener Lifecycles

```python
def on_player_added(event: MassEvent) -> None:
    print(f"New player registered: {event.object_id}")

# Store the remover function to unsubscribe later

remove_listener = mass.subscribe(EventType.PLAYER_ADDED, on_player_added)

# When the component shuts down or no longer needs updates:

remove_listener()

```

### Using the register_event Alias

```python
mass.register_event(
    EventType.PLAYER_REMOVED, 
    lambda ev: print(f"Removed player: {ev.object_id}")
)

```

## Advanced Implementation Details

### Thread Safety and Execution Context

Callbacks execute within the event loop that owns the `Mass` instance. This guarantees that async APIs remain safe to use within coroutine callbacks, and that shared state mutations remain consistent across the application. The framework handles the event loop dispatching internally in [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py).

### Multiple Listener Support

The event registry supports multiple callbacks per event type, invoking them in registration order. This allows different subsystems—such as providers, web frontends, and automation engines—to independently subscribe to MassEvent callbacks for the same event without interference.

## Summary

- **Implement callbacks** using `EventCallBackType` signatures to receive `MassEvent` objects containing `event`, `object_id`, and `data` attributes.
- **Register listeners** via `Mass.subscribe()` or `register_event()` in [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py), passing an `EventType` enum and your handler function.
- **Support both sync and async** callbacks; the event bus automatically detects coroutines and awaits them appropriately.
- **Unsubscribe** by calling the remover function returned during registration to prevent memory leaks.
- **Access event metadata** through `event.object_id` for entity identification and `event.data` for payload-specific information.

## Frequently Asked Questions

### What is the difference between subscribe() and register_event()?

These methods are identical aliases defined in [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py). Both accept an `EventType` and callback function, returning a remover callable that deregisters the specific listener. Use whichever naming convention matches your codebase's architectural style; they invoke the same underlying registration logic.

### Can I use async functions as MassEvent callbacks?

Yes. The `EventCallBackType` union explicitly includes `Callable[[MassEvent], Coroutine[Any, Any, None]]`. When the event bus detects an async callback using `inspect.iscoroutinefunction()`, it awaits the coroutine within the `Mass` instance's event loop, allowing you to perform asynchronous I/O operations, database queries, or API calls safely.

### How do I unsubscribe from a MassEvent callback?

The `subscribe()` method returns a remover function that, when called, removes the specific callback from the internal registry for that event type. Store this remover when you register the listener and invoke it when your component shuts down or no longer requires updates to clean up resources.

### What data does the MassEvent object contain?

Each `MassEvent` instance provides three attributes: `event` (the `EventType` enum such as `PLAYER_UPDATED` or `MEDIA_ITEM_PLAYED`), `object_id` (the entity identifier such as a player ID or queue ID), and `data` (the payload varies by event type and may contain `PlayerState`, `MediaItem`, or progress report objects).