Monetization policy locked + dev pause recorded (docs changeset, 2026-08-14): the one paid line becomes an annual subscription — early access $49.99/yr → $99/yr list at GA, grandfather-while-subscribed with lapse → list on renewal (no escalation matrix); free tier unchanged (branding flash is the free tier's billboard + no Alerts, which is the one paid feature); billing NOT locked to itch.io (hunted fact: itch.io has no native subscription billing — candidates Gumroad, whose native affiliate program is the tiebreaker, and Lemon Squeezy); support = in-app bug-report → git issues with a when-I-get-around-to-it cadence + emergency patches. ai.md Monetization section rewritten + stale paid-unlock seams fixed (L115 subscription-entitlement wording, the About hub's tabled billing PremiumUrl seam); TASKS.md gains TASK 23 (Monetization — billing provider decision, unlock mechanism with lapse→flash-returns enforcement, in-app bug-report loop, Unlock Premium seam lighting, Alerts gating, and line-item pointers for the early-access strategy, affiliate/ads phase 2, and the channel revival); HANDOFF.md records the decisions + the development pause (resume streaming = pair-programming dev streams via OBS + The Division 2 under the gramps/GOAT persona, 67 and gaming since 1981, 3D-printing content retired; dogfood switch to ytLlive once TASK 5 lands; resume point = TASK 5). Stale itch.io assertions corrected across comments and indexes (MainViewModel.cs PremiumUrl/IsPremium seams, MainWindow.xaml tooltip, AboutHubTests comment, ViewModels/index.md, TASK 9 out-of-scope note). Build 0 warnings, 196 tests passing
This commit is contained in:
+34
-30
@@ -7,35 +7,39 @@
|
||||
|
||||
## Session state (last updated: 2026-08-14)
|
||||
|
||||
- **Branch:** `main` (audio milestone work is uncommitted, see "Uncommitted").
|
||||
- **AUDIO MILESTONE (TASK 9) — SHIPPED this session.** Real stream audio + voice filters + auto-duck
|
||||
+ free TRAX background music. Build **0 warnings**, full suite **196 passing** (ONE integration
|
||||
test: `AudioPipelineTests.Mix_WithFiltersDuckAndGain_Lands_On_AudioPipe`). Everything below in the
|
||||
milestone section is DONE. **NOT yet committed** — next step is review + commit (with `ai.md`/
|
||||
`TASKS.md`/this file already updated in the same changeset).
|
||||
- **Two build-time deviations from the settled plan (both recorded in ai.md + TASKS.md):**
|
||||
1. `MusicPlayer` uses **`WaveOutEvent`** (new sibling package `NAudio.WinMM` 2.2.1) — not `WasapiOut`
|
||||
as planned; `WaveOutEvent` isn't in `NAudio.Wasapi`/`NAudio.Core`.
|
||||
2. No forced 48 kHz capture — NAudio 2.2.1's `WasapiCapture` has no overridable
|
||||
`GetDefaultMixFormat`, so both sources run the device mix format and the mixer's
|
||||
`TinyResampler` normalizes any rate to 48 kHz (resampler IS the design, not a fallback).
|
||||
3. `FfmpegEncoder.cs` is **untouched** — the VM closes the audio pipe (`_audioMixer.StopLive()`) in
|
||||
`StopStream` BEFORE the frame pump stops (audio EOF then video EOF, in order).
|
||||
- **SERVER RECOVERY (llamachile.tube / YunoHost / DO droplet) — done earlier session.** Side task
|
||||
complete; **rotate the DO API token** (control-panel only) and any password no longer trusted.
|
||||
- **Shipped, all pushed before this session:** TASK 21 (creator-hub About, `3d92bd0`), TASK 8 (meter
|
||||
+10 dB), TASK 4 ship step 7 (one-click go-live + private-only). **174 tests passing, 0 warnings.**
|
||||
- **Uncommitted (whole TASK 9 milestone):** `Services/Audio/` (new `VoiceFilterChain.cs`,
|
||||
`AudioRingBuffer.cs`, `AutoDucker.cs`, `TinyResampler.cs`, `MusicPlayer.cs`, `NamedPipeAudioWriter.cs`,
|
||||
rewritten `AudioMixer.cs`, simplified `WasapiMicAudioSource.cs`), `Models/Music.cs` (new),
|
||||
`FfmpegArgs.cs`, `EncoderOptions.cs`, `LayoutStore.cs` (v9), `MainViewModel.cs`, `MainWindow.xaml`,
|
||||
`MainWindow.xaml.cs`, `ytLive.csproj` (NAudio.WinMM), `ytLive.Tests/` (`AudioPipelineTests.cs` new,
|
||||
`FfmpegEncoderTests.cs` + `LayoutStorePersistenceTests.cs` updated), and docs `ai.md`, `TASKS.md`,
|
||||
`HANDOFF.md`.
|
||||
- **Next step:** commit the milestone (docs already staged in the same changeset), push, then start
|
||||
**TASK 5** (reusable stream → `_rtmpUrlProvider` — the last blocker before go-live actually encodes).
|
||||
Then **itch.io product page setup** (premium URL lands in `MainViewModel.PremiumUrl`, lights up
|
||||
Unlock Premium). Optional, not queued: rewriting the healed entry's `ProfileUrl` to
|
||||
- **Branch:** `main`, tracking `origin/main` at **`f2f6401`** (pushed). Working tree was clean until the
|
||||
monetization docs changeset below.
|
||||
- **AUDIO MILESTONE (TASK 9) — SHIPPED + COMMITTED + PUSHED this session.** Real stream audio + voice
|
||||
filters + auto-duck + free TRAX background music. Build **0 warnings**, full suite **196 passing**
|
||||
(ONE integration test: `AudioPipelineTests.Mix_WithFiltersDuckAndGain_Lands_On_AudioPipe`). Commits
|
||||
**`6d71ace`** (milestone) + **`f2f6401`** (secrets cleanup), force-pushed (`725f5a7...f2f6401`).
|
||||
- **Secrets scrubbed from git history.** The DO token + passwords recorded in `HANDOFF.md` were removed
|
||||
from the working file and **purged from all history** via `git filter-branch` over `3d92bd0..main`
|
||||
+ `git gc --prune=now --aggressive` (scan of all refs = 0 hits). They still exist in chat — **keep
|
||||
treating as compromised**; rotate the DO API token. Old objects remain in llgit's store until server
|
||||
GC (unreachable, not fetchable). If a value is ever needed, `git reflog` is expired — it's gone.
|
||||
- **MONETIZATION LOCKED (2026-08-14, creator).** One paid line = **annual subscription**: early access
|
||||
**$49.99/yr** → **$99/yr list at GA**, **grandfather-while-subscribed**, lapse → list on renewal.
|
||||
Free tier unchanged (branding flash = the billboard + no Alerts; Alerts is the paid feature).
|
||||
**Billing NOT itch-locked** — itch.io has no native subscription billing; candidates Gumroad (native
|
||||
affiliates = tiebreaker) / Lemon Squeezy. Support = in-app bug-report → git issues, "when I get
|
||||
around to it" + emergency patches. Full policy in `ai.md` → Monetization; scoped plan + line-items
|
||||
in `TASKS.md` **TASK 23**.
|
||||
- **DEVELOPMENT PAUSED (creator's call 2026-08-14) — resuming streaming instead.** Channel revival =
|
||||
**pair-programming dev streams via OBS** (ytLlive can't go live yet: private-only + `_rtmpUrlProvider`
|
||||
null) + **The Division 2** gaming streams under the **"gramps/GOAT"** persona (67, gaming since 1981).
|
||||
3D-printing content retired (printer hard down, novelty gone). **Dogfood switch** later: when TASK 5 +
|
||||
go-live capability land, stream *with* ytLlive — Division 2 (loud game + voice) is the demo for the
|
||||
ducking/voice-filter story. Early-access strategy + affiliate/ads are their own future line-item tasks.
|
||||
- **Docs changeset in flight (THIS changeset):** `ai.md` Monetization rewritten + stale paid-unlock seams
|
||||
fixed; `TASKS.md` TASK 23 added + TASK 21's itch.io URL mention corrected; this file rewritten. Commit
|
||||
+ push as one change.
|
||||
- **Shipped, all pushed:** TASK 21 (creator-hub About, `3d92bd0`), TASK 8 (meter +10 dB), TASK 4 ship
|
||||
step 7 (one-click go-live + private-only), TASK 9 audio milestone (`6d71ace`), secrets cleanup
|
||||
(`f2f6401`).
|
||||
- **Resume point (when dev starts again):** **TASK 5** — reusable stream → `_rtmpUrlProvider` (the last
|
||||
blocker before go-live actually encodes + pushes). Then TASK 23 (billing + unlock + support).
|
||||
Optional, not queued: rewriting the healed entry's `ProfileUrl` to
|
||||
`https://mastodon.llamachile.tube/@gramps` (user must say the word).
|
||||
|
||||
## TASK 9 audio milestone — SHIPPED 2026-08-14 (what changed)
|
||||
@@ -67,7 +71,7 @@ and `ai.md` ("Live audio capture"). Highlights:
|
||||
start-of-stream silence race was seen and eliminated).
|
||||
|
||||
**Out of scope this branch:** IP webcam, chat box, alt-key crop, credits, bg removal, music-off-VOD
|
||||
track (YouTube mutes VODs with copyrighted music — future feature), itch.io `PremiumUrl`.
|
||||
track (YouTube mutes VODs with copyrighted music — future feature), `PremiumUrl` (TASK 23 seam).
|
||||
|
||||
- **Landmines:**
|
||||
- Never add another test that constructs `new App()` — use `RealAppHost.Run(...)` (shared STA host
|
||||
|
||||
+1
-1
@@ -1158,7 +1158,7 @@
|
||||
<Button Content="Unlock Premium" Command="{Binding OpenPremiumCommand}"
|
||||
Style="{StaticResource YtButtonSecondary}"
|
||||
IsEnabled="{Binding IsPremiumAvailable}"
|
||||
ToolTip="Coming soon — the itch.io unlock page is being set up"/>
|
||||
ToolTip="Coming soon — subscription billing is on the way (TASK 23)"/>
|
||||
</StackPanel>
|
||||
|
||||
<Button Content="Licenses & legal" Command="{Binding OpenLicensesCommand}"
|
||||
|
||||
@@ -115,7 +115,7 @@ Lifecycle: `created → ready → [testing] → live → complete` (transitional
|
||||
18. ☐ **Chat box** — YouTube live chat rendered *on* the stream so viewers read along in-video
|
||||
19. ❌ **Background removal (milestone 2)** — ONNX Runtime + DirectML, MediaPipe Selfie Segmentation — deliberately NOT in this build
|
||||
20. ☐ **Alerts** — Super Chat / membership / subscribe pop-ins; build after the six; **the one paid feature** (see Monetization in `ai.md`)
|
||||
21. ✅ **Logo + richer in-app About (2026-08-13, queued → 2026-08-14 SHIPPED)** — the ytLlive wordmark in the top bar opens the About overlay (already wired); the About overlay is now the **creator hub**: the real logo (the "llama fortnite superman logo" from the creator's vault, copied to `Assets/llama-logo.png` — the creator's own art, no third-party license), plus **creator-hub links** — llama chile shop on YouTube (`MainViewModel.ChannelUrl`), Mastodon (`https://mastodon.llamachile.tube/@gramps`), **Buy me a coffee** (`https://buymeacoffee.com/llamachiley` — live), and **Unlock Premium** (greyed "coming soon" — the itch.io product URL is a tabled seam, `PremiumUrl`, until the account is set up). A **Licenses & legal** button flips the About overlay to an in-app scrolling panel that loads the full `THIRD-PARTY-NOTICES.txt` text (`MainViewModel.ShowLicenses` reads the shipped file from the executable directory on first open; graceful "not found" fallback — **never the OS viewer, everything stays in-app**); "← Back to About" returns. The About overlay is also the in-app home of the notices — the top-bar About button that opened the file in the OS viewer was removed on 2026-08-13 for exactly this reason. Integration test (Good Dog Rule — ONE): `AboutHubTests.About_Opens_InApp_Licensing_Loads_Shipped_Notices` drives the real window + VM, asserting the hub opens, the link URLs are real, the licensing panel loads the shipped notices text (contains "Third-Party Notices" + "LGPL"), and back returns to the hub. 174 tests passing, 0 warnings
|
||||
21. ✅ **Logo + richer in-app About (2026-08-13, queued → 2026-08-14 SHIPPED)** — the ytLlive wordmark in the top bar opens the About overlay (already wired); the About overlay is now the **creator hub**: the real logo (the "llama fortnite superman logo" from the creator's vault, copied to `Assets/llama-logo.png` — the creator's own art, no third-party license), plus **creator-hub links** — llama chile shop on YouTube (`MainViewModel.ChannelUrl`), Mastodon (`https://mastodon.llamachile.tube/@gramps`), **Buy me a coffee** (`https://buymeacoffee.com/llamachiley` — live), and **Unlock Premium** (greyed "coming soon" — the **billing** product URL is a tabled seam, `PremiumUrl`, until TASK 23 picks the subscription provider). A **Licenses & legal** button flips the About overlay to an in-app scrolling panel that loads the full `THIRD-PARTY-NOTICES.txt` text (`MainViewModel.ShowLicenses` reads the shipped file from the executable directory on first open; graceful "not found" fallback — **never the OS viewer, everything stays in-app**); "← Back to About" returns. The About overlay is also the in-app home of the notices — the top-bar About button that opened the file in the OS viewer was removed on 2026-08-13 for exactly this reason. Integration test (Good Dog Rule — ONE): `AboutHubTests.About_Opens_InApp_Licensing_Loads_Shipped_Notices` drives the real window + VM, asserting the hub opens, the link URLs are real, the licensing panel loads the shipped notices text (contains "Third-Party Notices" + "LGPL"), and back returns to the hub. 174 tests passing, 0 warnings
|
||||
22. ✅ **Voice filters on the mic channel (2026-08-13 queued → SHIPPED 2026-08-14 inside TASK 9, the audio milestone)** — the standard four applied to the sound input path (before the meter/encoder mix): **bass boost, treble, noise suppression, compressor** (set decided with the creator 2026-08-13). Noise suppression = a **pure-C# noise gate** (creator chose over RNNoise / a second ffmpeg `afftdn` pipe, 2026-08-14 — KISS). Always-on — no UI knobs; the mic stays the creator's single audio control
|
||||
|
||||
### The Minimal Source Set (design decision — do not expand casually)
|
||||
@@ -554,7 +554,7 @@ the validator → persisted), compositor bar overlay (top/bottom + above-flash),
|
||||
|
||||
**Testing (Good Dog Rule — ONE integration test):** `AudioPipelineTests.Mix_WithFiltersDuckAndGain_Lands_On_AudioPipe` — real filter chain + mixer + ducker + `NamedPipeAudioWriter`, fake `IAudioSource`s, test-side `NamedPipeClientStream` reads the bytes and asserts post-filter/post-gain/post-duck mixed stereo (ring buffers pre-filled so the first pipe tick already carries real audio — avoids a start-of-stream silence race). Units: each DSP stage (known sine-in → expected gain), ring buffer wrap/underflow/overwrite, ducker envelope, resampler (down/up/stateful/identity), `FfmpegArgs` pipe+map, schema v9 roundtrip + no-music-row. Existing tests stay green.
|
||||
|
||||
**Out of scope (this branch):** IP webcam (video-only when it lands — never an audio input), chat box source (TASK 3 item 18), alt-key crop, credits, background removal, music-off-VOD, itch.io `PremiumUrl` (TASK 21 seam).
|
||||
**Out of scope (this branch):** IP webcam (video-only when it lands — never an audio input), chat box source (TASK 3 item 18), alt-key crop, credits, background removal, music-off-VOD, `PremiumUrl` (TASK 23 seam).
|
||||
|
||||
---
|
||||
|
||||
@@ -590,6 +590,51 @@ the validator → persisted), compositor bar overlay (top/bottom + above-flash),
|
||||
|
||||
---
|
||||
|
||||
## TASK 23 — Monetization: subscription billing + in-app unlock + support (2026-08-14, scoped)
|
||||
|
||||
**Goal:** turn the settled monetization policy (see `ai.md` → Monetization) into a paying product line:
|
||||
annual subscription billing, an in-app unlock seam, and the in-app bug-report → git-issues support loop.
|
||||
|
||||
**Decisions already locked (2026-08-14, creator):** one paid line = **annual subscription** (early
|
||||
access **$49.99/yr** → **$99/yr list at GA**; grandfather-while-subscribed, lapse → list on renewal;
|
||||
no escalation matrix). Free tier unchanged (branding flash + no Alerts; the flash is the free tier's
|
||||
billboard — distribution, not compromise). Billing **not locked to itch.io** (hunted fact: itch.io has
|
||||
no native subscription billing); candidates **Gumroad** (native affiliates = tiebreaker) and **Lemon
|
||||
Squeezy**. Support = in-app bug-report → git issues; "when I get around to it" cadence + emergency
|
||||
patches. Unlock outcome: flash removal flips `BrandFlashEnabled` off + **Alerts** on.
|
||||
|
||||
### Status: 🔶 Scoped — nothing built; this entry records the plan + decisions
|
||||
|
||||
1. ☐ **Billing provider decision** — Gumroad vs Lemon Squeezy (annual subs + license-key verification
|
||||
APIs; Gumroad's native affiliate program is the tiebreaker if the TASK 23 affiliate line-item matters)
|
||||
2. ☐ **Unlock mechanism** — license-key entry + online/offline verification + persisted entitlement
|
||||
(extends `LayoutStore`, schema bump) + renewal/lapse handling (**lapse → branding flash returns** —
|
||||
the flash is the enforcement and the billboard)
|
||||
3. ☐ **In-app bug-report → git issues** — a built-in report control that files an issue (title/body/
|
||||
log attachment) into the creator's git repo; the support loop, per the settled support model
|
||||
4. ☐ **"Unlock Premium" seam** — the About hub's greyed button (TASK 21; `PremiumUrl` is the tabled
|
||||
billing URL) lights up and routes to the billing page / unlock dialog
|
||||
5. ☐ **Alerts gating** — the Super Chat pop-ins (TASK 3 item 18 / minimal source set item 7) are the
|
||||
one paid feature; subscription state gates them
|
||||
6. **Line-item pointers (their own big tasks — do NOT start here):**
|
||||
- ☐ **Early-access strategy** — the 90-day funnel (founder window $49.99 → GA $99), waitlist,
|
||||
testimonials/QA, clips → Shorts. Separate planning effort.
|
||||
- ☐ **Affiliate & ads (phase 2)** — own-channel gear affiliates from day one (organic), a
|
||||
ytLlive referral program gated behind GA + happy users, YPP as the channel's ad revenue, paid
|
||||
ads deferred.
|
||||
- ☐ **Channel revival (the prequel to both)** — **resume streaming**: pair-programming dev
|
||||
streams via **OBS** (ytLlive can't go live yet — private-only + `_rtmpUrlProvider` null) +
|
||||
**The Division 2** gaming streams under the **"gramps/GOAT"** persona (67, gaming since 1981).
|
||||
3D-printing content retired (printer hard down, novelty gone). **Dogfood switch** — once TASK 5 +
|
||||
go-live capability land, stream *with* ytLlive; Division 2 (loud game + voice) is the demo for the
|
||||
ducking/voice-filter story. The flash billboard + the grandfather promise are the monetization
|
||||
hooks this line-item feeds.
|
||||
|
||||
**Out of scope (TASK 23):** the early-access strategy itself, the affiliate program, channel content
|
||||
planning — those are separate line-item tasks. TASK 23 owns billing + unlock + support + gating only.
|
||||
|
||||
---
|
||||
|
||||
## TASK 6 — Layout Persistence (SQLite)
|
||||
|
||||
**Goal:** Scenes, sources, and asset bytes survive restarts; assets are always available.
|
||||
|
||||
@@ -140,14 +140,14 @@ public class MainViewModel : ViewModelBase
|
||||
// integration test can assert they point at real destinations.
|
||||
public const string ChannelUrl = "https://youtube.com/@llamachileshop";
|
||||
public const string MastodonUrl = "https://mastodon.llamachile.tube/@gramps";
|
||||
// Tabled 2026-08-14: the itch.io account was just created — the product page
|
||||
// (premium unlock) lands here once set up. Coffee/thanks URL is live.
|
||||
// Tabled 2026-08-14: the billing provider is not chosen yet (Gumroad/Lemon Squeezy — TASK 23);
|
||||
// the subscription page (premium unlock) lands here once set up. Coffee/thanks URL is live.
|
||||
public const string PremiumUrl = "";
|
||||
public const string CoffeeUrl = "https://buymeacoffee.com/llamachiley";
|
||||
public static string AppVersionLabel => $"Version {typeof(MainViewModel).Assembly.GetName().Version?.ToString(3)}";
|
||||
|
||||
/// <summary>The About hub's Unlock Premium button lights up only once the
|
||||
/// itch.io product URL is set (tabled 2026-08-14 — account in setup).</summary>
|
||||
/// billing URL is set (tabled 2026-08-14 — provider undecided, TASK 23).</summary>
|
||||
public bool IsPremiumAvailable => PremiumUrl.Length > 0;
|
||||
|
||||
/// <summary>Full in-app licensing/legal text, loaded on demand from the
|
||||
@@ -244,7 +244,7 @@ public class MainViewModel : ViewModelBase
|
||||
public SocialsConfig? Socials => _socials;
|
||||
|
||||
/// <summary>Freemium: 6 slots — YT + 1 other, the rest locked. Premium seam: all 6 open.</summary>
|
||||
private static bool IsPremium => false; // itch.io unlock deferred — seam only
|
||||
private static bool IsPremium => false; // unlock deferred (TASK 23) — seam only
|
||||
|
||||
/// <summary>Canvas.Top for the social bar: 0 = top, 1040 = bottom (40px from the 1080 edge).</summary>
|
||||
public double SocialBarTop => _socials?.BarPosition == SocialBarPosition.Top ? 0 : 1040;
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -112,7 +112,7 @@ C# / WPF (.NET 8) following MVVM:
|
||||
### Current limitations / TODOs
|
||||
|
||||
- `Helpers/OAuthCredentials.cs` contains the real ClientId/ClientSecret. Auth is complete and the session **persists via Windows DPAPI** (`Helpers/TokenStore.cs` → `%APPDATA%\ytLlive\ytLlive.auth`, CurrentUser scope), reloaded best-effort at startup with a proactive refresh of a near-expiry access token. Sign-in/Change Account lives **inside the Start Stream dialog** (two-state flow — no separate Connect button). A **graceful End Livestream signs out**: `StopStream()` clears the session + token, so the next go-live needs a fresh sign-in; a crash never runs End, so the token survives and the creator stays signed in. `YouTubeAuthService` takes an optional `HttpClient` + `sessionChanged` callback (test seam + save hook; services are still constructed in `MainViewModel`)
|
||||
- Scene/source/asset layout + the social bar persist (SQLite, schema v8); the OAuth session persists (DPAPI); the paid-unlock state does not (yet — itch.io key verification pending)
|
||||
- Scene/source/asset layout + the social bar persist (SQLite, schema v8); the OAuth session persists (DPAPI); the paid-unlock state does not (yet — subscription entitlement verification pending, TASK 23)
|
||||
- `YouTubeStreamService` uses hardcoded `1080p`/`60fps` and per-broadcast streams — must switch to the v3 `variable` reusable stream
|
||||
- Webcam capture is shipped (milestone 1); the live desktop/game backdrop is shipped (ship task #1); **the output compositor (TASK 4 ship step 1) is SHIPPED**, **the FFmpeg locator (TASK 4 ship step 2) is SHIPPED**, **the encoder + RTMP push (TASK 4 ship step 3) is SHIPPED**, **WASAPI audio capture (TASK 4 ship step 4) is SHIPPED** — full plan in `TASKS.md`; the frame-pipeline wiring follows (its own PR)
|
||||
- `StreamConfig` defaults (`TargetBitrate=6000`, `Resolution="1920x1080"`) are stale — the live dropdown drives `StreamHealth.CurrentBitrate`/`FPS` instead
|
||||
@@ -558,10 +558,10 @@ guardrails below — written down so a future "quick fix" never reintroduces a G
|
||||
release blocker). Queued early is wrong; the release pass owns it.
|
||||
- **Break the in-app About requirement** — since TASK 21 the About overlay is the app's in-app creator
|
||||
hub: the real logo, the creator links (YouTube channel, Mastodon, Buy me a coffee, and the greyed
|
||||
"Unlock Premium" seam whose itch.io URL is tabled), and a **Licenses & legal** sub-panel that loads
|
||||
"Unlock Premium" seam whose billing URL is tabled), and a **Licenses & legal** sub-panel that loads
|
||||
`THIRD-PARTY-NOTICES.txt` from the executable directory and renders it in a scrolling panel —
|
||||
**never the OS viewer, no Notepad, no browser tab**. The premium/coffee link constants live in
|
||||
`MainViewModel` (`PremiumUrl`, `CoffeeUrl`, `ChannelUrl`, `MastodonUrl`); filling in the itch.io URL
|
||||
`MainViewModel` (`PremiumUrl`, `CoffeeUrl`, `ChannelUrl`, `MastodonUrl`); filling in the billing URL
|
||||
lights up the premium button. Do not reintroduce an "open the notices in Notepad" button.
|
||||
|
||||
## Design Principle
|
||||
@@ -576,9 +576,8 @@ Apply this to every UI decision:
|
||||
|
||||
## Monetization (design decision — the branding flash 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 forever: all streams unlimited, no time caps, no per-feature paywalls. The **one paid line is an
|
||||
annual subscription** (settled 2026-08-14 — replaces the earlier "one-time unlock" shape):
|
||||
|
||||
- **Free:** a periodic full-frame branding flash — "made with ytLlive!" rendered big and centered at
|
||||
~25% opacity for about one second (soft 250ms fade in/out), repeated every 300s, on the live output
|
||||
@@ -586,8 +585,23 @@ delivery; we never own a server or a key shop):
|
||||
(`MainWindow.xaml` CanvasGrid) + `BrandFlashTimer` in `MainViewModel` — cadence 300s, first flash
|
||||
~5s after go-live, only while live or recording. An always-on watermark can be cropped or covered;
|
||||
an intermittent full-frame flash can't be cropped and is impractical to edit around on a live feed.
|
||||
- **Paid (one-time):** branding flash removed (flips `BrandFlashEnabled` off) + **Alerts**
|
||||
(Super Chat / membership / subscribe pop-ins).
|
||||
**The flash is also the free tier's billboard** — every free stream advertises ytLlive to its own
|
||||
viewers; the free tier is distribution, not compromise.
|
||||
- **Paid (annual subscription):** branding flash removed (flips `BrandFlashEnabled` off) + **Alerts**
|
||||
(Super Chat / membership / subscribe pop-ins). Alerts is the recurring-value engine — a live-API
|
||||
feature we maintain forever, which is the strongest argument for a yearly bill.
|
||||
- **Pricing:** early-access founders rate **$49.99/yr** → **$99/yr list at GA** (v1). **Grandfathering:
|
||||
early adopters keep $49.99/yr for as long as the subscription is maintained**; a lapse means renewal
|
||||
at list. That's the whole policy — no escalation matrix (a realistic product lifetime is a few years;
|
||||
keep the promise simple).
|
||||
- **Billing:** NOT locked to itch.io (creator's call 2026-08-14). Hunted fact — **itch.io has no native
|
||||
subscription billing** (no annual/recurring product billing; only pay-what-you-want, pre-orders,
|
||||
early-access, keys, and a Patreon integration). Candidates for annual subs + license-key verification:
|
||||
**Gumroad** (its native affiliate program is a tiebreaker if the affiliate angle in TASK 23 matters)
|
||||
and **Lemon Squeezy**. Decision deferred to TASK 23.
|
||||
- **Support (creator's model):** in-app bug-reporting mechanism → issues into git; most queries are
|
||||
how-tos / feature requests / manual-skimmers. Maintenance cadence = "when I get around to it" with
|
||||
emergency patches; not a 24/7 service promise.
|
||||
|
||||
Deliberately rejected: always-on watermark (obscurable — replaced by the flash), hard stream-time
|
||||
cutoffs (the worst dead end — a stream dying mid-broadcast reads as broken, and YouTube streams
|
||||
|
||||
@@ -42,7 +42,7 @@ public sealed class AboutHubTests
|
||||
Assert.False(vm.IsLicensesOpen);
|
||||
|
||||
// The hub links resolve to real destinations (channel + mastodon +
|
||||
// coffee are live; premium is the tabled itch.io seam).
|
||||
// coffee are live; premium is the tabled billing seam).
|
||||
Assert.Equal("https://youtube.com/@llamachileshop", MainViewModel.ChannelUrl);
|
||||
Assert.Equal("https://mastodon.llamachile.tube/@gramps", MainViewModel.MastodonUrl);
|
||||
Assert.Equal("https://buymeacoffee.com/llamachiley", MainViewModel.CoffeeUrl);
|
||||
|
||||
Reference in New Issue
Block a user