A television raised the whole OSD and timebar on every playback start. The
chrome controller is born visible, and its auto-hide clock cannot arm until the
first frame lands, so the controls did not merely appear early: they appeared
exactly when the picture did, and then sat over the opening five seconds of
every movie and episode. The timeline is gated behind the first frame, so the
bar materialised on top of the video rather than over the loading spinner,
which is what makes it read as a pop-up rather than as chrome that was already
there.
The route now opens with no chrome on TV. Nothing is lost: the loading spinner
and buffering overlay are their own overlays, the screen focus node owns back,
and the first D-pad press raises the controls the way it already does after
every auto-hide. Pointer and touch platforms keep the chrome, where the
viewer's hand is on the surface and the title and back affordance belong over
the spinner.
Initial presentation now follows initial visibility. They were separate:
seeding only visibility would leave the route claiming its chrome was still
presented, so PlayerNavigationCoordinator would read back as "hide the chrome",
hide() would no-op against chrome that was never up, and the press would be
swallowed instead of leaving the player.
Controls that mount with the chrome already down now claim focus themselves.
Focus normally reaches them through the hide transition, and their own
autofocus cannot win it back because the screen node took it during the loading
phase. Left alone, the screen node kept primary focus and its self-heal raised
the entire OSD on the first D-pad press, which put the chrome straight back
over the picture and bypassed the transient seek and transport indicators.
Both player spinners now carry a label. They were bare progress indicators, so
a screen reader announced nothing at all while the picture was coming up, and
the TV Maestro flows had no way left to tell a loading player from a playing
one once the Pause button stopped appearing on its own.
The two TV flows are repaired to match. They waited on that button, and now
wait for the labelled spinner to clear, which cannot happen before the media is
opened. 05 additionally reaches Search by D-pad rather than a percentage
coordinate, because a tap flips InputModeTracker to pointer mode and collapses
the rail it is aiming at, and it gates on the play-next prompt's own Cancel
action: "Next Episode" is also the credits skip button, so the old assertion
could pass without the prompt ever opening.
close#1765
Bare Backspace and Home are player navigation keys, but they are also
caret editing keys. The player screen's Focus wraps its OverlaySheetHost,
so it saw them before the subtitle-search field could act: the press was
consumed on key-down, DefaultTextEditingShortcuts never turned it into a
deletion, and the back pipeline hid the chrome and then left the player.
A focused text editor now takes both keys back, but only for physical
keyboard presses — a synthesized dpad/gamepad press has no caret, and
browserHome has no editing role at all.
The screen also resolved its overlay-sheet controller from the State's
own context, which sits above the host it was querying, so the lookup
always returned null and Back skipped the sheet stage entirely. Resolve
it from a context below the host instead, matching NowPlayingScreen.
close#1741
Pressing pause or seeking while the player's on-screen controls were hidden raised
the entire OSD, covering the subtitles the viewer was rewinding to read. Transport
keys now answer with a transient indicator and leave the chrome down; Select,
D-pad Center and a centre tap remain the deliberate way to bring the controls
back.
Play/pause confirms with an icon-only translucent disc at the centre of the frame,
72px around a 44px glyph, which grows and fades in, holds half a second at rest,
then runs the same motion in reverse. Seeking shows the amount plus a single
chevron on the same line at the edge it travels toward, with no backdrop at all:
anything large enough to read as a surface is large enough to cover picture and
subtitles, so legibility comes from shadows instead. Only the chevron moves, and
it eases outward across most of its cycle and returns briefly, holding a visible
opacity floor rather than blinking out. Type is scaled per platform, since a
television is read from across the room. The existing text pill stays for genuine
notices - rate changes, chapter titles, zoom, errors - because an earlier centred
pill overlapped ASS \an8 subtitle placement, which is the readability complaint
this feedback exists to answer.
Every relative seek entry point now shares one coalescing primitive. The keyboard
shortcuts fell through to KeyboardShortcutsService and previously reported
nothing, and both they and the remote's chapter fallback rebased each press off
player.state.position, so a burst against a slow backend pinned every request near
one step while the indicator climbed to a total that was never committed. A
released key commits its pending target immediately and resets the acceleration
tier, including on live TV where seeks bypass the accumulator. A chapter seek with
nowhere to go, past the last chapter or already at the start, no longer announces a
jump it does not perform.
Rewind-on-resume follows the resolved intent rather than the current state, so a
directed pause on an already-paused video neither resumes nor rewinds. Indicators
carry their own liveRegion semantics nodes: their labels previously merged into the
full-screen "show playback controls" target, corrupting its accessible name, and
they keep announcing "Paused"/"Playing" and the seek amount from icon-only visuals.
close#1676
Consolidates duplicated logic behind shared implementations — paginated
grid tabs, focus chrome, cached remote stores, sheet selection columns,
the server artifact store and a test fixture layer — and removes code
that had become unreachable. Net reduction of about 5,500 lines with no
behaviour change.
Where a fix had landed separately in code that moved into a shared
helper, the fix was re-applied inside the helper rather than left behind
in the copy that went away.
Collapse duplicated setup across the suite into six shared helpers under
test/test_helpers/ and rewrite the 28 suites that were open-coding it:
http_fixtures.dart jsonResponse() for http.Response JSON stubs
library_tab_scaffold.dart pumps library tabs under their required ancestors
multi_server_fixtures.dart MultiServerProvider wiring for widget tests
playback_report_fakes.dart PlaybackReportCall + fake report sinks
profile_stack.dart production-shaped profile dependency graph
theme.dart testMonoTokens for fast-settling widget tests
Net -1245 lines with no change in coverage or assertions.
Use Flutter's supported desktop application-exit API so the second root Back press closes Plezy on Windows. Preserve fullscreen when video player navigation is enabled while keeping fullscreen-first Escape behavior for normal desktop use.
close#1582
* fix(plex): show PGS subtitles when transcoding
Bitmap subtitles (PGS/VOBSUB) disappeared from the menu while transcoding on Plex, leaving only "Off". The MKV transcode already supports them, so embed them in the stream like text subs. Now they show up and can be turned on.
* fix(plex): give bitmap subtitle codecs the right sidecar extension
getSubtitleExtension fell back to .srt for vobsub, pgssub and dvb subs. Map them to .sub/.sup so it stays consistent with isImageSubtitleCodec and any sidecar URL or download uses the correct extension.