Subscribe to the Sessions WebSocket instead of polling #7

Closed
opened 2026-08-10 08:03:29 +00:00 by aiko · 2 comments
Owner

Parent

#1 — PRD: Kurageyomi

What to build

Replace the 2s poll of /Kurageyomi/api/Sessions with Jellyfin's session push.

Confirmed in use: pause and unpause resume with up to two seconds of lag, because the client
reports the change immediately but the phone only learns about it on its next poll. The clock
itself is right — this is purely how late the news arrives.

Jellyfin pushes session state over its WebSocket. A client sends
{"MessageType":"SessionsStart","Data":"0,1000"} and the server sends Sessions
messages on that interval (BasePeriodicWebSocketListener, which parses the Data field as
dueTimeMs,periodMs), stopping on SessionsStop.

Worth knowing before building it: pushing more often than every ten seconds does not get a
fresher Playhead while playing — the server reports whatever the client last sent, and clients
throttle progress to 10s. What the push buys is that pause, unpause and seeks beyond ~5s of
drift are reported immediately, so those arrive in one round trip instead of up to a poll
interval late. That is exactly the lag being felt.

Keep polling as a fallback for when the socket drops; do not let a dead socket silently freeze
the phone on a stale position.

Acceptance criteria

  • The phone subscribes to the session push and stops polling while the socket is up
  • Pause and unpause are reflected in about one round trip rather than up to a poll interval
  • A seek is reflected as promptly
  • The socket dropping falls back to polling rather than freezing on the last state
  • Reconnecting resumes the push without duplicate subscriptions
  • The parsing of pushed session state is tested against a recorded message

Blocked by

None - can start immediately

## Parent #1 — PRD: Kurageyomi ## What to build Replace the 2s poll of `/Kurageyomi/api/Sessions` with Jellyfin's session push. Confirmed in use: pause and unpause resume with up to two seconds of lag, because the client reports the change immediately but the phone only learns about it on its next poll. The clock itself is right — this is purely how late the news arrives. Jellyfin pushes session state over its WebSocket. A client sends `{"MessageType":"SessionsStart","Data":"0,1000"}` and the server sends `Sessions` messages on that interval (`BasePeriodicWebSocketListener`, which parses the Data field as `dueTimeMs,periodMs`), stopping on `SessionsStop`. Worth knowing before building it: pushing more often than every ten seconds does **not** get a fresher Playhead while playing — the server reports whatever the client last sent, and clients throttle progress to 10s. What the push buys is that pause, unpause and seeks beyond ~5s of drift are reported immediately, so those arrive in one round trip instead of up to a poll interval late. That is exactly the lag being felt. Keep polling as a fallback for when the socket drops; do not let a dead socket silently freeze the phone on a stale position. ## Acceptance criteria - [ ] The phone subscribes to the session push and stops polling while the socket is up - [ ] Pause and unpause are reflected in about one round trip rather than up to a poll interval - [ ] A seek is reflected as promptly - [ ] The socket dropping falls back to polling rather than freezing on the last state - [ ] Reconnecting resumes the push without duplicate subscriptions - [ ] The parsing of pushed session state is tested against a recorded message ## Blocked by None - can start immediately
Author
Owner

This was generated by AI during triage.

Implemented and deployed — awaiting verification, do not rebuild.

Landed in main: web/src/sessionSocketState.ts (pure state machine, 19 tests), the socket plumbing in main.ts, and toPlaybackSessionFromSessionInfo in wire.ts.

Three protocol facts corrected during the work, all now in docs/websocket-notes.md:

  • BasePeriodicWebSocketListener has no timerIntervalMs is only a floor between event-driven sends, and nothing is pushed in response to SessionsStart. The first state still comes from REST after subscribing.
  • The query parameter is ApiKey. Lowercase api_key only works while EnableLegacyAuthorization is enabled.
  • The push carries Jellyfin's SessionInfoDto (nested PlayState.*), not our flat DTO — hence the separate mapper.
  • ForceKeepAlive must be answered. Ignoring it does not close the socket in 10.11.11, but drops the connection from the watchlist, so pushes stop while the socket still looks alive.

Remaining to close this: confirm on the device that pause/unpause is now near-instant, and that DevTools → Network → WS shows a 101 on /socket. If the socket never subscribes, the polling fallback means behaviour is unchanged from before rather than broken.

> *This was generated by AI during triage.* **Implemented and deployed — awaiting verification, do not rebuild.** Landed in `main`: `web/src/sessionSocketState.ts` (pure state machine, 19 tests), the socket plumbing in `main.ts`, and `toPlaybackSessionFromSessionInfo` in `wire.ts`. Three protocol facts corrected during the work, all now in `docs/websocket-notes.md`: - `BasePeriodicWebSocketListener` has **no timer** — `IntervalMs` is only a floor between event-driven sends, and **nothing is pushed in response to `SessionsStart`**. The first state still comes from REST after subscribing. - The query parameter is **`ApiKey`**. Lowercase `api_key` only works while `EnableLegacyAuthorization` is enabled. - The push carries Jellyfin's **`SessionInfoDto`** (nested `PlayState.*`), not our flat DTO — hence the separate mapper. - `ForceKeepAlive` must be answered. Ignoring it does not close the socket in 10.11.11, but drops the connection from the watchlist, so pushes stop while the socket still looks alive. Remaining to close this: confirm on the device that pause/unpause is now near-instant, and that DevTools → Network → WS shows a `101` on `/socket`. If the socket never subscribes, the polling fallback means behaviour is unchanged from before rather than broken.
Author
Owner

Shipped in ddce440web/src/sessionSocketState.ts follows sessions over the WebSocket with polling as the fallback. Closing.

Shipped in ddce440 � `web/src/sessionSocketState.ts` follows sessions over the WebSocket with polling as the fallback. Closing.
aiko closed this issue 2026-08-10 09:27:18 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mobrule/kurageyomi#7
No description provided.