Skip to content

Real-Time Sync

Librariarr keeps a live WebSocket connection to each enabled media server, so it reacts to what happens on your servers within seconds instead of waiting for the next scheduled poll.

This runs alongside — not instead of — the scheduled sync. Scheduled full syncs remain the source of truth and reconciliation backstop; real-time events just make the app react sooner between them.

Capability Without real-time With real-time
Active sessions view Refreshes every 5s Pushes within ~2s of a play/pause/stop
Maintenance / blackout / transcode enforcement New streams seen up to 30s late Enforcers run as soon as a stream changes (at most once every 2s), so the termination delay starts promptly
New / removed / updated media Appears on the next scheduled sync Syncs just the changed/removed items seconds after the server’s scan settles
Watch state (played / unplayed, last watched) Refreshed on the next full sync Refreshed shortly after a play finishes — on Plex, by fetching only the plays since the last refresh
Sidebar stream count Polled every 5 minutes Pushed when a stream starts or stops
Server-unreachable banner Appears when the 5-minute session poll finds the server unreachable Appears as soon as the connection drops

Watch state comes from your server’s server-wide history, so a play by anyone in the household counts — not just the account Librariarr signs in as. Each refresh updates the Play Count and Last Played values that rules and queries read, so the History page and your rules stay in agreement.

On Plex the refresh is incremental: it asks the server only for plays recorded since the newest one already stored (with an hour of overlap, so nothing falls through the gap) and appends the new ones. The full import — which re-reads the whole history and is what removes plays the server has since deleted — still runs on the scheduled sync and when you press Refresh on the History page. Jellyfin and Emby report a per-user “played” set rather than dated play events, so their refresh always re-reads the full set.

Real-time library changes carry the specific items that changed, so Librariarr fetches and updates only those items (and deletes the removed ones) instead of re-scanning the whole server. Adding one movie syncs one movie, and deleting one episode removes one row. An item reported both removed and added (or updated) in the same batch is kept when the server still has it. The sync starts once the server has sent no library change for 5 seconds (so a scan that adds many items is applied as one batch, at most 5 minutes after it began) and then takes well under a second for a handful of items.

Only item-level events drive a sync. Servers also announce that a scan or metadata refresh finished, without saying what (if anything) changed; those announcements are ignored, because the only thing they could ever justify is re-scanning everything, and they fire constantly whether or not any media actually changed. Every real change — additions, updates and deletions alike — is reported at the item level, so nothing is lost. The scheduled full sync remains the periodic backstop for anything a push channel never reports at all.

Only changes that are actually library media are applied. Three kinds are skipped:

  • Containers — a Plex collection, a Jellyfin/Emby box set, a playlist. Plex names the type of every changed item, so its collections and playlists are dropped before anything is fetched at all; a Jellyfin/Emby box set is recognised once fetched. Either way a container can never become a phantom item.
  • The levels above the item that changed — adding one episode makes a server report the episode, its season and its show as changed. A series library stores episodes (and a music library stores tracks), so only the episode is applied; the show and season would otherwise appear as phantom rows until the next full sync cleared them.
  • Anything outside a library section — a movie’s extras, trailers and featurettes belong to no library, so there is nothing to file them under. Plex reports these on every add: one added movie was announced alongside 27 of them.

Items in a library you have disabled are recognised and skipped, exactly as the full sync skips them.

Librariarr’s own writes are recognised and ignored. Keeping a rule set’s matches in a Plex collection is, to Plex, a metadata change on the collection and on every item it tags — so a detection run that refreshes a 150-item “Leaving Soon” shelf is announced back over the notification socket as 150 changed movies. Nothing Librariarr stores changes when an item joins or leaves a collection, so those announcements are dropped rather than re-fetched or, past the 100-item limit, turned into an off-schedule full sync. Only the items Librariarr itself just wrote are affected, only on that server, and only for a couple of minutes after the write; a deletion is never dropped this way. The system log records how many were ignored (Ignored N library change(s) … echo librariarr's own Plex collection writes).

A full sync is still requested when the real-time path genuinely can’t do the job: a change too large to apply item-by-item (100+ at once), an item naming a library section Librariarr has no record of (a new library — only a full sync can create one), a transient error reaching the server, or a full sync already running. An item that simply can’t be placed in any library is skipped and logged rather than escalated.

Jellyfin and Emby do not label an item with its library the way Plex does, so a newly added item there is placed by asking the server for the item’s parent chain and taking the library in it. Adding an item to Jellyfin or Emby is therefore incremental too; before this every add on those servers fell back to a full sync.

Every full sync — scheduled, manual, real-time or lifecycle-triggered — logs what started it on its Starting sync for server … — triggered by … line in System → Logs, so a sync that appears off-schedule can always be traced to its cause.

The WebSocket connections above run on the server. A second, separate channel keeps the pages you have open current: the browser holds one Server-Sent Events stream to Librariarr, and the app pushes an event down it whenever something changes that a page might be showing.

This is why the UI updates on its own. A sync finishing, a lifecycle rule matching, an action executing, a server being added, maintenance mode being switched on, watch history being imported — each of these tells every open tab to refresh just the data it affects, instead of leaving you looking at stale figures until you reload.

What that means in practice:

  • Progress is pushed, not polled. A running sync reports each committed batch, so progress bars advance without the page asking every couple of seconds. The same applies to a Tracearr history import, which can run for hours.
  • Every way a sync can end is pushed. Finished, failed or stopped with Stop Sync, every open tab updates as soon as it happens, and only after the sync’s changes are visible, so a refreshed list never shows the data from before the sync. Only one background job runs at a time, so a sync you start while something else is running waits its turn — it appears as Queued on Settings → Media Servers the moment you press Sync (or Sync All). A sync you asked for goes ahead of queued background work, and a Tracearr history import that is mid-slice pauses at its next page to let it start, then carries on afterwards. The Sync button on a library page queues one sync per library, even when that server is already syncing: they run as soon as the current one finishes, Settings → Media Servers shows the server as Queued meanwhile, and the button keeps spinning until every library has synced. Pressing Stop Sync on a queued sync cancels it straight away; on a server with a sync running and others queued it stops all of them, including libraries that have not started yet.
  • Stop Sync on a running sync takes effect at the sync’s next checkpoint: between batches of items, between libraries, or between stages. A watch-history import stops at its next page. The only step that can’t be interrupted is downloading a Plex, Jellyfin or Emby server’s own watch history, so a stop pressed then takes effect once that download finishes.
  • Changes reach every tab. Toggling maintenance mode, adding a server, or executing a pending action updates every window you have open, not just the one you did it in.
  • Only the affected data is refetched. Events carry no figures of their own — a page receiving one re-reads the endpoint that owns the number. That keeps a single source of truth for every value on screen, and stops a play import from forcing a full re-read of your library.

All three server types are supported, and each connected server gets its own independent connection — including multiple servers of the same type (several Plex, Jellyfin, or Emby instances all connect in parallel).

Server Endpoint
Plex /:/websockets/notifications (play, library timeline)
Jellyfin /socket (sessions, LibraryChanged, UserDataChanged)
Emby /embywebsocket (same protocol as Jellyfin)
  • Self-healing. If a connection drops, Librariarr reconnects automatically with exponential backoff (1s up to 60s). On reconnect it re-reads current state so nothing is missed.
  • Debounced and deduplicated. A library scan that emits hundreds of change events is coalesced — the changed item IDs are accumulated, deduplicated and applied in a single incremental pass; a burst of playback progress events is throttled so your server isn’t hammered. A server typically reports the same item several times as it walks it through scanning, analysing and loading; that is one change, not several. If the job queue cannot take the sync (the database briefly unreachable, say), the changes are kept and tried again once the server has been quiet for another few seconds.
  • Reconciled. Adding, editing, enabling/disabling, or removing a server immediately opens or closes its connection. Changing a server’s URL, token or certificate setting reconnects it, and first applies any library or watch changes it had already reported. A safety re-check also runs every minute. A re-check that cannot read the database leaves every connection and every change still to apply as it is, and the next one tries again — so changes reported during a database outage are applied once it ends.

Real-time sync is on by default. Toggle it in Settings → General → Display → Real-time server sync.

Turn it off to rely on scheduled polling only. This is useful when a reverse proxy in front of your server doesn’t allow WebSocket upgrades — in that case the connection would fail and reconnect in the background, and disabling it avoids the churn.

While real-time sync is on, the toggle has one line under it for each enabled server, showing that server’s connection:

Status Meaning
Connected The WebSocket is open and events are arriving.
Connecting… Librariarr is opening the connection, or trying again after it dropped. A server you have just added or turned on shows this until its first connection.
Not connected The last attempt failed. Librariarr keeps retrying (every minute at most), and scheduled syncs still run.

The lines update as soon as a connection opens or drops. A server whose WebSocket fails while normal sync still works is almost always behind a reverse proxy that doesn’t forward WebSocket upgrades.