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 New streams seen within ~½s, 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 30s Pushed when a stream starts or stops
Server-unreachable banner Appeared when a 30s poll failed 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 — typically in well under a second.

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.
  • 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.
  • Reconciled. Adding, editing, enabling/disabling, or removing a server immediately opens or closes its connection. A safety re-check also runs every minute.

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.