d4aa588da0
Replace the external StreamElements feed with NATIVE YouTube events per the creator's 2026-09-23 question. Chat parity half: - YouTubeChatService decodes ALL SIX liveChat/messages event types into ChatMessage.Kind (superChat/superSticker/newSponsor/membershipGifting/ giftMembershipReceived/memberMilestoneChat) — the four formerly-empty overlay rows are real now — and re-arms its one-shot poll on the server's pollingIntervalMillis (streamList connection semantics; clamp 1000-6000ms, maxResults=2000). ParsePage internal static seam + ChatPage record for deterministic tests; optional HttpClient ctor seam kept; InjectSimulatedMessage (TASK 41) preserved. Alerts half (creator rulings: one celebration zone, six UNIQUE animations, no menus/polls, sub mention = chat row only, NO viewer count): - New SourceType.AlertBox 'Stream Alerts' (one per layout, CanAddAlerts gate mirroring chat; idle = transparent). - AlertRenderer: six distinct branded animations (SuperChat slide-up/shine/ count-up, SuperSticker scale-pop, NewMember drop-in/flash, MemberGift slide-left/ chip-fan, GiftReceived confetti, MemberMilestone rise/growth-bar), every card drawing the 'made with LlamaCasty!' brand line. - AlertOverlayLayer: true component (Commit-G pattern) — queue (cap 10, drop tail never stall), 33ms UI ticker, cache-first RenderFrame + UpdatePreview, Advance(double) as the deterministic test clock; ChatEventKind.None rows never enqueue. - Wired: resolver RenderAlertBox, _alertLayer ctor + dispose, LoadLayout previews, Add menu item (Controls/LeftPanel.xaml), TestSessionViewModel sims tagged (member->NewMember, superchat->SuperChat). Good Dog ONE integration test: AlertLayerTests (RealApp STA, real WPF raster) — ParsePage classifies all six kinds + cadence fields; None rows enqueue nothing; six events play pairwise-distinct moving frames then drain to null. Gate: clean build 0 warnings; full suite 316/317, the one failure (LayerReorderPersistenceTests.RealMouseDrag) repros on the clean tree — the known environmental class (real-mouse-drag no-ops with a game/fullscreen window focused). References (OBS/overlay ecosystem): - streamList semantics: https://developers.google.com/youtube/v3/live/docs/liveChatMessages/streamList - OBS alert-box pattern (designated celebration zone, idle transparent): creator-chosen model
64 lines
2.3 KiB
C#
64 lines
2.3 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using ytLive.Helpers;
|
|
using ytLive.Models;
|
|
using ytLive.Services;
|
|
|
|
namespace ytLive.ViewModels;
|
|
|
|
public partial class MainViewModel : ViewModelBase
|
|
{
|
|
|
|
private readonly ChatOverlayLayer _chatLayer;
|
|
private readonly AlertOverlayLayer _alertLayer;
|
|
|
|
/// <summary>The live chat message buffer — the same collection the
|
|
/// <see cref="ChatOverlayLayer"/> owns; here so XAML can bind it and the
|
|
/// left panel can subscribe to its changes.</summary>
|
|
public ObservableCollection<ChatMessage> ChatMessages => _chatLayer.Messages;
|
|
|
|
/// <summary>The Add → YouTube Chat menu item: enabled only while no chat layer exists
|
|
/// anywhere — one chat overlay per layout.</summary>
|
|
public bool CanAddYouTubeChat
|
|
=> StagedScene != null
|
|
&& !Scenes.Any(s => s.Elements.OfType<Source>().Any(e => e.Type == SourceType.ChatBox));
|
|
|
|
/// <summary>The Add → Stream Alerts menu item: one alert zone per layout
|
|
/// (multiple zones would double-play every event).</summary>
|
|
public bool CanAddAlerts
|
|
=> StagedScene != null
|
|
&& !Scenes.Any(s => s.Elements.OfType<Source>().Any(e => e.Type == SourceType.AlertBox));
|
|
|
|
public bool ShowChatInactiveMessage => !IsLive;
|
|
|
|
private void OnChatMessageReceived(ChatMessage message)
|
|
{
|
|
_chatLayer.OnMessageReceived(message, Scenes);
|
|
_alertLayer.OnMessageReceived(message, Scenes);
|
|
}
|
|
|
|
private void UpdateChatBoxPreview(Source chatBox, IReadOnlyList<ChatMessage>? messages = null)
|
|
=> _chatLayer.UpdatePreview(chatBox, messages);
|
|
|
|
private VideoFrame? RenderChatBox(Source chatBox)
|
|
=> _chatLayer.RenderFrame(chatBox);
|
|
|
|
private void UpdateAlertBoxPreview(Source alertBox)
|
|
=> _alertLayer.UpdatePreview(alertBox);
|
|
|
|
private VideoFrame? RenderAlertBox(Source alertBox)
|
|
=> _alertLayer.RenderFrame(alertBox);
|
|
|
|
private void StartMockChatPreview()
|
|
=> _chatLayer.StartMockPreview(Scenes);
|
|
|
|
private void StopMockChatPreview()
|
|
=> _chatLayer.StopMockPreview();
|
|
|
|
private void UpdateActiveChatBoxPreviews()
|
|
=> _chatLayer.UpdateConfiguredBoxes(Scenes);
|
|
|
|
private void UpdateActiveAlertBoxPreviews()
|
|
=> _alertLayer.UpdateConfiguredBoxes(Scenes);
|
|
}
|