* test(jellycompat): implement ListProgressSince on userstore test fake
PR #258 added ListProgressSince to the userstore.UserStore interface but
missed the progressCountingStore stub, leaving the jellycompat test build
broken on main. Stub it with the same panic("unused") the sibling methods
use.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jellycompat): keep still-playing direct-play sessions in the activity view
An Infuse Static=true direct play skips PlaybackInfo, so its playback
reports carry a client-generated PlaySessionId. handlePlaybackReport only
resolved reports by exact PlaySessionId, so every report silently no-op'd:
the admin Activity View position froze, nothing refreshed the upstream
session's activity, and stale cleanup reaped the session ~45s in while the
client kept playing. Once reaped, ensureUpstreamPlayback early-returned the
dangling session id forever, so the stream card never came back.
Fix all three legs:
- handlePlaybackReport falls back to the same token-scoped route lookup the
stream path uses (ItemId, then MediaSourceId) when the PlaySessionId is
unknown, and revives a reaped upstream session when a progress report
proves the client is still playing.
- HandleVideoStream marks an in-flight media transport around direct-play
and remux serving, mirroring the native stream handler, so long-lived
range transfers keep the session alive without progress reports.
- ensureUpstreamPlayback verifies the upstream session still exists before
reusing it, recreating it under the same play session when it was reaped.
FindByRoute now compares RouteItemID with UUID normalization so report ids
match regardless of client dash/case formatting.
Closes#244
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jellycompat): harden report fallback and upstream revive per adversarial review
Address three review findings on the issue #244 fix:
- Deterministic report binding: the stream path now records the client's
own PlaySessionId (Static=true direct play never learns the server id)
as an alias on the play session it binds, and playback reports resolve
by that alias before falling back to the ambiguous item/source route
scan. A Stopped report that only matched by bare route no longer tears
the session down (it may not own it when the same item plays twice
under one token); stale cleanup owns that path.
- Same-method revive closes any transcode still keyed to the reaped
upstream id before recreating the session, so a second ffmpeg cannot
start alongside an orphaned one.
- Attaching a new upstream session is now a compare-and-swap on the
observed upstream id: the loser of a concurrent revive/range-request
race stops its session and adopts the winner instead of leaving an
orphan counted against the user's stream limits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jellycompat): guard alias ambiguity and method switches in upstream attach
Second-round adversarial findings on the issue #244 fix:
- A client PlaySessionId alias now only resolves when it identifies
exactly one live session for the token, and the candidate must agree
with the report's ItemId/MediaSourceId. A reused or stale client id
degrades to the route scan (progress still lands on the right item)
instead of binding an arbitrary session, and an ambiguous Stopped
report tears nothing down.
- A CAS loser only adopts the concurrent winner when it serves the same
play method; a concurrent method switch surfaces 409 Conflict instead
of continuing with mismatched transcode bookkeeping.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jellycompat): key audio-selection updates by the resolved play session id
Review follow-up (PR #262): after an alias or route fallback,
req.PlaySessionID is the client's own generated id and not a playback
store key, so the audio-track-change branch in handlePlaybackReport
silently dropped selection updates for Static direct-play sessions.
Use the resolved playSession.ID for the store mutation and logging.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>