234 lines
16 KiB
Markdown
234 lines
16 KiB
Markdown
# ytLlive — Task List
|
|
|
|
## YouTube Live API — research facts (authoritative, v3 build)
|
|
|
|
Lifecycle: `created → ready → [testing] → live → complete` (transitional `liveStarting` / `testStarting`).
|
|
|
|
- **liveBroadcasts.insert** requires: `snippet.title`, `snippet.scheduledStartTime`, `status.privacyStatus`, `status.selfDeclaredMadeForKids` (COPPA).
|
|
- **liveStreams.insert** requires: `snippet.title`, `cdn.frameRate`, `cdn.ingestionType`, `cdn.resolution`. **None of the four (except title) can ever change after creation** — changing them means delete + recreate the stream. This is the hard constraint behind the quality grey-out.
|
|
- **Title / description / privacy**: editable at any time, including while live (`liveBroadcasts.update`, part=`snippet,status`).
|
|
- **contentDetails** (DVR, recordFromStart, monitorStream, embed, latency): editable only in `created` / `ready`.
|
|
- **Transition to live** only allowed when the bound stream's `status.streamStatus == active`.
|
|
|
|
### Two features that reshape the design
|
|
|
|
1. **enableAutoStart / enableAutoStop** — instant one-click go-live, no transition call. With `enableAutoStart=true` we never call `transition(live)`: the broadcast auto-goes-live the moment the encoder starts. Combined with `enableMonitorStream=false` (our preview pane replaces YouTube's monitor stream — the thing that forces a testing stage), the flow is **create → bind → Start Stream → encoder starts → YouTube brings it live**. No testing, no transition polling, no liveStarting stuck-state handling.
|
|
2. **`cdn.resolution=variable` / `cdn.frameRate=variable`** — free auto step-down. YouTube auto-detects what we send; since we ARE the encoder we can drop bitrate/resolution on the fly with zero API calls. Declaring an explicit resolution instead (e.g. 1080p) requires a new stream, which can't happen mid-broadcast. Variable is the enabler for the whole auto step-down feature.
|
|
|
|
### Compliance gotchas (maps perfectly to report-by-exception)
|
|
|
|
- `liveStreams.status.healthStatus`: `good | ok | bad | noData` plus `configurationIssues[]` with `type` + `severity` (`info|warning|error`). Literally built for report-by-exception — poll it, render nothing on good/ok, surface a banner only on warning/error. No need to invent our own health logic.
|
|
- Encoder must comply or YouTube flags it: keyframes ≤ 4s (`gopSizeLong`), closed GOP, H.264, audio AAC/MP3 @ 44.1/48kHz, mono/stereo only.
|
|
- Error codes to handle: `errorStreamInactive`, `invalidTransition`, `redundantTransition`, `liveStreamDeletionNotAllowed`, `liveStreamModificationNotAllowed`, `liveBroadcastBindingNotAllowed`.
|
|
|
|
### Tips we should take advantage of
|
|
|
|
- **Reusable streams** (`isReusable=true`): one stream per channel, cache its ingestion URL + stream name, reuse for every broadcast. No rebinding dance each go-live. This is exactly the manual-stream-key baseline.
|
|
- **Backup ingestion address**: YouTube provides a simultaneous-push backup — future hardening, not v1.
|
|
- **`recordFromStart` + `enableDvr` default true** → every live is auto-recorded and immediately replayable. Free VOD archive, matches the v0.2 recording goal.
|
|
- **`latencyPreference`: `normal | low | ultraLow`** — for homelab streamers talking to chat, `low` (or `ultraLow`, capped at 1080p) is a real feature.
|
|
- **Broadcast ID == Video ID** — one ID to track everything.
|
|
|
|
---
|
|
|
|
## TASK 1 — Initial Scaffold
|
|
|
|
**Goal:** Working C# / WPF project with MVVM architecture, dark-theme main window, and YouTube service stubs.
|
|
|
|
### Status: ✅ Done
|
|
- Models: Scene, Source, StreamConfig, StreamHealth, YouTubeChannel, ChatMessage
|
|
- Services: YouTubeAuthService (OAuth2), YouTubeStreamService (broadcast/health), YouTubeChatService (chat polling)
|
|
- MainViewModel: scene management, stream controls, chat
|
|
- MainWindow: scene/source panel, preview area, chat panel, status bar
|
|
- Clean build, 0 warnings (WSL + Windows)
|
|
|
|
---
|
|
|
|
## TASK 2 — YouTube OAuth2 Authentication
|
|
|
|
**Goal:** Fully working Google OAuth2 flow — user clicks "YouTube", browser opens, authorization callback lands, channel info is stored.
|
|
|
|
**Design constraint:** Sign-in must NEVER block core exploration. Users can build scenes, add sources, and audition the software without authenticating. But **going live requires authentication** — the "Start Stream" dialog is where the account sign-in lives, alongside all stream metadata.
|
|
|
|
**Two-state flow:** There is no separate "Connect" button. The top bar shows a single button — **Start Stream** when idle, **End Stream** when live. Clicking Start Stream opens one dialog that supplies everything: account (previously-saved account shown as default, with a Change Account action) + title/description/visibility.
|
|
|
|
**Live indicators (unmissable):** Top bar + window title bar flip red, a pulsing **● LIVE** badge with elapsed timer appears in the top bar, the preview area gets a red glow, and the taskbar icon shows a red overlay dot. Window title bar shows the stream title once validated.
|
|
|
|
### Requirements:
|
|
|
|
1. **Google Cloud OAuth credentials** — client ID + secret, **baked into `Helpers/OAuthCredentials.cs`** (desktop "Desktop app" OAuth client; loopback callback — no console redirect URI registration needed; creators never configure)
|
|
2. **Local HTTP listener** — `HttpListener` on `http://localhost:PORT/oauth2/callback` to catch the redirect
|
|
3. **Browser launch** — open the authorization URL in the default browser
|
|
4. **Token persistence** — store access/refresh tokens securely (Windows DPAPI), reload on startup
|
|
5. **UI state** — account shown in the Start Stream dialog; "Change Account" action triggers re-auth
|
|
6. **Go Live gated on auth** — Start Stream dialog requires sign-in to enable the Start button; scene building works without it
|
|
|
|
### Tests:
|
|
|
|
- Mock token exchange response, verify channel info parsed
|
|
- Verify token refresh triggers when near expiry
|
|
- Verify credential load/save roundtrip
|
|
|
|
### Status: 🔶 UI flow done — auth wiring pending
|
|
- ✅ Two-state Start/End Stream button, go-live dialog (account + title/description/visibility), red top bar, pulsing LIVE badge + elapsed timer, preview glow, taskbar red dot
|
|
- ⬜ Real OAuth2 with the baked-in Google credentials (desktop client type; loopback callback, no redirect registration)
|
|
- ⬜ Token persistence via Windows DPAPI, reload on startup
|
|
- ⬜ Account sign-in/change wired to YouTubeAuthService (currently simulated in GoLiveViewModel)
|
|
|
|
---
|
|
|
|
## TASK 3 — Capture Pipeline (Scenes/Sources)
|
|
|
|
**Goal:** Real video preview in the center panel — the minimal source set below, composited per scene.
|
|
|
|
### The Minimal Source Set (design decision — do not expand casually)
|
|
|
|
ytLlive is YouTube-only and 90% of users are casual. OBS's long source list is off-putting; we ship
|
|
the hot few and nothing esoteric. If a user needs more, they've graduated to OBS.
|
|
|
|
1. **Webcam** — the face cam. Non-negotiable.
|
|
2. **Screen** — the main event (game, slides, browser). One source; a picker chooses a monitor *or* a
|
|
window. (Window capture is absorbed here — no separate source type.)
|
|
3. **Background** — a full-canvas backdrop image. Fills the whole scene automatically, zero fiddling.
|
|
Kept separate from Image on purpose: same pixels, but this one needs no positioning.
|
|
4. **Image** — a floating graphic/logo overlay (watermark, badge, corner branding). Free-positioned.
|
|
5. **Text** — live text ("Starting soon", "Back in 5", handle, callout). Casual streamers live on this.
|
|
6. **Chat box** — YouTube live chat rendered *on* the stream so viewers read along in-video. YT-native.
|
|
7. **Alerts** — Super Chat / membership / subscribe pop-ins. The dopamine source. **The one big lift**
|
|
(Super Chat event streaming + on-stream rendering/animation); build after the six. **Also the one
|
|
paid feature** — see Monetization in `ai.md`.
|
|
|
|
Deliberately NOT supported: game capture, browser source, media playlist, VLC, color-key voodoo, MIDI.
|
|
|
|
### Source memory model (design decision)
|
|
|
|
- A scene has resources. Resources can be shared across scenes.
|
|
- A resource exists exactly once in memory, no matter how many scenes use it (a logo in five
|
|
scenes = one loaded bitmap).
|
|
- Every resource carries a **catalog of scenes**: one usage entry per scene it appears in, each
|
|
entry dictating that scene's use — placement (X/Y/Width/Height), opacity, z-order, enabled,
|
|
scale mode, crop.
|
|
- Usages are named `{resourceName}.{sceneName}` — whatever the user named the resource, dot, the
|
|
scene name: `logo.starting`, `logo.live`, `myPic.brb`. Not a hardcoded "logo".
|
|
- A webcam in two scenes = one capture session, two catalog entries.
|
|
- Refcount by catalog size: the last usage removed → the resource is disposed and evicted.
|
|
- The resource (not a per-scene node) owns everything `IDisposable`.
|
|
|
|
### Scene transitions (design decision)
|
|
|
|
Scene switching while live must never stutter. Supported types, most → least economical:
|
|
|
|
1. **Cut** — instant switch. The default. Zero cost.
|
|
2. **Fade** — short crossfade (~300ms).
|
|
3. **Move** — a simple, economical move transition, done to perfection and memory-efficient. The
|
|
smart streamer's bread and butter.
|
|
4. **Custom (media) transitions** — require media elements (video/stinger playback during the
|
|
transition). Heavier, but creators pay for these, so we support them. Their media follows the
|
|
same resource memory model: loaded once, catalogued by scene.
|
|
|
|
Preview shows the transition too (WYSIWYG). No wipes/slides/LUTs beyond the four above.
|
|
|
|
### Requirements:
|
|
|
|
1. **Screen** — Windows.Graphics.Capture (WinRT), enumerate displays/windows, picker
|
|
2. **Webcam** — MediaCapture (WinRT) with device enumeration
|
|
3. **Background / Image / Text** — static sources positioned/scaled/opacity
|
|
4. **Chat box** — rendered from the live chat poll (right panel is the same feed, raw)
|
|
5. **Scene compositing** — per-scene source layering (z-order = sources list order, top-to-bottom
|
|
back-to-front), preview rendered via D3DImage or MediaElement
|
|
6. **Drag/drop placement & reorder** — intuitive, visual (per design principle):
|
|
- **Preview:** click-drag a source in the center panel to reposition it; resize via handles
|
|
- **Scenes list:** drag rows to reorder scenes
|
|
- **Sources list:** drag rows to reorder sources (this *is* the z-order) — implemented
|
|
|
|
### Status: 🔶 In progress — scenes/sources UI built (add/reorder/rename, image + background overlays with move/resize/opacity/reuse); real capture/encoding pending
|
|
|
|
---
|
|
|
|
## TASK 4 — RTMP Ingest to YouTube
|
|
|
|
**Goal:** Push encoded video to YouTube's RTMP ingest.
|
|
|
|
### Requirements:
|
|
|
|
1. **Encoding** — H.264 (hardware via NVENC/AMD, fallback x264) + AAC audio; **must comply**: keyframes ≤ 4s (gopSizeLong), closed GOP, AAC/MP3 @ 44.1/48kHz, mono/stereo only
|
|
2. **RTMP push** — FFmpeg subprocess or native RTMP library, to the cached reusable stream's ingestion URL
|
|
3. **Quality ladder** — the offered tiers, with **1080p60 @ 8 Mbps as the standard/default**:
|
|
- Phone 480p @ 2.5 Mbps
|
|
- 720p60 @ 6 Mbps
|
|
- 1080p30 @ 8 Mbps
|
|
- **1080p60 @ 8 Mbps** (default — mainstream ceiling, GPU hardware-encoded so the gaming
|
|
machine never notices; upload headroom stays comfortable)
|
|
- 1440p/4K = the paid unlock tiers (monetization), not the standard offering
|
|
Ladder is sculpted by a **cached probe** (IP-only TCP vs public ingest host; no auth required).
|
|
Quality is greyed out while live because the declared resolution can't change mid-stream — but
|
|
with `variable`, we can **auto step-down** bitrate/resolution on the fly with zero API calls
|
|
(no stream recreation); 60fps presumes a hardware encoder — no hardware encoder → auto
|
|
fallback to 720p60/1080p30
|
|
4. **Stream key management** — reuse the cached reusable stream (one per channel) instead of creating a new one per go-live; prefill default YouTube ingest URL `rtmp://a.rtmp.youtube.com/live2`
|
|
5. **Health stats** — bitrate, FPS, dropped frames reported live in the bottom bar (encoder-side)
|
|
6. **One-click go live** — defaults that work out of the box
|
|
|
|
### Status: Not started
|
|
|
|
---
|
|
|
|
## TASK 5 — YouTube Live Stream Management
|
|
|
|
**Goal:** Create/bind broadcasts, monitor YouTube-side stream health — the v3 way.
|
|
|
|
### Design decisions (v3)
|
|
|
|
1. **One-click go-live** — `liveBroadcasts.insert` with `enableAutoStart=true`, `enableAutoStop=true`, `enableMonitorStream=false`, `selfDeclaredMadeForKids=false`, `latencyPreference=low`. No `transition(live)` call, no testing stage, no liveStarting polling. Encoder starts → YouTube brings it live by itself.
|
|
2. **Variable reusable stream** — `cdn.resolution=variable`, `cdn.frameRate=variable`, `isReusable=true`. Create once per channel, cache ingestion URL + stream name, reuse for every broadcast. Any quality tier works without recreation; auto step-down needs no API calls.
|
|
3. **Report-by-exception** — poll `liveStreams.list`; banner only on `healthStatus` warning/error issues (`configurationIssues[]`). Bottom strip = YouTube logo + green/red connection dot (clickable → opens the dialog).
|
|
4. **One dialog, three states** — `not connected` (sign-in) / `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.
|
|
5. **Live edits** — `liveBroadcasts.update` with part=`snippet,status` for title/description/privacy.
|
|
6. **End stream** — stop encoder → `transition(complete)`, with `enableAutoStop` as the safety net.
|
|
7. **Broadcast ID == Video ID** — one ID to track status, health, and the auto-created VOD (`recordFromStart` + `enableDvr`).
|
|
|
|
### Requirements:
|
|
|
|
1. **Broadcast creation** — title/description/privacy/scheduledStartTime via API, with the v3 flags above
|
|
2. **Reusable stream** — create once, cache + reuse; bind to broadcast
|
|
3. **Health monitoring** — poll `liveStreams.list` `healthStatus` + `configurationIssues[]`, surface banner only on warning/error
|
|
4. **Live chat** — poll `liveChat/messages`, render in right panel, support Super Chat + membership badges
|
|
5. **Error handling** — the YouTube error codes: `errorStreamInactive`, `invalidTransition`, `redundantTransition`, `liveStreamDeletionNotAllowed`, `liveStreamModificationNotAllowed`, `liveBroadcastBindingNotAllowed`
|
|
|
|
### Status: Not started
|
|
|
|
---
|
|
|
|
## TASK 6 — Layout Persistence (SQLite)
|
|
|
|
**Goal:** Scenes, sources, and asset bytes survive restarts; assets are always available.
|
|
|
|
### Design decisions
|
|
|
|
1. **SQLite database** (`Microsoft.Data.Sqlite`) at `%APPDATA%\ytLlive\ytLlive.db`; schema versioned
|
|
via `PRAGMA user_version`.
|
|
2. **Assets live in the DB, not on disk** — `Asset` table stores image bytes (BLOB) keyed by a
|
|
SHA-256 content hash (unique). Identical image content collapses to one row regardless of file
|
|
name — the 1:M resource memory model, enforced by the database. No file paths; deleting the
|
|
original file never breaks a scene.
|
|
3. **File-model save/open** — the active layout file is tracked (default is the AppData DB).
|
|
**Save Layout As… / Open Layout…** switch the active file; auto-save writes to whatever is active.
|
|
4. **Auto-save (invisible)** — ~1.5s debounce on scene add/remove/reorder/rename/hide, source
|
|
add/remove/reorder, and any source transform change; flush on window close.
|
|
5. **Schema** — `Scene` (Id, Name, IsHidden, IsChatScene, SortOrder), `Asset` (Id, Hash, Data,
|
|
PixelWidth, PixelHeight), `Source` (Id, SceneId FK cascade, AssetId FK, Type, Name, IsEnabled,
|
|
X/Y/Width/Height/Opacity, MonitorIndex, DeviceId, SortOrder). `WindowHandle` stays in-memory
|
|
(per-session). Save = transactional rewrite; orphaned assets pruned.
|
|
6. **Startup** — load the active file; seed the default five scenes (Starting/Live/BRB/Chat/Ending)
|
|
only when the DB is empty.
|
|
|
|
### Status: ✅ Implemented
|
|
|
|
---
|
|
|
|
## Backlog (future versions)
|
|
|
|
- v0.2 — Recording to local file
|
|
- v0.3 — Stream scheduling
|
|
- v0.4 — Multi-destination restreaming
|