branding credit: run in every scene and in recordings, not only while live
Creator 2026-09-26: "the made with llamacasty flash should appear in all scenes,
not just live" and "should also appear in recordings". The presenter was
Start()/Stop()-ed from UpdateLiveVisuals()'s IsLive branch, so a recording made
WITHOUT ever going live carried no credit at all -- the exact case the creator hit
while judging compositing from local recordings.
It is now Start()ed once in the MainViewModel ctor and never stopped on live-state
churn. One start covers every scene, the preview, the stream and the recording,
because go-live and local recording are the SAME FramePump: both
Streaming.Operations.cs:68 and :199 call StartAsync with the same brandFlash:
delegate. Verified by reading both call sites, not assumed -- there is no second
encoder path that needed a "redirect". The licence gate needs no live branch at
all: IsPremium's setter already pushes BrandFlashPresenter.Enabled from anywhere.
Fixed a trap that app-lifetime exposed: Enabled = false stops the presenter's
DispatcherTimer to cut the advertisement mid-credit. When Start() was per-go-live
the next go-live restarted it; with a single app-lifetime Start() nothing would,
so a key entered mid-session would leave the credit dead until the process was
restarted. The setter now restarts the timer when re-enabling while _running.
Tests (12 facts in BrandFlashOutputTests, +2):
Credit_IsComposited_WithNoLiveSession_AndSoARecordingCarriesIt -- drives a real
never-live VM past the 5s first-flash delay and asks the frame the pump would
composite. Uses a new internal AdvanceBrandFlash seam; because Advance only
advances the cadence while the presenter is running, a credit coming out
proves Start() happened at construction.
ADowngradeMidSession_RestartsTheCadenceTimer -- asserts the premium/downgrade
edge decision directly (IsCadenceTimerEnabled) instead of sleeping through a
30-60s interval, which a synchronous test body cannot observe.
Full suite 364/364.
This commit is contained in:
@@ -98,6 +98,7 @@ public sealed class BrandFlashPresenter : IDisposable
|
||||
get { lock (_gate) return _enabled; }
|
||||
set
|
||||
{
|
||||
bool restart;
|
||||
lock (_gate)
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
@@ -108,8 +109,19 @@ public sealed class BrandFlashPresenter : IDisposable
|
||||
// presentation short instead of running it out ungated.
|
||||
_elapsed = PresentationSeconds;
|
||||
_timer?.Stop();
|
||||
restart = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// …but a DOWNGRADE (key entered mid-session) must bring the cadence
|
||||
// back. The presenter now runs for the life of the app — Start() is
|
||||
// called once at startup, not per go-live — so stopping the timer on
|
||||
// the premium edge without restarting it here would leave the credit
|
||||
// dead until the process is restarted, even after a valid key.
|
||||
restart = _running;
|
||||
}
|
||||
}
|
||||
if (restart) _timer?.Start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +131,14 @@ public sealed class BrandFlashPresenter : IDisposable
|
||||
get { lock (_gate) return _enabled && _elapsed < PresentationSeconds; }
|
||||
}
|
||||
|
||||
/// <summary>Test-only: is the <see cref="DispatcherTimer"/> that drives
|
||||
/// <see cref="Advance"/> actually running? The premium/downgrade edge is the only
|
||||
/// thing that ever stops and restarts it now that the presenter is started once for
|
||||
/// the life of the app, and a synchronous test body cannot observe a real timer
|
||||
/// tick — so assert the decision directly instead of sleeping through a 30–60s
|
||||
/// interval. Never call this from product code.</summary>
|
||||
internal bool IsCadenceTimerEnabled => _timer?.IsEnabled ?? false;
|
||||
|
||||
/// <summary>Begin the cadence: first flash <see cref="FirstFlashDelaySeconds"/>
|
||||
/// after the call, then every <c>rand(30s) + 30s</c>.</summary>
|
||||
public void Start()
|
||||
|
||||
Reference in New Issue
Block a user