IEventBus
The event bus is the connective tissue between the player, its plugins, and your application code.
Every on(), once(), off(), and emit() call inside the core goes through EventEmitter, the default implementation.
You almost certainly never swap this out.
It is wired internally before setup() runs and is not a setup() option.
The only realistic reason to replace it is if you are embedding the player inside a reactive store system (Vuex, Pinia, Redux) and want all events to flow through that store’s own event mechanism instead.
In that case you implement IEventBus and hand it to the player constructor directly.
import { EventEmitter } from '@nomercy-entertainment/nomercy-player-core';
import type { IEventBus } from '@nomercy-entertainment/nomercy-player-core/adapters/event-bus';
Built-in adapter
EventEmitter
A plain Map<event, Set<handler>> emitter.
Both player classes extend it, so calling player.on(...) is literally calling EventEmitter.on(...) on the player instance.
Plugins access it through this.on / this.emit, which delegate to the same player-scoped instance.
A few design details worth knowing:
- Registering the same function reference twice for the same event adds it only once, the underlying
Setdeduplicates. once()stores a wrapper in aWeakMapkeyed on the original handler reference, sooff(event, originalFn)can find and remove the wrapper even though the caller never touched it.emit()snapshots the listener set before iterating, so a handler callingoff()mid-dispatch does not mutate the live iteration.- Each listener is wrapped in a try/catch so one bad handler cannot abort the rest of the dispatch chain.
on()(andoff()) fire a microtask-delayedlisteners-changedevent when an event gains its first listener or loses its last, useful for lazy-load logic.
The __eventMap__ phantom field on EventEmitter is a compile-time brand used by the generic type inference machinery.
It is never assigned at runtime.
Interface
interface IEventBus<E extends Record<string, any> = Record<string, any>> {
on<K extends keyof E>(event: K, fn: (data: E[K]) => void): void;
on(event: string, fn: (data: any) => void): void;
once<K extends keyof E>(event: K, fn: (data: E[K]) => void): void;
once(event: string, fn: (data: any) => void): void;
off<K extends keyof E>(event: K, fn?: (data: E[K]) => void): void;
off(event: 'all'): void;
off(event: string, fn?: (data: any) => void): void;
emit<K extends keyof E>(event: K, data?: E[K]): void;
emit(event: string, data?: any): void;
hasListeners<K extends keyof E>(event: K): boolean;
hasListeners(event: string): boolean;
listenerCount(): number;
}
off('all') removes every listener across all events.
The player calls this during disposal to prevent handler leaks.
listenerCount() returns the total live listener count across all events, which the testing leak harness uses to assert that plugins clean up after themselves across use-then-dispose cycles.
When you’d replace it
You probably won’t.
This port has no setup() config key because the core constructs the bus before setup() runs.
If you do need to replace it, the typical case is routing all player events through an existing pub/sub or store-event system so you can observe them in one place without adding a separate subscriber.
Implement IEventBus<E> with the store’s publish/subscribe primitives, pass it to the player constructor, and the rest of the core keeps calling this.emit without knowing anything changed.
import type { IEventBus } from '@nomercy-entertainment/nomercy-player-core/adapters/event-bus';
class StoreEventBus<E extends Record<string, any>> implements IEventBus<E> {
on(event: any, fn: (data: any) => void): void {
myStore.subscribe(String(event), fn);
}
once(event: any, fn: (data: any) => void): void {
const wrapped = (data: any) => {
myStore.unsubscribe(String(event), wrapped);
fn(data);
};
myStore.subscribe(String(event), wrapped);
}
off(event: any, fn?: (data: any) => void): void {
if (event === 'all') {
myStore.unsubscribeAll();
return;
}
myStore.unsubscribe(String(event), fn);
}
emit(event: any, data?: any): void {
myStore.publish(String(event), data);
}
hasListeners(event: any): boolean {
return myStore.subscriberCount(String(event)) > 0;
}
listenerCount(): number {
return myStore.totalSubscriberCount();
}
}
See also
- Adapters, the full port catalog
- Lifecycle Registry, the other internal resource plugins depend on