Skip to content

Event reference

The Phlix server publishes a small, typed set of PSR-14 events. Plugins subscribe to these by class FQCN (or by their manifest alias once the Phase A.4 plugin loader lands) and react to playback, library scans, and auth lifecycle.

This is the canonical catalog. The doc-generator tool added in a later phase reads it; reviewers cross-check it against every class under src/Common/Events/. Add a row here whenever a new event class is added to the codebase.

How dispatch works

Phlix uses Crell\Tukio as its PSR-14 implementation. The container exposes three relevant bindings:

Container IDPurpose
Psr\EventDispatcher\EventDispatcherInterfaceThe dispatcher. Inject this to publish events.
Phlix\Common\Events\ListenerRegistryFacade for subscribing listeners.
Phlix\Common\Events\EventDispatcherFactoryBuilds the dispatcher; rarely needed by application code.

When the environment variable PHLIX_DEBUG_EVENTS is truthy (1 / true / yes / on) every dispatched event is logged at debug level on the events channel (.logs/events.log by default).

How to subscribe

php
use Phlix\Common\Events\ListenerRegistry;
use Phlix\Shared\Events\Playback\PlaybackStarted;

/** @var ListenerRegistry $registry */
$registry = $container->get(ListenerRegistry::class);

$registry->subscribe(
    PlaybackStarted::class,
    function (PlaybackStarted $event): void {
        // do something with $event->userId, $event->mediaItemId, …
    },
    priority: 10,   // optional — higher runs first
);

To unsubscribe later (e.g. when a plugin is disabled), call $registry->unsubscribe(PlaybackStarted::class, $sameCallable). Calling unsubscribe() on a callable that was never subscribed (or is already inactive) emits a warning on the events log channel but does not throw — clean plugin disable cycles matter more than strict bookkeeping.

Event catalog

Event FQCNManifest aliasPayload fieldsFired byTypical listener
Phlix\Shared\Events\Playback\PlaybackStartedphlix.playback.startedsessionId, userId, mediaItemId, deviceId, positionTicksPhlix\Session\PlaybackController::reportProgress() on first progress for a pairScrobble plugins (Trakt, Last.fm), analytics, Discord rich presence, watch-history
Phlix\Shared\Events\Playback\PlaybackPausedphlix.playback.pausedsessionId, userId, mediaItemId, deviceId, positionTicksPhlix\Session\PlaybackController::reportProgress() when status flips to pausedScrobble plugins (mark stopped), now-playing dashboards, AFK presence
Phlix\Shared\Events\Playback\PlaybackResumedphlix.playback.resumedsessionId, userId, mediaItemId, deviceId, positionTicksPhlix\Session\PlaybackController::reportProgress() when status flips to playingScrobble plugins (restart), dashboards, "movie mode" integrations
Phlix\Shared\Events\Playback\PlaybackStoppedphlix.playback.stoppedsessionId, userId, mediaItemId, deviceId, finalPositionTicks, reachedEndPhlix\Session\PlaybackController::markAsWatched() / clearProgress()Scrobble plugins (final), watch-history complete markers, recommendation refreshers
Phlix\Shared\Events\Library\LibraryScanStartedphlix.library.scan.startedlibraryId, libraryName, pathPhlix\Media\Library\MediaScanner::scan() at the top of the walkProgress dashboard, scan-started notifier, maintenance-window coordinator
Phlix\Shared\Events\Library\LibraryScanCompletedphlix.library.scan.completedlibraryId, itemsAdded, itemsUpdated, itemsRemoved, durationMsPhlix\Media\Library\MediaScanner::scan() after the walk completesWebhook notifier, dashboard refresher, recommendation cache invalidator
Phlix\Shared\Events\Library\MediaItemAddedphlix.library.item.addedmediaItemId, libraryId, path, typePhlix\Media\Library\MediaScanner::processFile() after a new item is persistedMetadata-refresh queue worker, "what's new" notifier, intro-detection job queuer
Phlix\Shared\Events\Library\MediaItemUpdatedphlix.library.item.updatedmediaItemId, changedFields[]Metadata-refresh writes in Phlix\Media\Library\ItemRepository (wired later)Search-index re-indexer, recommendation cache invalidator, third-party mirrors
Phlix\Shared\Events\Library\MediaItemRemovedphlix.library.item.removedmediaItemId, libraryIdCleanup passes in ItemRepository / MediaScanner (wired later)Search-index cleaner, "file is gone" notifier, watch-history archiver
Phlix\Shared\Events\Auth\UserCreatedphlix.user.createduserId, username, emailPhlix\Auth\AuthManager::register() after the user row is persistedWelcome-email sender, audit-log writer, default-permissions bootstrap
Phlix\Shared\Events\Auth\UserLoggedInphlix.user.logged_inuserId, sessionId, ipAddress, userAgentPhlix\Auth\AuthManager::login() after credential verification succeedsPresence integrations, security-anomaly detector, device-registry updater
Phlix\Shared\Events\Auth\UserLoggedOutphlix.user.logged_outuserId, sessionId, reason (explicit / expired / revoked)Phlix\Auth\AuthManager::logout() plus token-revocation paths (wired later)Presence integrations, audit-log writer, "session revoked" notifier, hub mirror

Every event also exposes the int $timestamp field (UNIX seconds at construction) inherited from Phlix\Shared\Events\AbstractEvent.

Note on manifest aliases. PSR-14 dispatch in Phlix is keyed by event class FQCN — string topics are not used internally. The "manifest alias" column documents the string identifier that Phase A.4's plugin loader will map to the FQCN when a plugin manifest declares "events": ["phlix.playback.started"]. Application code outside plugin manifests should always use the FQCN form.

BSD-3-Clause