The device cache was filled once at construction, so a token minted by one
replica was unknown to every other replica already running and a token revoked
on one replica stayed valid on the others until they restarted.
- drop the eager device and device options dictionaries
- read devices and device options from the database on every query
- push the device query filters and ordering into SQL
- open the request's database context only for the api key fallback
- cover both directions against real PostgreSQL with two manager instances
Cleanup never matched a live session because the controller registered empty
manifest and segment paths, renewal was a read-modify-write that could revert a
takeover, and the takeover script compared an ISO date to a number, so it errored.
- populate the session record's manifest and segment paths from the playlist path
- renew the lease via a Lua compare-and-set on the owning pod
- store the lease expiry as unix milliseconds so the scripts can compare it
- retain the session record past its lease so an orphan can still be taken over
- test the Redis store against a real Redis, including the renew-vs-takeover race
- drop the live stream record nothing ever read back
Live stream ownership is only tracked in process memory, so no replica can tell
which pod holds a stream open once that pod is gone.
- persist a LiveStreamSession record when the live stream mappings change
- delete the record when the stream is closed
- keep closing the stream when the store is unreachable
Some clients omit AudioStreamIndex or SubtitleStreamIndex in playback progress reports and it causes previously saved track selections to be erased.
Add .HasValue checks so only explicit track changes are persisted.
When a stalled HLS client sends ReportPlaybackStopped with a negative
PositionTicks (e.g. HLS.js reporting position on buffer stall), the
ArgumentOutOfRangeException was thrown before GetSession was called,
causing CloseLiveStreamIfNeededAsync to never run. This left the
ILiveStream.ConsumerCount permanently incremented, permanently holding
a tuner open with no active viewer.
Fix by acquiring the session and performing live stream cleanup before
throwing the validation exception, so tuner resources are always freed
even when playback reporting fails with invalid position data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit integrates remaining performance changes:
- Add batch user data fetching in DtoService to reduce N+1 queries
- Add GetNextUpEpisodesBatch in TVSeriesManager for efficient batch retrieval
- Update Video/Movie/BoxSet to use LibraryManager for alternate versions
- Transition LinkedChild to use ItemId instead of Path (obsolete Path/LibraryItemId)
- Update providers and controllers for LinkedChildren-based references
- Add NextUpEpisodeBatchResult for batched episode queries
- Integrate IDescendantQueryProvider in SqliteDatabaseProvider
Fixed possible NullReferenceException in SessionManager
Original-merge: 3592c629e7
Merged-by: crobibero <cody@robibe.ro>
Backported-by: Joshua M. Boniface <joshua@boniface.me>