Model the AlertBox and standalone ticker with shared settings; scene tickers render the in-memory active and queued events without saving history. Keep AlertBox content active-event-only. Ref: https://obsproject.com/kb/obs-studio-overview
94 lines
4.3 KiB
C#
94 lines
4.3 KiB
C#
using System.Linq;
|
|
using ytLive.Models;
|
|
using ytLive.Services;
|
|
|
|
namespace ytLive.ViewModels;
|
|
|
|
public partial class MainViewModel
|
|
{
|
|
/// <summary>The alert announcement strip as the preview pane shows it: a bitmap the
|
|
/// size of the Stream Alerts box, or null when no alert is playing (or a Flash is
|
|
/// in its off half).
|
|
/// <para><b>Creator ruling 2026-09-26:</b> the strip draws INSIDE the alert box, so
|
|
/// the pane positions and sizes this element to the box's own rect
|
|
/// (<see cref="AlertTickerLeft"/>/<see cref="AlertTickerTop"/>/
|
|
/// <see cref="AlertTickerWidth"/>/<see cref="AlertTickerHeight"/>) rather than
|
|
/// pinning a 1920px bar to the top edge of the preview. The frame carries its
|
|
/// origin on <see cref="VideoFrame.Placement"/>, which is the same number the
|
|
/// encoder composites at — so the preview cannot drift from the broadcast, exactly
|
|
/// like the branding credit.</para></summary>
|
|
private System.Windows.Media.Imaging.WriteableBitmap? _alertTickerBitmap;
|
|
|
|
public System.Windows.Media.Imaging.WriteableBitmap? AlertTickerImageSource
|
|
{
|
|
get => _alertTickerBitmap;
|
|
private set
|
|
{
|
|
_alertTickerBitmap = value;
|
|
OnPropertyChanged(nameof(AlertTickerImageSource));
|
|
}
|
|
}
|
|
|
|
public bool AlertTickerVisible => _alertTickerBitmap != null;
|
|
|
|
/// <summary>Alert-box rect (master pixels) the ticker element is placed at in the
|
|
/// preview canvas. Set from the published frame, so preview and output agree.</summary>
|
|
public double AlertTickerLeft { get; private set; }
|
|
|
|
public double AlertTickerTop { get; private set; }
|
|
|
|
public double AlertTickerWidth { get; private set; }
|
|
|
|
public double AlertTickerHeight { get; private set; }
|
|
|
|
/// <summary>Publishes one ticker frame to the preview pane. Called on the UI
|
|
/// thread from the alert layer's own tick, so the WPF bitmap write is legal.
|
|
/// Reuses one <see cref="System.Windows.Media.Imaging.WriteableBitmap"/> and
|
|
/// overwrites its back buffer — a fresh bitmap every tick would churn the
|
|
/// render cache for no reason.</summary>
|
|
internal void PublishAlertTickerPreview(VideoFrame? incomingFrame)
|
|
{
|
|
var frame = incomingFrame;
|
|
var previewScene = IsEditMode ? StagedScene : LiveScene ?? StagedScene;
|
|
var alertBox = previewScene?.Elements.OfType<Source>()
|
|
.FirstOrDefault(source => source.Type == SourceType.AlertBox && source.IsVisible);
|
|
if (_alertLayer.IsPlaying)
|
|
frame = _alertLayer.AlertBoxTickerFrame(alertBox);
|
|
|
|
if (frame == null)
|
|
{
|
|
if (_alertTickerBitmap != null)
|
|
{
|
|
AlertTickerImageSource = null;
|
|
OnPropertyChanged(nameof(AlertTickerVisible));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// The box can be moved or resized mid-alert, and the element has to track it —
|
|
// raise the rect unconditionally rather than only on a bitmap resize.
|
|
if (AlertTickerLeft != frame.OriginX) { AlertTickerLeft = frame.OriginX; OnPropertyChanged(nameof(AlertTickerLeft)); }
|
|
if (AlertTickerTop != frame.OriginY) { AlertTickerTop = frame.OriginY; OnPropertyChanged(nameof(AlertTickerTop)); }
|
|
if (AlertTickerWidth != frame.Width) { AlertTickerWidth = frame.Width; OnPropertyChanged(nameof(AlertTickerWidth)); }
|
|
if (AlertTickerHeight != frame.Height) { AlertTickerHeight = frame.Height; OnPropertyChanged(nameof(AlertTickerHeight)); }
|
|
|
|
if (_alertTickerBitmap == null
|
|
|| _alertTickerBitmap.PixelWidth != frame.Width
|
|
|| _alertTickerBitmap.PixelHeight != frame.Height)
|
|
{
|
|
AlertTickerImageSource = new System.Windows.Media.Imaging.WriteableBitmap(
|
|
frame.Width, frame.Height, 96, 96,
|
|
System.Windows.Media.PixelFormats.Bgra32, null);
|
|
OnPropertyChanged(nameof(AlertTickerVisible));
|
|
}
|
|
|
|
var target = _alertTickerBitmap!;
|
|
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(AlertTickerImageSource));
|
|
}
|
|
}
|