Both Player and individual Sources expose events namespace, and behind it is a dynamic EventBus.

These EventBus components are dynamic - meaning any package could extend it to dispatch additional events on it.

export type EventBus<EventMap extends { [key: string]: object }> = {
  on<Name extends keyof EventMap>(name: Name, listener: EventListener<EventMap[Name]>): void;
  off<Name extends keyof EventMap>(name: Name, listener: EventListener<EventMap[Name]>): void;
  dispatch<Name extends keyof EventMap>(
    context: EventBusContext,
    name: Name,
    event: EventMap[Name] & Partial<EventBase>,
  ): void;
};

The API is pretty standard - on the outside, this means that we are able to access it simply and add/remove our listeners, similar to any DOM listeners.

function onSourceAdded(event) {
	console.warn('SourceAdded', event);
}
// Adding
player.events.on('source-added', onSourceAdded);
// Removing
player.events.off('source-added', onSourceAdded);