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>
- 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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>