Migrate remaining legacy operator workflows

This commit is contained in:
2026-07-12 05:39:27 +09:00
parent ffb8f43c19
commit a01836a2d7
132 changed files with 20566 additions and 720 deletions

View File

@@ -0,0 +1,124 @@
using MBN_STOCK_WEBVIEW.Core.Playout;
using Microsoft.UI.Windowing;
using WinRT.Interop;
namespace MBN_STOCK_WEBVIEW;
public sealed partial class MainWindow
{
private AppWindow? _closeConfirmationAppWindow;
private int _closeConfirmationPending;
private int _closeConfirmed;
private void EnsureCloseConfirmationAttached()
{
if (_closeConfirmationAppWindow is not null)
{
return;
}
try
{
var windowHandle = WindowNative.GetWindowHandle(this);
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle);
var appWindow = AppWindow.GetFromWindowId(windowId);
appWindow.Closing += OnAppWindowClosing;
_closeConfirmationAppWindow = appWindow;
}
catch
{
// Root.Loaded retries the attachment after the native window has been created.
}
}
private async void OnAppWindowClosing(AppWindow sender, AppWindowClosingEventArgs args)
{
if (Volatile.Read(ref _closeConfirmed) != 0)
{
return;
}
// AppWindow.Closing cannot be deferred. Cancel this close synchronously and issue a
// second, explicitly confirmed close only after the modal result is known.
args.Cancel = true;
if (Interlocked.CompareExchange(ref _closeConfirmationPending, 1, 0) != 0)
{
return;
}
try
{
if (Root.XamlRoot is null)
{
return;
}
var dialog = new ContentDialog
{
XamlRoot = Root.XamlRoot,
Title = "프로그램 종료",
Content = BuildCloseConfirmationMessage(),
PrimaryButtonText = "종료",
CloseButtonText = "취소",
DefaultButton = ContentDialogButton.Close
};
var result = await dialog.ShowAsync();
if (result != ContentDialogResult.Primary)
{
return;
}
Interlocked.Exchange(ref _closeConfirmed, 1);
Close();
}
catch
{
// If the confirmation surface cannot be shown, fail closed and keep the app open.
}
finally
{
Interlocked.Exchange(ref _closeConfirmationPending, 0);
}
}
private string BuildCloseConfirmationMessage()
{
var status = _playoutEngine?.Status;
var hasPreparedScene = !string.IsNullOrWhiteSpace(status?.PreparedSceneName);
var hasOnAirScene = !string.IsNullOrWhiteSpace(status?.OnAirSceneName);
var commandPending = Volatile.Read(ref _playoutCommandInFlight) != 0 ||
status?.IsPlayCompletionPending == true;
var outcomeUnknown = Volatile.Read(ref _browserCorrelationQuarantined) != 0 ||
status?.State == PlayoutConnectionState.OutcomeUnknown;
if (outcomeUnknown)
{
return "송출 결과를 확정할 수 없는 상태입니다. PGM과 Network Monitoring을 확인한 뒤 종료하세요. " +
"종료 과정에서 TAKE OUT 또는 다른 송출 명령을 자동으로 반복하지 않습니다. 그래도 종료하시겠습니까?";
}
if (commandPending)
{
return "송출 명령 또는 callback 처리가 진행 중입니다. 결과를 확인한 뒤 종료하는 것이 안전합니다. " +
"그래도 종료하시겠습니까?";
}
if (hasOnAirScene || hasPreparedScene)
{
return "PREPARED 또는 PROGRAM 장면이 남아 있습니다. 필요한 경우 먼저 TAKE OUT을 실행하고 " +
"PGM을 확인하세요. 종료 과정에서는 TAKE OUT을 자동 실행하지 않습니다. 그래도 종료하시겠습니까?";
}
return "프로그램을 종료하시겠습니까?";
}
private void DetachCloseConfirmation()
{
var appWindow = Interlocked.Exchange(ref _closeConfirmationAppWindow, null);
if (appWindow is not null)
{
appWindow.Closing -= OnAppWindowClosing;
}
}
}