Commit graph

27 commits

Author SHA1 Message Date
Tommy Chow
f2b932bc93
feat(video,chat): native player, ad-break awareness, chat send/delay reliability, and tab redesign (#567)
Squash-merge commit. Covers four areas: native video player, chat reliability, multi-tab, and hardening.

## Native video player
- Direct HLS playback via `better_native_video_player` (forked from `plug-and-pay/flutter-native-video-player`, patched on `frosty-patches` branch)
- Twitch GQL playback token fetching with platform cookie extraction (iOS Swift + Android Kotlin plugins) for authenticated streams
- Quality selection (source, 720p, 480p, 360p, 160p, audio-only) persisted per channel
- Picture-in-picture: iOS automatic inline PiP, Android auto-PiP on API 31+ guarded against older devices
- Latency polling, chat latency sync, stall detection/recovery, startup auto-pause recovery, watchdog timers
- **Twitch stitched-ad break detection** (HLS `EXT-X-DATERANGE`) on both platforms, emitting `adBreakChanged` so the host suspends latency/stall recovery and chat sync during ads (program-date-time is unreliable then). Matches `CLASS="twitch-stitched-ad"` / `ID="stitched-ad-"` **and** the `X-TV-TWITCH-AD-*` attribute prefix (the durable signal streamlink/yt-dlp use)
- Shared `VideoPlayerInterface` so WebView and native paths are interchangeable via settings toggle
- `PlaybackAccessToken` model, `TwitchGqlApi` client

## Chat delay & send reliability
- **Optimistic send-finalize**: Twitch acks a sent message with a per-PRIVMSG USERSTATE (not an echo), which it can intermittently drop — more so under a long chat delay. A missing ack no longer shows a false "message may not have been sent" error; the send is finalized optimistically (local echo rendered), and only genuine rejection NOTICEs surface an error. Ack timeout scaled by the chat delay.
- **Per-line chat-delay bypass**: control/state lines (USERSTATE/ROOMSTATE/GLOBALUSERSTATE), rejection NOTICEs, the reconnect notice, and untagged server lines bypass the delay so send-acks and PONGs aren't stalled; only displayable chat content is delayed, staying in sync with delayed video.
- **Measured chat bottom-bar height**: the message list reflows to the composer's real measured height, fixing the input overlapping messages when a long message expands it (replaces a fixed-constant estimate).
- IRC tag parsing handles valueless and escaped tags per the IRCv3 message-tags spec (fixes a recent-messages RangeError on certain tags).

## Chat / multi-tab
- Redesigned tab chips: unread dot indicator, chips dim until WebSocket connects
- Animated tab options popover replacing `MenuAnchor` (no animation hook in Flutter 3.44)
- Simplified `ChatTabs` from `StatefulWidget` to `StatelessWidget`
- Focus-current-channel fade bump to 0.55; section-header alpha lifted to match the muted-label tier
- `addTab` now calls `setActiveTab` to snapshot outgoing tab's `lastSeenMessageId` — fixes spurious unread dots after adding a tab
- `onActivateSourceTab` threaded into `ChatUserModal` so Reply in merged mode switches to the correct source tab's composer

## Bug fixes and hardening
- **Reverted the experimental iOS low-latency mode** (5s live-edge join + 1.05x catch-up): AVPlayer can't sustain <~18s on Twitch (declared `TARGETDURATION:6` paces playlist refreshes), and the offset starved the buffer into pause-on-launch. Default join distance restored.
- Fork (Android): **cap `BEHIND_LIVE_WINDOW` recovery** so a stream repeatedly falling off the live window can't silently re-prepare forever; emits `error` on exhaustion so the host recovery ladder takes over
- Guard all Android `setAutoPipMode` calls with `_autoPipAvailable` — prevents `PlatformException` on Android 8–11; check `_disposed` after `isAutoPipAvailable` await to prevent post-dispose auto-PiP re-enable
- Remove `_isInPipMode` guard from `_onPaused` so `_paused` tracks actual state, fixing PiP play/pause toggle getting stuck
- Tighten iOS cookie domain check to exact `twitch.tv` / `.twitch.tv` suffix
- Invalidate the stored web session token when Twitch rejects it with a 401 (profile card flips back to unlinked; future loads skip the doomed authenticated request), and re-extract the cookie on launch when logged in without a linked session
- Send `play_session_id` and `player_backend=mediaplayer` to Usher, matching the web client / streamlink
- Fork (iOS): restore program-date-time latency measurement (a prior rev measured distance to the manifest edge, reading ~0s and silently disabling high-latency recovery / chat-delay auto-sync); release platform views on dispose (per-stream view + AVPlayerViewController leak), track the observed AVPlayerItem for KVO removal (sibling-view crash), main-thread seek results, resolve superseded load futures
- Fork (Android): clean up method handlers orphaned by PiP-time view disposal (leaked audio-focus listeners), drop the live-offset re-anchoring seekTo on metadata updates (latency creep), restore pre-duck volume, respect user pause in the stall watchdog, resolve superseded load futures
- Deep review pass: expire the quality-switch flag so stall recovery isn't disabled for the session, cap latency-driven recovery (clock-skew loop), generation-guard the error overlay, two-strike offline confirmation, controller null-safety on quality switch
- Tighten `/me` local-echo match (no longer renders `/meow ...` as an action); clear pending send text on disconnect

## Web / docs
- Remove stale Cloudflare Images binding from `wrangler.jsonc` (covered by `images.unoptimized: true`)
- Correct `web/AGENTS.md` Cache Components claim (feature not enabled)
- Repo and agent config cleanup

## Test plan
- [x] `flutter analyze` — clean (frosty + plugin lib)
- [x] `flutter test` — 319/319 pass (adds `ChatLatencySync`, `StreamInfoPoller`, video timing invariants, `PlaybackAccessToken`, IRC tag parsing)
- [x] Android plugin Kotlin compiles against the bumped fork rev
- [ ] Native player plays live streams on iOS and Android
- [ ] Quality switching works and persists across channel reopens
- [ ] PiP enters/exits correctly; pause/resume toggle in PiP window works
- [ ] Audio-only mode does not crash on Android < API 31
- [ ] Sending under a long synced chat delay no longer shows a false "may not have been sent" error
- [ ] Long composer text reflows the message list (no input/message overlap)
- [ ] Multi-tab: adding a tab does not mark previous tab as unread
- [ ] Merged chat: Reply from user modal targets the correct tab's composer

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-13 17:06:24 -04:00
Tommy Chow
7772c4d9f5 disable image optimization until free transformation quota resets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 17:49:57 -07:00
Tommy Chow
092cbc3cbb add sizes prop to carousel images to reduce Cloudflare transformations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 17:40:32 -07:00
Tommy Chow
59f5d2cb81 enlarge mobile carousel phones and tighten description spacing
Increase mobile phone frame from 192×440 to 240×520 (max-w-60,
max-h-130) with paired aspect-ratio constraints. Widen carousel
step to 260 to maintain gap between slides. Remove py-3 from
phone media to eliminate black letterboxing. Cap track height to
match phone so description stays close via justify-center.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 02:50:21 -08:00
Tommy Chow
10e924b4ab gate emote entrance animations on image load and optimize fetching
Emotes now animate (fade + deblur) only after each image loads from
cdn.7tv.app, with stagger based on load order. Also reduces network
requests by lazily assigning src to only the active particle count,
adds preconnect for the CDN, and marks emotes with decoding=async
and fetchpriority=low.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 02:23:42 -08:00
Tommy Chow
3a50e06c63 add responsive emote scaling with 118 7TV emotes, mobile support, and motion entrance
- expand emote list from 36 to 118 (xQc channel + 7TV global + trending)
- scale particle count dynamically from 12 (mobile) to 100 (4K) via container diagonal
- scale emote size (28–44px), opacity (0.25–0.35), and repulse radius per breakpoint
- enable on mobile with touch repulsion (was desktop-only)
- shuffle emotes per visit via Fisher-Yates in useEffect
- replace motion.img with plain img + motion animate() for blur+fade entrance
- move opacity to container layer for single-point control and WCAG AA safety

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 01:47:39 -08:00
Tommy Chow
1902761945 keep github text visible on mobile header and replace deprecated icon 2026-02-19 00:51:19 -08:00
Tommy Chow
a689991a43 make header and footer flow in layout, fix mobile viewport scroll
- header/footer are now flex children instead of absolute overlays
- mobile grid uses auto+1fr rows so intro sizes to content
- h-dvh + overflow-hidden + overscroll-none prevents mobile scroll bounce
- add horizontal padding to intro section

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 23:18:14 -08:00
Tommy Chow
246e1314aa remove redundant color-scheme style from html
next-themes enableColorScheme (default true) already sets this
dynamically to match the active theme.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 23:31:48 -08:00
Tommy Chow
374cb23111 update wrangler config: enable source maps and keep variable names 2026-02-17 23:11:47 -08:00
Tommy Chow
a2e9701434 add carousel accessibility: ARIA semantics, keyboard nav, reduced motion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 22:00:31 -08:00
Tommy Chow
f209d73352 inset carousel prev/next buttons on landing page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 21:38:21 -08:00
Tommy Chow
862839e44a rename web worker config to frosty-web 2026-02-17 20:31:56 -08:00
Tommy Chow
62d698a99b fix worker self-reference to match cloudflare project name 2026-02-17 20:11:02 -08:00
Tommy Chow
f19da4151c
migrate web to next 16 template and redesign landing page (#526)
* migrate web to next-template stack (next 16, react 19, tailwind 4)

replace all infrastructure with next-template conventions:
- next.js 14 -> 16 (turbopack default, typed routes, react compiler)
- react 18 -> 19
- tailwind css 3 -> 4 (css-first config, oklch theme system)
- eslint 8 (.eslintrc) -> 9 (flat config with stricter rules)
- add shadcn/ui, base-ui, cva component primitives
- upgrade theme toggle to light/dark/system dropdown
- migrate hardcoded neutral classes to semantic oklch tokens
- drop unused @tailwindcss/forms, autoprefixer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add no-misused-promises config for async JSX handlers, warn on array index keys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* base-vega

* sync frosty web with next-template runtime and claude tooling for cloudflare parity

* gitignore .dev.vars per cloudflare best practice

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add observability configuration to wrangler.jsonc

* remove marquee emote background from hero section

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* improve CLAUDE.md and AGENTS.md files across project

Remove redundant root CLAUDE.md (kept in .claude/ instead), add Commands
and Source Structure sections to Flutter AGENTS.md, clarify global vs
feature store placement, and fill gaps in web AGENTS.md (shadcn base-vega
style, constants.ts, no-array-index-key rule, commit conventions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* switch shadcn theme from neutral to violet, replace twitch-purple with primary token

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* initial redesign

* auto-infer nativeButton in Button wrapper when render prop is used

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* replace skeleton carousel with real screenshots and video, add responsive phone scaling and mobile menu

- Replace PhoneSkeleton with PhoneFrame/PhoneMedia using actual app screenshots and video
- Fluid phone sizing via aspect-ratio with responsive max-h/max-w caps
- Non-circular carousel with swipe gestures, dot indicator, and hover opacity boost
- Video plays only when focused, pauses when off-screen
- Collapse header actions into dropdown menu on mobile, add Contact link
- Viewport-height layout with no scrolling on any screen size
- Remove footer, move contact to header

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* improve header consistency, carousel layout, and responsive centering

- add icons to all header buttons (Contact, Donate, GitHub)
- switch theme toggle to ghost variant for consistency
- bump header collapse breakpoint from md to lg
- move carousel arrows to overlay positions with animated enter/exit
- hide arrows at first/last slide
- fix vertical centering of phone+description group across all screen sizes
- add smooth blur+slide transition for description text

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add contact dropdown with copy email toast

- replace direct mailto link with dropdown: send email or copy email
- install sonner for toast notifications, show top-center on copy
- extract emailAddress constant for clipboard use
- add copy option to mobile hamburger menu too

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* replace mobile hamburger menu with icon buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* redesign hero copy, badge-style download buttons, and mobile layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add consistent padding and object-contain to carousel media

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add emote physics background to hero section

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* move container styling to hero panel, refresh emote set with 2025 trending picks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add staggered entrance animations and remove grid gap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add 30-day cache headers for video and logo assets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add emote collisions and aspect-ratio tracking, refine hero layout

Emote physics: add particle-particle elastic collisions, per-particle
width measurement for correct aspect ratios, new emotes (aga, WW, o7,
FLASHBANG, LULW, Deadass), bump count to 30. Layout: absolute-position
header with z-20, increase mobile carousel ratio to 2fr/4fr, reserve
min-h-10 for description text, remove panel rounding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix carousel lint warnings: useSyncExternalStore, ref naming, stable keys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add seo metadata, sitemap, and robots.txt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add privacy link to header and footer credit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* update privacy policy content and restyle with mono layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* reorder header nav and add mobile hamburger menu

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refresh web dependencies and smooth footer credit hover transition

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:53:43 -08:00
Tommy Chow
f5ef9bfe24 Update web header styles 2024-09-19 23:44:30 -07:00
Tommy Chow
5f4e830e3a Enable disableTransitionOnChange 2024-09-19 23:13:16 -07:00
Tommy Chow
38fb74a886 Update web design 2024-09-19 21:39:45 -07:00
Tommy Chow
4f7258c635 Don't use dvh
Causes image layout vertical shifting on Chrome mobile.
2024-09-15 22:03:31 -07:00
Tommy Chow
c7d6de8924
Migrate and redesign website (#398)
* add initial web redesign

* add emote marquee

* add faq section

* tweak copy

* tweak faq styles

* update video and style tweaks

* add header links

* fix bg color

* tweak styles

* delete old image

* update metadata and favicon

* add ogimage

* update faq

* upgrade packages
2024-09-15 21:43:51 -07:00
tommyxchow
f61dc2c124 Delete web folder 2022-05-12 04:15:20 -04:00
tommyxchow
c146ac551e Upgrade packages & icons/splash 2022-03-29 15:05:25 -04:00
tommyxchow
8060b36acc Upgrade packages 2022-02-24 23:32:08 -05:00
Tommy Chow
ecd0cea816 Update splash screen 2022-02-04 21:46:07 -05:00
Tommy Chow
a0b6baa971 Add placeholder app icon & change launch color 2022-01-05 12:56:00 -05:00
tommyxchow
122d88bba5 Update files to match latest template 2021-12-16 01:13:23 -05:00
Tommy Chow
7cec28d839 Initial commit 2021-08-18 09:38:00 -04:00