feat: SPA playback troubleshooting screen + status/subtitles/stream-selectors endpoints (#145) #199

Merged
timothy merged 12 commits from feat/145-troubleshooting-spa into main 2026-07-09 08:21:04 +02:00
12 Commits
Author SHA1 Message Date
timothyandClaude Fable 5 63983244ab fix(web): channel-mode Play sent ffmpegProfile=0 (runtime/spec key drift, #198)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m16s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 13s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m16s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Live E2E caught what unit tests could not: the runtime JSON from GET
/api/channels/{id} is Newtonsoft-cased ('ffmpegProfileId'), while the
generated OpenAPI types say 'fFmpegProfileId' (STJ naming) — so the
channel-mode default-profile read was undefined, the form held null, and
buildPlaybackUrl fell back to ffmpegProfile=0. The unit-test channel mock was
typed from the same wrong generated key, so mocks conformed to the spec, not
the server.

- Read the runtime key first via a typed escape hatch, fall back to the spec
  key, then to the first profile — never 0 (see #198 for the root spec-
  generation fix; ChannelEditScreen's same latent bug is #198's scope).
- buildPlaybackUrl now refuses to build a URL with a null profile, and Play
  is additionally gated on ffmpegProfileId != null.
- streamSelectorMode/streamSelector verified against live runtime JSON: no
  leading acronym, keys match the generated types — noted in a comment.
- Channel mock now pins the RUNTIME shape (ffmpegProfileId), and a new
  channel-mode Play test asserts ffmpegProfile=2 + channel=7 + ISO start in
  the URL (web suite 384 -> 385).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 08:04:24 +02:00
timothyandClaude Fable 5 1ae8facaab docs(spa-conventions): document HlsPlayer playToken prop
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 07:53:03 +02:00
timothyandClaude Fable 5 284b65a936 docs: #145 review nits — media-sample gating decision, entry-point note
- decisions.md: Download Media Sample is gated while a session is active in
  the SPA (Blazor only gated Download Results) — intentional deviation.
- blazor-route-parity.md: playback row notes entry points are the channels
  table + movie detail page only for now (other media kinds need a hand-built
  ?mediaItem= URL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 07:52:35 +02:00
timothyandClaude Fable 5 58a3bb1a2b fix(web): repeat Play + startup race in playback troubleshooting
Review MUST-FIX + SHOULD-FIX (#145):

- Repeat Play with unchanged settings was fully broken: the manifest GET is
  what starts a session server-side, but an identical URL made setPlayerSrc a
  state no-op, so HlsPlayer never re-attached and no request was issued —
  while the poll saw the previous session's stored result and toasted a
  spurious success over a dead player. HlsPlayer now takes a playToken prop
  (incremented per Play) that forces teardown/re-attach even for identical src.

- First-poll race: PrepareTroubleshootingPlayback runs DB queries BEFORE
  taking the playback lock, so polls right after Play can still see the stale
  settled result. Replaced the manually-seeded 'running' assumption with a
  play-phase machine (idle -> starting -> running -> idle): settled statuses
  are ignored during 'starting' until the server has reported running at
  least once, with a 30s timeout that surfaces an error and stops polling.
  Play + downloads are gated and a Starting/Transcoding indicator shows for
  the whole busy window.

Nits: mediaItem takes precedence over channel when both query params are
present (matching Blazor); on-mount poll comment notes the intentional
post-restart log recovery. Tests: repeat-play re-issues loadSource, stale
settled ignored until running observed, starting timeout, download gating
while busy, both-params precedence (web suite 378 -> 384).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 07:52:27 +02:00
timothyandClaude Fable 5 6e2e0e1858 feat(api): optional refresh param on GET /api/graphics-elements
Review SHOULD-FIX (#145): the SPA graphics picker could go stale because
Blazor ran RefreshGraphicsElements (disk->DB sync) before listing, while the
API endpoint never refreshed — a newly added .yml would not appear. GET
/api/graphics-elements?refresh=true now sends RefreshGraphicsElements before
the list query; default false leaves existing callers untouched. The playback
troubleshooting screen passes refresh=true. Controller tests cover
refresh-iff-true ordering; regenerated OpenAPI v1.json (endpoint index and
generated TS schemas unchanged - query params are not part of either).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 07:52:14 +02:00
timothyandClaude Fable 5 add5d4c11d docs: record #145 SPA playback troubleshooting parity, decisions, HLS convention
- blazor-route-parity.md: move /system/troubleshooting/playback from Section 3
  (BLAZOR-ONLY) to Section 2 (SPA-ready); Section 3 now has no blocking rows.
- decisions.md: poll-based completion feedback (no SPA push channel) and the
  datetime-local vs Chronic deviation.
- spa-conventions.md: new HlsPlayer reusable-component + test-mocking note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 00:03:46 +02:00
timothyandClaude Fable 5 6202886b8f feat(web): playback troubleshooting screen (#145)
Ports Blazor Pages/Troubleshooting/PlaybackTroubleshooting.razor to the SPA at
/app/troubleshooting/playback. Driven by ?mediaItem={id} (media mode) or
?channel={id} (channel mode); neither shows a friendly empty state (no redirect).

- Channel mode: FFmpeg profile (channel's preselected), stream selector, and a
  datetime-local Date/Time (deviation from Blazor's Chronic natural-language
  parse) sent as the m3u8 'start' ISO param.
- Media mode: subtitle (mutually exclusive with stream selector), watermark +
  graphics-element multi-select (repeated watermark/graphicsElement id params),
  Start From Beginning (disabled for RemoteStream) + Seek Seconds.
- Play builds playback.m3u8, plays via HlsPlayer, and polls
  /api/troubleshoot/playback/status every 2s; on running->completed/failed it
  stops polling, shows the log tail + a colored speed badge, and a completion
  notice. Polls once on mount to reflect a session running elsewhere.
- Entry points: enable the Channels table Troubleshoot action and add a
  Troubleshoot Playback button to the movie detail page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 00:03:39 +02:00
timothyandClaude Fable 5 40299f5ee6 feat(api): SPA wrappers for troubleshoot playback stream-selectors/subtitles/status
Adds thin client wrappers (+ URL-building tests) for GET
/api/troubleshoot/playback/{stream-selectors,subtitles/{id},status}, used by
the playback troubleshooting screen (#145).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 00:03:26 +02:00
timothyandClaude Fable 5 6d9619a3bc feat(web): add hls.js and reusable HlsPlayer component
Adds hls.js as a web dependency and a small HlsPlayer wrapper that attaches
hls.js when MSE is available (config mirrors the legacy _Host.cshtml
previewChannel: liveDurationInfinity + unbounded manifest time-to-first-byte,
since the troubleshooting playback.m3u8 endpoint blocks until segments exist)
and falls back to native HLS on Safari. Tears down the instance on src change
and unmount.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 00:03:21 +02:00
timothyandClaude Fable 5 8ada776324 test(troubleshoot): cover new troubleshooting playback endpoints
Add controller tests for stream-selectors, subtitles (404 + mapping), and
the status endpoint's idle/running/completed/failed transitions (using the
real status store), plus a unit test for the notification handler. Add the
subtitles 404 case to the OpenAPI ProblemDetails contract test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 23:46:41 +02:00
timothyandClaude Fable 5 5815e4b437 feat(api): add troubleshooting stream-selectors, subtitles, and status endpoints
Add three GET endpoints to TroubleshootController for the SPA port of the
playback troubleshooting page:
- /api/troubleshoot/playback/stream-selectors (List<string>)
- /api/troubleshoot/playback/subtitles/{mediaItemId} (404 pre-check via
  GetMediaItemInfo; maps SubtitleViewModel -> TroubleshootingSubtitleResponseModel)
- /api/troubleshoot/playback/status (TroubleshootingPlaybackStatusResponseModel:
  idle/running/completed/failed + exitCode/speed + logs.txt tail)

Regenerate v1.json, endpoint-index.md, and the web API types.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 23:46:35 +02:00
timothyandClaude Fable 5 cf33af4572 feat(troubleshoot): add troubleshooting playback status store
Add a singleton ITroubleshootingPlaybackStatusStore (Core, alongside
TroubleshootingNotifier) that records the exit code + speed of the most
recent troubleshooting playback session. A new MediatR notification
handler writes to it on PlaybackTroubleshootingCompletedNotification, and
PrepareTroubleshootingPlaybackHandler resets it when a new session starts
(both the channel and media-item lock paths).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 23:46:25 +02:00