using ytLive.Services; namespace ytLive.ViewModels; public partial class MainViewModel { /// The branding credit as the preview pane shows it: a master-space /// bitmap, or null while no credit is playing. It is the SAME frame the encoder /// composites into the recording/stream (), so what /// the creator sees is what the viewers get — the old preview-only TextBlock lied /// about this, and a 1080p credit that is only in the preview is not advertising /// anyone. It cannot ride a per-source VideoImageSource: it is a global /// overlay, like the social bar and the alert ticker. private System.Windows.Media.Imaging.WriteableBitmap? _brandFlashBitmap; public System.Windows.Media.Imaging.WriteableBitmap? BrandFlashImageSource { get => _brandFlashBitmap; private set { _brandFlashBitmap = value; OnPropertyChanged(nameof(BrandFlashImageSource)); OnPropertyChanged(nameof(BrandFlashVisible)); } } public bool BrandFlashVisible => _brandFlashBitmap != null; /// The credit for the output compositor, or null when none is due. Read by /// the frame pump every output frame; null for a premium user, always — /// the licence check lives inside the presenter so no caller can bypass it. public VideoFrame? BrandFlashFrame() => _brandFlash.Frame; /// Test-only seam: advances the credit's clock deterministically, exactly /// like . It proves the wiring /// (the presenter is started, and NOT gated on IsLive) without waiting out /// the real 5s DispatcherTimer — and because Advance only advances the /// cadence while the presenter is running, a credit coming out of here is proof /// that Start() happened at construction. Never call this from product code. internal void AdvanceBrandFlash(double seconds) => _brandFlash.Advance(seconds); /// Publish one credit frame to the preview pane, UI thread only (the /// presenter fires this from its own dispatcher tick, so the bitmap write is legal). /// Reuses one and /// overwrites its back buffer — a fresh 8MB bitmap per tick would churn the render /// cache for nothing. internal void PublishBrandFlashPreview(VideoFrame? frame) { if (frame == null) { if (_brandFlashBitmap != null) BrandFlashImageSource = null; return; } if (_brandFlashBitmap == null || _brandFlashBitmap.PixelWidth != frame.Width || _brandFlashBitmap.PixelHeight != frame.Height) { BrandFlashImageSource = new System.Windows.Media.Imaging.WriteableBitmap( frame.Width, frame.Height, 96, 96, System.Windows.Media.PixelFormats.Bgra32, null); } var target = _brandFlashBitmap!; target.WritePixels( new System.Windows.Int32Rect(0, 0, frame.Width, frame.Height), frame.BgraPixels, frame.Stride, 0); // The bitmap instance is unchanged, so the pane's Source binding still points // at it — nudge it so WPF re-reads the back buffer this tick. OnPropertyChanged(nameof(BrandFlashImageSource)); } }