How to Subscribe to MassEvent Callbacks in Music Assistant Server

Use the Mass.subscribe() method (or its alias register_event()) in 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, where the Mass class maintains an internal event registry. The framework defines EventCallBackType as a union of callable types:

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, 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

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

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

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

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.

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, 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. 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).

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 →