- AGENTS.md: auto-read session on-ramp — memory-map reading order, working rules, build command, and startup-log pointer - ai.md: Response style section (no default planning boilerplate) plus an optional, on-demand No-Fluff Mode for ruthless review; never the default - schema.md: AGENTS.md listed as the always-read first page
8.5 KiB
ytLlive — AI Guide
Memory map entry point. Conventions live in
schema.md; task status and YouTube API research inTASKS.md; directory maps in each folder'sindex.md. Reading order: this file →TASKS.md→<dir>/index.md→ source.
Response style
No default "Plans & Pitfalls" / planning boilerplate. Respond directly and concisely: do the queued work, then report what changed and what's next. Skip feature pitch, step-by-step implementation plans, pros/cons tables, and "potential pitfalls" sections unless the user explicitly asks for a plan first. A short diff-style summary beats a proposal document every time.
No-Fluff Mode (on demand)
Invoke with "no-fluff mode" (or similar) when you want ruthless review instead of reassurance. In that mode:
- Strip all polite pleasantries, emojis, transitions, and conversational padding.
- Treat the user's input as a draft to be methodically deconstructed or strengthened — argue, correct, and sharpen rather than agree.
- Give unvarnished truth, not reassurance.
This is an occasional, explicitly-invoked mode — never the default. The default response style above stays in effect unless invoked.
Run
dotnet build # Windows only — WPF requires Windows target
dotnet run
Note: EnableWindowsTargeting=true is set in ytLive.csproj, so the project can be restored/built from WSL, but running requires Windows.
Tests
No test framework set up yet. When added: dotnet test.
Architecture
C# / WPF (.NET 8) following MVVM:
| Path | Role |
|---|---|
Models/ |
Plain data types — Scene, Source, QualityOption, StreamConfig, StreamHealth, YouTubeChannel, ChatMessage |
ViewModels/ |
MainViewModel — exposes collections + commands for the UI; GoLiveViewModel, ReuseImageViewModel |
Services/ |
YouTube OAuth2, stream/broadcast management, live chat polling, LayoutStore (SQLite) |
Helpers/ |
ViewModelBase (INotifyPropertyChanged), RelayCommand, ImageCache, AppLog (file logger), FocusPreservingListBox, OAuthCredentials, visibility converters |
Themes/ |
Controls.xaml — the single dark-theme source, merged once in App.xaml (see Themes/index.md) |
MainWindow.xaml |
Dark theme; layout: top bar (controls), center (preview), left (scenes/sources), right (chat), bottom (health) |
Key patterns
ViewModelBase.SetProperty<T>()for property change notificationsRelayCommandfor all button actions; commands gate on state (e.g. Start only when Offline)- ViewModels are constructed in XAML (
<vm:MainViewModel/>as DataContext) - Services are currently instantiated in MainViewModel's constructor — no DI container yet
- Layout persists to SQLite (
Microsoft.Data.Sqlite); scenes/sources/asset bytes stored in the DB, asset identity is a SHA-256 content hash (1:M reuse, no file paths — assets are always available) - Theming: all custom styles live in
Themes/Controls.xaml, merged inApp.xaml— never duplicate styles per-window (dialog duplicates were consolidated into this dictionary) - Resolution tiers (bottom bar): 1080p60@8 → 1080p30@8 → 720p60@6 → 480p30@2.5 Mbps; default = first. A tooltip explains finding upload bandwidth — an in-app speed test was deliberately dropped (unreliable)
- Crash diagnosis:
AppLogwrites startup checkpoints to%APPDATA%\ytLlive\startup.log;App.xaml.cslogsDispatcherUnhandledException/AppDomain.UnhandledException. When WPF won't run from WSL, this log is how you find the failure (it caught theMenuItemRole.SeparatorXAML crash and the ComboBox SelectionBoxItem bug)
Current limitations / TODOs
Helpers/OAuthCredentials.csnow contains the real ClientId/ClientSecret — auth service is implemented, but tokens still don't persist (Windows DPAPI planned; account UI in the GoLive dialog is simulated)GoLiveViewModel.SignIn/ChangeAccountremoved — Connect (OAuth) is the only entry to streaming- Scene/source/asset layout does persist (SQLite); token persistence does not (yet)
YouTubeStreamServiceuses hardcoded1080p/60fpsand per-broadcast streams — must switch to the v3variablereusable stream- No capture/encoding/RTMP yet
StreamConfigdefaults (TargetBitrate=6000,Resolution="1920x1080") are stale — the live dropdown drivesStreamHealth.CurrentBitrate/FPSinstead
Design Principle
This software is so intuitive that even the most right-brained person can easily intuit and use it.
Apply this to every UI decision:
- One-click go-live with working defaults
- Prefilled YouTube defaults (RTMP URL, bitrate, resolution, latency)
- Visual/drag-and-drop scene building over property panels
- Every action produces a visible outcome — no dead ends
Monetization (design decision — the watermark is the sword)
Free forever: all streams unlimited, no time caps, no subscription, no per-feature paywalls. The one paid line is a one-time unlock (delivered via itch.io — they handle hosting, payment, and key delivery; we never own a server or a key shop):
- Free: a small "made with ytLlive" watermark is always on, every frame, every stream — the sword of Damocles. Standard practice; only Streamlabs runs watermark-nagging to a capitalist extreme.
- Paid (one-time): watermark removed + Alerts (Super Chat / membership / subscribe pop-ins).
Deliberately rejected: hard stream-time cutoffs (the worst dead end — a stream dying mid-broadcast reads as broken, and YouTube streams routinely run 2-4 hours), soft-limit nagging, freemium tiers, and donation-only (relies on the kindness of strangers). Resolution/quality ceilings are deferred — that decision belongs to the resolution & streaming-constraints conversation, not monetization.
Auth gates Go Live, but not exploration
The app is fully usable without authentication: users can build scenes, add sources, compose previews, and audition the software with zero commitment. But going live requires authentication — it's the one capability gated behind YouTube sign-in. The sign-in button should never pressure the user ("sign in (optional)", not a modal wall), but "Go Live" only appears once connected.
Account assumption (do not build an account setup flow)
Connecting uses Google OAuth ("Sign in with Google") to link an existing YouTube creator account. ytLlive never creates or sets up accounts — that is YouTube's job. If the creator has no YouTube channel, they go to YouTube first. This assumption is explicit and must never be silently replaced by an in-app account-creation step. Zero state = a Connect button that starts OAuth; going live is unreachable until the account is connected.
YouTube Live API — design constraints (do not violate)
These are the hard facts behind every decision. Full list in TASKS.md.
- One-click go-live — never call
transition(live). Insert the broadcast withenableAutoStart=true,enableAutoStop=true,enableMonitorStream=false,selfDeclaredMadeForKids=false,latencyPreference=low. The encoder starting brings YouTube live.enableMonitorStream=falseis what lets us skip the testing stage. - Variable reusable stream —
liveStreams.insertonce per channel withcdn.resolution=variable,cdn.frameRate=variable,isReusable=true; cache the ingestion URL + stream name and reuse for every broadcast. Any quality tier works without recreating the stream, and auto step-down is done by us dropping bitrate on the fly (zero API calls). - Quality is greyed out while live — resolution/frameRate/ingestionType are immutable after stream creation; editing title/description/privacy is fine at any time.
- Report-by-exception health — poll
liveStreams.list; render nothing ongood/ok, surface a banner only onconfigurationIssues[]withwarning/errorseverity. Bottom strip = YouTube logo- green/red connection dot (clickable → opens the dialog).
- One dialog, three states — not connected / connected-offline (all editable) / live (title + description + visibility editable; quality + account greyed out). Both entry points (Start Stream button + bottom strip) open it; prefilled from saved session profile.
- End stream — stop encoder →
transition(complete),enableAutoStopas the safety net. - Encoder compliance — keyframes ≤ 4s (gopSizeLong), closed GOP, H.264, AAC/MP3 @ 44.1/48kHz, mono/stereo only. YouTube flags violations via health status.
- Broadcast ID == Video ID — one ID tracks status, health, and the auto-created VOD
(
recordFromStart+enableDvrdefault true).