feat(ui): show YouTube-confirmed live state in top bar
Normal ON-AIR stays red until YouTube reports lifeCycleStatus=live, then turns green; TEST stays gold and stop restores the idle color. Status-color reference: https://www.carbondesignsystem.com/building-blocks/core/patterns/status-indicators
This commit is contained in:
@@ -234,6 +234,12 @@ public partial class MainViewModel : ViewModelBase
|
||||
EditingActive = false;
|
||||
await _framePump.StartAsync(); // never throws; failures log + surface via Failed
|
||||
|
||||
// Check promptly until YouTube confirms the broadcast is live. Health
|
||||
// polling returns to its normal interval after that confirmation.
|
||||
_healthPollTimer.Interval = TimeSpan.FromSeconds(2);
|
||||
_healthPollTimer.Start();
|
||||
_ = PollHealthAsync();
|
||||
|
||||
string? liveChatId = null;
|
||||
try
|
||||
{
|
||||
@@ -264,10 +270,6 @@ public partial class MainViewModel : ViewModelBase
|
||||
AppLog.Write($"Test session dock open for {_currentBroadcastId}");
|
||||
}
|
||||
|
||||
// Health polling (TASK 5 item 3): poll immediately, then every 30s while
|
||||
// live. Report-by-exception — a banner appears only on warning/error.
|
||||
_healthPollTimer.Start();
|
||||
_ = PollHealthAsync();
|
||||
}
|
||||
|
||||
private void OnHealthPollTick(object? sender, EventArgs e) => _ = PollHealthAsync();
|
||||
@@ -293,19 +295,21 @@ public partial class MainViewModel : ViewModelBase
|
||||
{
|
||||
AppLog.Write("Stream health poll failed; check the OAuth session",
|
||||
AppLogMessageType.Error, AppLogErrorClass.Class2);
|
||||
return;
|
||||
}
|
||||
CurrentHealth.HealthStatus = health.HealthStatus;
|
||||
CurrentHealth.HealthMessage = health.HealthStatus;
|
||||
CurrentHealth.ConfigurationIssues.Clear();
|
||||
CurrentHealth.ConfigurationIssues.AddRange(health.ConfigurationIssues);
|
||||
OnPropertyChanged(nameof(CurrentHealth));
|
||||
ApplyHealthIssue(health.ConfigurationIssues);
|
||||
else
|
||||
{
|
||||
CurrentHealth.HealthStatus = health.HealthStatus;
|
||||
CurrentHealth.HealthMessage = health.HealthStatus;
|
||||
CurrentHealth.ConfigurationIssues.Clear();
|
||||
CurrentHealth.ConfigurationIssues.AddRange(health.ConfigurationIssues);
|
||||
OnPropertyChanged(nameof(CurrentHealth));
|
||||
ApplyHealthIssue(health.ConfigurationIssues);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_currentBroadcastId) && !_broadcastSeenLive)
|
||||
{
|
||||
var lifeCycle = await _youtubeStream.LogBroadcastDiagnosticsAsync(_currentBroadcastId, _reusableStream.Id);
|
||||
if (lifeCycle is "live" or "liveStarting") _broadcastSeenLive = true;
|
||||
ApplyBroadcastLifecycleStatus(lifeCycle);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -505,17 +509,23 @@ public partial class MainViewModel : ViewModelBase
|
||||
private void UpdateLiveVisuals()
|
||||
{
|
||||
var live = IsLive;
|
||||
// A live TEST broadcast wears gold, not the live red — the TEST badge +
|
||||
// gold are the "this is a rehearsal" cue (TASK 41).
|
||||
// TEST keeps its gold identity. For a normal ON-AIR session, green means
|
||||
// YouTube has confirmed live; before then it keeps the existing red color.
|
||||
TopBarBackground = live
|
||||
? ThemeBrush.Hex(IsTestStream ? "Brush.TestContainer" : "Brush.Primary")
|
||||
? IsTestStream
|
||||
? ThemeBrush.Hex("Brush.TestContainer")
|
||||
: ThemeBrush.Hex(_youtubeBroadcastLive ? "Brush.Positive" : "Brush.Primary")
|
||||
: ThemeBrush.Hex("Brush.SurfaceContainer");
|
||||
PreviewGlowBrush = live
|
||||
? ThemeBrush.Hex(IsTestStream ? "Brush.TestContainer" : "Brush.Primary")
|
||||
: "Transparent";
|
||||
PreviewGlowThickness = live ? new Thickness(3) : new Thickness(0);
|
||||
|
||||
if (!live) _healthPollTimer.Stop();
|
||||
if (!live)
|
||||
{
|
||||
_healthPollTimer.Stop();
|
||||
_healthPollTimer.Interval = TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
// NOTE: the branding credit is deliberately NOT started/stopped here.
|
||||
// CREATOR RULING 2026-09-26 — it must appear in EVERY scene and in
|
||||
|
||||
@@ -24,6 +24,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
/// <summary>Latches once the broadcast reaches live so the per-poll broadcast
|
||||
/// diagnostic stops logging (2026-10-04). Reset at broadcast creation.</summary>
|
||||
private bool _broadcastSeenLive;
|
||||
private bool _youtubeBroadcastLive;
|
||||
private string? _reusableStreamUrl;
|
||||
private ReusableStream? _reusableStream;
|
||||
private string? _healthIssueBanner;
|
||||
@@ -63,6 +64,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
{
|
||||
if (SetProperty(ref _streamStatus, value))
|
||||
{
|
||||
_youtubeBroadcastLive = false;
|
||||
OnPropertyChanged(nameof(IsOffline));
|
||||
OnPropertyChanged(nameof(IsLive));
|
||||
OnPropertyChanged(nameof(IsEditMode));
|
||||
@@ -373,6 +375,22 @@ public partial class MainViewModel : ViewModelBase
|
||||
set => SetProperty(ref _topBarBackground, value);
|
||||
}
|
||||
|
||||
internal void ApplyBroadcastLifecycleStatus(string? lifeCycleStatus)
|
||||
{
|
||||
if (!IsLive) return;
|
||||
if (!string.Equals(lifeCycleStatus, "live", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!_broadcastSeenLive)
|
||||
_healthPollTimer.Interval = TimeSpan.FromSeconds(2);
|
||||
return;
|
||||
}
|
||||
|
||||
_broadcastSeenLive = true;
|
||||
_youtubeBroadcastLive = true;
|
||||
_healthPollTimer.Interval = TimeSpan.FromSeconds(30);
|
||||
UpdateLiveVisuals();
|
||||
}
|
||||
|
||||
public string PreviewGlowBrush
|
||||
{
|
||||
get => _previewGlowBrush;
|
||||
|
||||
Reference in New Issue
Block a user