diff --git a/TASKS.md b/TASKS.md index 2935c28..f756420 100644 --- a/TASKS.md +++ b/TASKS.md @@ -65,6 +65,7 @@ | 47 | Alert box video: built-in/custom alert clip (+ six-animation fallback) with read-time fade in/out + ticker (now in the preview too, 3 display methods) + alert volume in the live mix | โœ… Done (2026-09-26) | [`TASKS/task-47-alert-videos.md`](TASKS/task-47-alert-videos.md) | | 48 | Distribution & packaging: **MSIX + Store IAP** | ๐Ÿ”ถ In progress โ€” **โœ… route decided 2026-09-27 (Store MSIX + Store IAP)**; **item 1 (bundle ffmpeg) SHIPPED 2026-09-28** | [`TASKS/task-48-distribution-msix.md`](TASKS/task-48-distribution-msix.md) | | 49 | Chat profanity filter (local, opt-in, non-persistent, user word list) | โ˜ Queued (2026-09-27) | [`TASKS/task-49-chat-profanity-filter.md`](TASKS/task-49-chat-profanity-filter.md) | +| 50 | One-button "Publish this video" (resumable upload of the local recording) | โ˜ Queued (2026-09-28) | [`TASKS/task-50-publish-recording.md`](TASKS/task-50-publish-recording.md) | --- diff --git a/TASKS/task-50-publish-recording.md b/TASKS/task-50-publish-recording.md new file mode 100644 index 0000000..a91e2dc --- /dev/null +++ b/TASKS/task-50-publish-recording.md @@ -0,0 +1,101 @@ +# TASK 50 โ€” One-button "Publish this video" (resumable upload of the local recording) + +> Catalog: [`TASKS.md`](../TASKS.md) โ€” status and requirements live here. +> Carved 2026-09-28 from a creator ruling: *"For me, as a creator, I'd love a one-button +> publish this video feature. Who wouldn't?"* + +**Status:** โ˜ Queued โ€” creator-ruled, not yet scoped in detail + +**Goal:** turn the local REC-side MP4 into a channel video with one button, and stop relying on +YouTube's silent automatic archiving to get a recording onto the channel at all. + +**Size:** Sโ€“M. Media already exists; the work is a resumable-transfer lifecycle. **Not a quarter.** + +--- + +## Why + +Two reasons, and the second is the one that actually justifies it. + +**1. It fixes a defect the creator already reported as broken.** On 2026-09-26 the creator could +not get private test recordings saved to YouTube, and used the local recording as the proof of +concept instead. The mechanism is now understood (ยง Mechanism below): the archive *is* created, +the app just offers no way to see or reach it. A deliberate upload is the actual fix; the current +behaviour is a side-effect nobody designed. + +**2. It is not the heavy feature it looks like.** The instinct is to call upload a big risky +addition because it moves bytes. It doesn't: ffmpeg already moves the media, and the recording +already exists as a finished local MP4. The genuine new work is a **discrete long-running job +with a lifecycle** โ€” resumable, with progress, retry, cancel and quota. RTMP has none of that +because the connection belongs to the encoder process and simply reconnects. Different protocol, +different failure model. That is the whole cost. + +**Creator ruling, verbatim:** one button. Not a media manager, not a playlist tool. + +--- + +## Mechanism โ€” why recordings already reach YouTube, and why that's a problem + +Verified 2026-09-28 against the code: + +- `enableAutomaticArchiving` is **never set** by the app. YouTube's default for + `liveBroadcasts.insert` is `true`, so **every** broadcast the app creates is archived as a VOD + when it ends. +- Broadcasts are inserted with `privacyStatus = "private"` + (`Services/YouTubeStreamService.cs:49`), so those orphans are not public. That is correct and + must stay. +- The app has **no delete path** โ€” nothing in `Services/` deletes a broadcast or a video. The + creator clears each orphan by hand in Studio. + +So "dev mode deletes the video immediately" is a **manual Studio step today**, not app behaviour. + +### Sequencing constraint โ€” do not flip the flag early + +`enableAutomaticArchiving: false` is the correct end state: the app uploads the local file +deliberately, on demand, instead of YouTube archiving silently. **But automatic archiving is +currently the only route a recording takes to YouTube.** Setting it to `false` before publish +ships would leave test streams with nothing on the channel at all and re-create the 2026-09-26 +complaint in a worse form. + +**Therefore: ship the upload first, flip the flag as part of the same task, never before.** + +--- + +## Design notes + +- **Source is the local MP4**, not the YouTube archive. The app already has it (REC side, + `EncoderOptions.RecordPath`); the archived VOD is a duplicate and an orphan. +- **Resumable upload is mandatory, not optional.** Stream-length recordings are large (hours of + 4โ€“8 Mbps video); a plain single POST is not viable. YouTube's `videos.insert` supports + resumable sessions, and the failure mode that matters is a drop at 80%. +- **Default visibility must be `private` or `unlisted`, never `public`.** A bad take going public + is unrecoverable, and this app is used for test streams by definition. The creator picks, and + the default is the safe one. +- **A title still has to come from somewhere.** One button, not zero decisions: default to the + recording's filename, allow an edit, do not block on it. +- **Quota:** `videos.insert` costs 1600 units against a 10,000/day project allowance โ€” roughly + six uploads per day. Fine for a creator, but it is a real ceiling and the app should surface a + quota error rather than retrying into it. +- Scope already permits it: the single `auth/youtube` scope covers upload. No scope change needed. + +--- + +## Open question to settle before coding + +**Microsoft Store 11.12 (User-Generated Content).** The standing answer is that the app hosts no +user content and does not interact with third-party UGC โ€” chat is read-only and never persisted, +which is load-bearing for that answer. A publish feature makes the app *push* creator content to +a third-party platform, which touches the same area. It does not obviously break the answer (the +destination is YouTube, which has its own mature moderation, and the app stores nothing), but it +**must be re-checked against +[`research-store-certification.md`](research-store-certification.md) ยง11.12 and the IARC answers +in [`task-48`](task-48-distribution-msix.md) before submission** โ€” do not let the IARC answers go +to certification describing an app that cannot publish. + +--- + +## Precedence + +Belongs to **TASK 48** (MSIX/Store), but does **not** block the Store submission: the app is +fully shippable without it. Recommend MSIX submission first, publish second โ€” unless the creator +reverses that, in which case it moves ahead of the remaining TASK 48 items.