feat(stream): TASK 41 Test Stream mode — private test broadcast + TEST drawer
Test button next to Start runs the real private-only go-live pipeline as a session variant (IsTestStream): BeginTestStream -> extracted StartStreamingSession(alsoRecord:false) shared with the dialog path — no Go Live dialog, no 'last live' stamp, no recording. Gold top bar + TEST badge + 'End Test' button face; TEST drawer (third right-rail pull-out, three-way exclusivity) auto-opens once the liveChatId resolves. Chat tooling: Mock Chat Input sends a REAL liveChat/messages.insert (YouTubeStreamService.InsertChatMessageAsync) that round-trips the real ~2s poll and renders through the live overlay; simulated Subscriber/New Member/Super Chat events inject through the poller's MessageReceived seam (YouTubeChatService.InjectSimulatedMessage, ChatMessage.IsSimulated) — the insert API only creates textMessageEvent, so non-text events are local-only by design (ref: developers.google.com/youtube/v3/live/docs/liveChatMessages/insert). Scopes youtube + youtube.force-ssl already cover insert; no re-consent. Good Dog: ytLive.Tests/TestStreamTests.cs — coordinator protocol with fake HTTP + real-window drawer exclusivity/gate. 0 warnings; 314/314 pass (clean run; intermittent host abort is the pre-existing WinRT-webcam flake). Docs ride in the same change: TASKS.md row 41, TASKS/task-41-test-stream-mode.md, ai.md (YouTube Live API constraints), HANDOFF rewrite.
This commit is contained in:
@@ -41,6 +41,13 @@ public partial class MainViewModel : ViewModelBase
|
||||
private bool _onAirPillOn;
|
||||
private bool _isRecording;
|
||||
|
||||
/// <summary>TASK 41: the current session is a private TEST broadcast — the real
|
||||
/// go-live pipeline runs (broadcast create, reusable stream, RTMP push, chat
|
||||
/// poll), but the visuals say TEST (top-bar gold + badge), no "last live" stamp
|
||||
/// is written, and the Test drawer is open. One flag flips the whole face of the
|
||||
/// session; cleared on stop / rollback.</summary>
|
||||
private bool _isTestStream;
|
||||
|
||||
/// <summary>Resolved record folder — the configured choice, or Downloads (0th case).</summary>
|
||||
|
||||
|
||||
@@ -64,6 +71,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
OnPropertyChanged(nameof(ShowPrimaryStartButton));
|
||||
OnPropertyChanged(nameof(ShowEndStreamButton));
|
||||
OnPropertyChanged(nameof(EndButtonText));
|
||||
OnPropertyChanged(nameof(ShowTestButton));
|
||||
OnPropertyChanged(nameof(AccountStatusLightToolTip));
|
||||
OnPropertyChanged(nameof(ShowChatInactiveMessage));
|
||||
OnPropertyChanged(nameof(ShowPreviewPlaceholder));
|
||||
@@ -126,6 +134,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
OnPropertyChanged(nameof(ShowEndStreamButton));
|
||||
OnPropertyChanged(nameof(ShowPrimaryStartButton));
|
||||
OnPropertyChanged(nameof(EndButtonText));
|
||||
OnPropertyChanged(nameof(ShowTestButton));
|
||||
UpdateSessionTimer();
|
||||
}
|
||||
}
|
||||
@@ -154,8 +163,35 @@ public partial class MainViewModel : ViewModelBase
|
||||
public bool ShowEndStreamButton => IsLive || IsRecording;
|
||||
|
||||
/// <summary>The one button whose label changes: while recording it reads
|
||||
/// "Stop Recording", while live "End Stream".</summary>
|
||||
public string EndButtonText => IsRecording && !IsLive ? "Stop Recording" : "End Stream";
|
||||
/// "Stop Recording", while live "End Stream" — and while a test broadcast runs,
|
||||
/// "End Test" so the creator knows exactly what they're stopping.</summary>
|
||||
public string EndButtonText =>
|
||||
IsRecording && !IsLive ? "Stop Recording"
|
||||
: IsTestStream ? "End Test"
|
||||
: "End Stream";
|
||||
|
||||
/// <summary>TASK 41 — the Test button (top bar, left of Start): a one-click
|
||||
/// private test broadcast. Only meaningful signed-in and idle — it disappears
|
||||
/// the moment a session (live OR recording) starts.</summary>
|
||||
public bool ShowTestButton => IsOffline && !IsRecording && IsConnected;
|
||||
|
||||
public bool CanStartTest => ShowTestButton;
|
||||
|
||||
/// <summary>TASK 41 — the current session is a test broadcast (drives the TEST
|
||||
/// badge + gold top bar + "End Test" face). Internal setter is a test seam like
|
||||
/// IsRecording: <see cref="BeginTestStream"/> flips it before <see cref="StreamStatus"/>
|
||||
/// goes Streaming so the visuals initialize amber, and the stop/rollback path
|
||||
/// clears it.</summary>
|
||||
public bool IsTestStream
|
||||
{
|
||||
get => _isTestStream;
|
||||
internal set
|
||||
{
|
||||
if (!SetProperty(ref _isTestStream, value)) return;
|
||||
OnPropertyChanged(nameof(EndButtonText));
|
||||
UpdateLiveVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
public StreamHealth CurrentHealth
|
||||
{
|
||||
@@ -330,6 +366,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
public ICommand StartStreamCommand { get; }
|
||||
public ICommand StartTestCommand { get; }
|
||||
public ICommand EndStreamCommand { get; }
|
||||
public ICommand SignInCommand { get; }
|
||||
public ICommand ChooseRecordFolderCommand { get; }
|
||||
|
||||
Reference in New Issue
Block a user