forked from Foxyz/sync_replay_project
Player: quick chat box, danmaku notifications, the player panel, smooth subtitle lift
- Quick chat (Enter in the full player, whatever plays): a box at the bottom centre in the control bar's place, compact and widening with the text, sharing its draft with the chat panel; Enter sends, Esc or an empty Enter closes, it closes by itself after 8 s without typing (the draft stays). Keys go to it; the Enter that opened it no longer reaches a focused button. Typing never shows the cursor or the controls: the frame wakes only on real mouse movement and holds the controls hidden while the box or its echo is up. Your sent line shows as an echo (or flies as your own danmaku line); your own chat toast is not repeated. - Subtitle lift: always for the box, for the control bar as a setting (off); 4 eased-out sub-pos steps 42 ms apart instead of one per frame. - Notifications: Toasts / Danmaku (off by default): chat and short notices fly over the full player, problems stay toasts; every knob of the look plus boldness, persisted, with a live preview strip in Settings. - Player panel: ⚙ → Player settings docks a foldable glass panel at the right (tracks, subtitle size and lift, picture, volume, notifications, controls) in place of the card; Esc closes it. - Danmaku preview: the mock dialogue follows the lift; the stand-in page has the chat box too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6048fbe7d1
commit
69fae18fdc
29 files changed
+2755
-581
No files matched your search
+121
-15
@@ -600,7 +600,7 @@ fails every seek until a fresh open, so the coordinator runs "Reload
|
||||
video" once per media for it (`mpvStreamLost`, `onStreamLost`).
|
||||
|
||||
Output size ("Sharp video and subtitles", `AppSettings.screenResVideo`,
|
||||
on by default, live; Settings → Player and the player settings card):
|
||||
on by default, live; Settings → Player and the player panel):
|
||||
media_kit renders mpv into a texture the size of the video and Flutter
|
||||
scales it, so libass subtitles, mpv's scaler and the shaders all work on
|
||||
a 720p image for a 720p file. With the setting on and the GPU renderer
|
||||
@@ -2121,7 +2121,8 @@ panel) and chat messages (Copy message, and a link in it).
|
||||
Jellyfin & integrations (linked servers, AnimeFlow's `/me` via
|
||||
`AnimeFlowClient.fromAccounts`, "Play the next episode automatically":
|
||||
whether this app adds the continuation, see "What's next"), notifications & sounds
|
||||
(what pops up, chat pop-ups, joins and leaves while sitting out or in
|
||||
(what pops up, chat pop-ups, toasts or danmaku over the player with the
|
||||
danmaku look, joins and leaves while sitting out or in
|
||||
the background), updates, shortcuts, about.
|
||||
- **Drop and paste** (`handoff.dart`, `fox_utils`): the shell is one
|
||||
`DropTarget` (a "Drop to open" overlay while dragging) and a
|
||||
@@ -2218,14 +2219,28 @@ panel) and chat messages (Copy message, and a link in it).
|
||||
and the members card says "can't play this: ...". 34 px avatars, a main line
|
||||
and a muted detail line. The pill glass, colour only for problems; it fades through
|
||||
between kinds. The mini shows its short line in its hover bar. ⚙ →
|
||||
Player settings (both skins) opens the player settings card (`controls/
|
||||
player_settings.dart`, `showPopover`, on `AppGlass`): player design, progress line
|
||||
when hidden, upscaling shader, sharp video and subtitles (the output
|
||||
at screen size, see "Media"), hardware decoding (set live on mpv's
|
||||
`hwdec`), subtitle and audio track. The rows (`playerSettingRows`) are
|
||||
the same widgets Settings → Player shows; all apply live. Settings →
|
||||
Player also has the controls' own choices (`controlsSettingRows`, see
|
||||
"Player controls"). The progress line (`AppSettings.progressLine`) is
|
||||
Player settings (both skins) toggles the player panel
|
||||
(`controls/player_panel.dart`, `PlayerControlsModel.panelOpen`): a
|
||||
312 px `AppGlass` column docked at the right of the full player,
|
||||
between the top bar and the control bar (it never covers the bar),
|
||||
sliding and fading in from the right; it stays open while the controls
|
||||
hide and show, until its × or Esc (the player's Esc closes it first),
|
||||
and leaving the full player closes it. The mouse on it holds the
|
||||
controls up (`ControlsHoldRegion`). Its sections fold (their titles
|
||||
toggle; folded ones are kept for the app's run, Notifications and
|
||||
Controls start folded): tracks (subtitle, audio; remembered as from
|
||||
the track menus), subtitles (size, mpv `sub-scale` from
|
||||
`AppSettings.subScale`, set on every player; above the controls, see
|
||||
"Subtitle lift"), picture (upscaling shader and quality, sharp video
|
||||
and subtitles, see "Media", hardware decoding, set live on mpv's
|
||||
`hwdec`), sound (volume), notifications (Toasts / Danmaku and, with
|
||||
Danmaku, the look's knobs, see "Danmaku notifications") and controls
|
||||
(design, progress line); a pointer to Settings → Player and Mark a
|
||||
problem at its foot. Compact rows of its own over the same settings and
|
||||
actions; Settings → Player shows the full rows (`playerSettingRows`:
|
||||
design, progress line, shader, sharp video, hardware decoding, subtitle
|
||||
size, subtitles above the controls) and the controls' own choices
|
||||
(`controlsSettingRows`, see "Player controls"); all apply live. The progress line (`AppSettings.progressLine`) is
|
||||
what the bottom controls leave while hidden: Standard a 2 px line,
|
||||
Minimal its collapsed timeline. Fullscreen adds the
|
||||
`WindowButtons` to the top bar while the controls are up (restore
|
||||
@@ -2261,7 +2276,8 @@ panel) and chat messages (Copy message, and a link in it).
|
||||
- **Chat** (`chat_panel.dart`): a 340 px panel sliding in at the right
|
||||
(strip button or Tab): chat with avatars; the "Activity" chip (off by
|
||||
default, `showActivity`, kept for the app's run only) adds the other
|
||||
feed lines between them. Opening focuses the field; Tab or Esc in it
|
||||
feed lines between them. Its field shares the quick chat box's draft
|
||||
(see "Quick chat"). Opening focuses the field; Tab or Esc in it
|
||||
close the panel and focus goes back where it was. Unread chat badges
|
||||
the strip's and the player controls' chat buttons; in fullscreen with
|
||||
the controls down and the video not playing a glass chip
|
||||
@@ -2279,7 +2295,96 @@ panel) and chat messages (Copy message, and a link in it).
|
||||
are up and come down to 16 px from the edge while they are hidden
|
||||
(`AnimatedPositioned`, the shell's `ease`; the player reports its
|
||||
controls through the shell's `controlsShown`); elsewhere 16 px from the
|
||||
bottom left. What pops up at all is the Notifier's policy.
|
||||
bottom left. What pops up at all is the Notifier's policy. Each
|
||||
`ToastItem` carries its `NoticeCategory` and whether it is your own
|
||||
(`own`); the shell leaves out those flying as danmaku and your own chat
|
||||
coming back for a line the quick chat box already showed
|
||||
(`PartyToasts.skip`).
|
||||
- **Quick chat** (`shell/quick_chat.dart`): Enter in the full player
|
||||
(not the mini: it is a corner thumbnail while you are elsewhere in the
|
||||
app, where Enter belongs to that page; nor while sitting out) opens a
|
||||
small box at the bottom centre, 16 px up, in the control bar's place,
|
||||
without opening the chat panel. Enter is a `HardwareKeyboard` handler
|
||||
of the player, so it works whatever plays and whatever button kept
|
||||
focus; a `FocusManager` early handler swallows that Enter so the
|
||||
focused button does not act on it too. `QuickChat` (the shell's, it
|
||||
outlives the player's modes) holds the state: the draft is the chat
|
||||
panel's `TextEditingController` (typing in either shows in both, a send
|
||||
from either clears both), its `FocusNode` takes focus at once (keys
|
||||
typed right after go to the box; `textInputFocused` keeps Space, Tab,
|
||||
F and the arrows off the player meanwhile), and it closes by itself
|
||||
after `idleClose` (8 s) without typing, Esc, or an empty Enter, keeping
|
||||
the draft for the next Enter. Enter with text (an input method's
|
||||
composing Enter excepted) sends it (`sendChat`) and closes. The box
|
||||
(`QuickChatBox`) starts 300 px wide and widens with the text up to
|
||||
640 px (or the room there is), then wraps up to four lines, growing
|
||||
upward; your avatar leads it; it fades and slides in on the shell's
|
||||
curve and stays mounted while closed (invisible, ignoring the pointer)
|
||||
so focus is immediate. What you sent shows where the box was for
|
||||
`echoTime` (2.6 s, `QuickChatEcho`, with an accent hairline), or, with
|
||||
danmaku notifications, flies at once as your own line; your own chat
|
||||
toast coming back from the server is not shown again
|
||||
(`QuickChat.echoed`). While the box or its echo is up the controls are
|
||||
held hidden (`ControlsFrame.suppress`: drawn hidden, `shown` false, the
|
||||
cursor still follows the mouse) and the action area rides above the
|
||||
box. Typing is not mouse activity: the frame wakes the controls only
|
||||
on real pointer movement (an enter or hover at the last seen position,
|
||||
as when the box comes or goes under a still pointer, does nothing), and
|
||||
the field's I-beam stays hidden until the mouse moves over it, so
|
||||
Enter, typing and sending never show the cursor or the controls.
|
||||
- **Subtitle lift** (`video_view.dart`): bottom subtitles move above what
|
||||
covers the bottom of the full player: the quick chat box or its echo
|
||||
(always), and the control bar while it is up when
|
||||
`AppSettings.liftSubtitles` is on (off by default; Settings → Player
|
||||
and the player panel, "Subtitles above the controls"); 8 px above it.
|
||||
`SubtitleLift` steps the covered height toward each new target in
|
||||
`liftStepCount` (4) eased-out steps `liftStepGap` (42 ms) apart, the
|
||||
first at once: about one step per frame of a 24 fps video, so each
|
||||
lands on its own video frame (a `sub-pos` change per screen frame
|
||||
landed unevenly on the video's frames and looked choppy).
|
||||
`SubtitleLifter` turns each step into mpv `sub-pos` (`subtitleLiftPos`:
|
||||
only as far as the letterbox under the picture leaves no room, at most
|
||||
half the picture), back to 100 when nothing covers and for a new
|
||||
player. What each mpv property does, on `tool/make_sub_test.sh`'s file
|
||||
(mpv 0.41, libmpv as the app uses it): `sub-pos` moves bottom ASS
|
||||
dialogue and SRT lines up and leaves `\an8` signs in place (with
|
||||
`sub-ass-override` at its default `scale` or `yes`; `no` ignores it
|
||||
for ASS); `sub-margin-y` moves SRT only (ASS only with
|
||||
`sub-ass-override=force`, which restyles every line, signs too);
|
||||
`sub-ass-style-overrides=MarginV=...` moves top signs down as well;
|
||||
`video-margin-ratio-bottom` shrinks and moves the picture itself (with
|
||||
`sub-ass-force-margins` the dialogue goes into the new black band).
|
||||
- **Danmaku notifications** (`danmaku/`): Settings → Notifications &
|
||||
sounds → Over the player → Notifications: Toasts (default) or Danmaku
|
||||
(`AppSettings.notifyStyle`, live; also in the player panel). With
|
||||
Danmaku and the full player up (not sitting out), what
|
||||
`fliesAsDanmaku` picks (chat, joins, leaves, lost connection and back,
|
||||
"starting now", Up next news someone caused) flies right to left
|
||||
across the picture (`DanmakuNotices` under the controls feeds new
|
||||
toasts to a `DanmakuLayer` through `danmakuForToast`); nudges,
|
||||
problems, your own state and the app's notices stay toasts, and so does
|
||||
everything with the player small. The player sets the shell's
|
||||
`danmakuActive` so the toasts leave the flying ones out. The layer:
|
||||
lanes in a band of the height (`DanmakuStyle.bandTop` / `bandBottom`,
|
||||
default 10 to 45 %, under top signs, never down into the dialogue);
|
||||
each line takes the topmost free lane (`DanmakuLanes`, pure: the last
|
||||
line in a lane must have fully entered with a gap, and the new one, at
|
||||
its own speed, cannot catch it before it leaves); at most
|
||||
`maxOnScreen` fly, the rest wait in order (dropped after 15 s, 60 at
|
||||
most); every line crosses in `crossSecs`, so long ones move faster;
|
||||
white text at the chosen weight (`weight`, 400 to 900, the name a step
|
||||
bolder) with a dark outline and no box, the sender's avatar and
|
||||
coloured name first, the app's own notices without one, own lines in
|
||||
an accent frame; the layer's own clock (lines fly on while paused),
|
||||
ticking only while lines fly or wait. Each line is a widget moved by a
|
||||
`Flow`: per frame only paint runs. The look (`AppSettings.danmakuStyle`,
|
||||
one map, `DanmakuStyle.toJson` / `fromJson`, which clamps every value
|
||||
to `DanmakuStyle.limits`) has every knob in the settings card
|
||||
(`danmaku_settings.dart`: crossing time, at most on screen, lane band,
|
||||
opacity, size, boldness, outline, avatars, frame my own lines, reset)
|
||||
over a preview strip (a moving stand-in picture, a top sign, bottom
|
||||
dialogue and sample lines in the current look; it plays for 16 s after
|
||||
it opens or a knob moves, then rests until clicked).
|
||||
|
||||
Settings persist in two Hive boxes: `connect_form` (last form, recent
|
||||
servers and links, the profile picture and colour; saved even when a join
|
||||
@@ -2399,7 +2504,8 @@ Player controls (`lib/src/controls/`): one controls model, two skins.
|
||||
mark a problem, player settings, leave. Icons, tooltips (with keys) and
|
||||
the clock text (`clockText`) are defined there too, once.
|
||||
- **Shared pieces**: `ControlsFrame` (`frame.dart`): when the controls
|
||||
are up (mouse movement shows them, `controlsHideSecs` without any or the
|
||||
are up (real mouse movement shows them, see "Quick chat"; `suppress`
|
||||
holds them hidden; `controlsHideSecs` without movement or the
|
||||
mouse leaving hides them and the cursor), holds (`hold` during a seek
|
||||
drag, `holdDuring` / `holdWhile` around menus and popovers, `holdBy`
|
||||
the session's `ControlsHold`), and the double-click on the picture
|
||||
@@ -2445,7 +2551,7 @@ part not downloaded still reads; chapter cuts are a light dark line.
|
||||
`mpvControls: true` from before becomes minimal), `timeDisplay`,
|
||||
`controlsSize` (normal / large: the bottom bar ×1.25),
|
||||
`controlsHideSecs` (2 / 3 / 5), `hiddenControls`, `progressLine`.
|
||||
The player settings card has the skin and the progress line; Settings →
|
||||
The player panel has the skin and the progress line; Settings →
|
||||
Player → Controls has the rest.
|
||||
- Pills (Skip OP / ED / 1:30, Next episode) and cards live outside the
|
||||
controls, in the action area ("Player overlay"). The cards stay up; a
|
||||
@@ -2597,7 +2703,7 @@ dedicated server (`package:logging` loggers named by area: `relay`,
|
||||
- **Opting out**: Settings → About → "Share diagnostics with the server"
|
||||
(default on). Off: no lines are kept or sent; one batch per connection
|
||||
says so (`share:false`). The local log is unchanged.
|
||||
- **Mark a problem**: Ctrl+Shift+B (`AppKeys`), the player's ⚙ card and
|
||||
- **Mark a problem**: Ctrl+Shift+B (`AppKeys`), the player panel and
|
||||
Settings → About open a small card with an optional note (200
|
||||
characters, one line); the mark goes at once with the lines waiting,
|
||||
opted out or not, and the server writes `[who] === PROBLEM marked by
|
||||
|
||||
+22
-47
@@ -85,52 +85,24 @@ video for subtitle placement and sharpness (default
|
||||
|
||||
Test builds and runs from source (a version with `-dev`) and debug runs
|
||||
(`devTools`, `screens/home/ui.dart`) get Settings → About → Developer;
|
||||
releases never show it. What is there is an experiment to look at, not
|
||||
a decided feature:
|
||||
releases never show it:
|
||||
|
||||
- **Danmaku preview** (`lib/src/danmaku/`): chat lines and short notices
|
||||
(joins, leaves, Up next adds, "starting now") flying right to left
|
||||
across the upper part of the picture. `DanmakuLayer` is the reusable
|
||||
widget: lanes in a band of the height (`DanmakuStyle.bandTop` /
|
||||
`bandBottom`, default 10 to 45 %, under top signs, never down into the
|
||||
dialogue); each line takes the topmost free lane (`DanmakuLanes`, pure:
|
||||
the last line in a lane must have fully entered with a gap, and the
|
||||
new one, at its own speed, cannot catch it before it leaves); at most
|
||||
`maxOnScreen` fly, the rest wait in order (dropped after 15 s, 60 at
|
||||
most); every line crosses in `crossSecs`, so long ones move faster;
|
||||
white text with a dark outline and no box, the sender's avatar
|
||||
(`StatusAvatar`: picture, else initial) and coloured name first, the
|
||||
app's own notices without one, own lines in an accent frame; the
|
||||
layer's own clock, so lines fly on while paused. Each line is a widget
|
||||
moved by a `Flow`: per frame only paint runs. The button opens it over
|
||||
the full player when one is up (under the controls; the panel top
|
||||
right holds them up), else on a page with a moving stand-in picture
|
||||
and a mock control bar of the skin in use. The panel (`DanmakuPanel`)
|
||||
fires samples (chat, own, join, leave, Up next, starting, a burst), a
|
||||
random stream (Poisson arrivals at a rate, short to long lines, CJK
|
||||
and emoji; in a session half the senders are the party's real
|
||||
members), this session's own chat and notice toasts, and every
|
||||
`DanmakuStyle` knob live (crossing time, cap, lane band, opacity,
|
||||
size, outline, avatars, own frame), plus mock subtitles (a top sign
|
||||
and bottom dialogue). Tests: `danmaku_test`.
|
||||
- **Lift subtitles above the chat line and controls**
|
||||
(`AppSettings.liftSubtitles`, off): Enter in the full player opens a
|
||||
stand-in for the planned chat line (a one-line box at the bottom in
|
||||
place of the control bar; Enter sends to the chat, Esc closes). While
|
||||
it is open, or the control bar is up, bottom subtitles move above it
|
||||
and back down when it goes (`SubtitleLifter`, `video_view.dart`): mpv
|
||||
`sub-pos`, animated over the shell's 320 ms curve in 0.1 steps, only as
|
||||
far as the letterbox under the picture leaves no room
|
||||
(`subtitleLiftPos`). What each mpv property does, on
|
||||
`make_sub_test.sh`'s file (mpv 0.41, libmpv as the app uses it):
|
||||
`sub-pos` moves bottom ASS dialogue and SRT lines up and leaves `\an8`
|
||||
signs in place (with `sub-ass-override` at its default `scale` or
|
||||
`yes`; `no` ignores it for ASS); `sub-margin-y` moves SRT only (ASS
|
||||
only with `sub-ass-override=force`, which restyles every line, signs
|
||||
too); `sub-ass-style-overrides=MarginV=...` moves top signs down as
|
||||
well; `video-margin-ratio-bottom` shrinks and moves the picture
|
||||
itself (with `sub-ass-force-margins` the dialogue goes into the new
|
||||
black band).
|
||||
- **Danmaku preview** (`danmaku/danmaku_preview.dart`): the danmaku layer
|
||||
of "Notifications: Danmaku" (ARCHITECTURE "Danmaku notifications") to
|
||||
play with, starting from your own look; its knobs change only the
|
||||
preview. The button opens it over the full player when one is up
|
||||
(under the controls; the panel top right holds them up), else on a
|
||||
page with a moving stand-in picture and a mock control bar of the skin
|
||||
in use. The panel (`DanmakuPanel`) fires samples (chat, own, join,
|
||||
leave, Up next, starting, a burst), a random stream (Poisson arrivals
|
||||
at a rate, short to long lines, CJK and emoji; in a session half the
|
||||
senders are the party's real members), this session's own chat and
|
||||
notice toasts (mapped as the real feature maps them), every
|
||||
`DanmakuStyle` knob, and mock subtitles (a top sign and bottom
|
||||
dialogue). The mock dialogue moves with the subtitle lift's steps as
|
||||
the real subtitles do: over the player when the quick chat box opens,
|
||||
and on the stand-in page, which has its own quick chat box (Enter;
|
||||
what you send flies as your own line). Tests: `danmaku_test`.
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -168,8 +140,11 @@ Three suites, run from each package's folder:
|
||||
the "Waiting for" status (`media_plan_test`), watch tracking and the
|
||||
device history (`watch_tracker_test`), what continues a file of a
|
||||
local show (`queue_logic_test`), the video view's output size and
|
||||
subtitle lift and the dev-tools gate (`video_view_test`), danmaku lanes
|
||||
(`danmaku_test`)), `singletons_test` (the `.instance` count, see
|
||||
subtitle lift steps and the dev-tools gate (`video_view_test`), danmaku
|
||||
lanes, style, the toasts that fly and an idle layer
|
||||
(`danmaku_test`), the quick chat box over the player (`quick_chat_test`),
|
||||
the player panel and the danmaku settings card (`player_settings_test`)),
|
||||
`singletons_test` (the `.instance` count, see
|
||||
ARCHITECTURE "Composition root"; lower its baseline when a use goes) and the real-mpv `*_e2e`
|
||||
tests plus `coord_presence`. The whole suite takes a couple of minutes;
|
||||
run it only before a restructure step lands, otherwise only the files
|
||||
|
||||
Reference in new issue
Block a user