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
EventTypeenum 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
PlayerStateorMediaItemobjects
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
EventCallBackTypesignatures to receiveMassEventobjects containingevent,object_id, anddataattributes. - Register listeners via
Mass.subscribe()orregister_event()inmusic_assistant/mass.py, passing anEventTypeenum 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_idfor entity identification andevent.datafor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →