Player: sharp video and subtitles at screen size; dev tools: danmaku preview, subtitle lift with a stand-in chat line
- "Sharp video and subtitles" (Settings → Player and the player settings card, on by default): the video output renders at the view's physical size (debounced, GPU rendering only), so libass, mpv's scaler and the shaders work at screen resolution; re-sent after media_kit's own reset on new video params, redrawn while paused. - Dev builds only (Settings → About → Developer): a danmaku preview (DanmakuLayer: free lanes in an upper band, density cap with a queue, equal crossing time, outlined text, avatars, own-line frame) over the real player or a stand-in picture, with samples, a random stream, live toasts, every knob and mock subtitles; and "Lift subtitles above the chat line and controls": Enter opens a stand-in chat line, bottom subtitles move up via mpv sub-pos while it or the control bar is up. - tool/make_sub_test.sh: a test video with a top \an8 sign, bottom dialogue and an SRT track. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
638a30e16c
commit
5bc252bf02
18 files changed
+2253
-16
No files matched your search
+28
-2
@@ -567,6 +567,30 @@ server without the relay's header rule, or the connection reuse above)
|
||||
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):
|
||||
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
|
||||
(`renderMode` gpu; never in software rendering, where every pixel is CPU
|
||||
work, nor before the probe knows), the session view
|
||||
(`shell/video_view.dart`, `VideoOutputSizer` around the `Video`) sends its
|
||||
size in physical pixels to the host (`PlayerHost.setOutputSize`): at
|
||||
once the first time, then once a resize has held 180 ms
|
||||
(`outputResizeDelay`: a window drag, fullscreen, full ↔ mini). The host
|
||||
applies it to every live output and to each new one once it is up
|
||||
(`NativeVideoController.setSize`); mpv then letterboxes inside it and
|
||||
draws subtitles at that size (text subtitles may sit in the black bars,
|
||||
as in mpv). media_kit's controller sets an output back to the video's
|
||||
size whenever the video's parameters change (each new file), so the host
|
||||
sends the size again after it, under the controller's own lock; a fresh
|
||||
player's first frame or two show at the video's size. A paused player
|
||||
draws no frame for the new size: the host nudges `video-pan-x` by a
|
||||
millionth and back so mpv redraws. Off: each output at its video's own
|
||||
size again (`setOutputSize(null)`). Measured on a GTX 1080, 1280x720
|
||||
H.264 at 2560x1080 fullscreen: no difference past the noise in the
|
||||
process's GPU (`nvidia-smi pmon`, 9 to 12 % either way) or CPU.
|
||||
|
||||
Render mode: media_kit's Linux/Windows video output creates mpv's render
|
||||
context (OpenGL, else its software renderer) when the `VideoController` is
|
||||
created, and nothing in its API says which. The host therefore requests
|
||||
@@ -606,7 +630,8 @@ their own): creates players by role (`session`: the full cache, `warn`;
|
||||
no outputs, no cache, no log) with every pin set before the caller's
|
||||
first command (`pinMismatches` lists the read-backs that differ), gives
|
||||
a player its video output (`HostedVideo`, with the hwdec policy:
|
||||
`SYNC_RELAY_HWDEC`, else the setting, `auto-copy` on Windows), runs the
|
||||
`SYNC_RELAY_HWDEC`, else the setting, `auto-copy` on Windows) at the
|
||||
view's size when asked (`setOutputSize`, see "Output size"), runs the
|
||||
render-mode probe and its diagnostics (`renderMode`, also after leaving),
|
||||
routes mpv's log, and owns the order players go in: `retire` (pause, let
|
||||
the view drop the texture: two frames and a route pop, then dispose; a
|
||||
@@ -2112,7 +2137,8 @@ panel) and chat messages (Copy message, and a link in it).
|
||||
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, hardware decoding (set live on mpv's
|
||||
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
|
||||
|
||||
+58
-1
@@ -75,6 +75,61 @@ Harnesses in `tool/` (own `pubspec.yaml`, `dart run tool/<name>.dart`):
|
||||
`range_logger` (serve a file and log every Range request), `shaper`
|
||||
(bandwidth cap, latency, `--kill-after` for outage tests), `fake_viewer`
|
||||
and `e2e_check` (simulated viewers against a live server).
|
||||
`tool/make_sub_test.sh [out.mkv] [seconds]` (ffmpeg) writes a 720p test
|
||||
video for subtitle placement and sharpness (default
|
||||
`~/Videos/starsync-sub-test.mkv`): an .ass track with a top sign
|
||||
(`\an8`) and bottom dialogue (English and Japanese), and an SRT track.
|
||||
|
||||
## Dev tools
|
||||
|
||||
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:
|
||||
|
||||
- **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).
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -109,7 +164,9 @@ Three suites, run from each package's folder:
|
||||
version gate, the media plan's table with the Jellyfin relay rule and
|
||||
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`)), `singletons_test` (the `.instance` count, see
|
||||
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
|
||||
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