- Status: computed from connection, presence, hold, relay, player facts;
no setters (health writes and HealthStatus gone); pill, strip and
source line read it; transitions go to the Notifier.
- Notifier: noticeFor(event, context) with self presence, alone, window
activity, playing and prefs; toasts for joins/leaves/returns/swaps/
Up next adds (with picture)/problems/chat, the rest logged; while
sitting out only chat, nudges, starting, Up next (+ joins if chosen);
alone no social notices; replayed lines logged; owns its toast timer.
- System notifications (flutter_local_notifications: D-Bus on Linux,
WinRT on Windows) when the window isn't focused; a click focuses it.
- Toasts sit above the control bar and move with it.
- Versions: each member's in the members card and avatar tooltip,
the server's in the card header; the strip no longer shows it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Server and core: version ranges on both sides with a coded refusal; a
client id maps to the same member and token for the server's life; a
drop is `gone` for a 10-minute grace keeping ready, presence, queue
items, relay and avatar; `bye` leaves at once; ordered takeover (seq,
replaced/stale); presence joining/in/away/gone with one PresencePolicy;
joiners never pause playback and hold a held gate at most ~15 s;
state.hold; feed ids, seq and replay; the relay lives through its
source's grace and continues on re-dial; queue `by` lasting, auto
items and cancel; MediaRef carried; client ids never logged.
App: persisted client id and seq; Connection with unstable/lost/
replaced states, backoff with jitter, no lost/back pair per flap; a drop
keeps the session (state, player, media, relay source); presence in the
strip and members card; sit out = away intent; feed matched by id;
auto sit-out (idle when not playing, minimised for a while; never when
alone) replaces auto-unready; bye on close and Leave, not on update
restart; relay source re-dials through its grace.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Linux app id dev.foxyz.starsync, binary starsync; Windows starsync.exe,
company foxyz; server starsync-server; Dart package starsync; archives
starsync-<ver>-*.
- 1.2.x compatibility: watch_together and watch-together-server
launchers (Linux) / copies (Windows) in the archive; the updater
accepts both names and relaunches the new binary; update.sh/.bat use
whichever server exists.
- First start copies (never moves) the old support folder and this
app's Hive boxes out of ~/Documents into <support>/hive; a marker
makes it run once; AppDirs is a port; settings schema version with
ordered migrations.
- Launcher entry dev.foxyz.starsync.desktop; the old one removed.
- Repository name and URLs unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- notice_table_test: one row per AppEvent variant through noticeFor.
- FlakyProxy (core/testing.dart): pass, blackhole (half-open), delay,
hold, cut after N bytes, reset; demo tests against today's server.
- Liveness (stale/drop/ping) on the engine clock, so grace can be tested
with a fake clock; Connection takes an injectable clock.
- AppDirs with STARSYNC_PROFILE=<name>: separate data and logs, so two
instances run on one machine.
- DEVELOPMENT: scenario list per restructure step; test tags follow the
new step order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Connection owns the socket lifecycle, connect/dial with epochs,
reconnect, the clock offset, diagnostics upload and a sealed ConnState
signal; the hello is built by an injectable builder, the resume token
stays private, and the reconnect delay and silence limit are parameters.
The coordinator maps ConnState to the same health texts and keeps the
reconnect restore and drop handling (step 1 replaces them).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tests: the four known failures fixed (test-side causes), one duplicate
file and four detail-pinning cases removed, every test that pins
behaviour the restructure changes tagged with its step.
- Every toast, sound and activity-log line goes through Notifier.notify
(AppEvent), mapped one-to-one to today's behaviour.
- Platform ports (shell, URLs, folders, link resolver, system
notifications) with desktop implementations; window_manager, Process
and file_picker now live only in platform/desktop/.
- ROADMAP: protocol 16 planned; app id dev.foxyz.starsync.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Close: hide the window, dispose every player, thumbnail job and the
sound engine, leave, then close normally; Linux runner _exit (the
NVIDIA EGL exit handler crashed 10/10 closes, 0/41 now); Windows tears
down inside the message loop, then TerminateProcess; watchdogs.
- Version under the Settings tab; an update band instead of a dot,
never over a video; checks at start and every 4 hours.
- Chat pop-ups default to All again.
- Shader read-back compared as a list (old mpv joins with ','), so
Windows stops turning shaders off; Windows hwdec auto-copy.
- Render diagnostics (`mpv: render` lines); RAM-only demuxer cache pin
silences the file-cache warning; a volume readout test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SoundEngine (platform/audio) opens the device once (2 s after start,
or on the first sound), loads each synthesized sound into memory (no
temp files), plays overlapping voices and is disposed on window close,
before leaving the server. The SfxService interface and its callers
are unchanged; loudness matches the old mpv path (v^6). WAV only, so
no codec libraries are bundled and no build packages are needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PlayerControlsModel holds the controls' state and actions once; the
Standard skin (our own, no more media_kit controls or private-state
reach-ins) and Minimal (the former uosc design, made the app's own and
credited) only draw it. One layout spec for both: play first, then
previous/next, volume, time; subtitles, audio, settings, chat,
fullscreen last; everything else in the settings menu. Settings ->
Player -> Controls: time display, size, hide delay, optional buttons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every app streams its redacted diagnostic lines (server time, batched
every 10 s, gzip, buffered while offline) to POST /l/; the server merges
them with its own into session-YYYY-MM-DD.log in time order, tagged by
who. Opt-out in Settings > About; Mark a problem (Ctrl+Shift+B, the
player menus, About) writes a marker with an optional note. Copy
diagnostics can include the server's session log. Chat text never
reaches any log.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Logs: one redaction pass (query strings to names, member tokens,
passwords, cookies, auth headers) for every line; daily files with size
and age caps in the app (hosted server included) and the dedicated
server (--log-dir); swaps, relay channels, per-member /r /m /j outcomes,
server fetches, mpv load errors, yt-dlp runs. Settings > About: Copy
diagnostics, Open logs folder.
Bilibili through someone else: the viewer opened /r/ before the source
dialled in; the server holds that request and mpv gives up after 5 s
("could not be opened"). Viewers now open a member relay once it is
live, the putter starts its source before its own player, and track
sizes are read in parallel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server keeps one small relay (32 MiB ahead, 8 behind) for Up next's
first relayed item, keyed by its queue id at a URL that stays the same
after the swap: fetched by the server for server-fetch links, fed by
the adder's app for through-me links and files, by a member with an
account for member-passed Jellyfin items. Every app preloads from it;
the swap promotes it with its buffer. Dropped when the item stops
being first or its adder leaves. Measured switch: ~1.6 s cold, 20-40 ms
preloaded. ROADMAP gains the research item on server-side fetching of
member-resolved URLs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every app gets the first Up next item ready itself (own Jellyfin plan,
direct links, guests via /j when state.relayNext says the server's
login can pass it on) and opens it in a hidden, muted, paused second
player; the swap promotes that player instead of opening cold, so its
buffer counts at once. Reports go out on open and when the buffer
reaches the gate's need. SwitchTrace times each phase. Measured in the
e2e harness: 1.5-3 s before, 35-103 ms with a preload. Relayed items
still open cold for everyone but their source (ROADMAP).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saved on resize, move, maximize and close; on start (and hot restart)
the normal size and maximized state come back everywhere, the position
on Windows and X11 when it still lands on a display (Wayland places
windows itself).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Overlay: one layout for both designs (top bar with Up next as one of
its buttons; one action band above the controls for the status pill,
skips, next episode and end cards), one glass recipe (AppGlass) and
one pill (FloatingPill); hovering any of them, or the open queue,
holds the controls up.
- Up next: the first item (or the first continuation episode) is kept
resolved on the app that puts it up, refreshed before it expires;
Schedule and drop/paste can queue too.
- Jellyfin: WatchSession (core) counts real watched coverage; an
episode is watched at 80 % or at its ending after half; a played one
is re-marked after Playing (10.11 unmarks on open) and never gets a
position; a peek leaves the old resume point; part-way resume points
stay under 85 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Up next: a server-side queue anyone edits by item id (add at end or
next, move, remove, play now, skip); each item says who added it and
how it plays. At the real end the first item swaps in after the end
card's countdown, put up by whoever can (its adder for through-me links
and files, someone with access for Jellyfin, else anyone); items that
can't play are skipped with the reason. The next item's link is
resolved ahead. Greyed "Continues with" episodes below. A chip with the
controls opens it as a popover (leaf_widgets, local path dependency for
now); "Add to queue" next to Put it up everywhere.
Delivery: the server measures the source's rate, the media's bitrate
and each member's read rate; the members card shows a source line
(who passes it on, through what, keeping up or not, source left) and
each member's route and buffer. A slow source is told once per media.
A failed server fetch is announced to everyone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- AppSelect / AppMenuButton (widgets/app_menu.dart): glass menu anchored
to its button, check on the current item, keyboard and Esc; replaces
the Material dropdowns in Settings, player settings, Jellyfin and the
member menu. Material menus elsewhere get the same surface by theme.
- Link page: Through me is the main choice (you can't know which sites
need cookies); Everyone / Server fetches it sit under "Other ways".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Next episode (known ending, else the last two minutes) and Skip ED
show only while the controls are up, like Skip 1:30 (PillGate).
- A web link's name and wide cover slot show from the first state, so
nothing flips when its thumbnail arrives.
- Chat pop-ups default to Only when paused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A swap stops the previous media at once; buffer counts only for the
media the player holds (client) and the current mediaId (server), so
the old video can no longer start the new one.
- The automatic frozen-picture reload is gone (false alarms on still
scenes); Reload video stays.
- The "Next episode" pill shows only from a known ending.
- A web link's thumbnail is its cover, under the video's own name.
- While playing, the unread chip stays off the picture; chat pop-ups
can be All / Only when paused / Off.
- Server fetch failures say when the server has no cookies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
X.Y.Z-dev.N builds read the dev prerelease's manifest as well as the
stable one and take the newer, so a tester gets each test build through
the normal update and lands back on stable at the release.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The danmaku XML is served raw-deflate; ffmpeg fails to inflate it on
every read, logging a warning each time (millions, drained on the UI
thread) while the video waits on the track: a frame every few seconds.
mpv's hook now asks yt-dlp for sub-langs all,-danmaku.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
report.error / state.members[].error (optional, no protocol bump): an
errored member is out of the ready gate until its player opens the media
or the media changes; others see "Bob can't play this: ..." and a mark
on the avatar; your own pill offers Retry (reload).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session player asked libmpv for every v-level message all session and
media_kit drains them on the UI thread: a retry storm could freeze the
window. ffmpeg reconnects only on 5xx, bounded. A load that fails before
a duration is known reports once ('can't play this: HTTP error 403 ...').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Personal tracking will come from the user's AniList lists; AnimeFlow keeps
downloads, availability and requests only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For shows AnimeFlow doesn't track, the app now looks the episode up in the
linked Jellyfin (one /Shows/{id}/Episodes per series, 3 min memo; TVDB
number via anilist_map offsets). No linked account: a neutral Aired.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server's version is stamped like the app's (APP_VERSION) when packaged,
so it can compare against the manifest; --upgrade swaps the whole install
folder (app and server) and asks for a restart. update.sh/.bat no longer
need GTK.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A track can be served as pieces: the server writes a VOD playlist whose
entries are its own piece URLs (segments, init maps, AES-128 keys; byte
ranges folded in; yt-dlp's DASH fragments become the same list). A piece
buffer per track fills like the byte buffer. Picks a single file, then a
split byte pair, then pieces. DRM, live, master playlists are refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Putting anything up offline starts a solo session: an in-app server bound
to 127.0.0.1 only, joined like hosting, so tracking, next episode and the
player all work. Invite friends re-hosts on the Host card's port and
password with the same media and position; Stop needs no confirmation.
Offline, the Library opens with a welcome band (Join a friend, Host, Watch
solo); without a Jellyfin account it leads with Files and Link, Jellyfin
is an optional card. Play buttons work offline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anyone can sit anyone out (host included); only you bring yourself back.
Out members stay in chat and the party, never block or count, and load no
media; a card with the live position and Join back (Space) replaces the
video. The mini player's x sits you out; optionally idle does too. Nudge:
the server forwards it to the target only (3/s, 20/min per sender); the
target hears the nudge sound, gets a toast and a taskbar attention
request. Kick, its messages, errors and dialog are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Relayed media can carry 2-4 byte tracks (video, audio): yt-dlp picks a
split pair when no single-file format exists (h264 first), the source
channel serves each track, the server keeps a buffer per track (budget
split by size, 8 MiB floor) and viewers open an mpv edl:// of the track
URLs. Expired URLs re-resolve once for all tracks. Upstream GETs are cut
into <= http_chunk_size (10 MiB) ranges: YouTube throttles larger ones.
Live streams and DRM are refused; HLS/DASH pieces are a follow-up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hot restart frees the old isolate's NativeCallables while mpv handles stay
alive; media_kit then quits them on the next start and mpv calls the dead
wakeup callback (SIGSEGV on the mpv 'core' thread). A debug-only runner
hook clears every live handle's wakeup callback on the engine's
on-pre-engine-restart signal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yt-dlp: a managed, checksum-verified, daily-updated yt-dlp in the app and
the dedicated server (--[no-]yt-dlp, --yt-dlp-dir). Pasted links are
probed: direct files stay links, pages are resolved for title, uploader,
duration and thumbnail. Everyone fetches it: mpv's ytdl hook on the
managed copy. I pass it on / the server fetches it: the best single-file
format, re-resolved once when its URL expires. Relay names never split
the /r/ path; viewers of server-fetched pages wait for the relay to go
live. No wire change.
Icon: two stars orbiting a play button (assets/icon/icon.svg, PNGs, the
Windows .ico). GTK app id moe.senkochan.watch_together; release builds
install their .desktop file and icons into ~/.local/share so Wayland
shows the icon; the window icon is set for X11. README shows the icon.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ready gate: whoever is in the server while it is not playing counts (joining
while playing does not pause anyone); a swap counts everyone present and
keeps ready flags; the quorum rule is gone. Optional 'Unready when the
video changes'. No 'Skip 1:30' guess when the media has chapters.
Drag & drop and Ctrl+V anywhere (fox_utils): files, folders and links get a
small card asking how to open them. Files series start collapsed.
UI: Tab never moves focus and Space is always ready outside text fields;
chat focuses its field; unread badge on uosc and a chip in fullscreen;
people-only chat with an Activity toggle; skip pills show 5 s then with
the controls; volume up to 150%; own avatar same size as the party.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eleven sounds for events that carry news (ready, unready, everyone ready,
join, leave, kick, new video, others' chat, idle warning, connection
lost/back); none for what the video already says. New voices: koto
(Karplus-Strong, bendable), furin chime, rin bowl, bamboo, hyoshigi,
shoji slide, in the Yo scale on D. Played by SynthSfx at the Settings
volume with per-sound switches and previews. The debug layout mock is
gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The swapper sends the media's poster and banner once (`art`, Jellyfin
images or AniList by parsed name); state carries their hashes and every
member fetches them from /i/ like avatars. state.media.swapBy names who
put the media up. uosc and the standard bar show cover, title and
episode; a banner card shows before playback starts. The top-left health
pill and the waiting pill become one frosted status pill in plain words.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Library: source switch, selectable cards with a details panel, one merged
Continue row, Latest grouped per series without re-releases, Link page with
previews and recent links, Files embedded. Player: gear popover in both
designs, progress line while controls hide, live hwdec. Settings rebuilt as
cards. State marks the host member (proto stays 9, unreleased); hosting
shows LAN invite addresses; window buttons in fullscreen. fadeThrough keeps
the outgoing view's state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shell (lib/src/shell/) replaces the Home and session screens:
- Party strip: profile, Library / Schedule / Settings tabs, Join and
Host cards (hosting no longer takes media), status-ring avatar stack
with the member list, chat & activity panel, Leave.
- The real session player is mounted once and shown full, as a
draggable corner-snapping mini player, or fullscreen (window_manager,
party and toasts overlaid); the Video is never mounted twice.
- Profile popover with inline rename, colour and a crop picker (256 px
PNG), pushed live to the session; avatar toasts; waiting pill.
- Motion kit (fastEaseInToSlowEaseOut, fade-through, popovers,
mouse-drag/wheel horizontal scrolling).
- Library = the existing picker for now (restyled next round); Schedule
= the new SchedulePage; Settings = left nav over the existing panels.
Protocol 9 (packages/core, packages/server, sync client):
- {"t":"profile"} renames / changes the picture mid-session ("rename",
"avatar" feed actions); names are cleaned the same way at hello.
- state.members carry avatarHash; pictures are served once from
/a/<sid>/<token>/<hash> (PNG/JPEG/WebP/GIF by magic bytes only) and
cached by hash in the app (AvatarCache).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Standalone FilesPage (plugged into the shell later): watched folders
(sidebar, chips with remove when narrow), a header saying where you are,
series sections and a 16:9 grid for loose videos, thumbnails requested
only for tiles that stay visible and cancelled when scrolled away,
hidden-files count with examples, rescan, Recent with missing-file
state, play errors in a banner, offline browsing with play disabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From real use: a show on Jellyfin read "not in AnimeFlow". AnimeFlow's
new GET /library lists every Jellyfin series with AniList ids per TVDB
season; matchSchedule uses it for untracked shows (status inLibrary,
Put it up, no Request) and to fill jellyfinSeriesId. A failing or
missing /library never fails the schedule. Episodes may carry
anilist_episode (split cours, absolute numbering), matched first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>