bb5dcb4ba2
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.
40 lines
1.6 KiB
C#
40 lines
1.6 KiB
C#
namespace ytLive.Models;
|
|
|
|
public class YouTubeChannel
|
|
{
|
|
public string ChannelId { get; set; } = string.Empty;
|
|
public string DisplayName { get; set; } = string.Empty;
|
|
public string ProfileImageUrl { get; set; } = string.Empty;
|
|
public string AccessToken { get; set; } = string.Empty;
|
|
public string RefreshToken { get; set; } = string.Empty;
|
|
public DateTime TokenExpiry { get; set; }
|
|
}
|
|
|
|
/// <summary>The channel's reusable live stream (TASK 5): created once, cached,
|
|
/// rebound to every broadcast. The RTMP URL the encoder pushes to is the
|
|
/// ingestion address + "/" + stream name.</summary>
|
|
public record ReusableStream(string Id, string IngestionAddress, string StreamName)
|
|
{
|
|
public string RtmpUrl => $"{IngestionAddress}/{StreamName}";
|
|
}
|
|
|
|
public class ChatMessage
|
|
{
|
|
public string Id { get; init; } = Guid.NewGuid().ToString();
|
|
public string AuthorName { get; set; } = string.Empty;
|
|
public string AuthorChannelId { get; set; } = string.Empty;
|
|
public string AuthorImageUrl { get; set; } = string.Empty;
|
|
public string Message { get; set; } = string.Empty;
|
|
public DateTime Timestamp { get; set; }
|
|
public bool IsSuperChat { get; set; }
|
|
public double SuperChatAmount { get; set; }
|
|
public string? SuperChatCurrency { get; set; }
|
|
public bool IsMember { get; set; }
|
|
public string? MembershipLevel { get; set; }
|
|
|
|
/// <summary>TASK 41: the Test Stream dock injects synthetic member / SuperChat /
|
|
/// subscriber events locally (YouTube's insert API only supports text), so the
|
|
/// buffer can label them honestly — simulated events never reach YouTube.</summary>
|
|
public bool IsSimulated { get; set; }
|
|
}
|