using System.Text.Json; using MBN_STOCK_WEBVIEW.Core.Playout.Scenes; using MBN_STOCK_WEBVIEW.Playout.Configuration; using Microsoft.UI.Xaml; using Windows.Storage.Pickers; using WinRT.Interop; namespace MBN_STOCK_WEBVIEW; public sealed partial class MainWindow { private int _backgroundSelectionInFlight; private void HandleBackgroundStatusRequest(JsonElement payload) { if (!TryParseBackgroundRequest(payload, out var requestId)) { PostBackgroundError(string.Empty, "올바르지 않은 배경 상태 요청입니다."); return; } PostBackgroundStatus(requestId); } private async Task HandleChooseBackgroundAsync(JsonElement payload) { if (!TryParseBackgroundRequest(payload, out var requestId)) { PostBackgroundError(string.Empty, "올바르지 않은 배경 파일 선택 요청입니다."); return; } if (!await _playoutCommandGate.WaitAsync(0)) { PostBackgroundError(requestId, "송출 명령 처리 중에는 배경을 변경할 수 없습니다."); return; } string? error = null; var message = "배경 파일 선택을 취소했습니다."; Interlocked.Exchange(ref _backgroundSelectionInFlight, 1); try { if (!CanChangeLegacyBackground(allowBackgroundOperation: true)) { error = "배경은 IDLE 상태에서 다음 PREPARE 전에만 변경할 수 있습니다."; return; } var source = _legacyCompositionOptionsSource ?? throw new InvalidOperationException("Background composition is unavailable."); // Original btnback_Click assigns 배경\기본.vrv before opening the dialog. // Keep that behavior only when the default passes all trusted-file checks. try { source.Update(PlayoutSceneCompositionFactory.CreateLegacyDefault( _playoutOptions!, source.Current.FadeDuration)); } catch (PlayoutConfigurationException) { // F2 may still select another valid file from the same trusted root. } var picker = new FileOpenPicker { SuggestedStartLocation = PickerLocationId.PicturesLibrary, ViewMode = PickerViewMode.List }; picker.FileTypeFilter.Add(".vrv"); picker.FileTypeFilter.Add(".jpg"); picker.FileTypeFilter.Add(".jpeg"); picker.FileTypeFilter.Add(".png"); InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this)); var selected = await picker.PickSingleFileAsync(); if (selected is null) { return; } if (!CanChangeLegacyBackground(allowBackgroundOperation: true)) { error = "배경 파일 선택 중 송출 상태가 바뀌어 변경을 취소했습니다."; return; } var composition = CreateOperatorBackgroundComposition(selected.Path); source.Update(composition); message = "선택한 배경을 다음 PREPARE부터 사용합니다."; } catch (Exception exception) when ( exception is ArgumentException or IOException or UnauthorizedAccessException or PlayoutConfigurationException) { error = "배경은 신뢰 배경 폴더 안의 일반 vrv/jpg/jpeg/png 파일만 선택할 수 있습니다."; } catch { error = "배경 파일을 선택하지 못했습니다."; } finally { Interlocked.Exchange(ref _backgroundSelectionInFlight, 0); _playoutCommandGate.Release(); if (error is null) { PostBackgroundStatus(requestId, message); } else { PostBackgroundError(requestId, error); } } } private async Task HandleToggleBackgroundAsync(JsonElement payload) { if (!TryParseBackgroundRequest(payload, out var requestId)) { PostBackgroundError(string.Empty, "올바르지 않은 배경 토글 요청입니다."); return; } if (!await _playoutCommandGate.WaitAsync(0)) { PostBackgroundError(requestId, "송출 명령 처리 중에는 배경을 변경할 수 없습니다."); return; } string? error = null; var message = string.Empty; Interlocked.Exchange(ref _backgroundSelectionInFlight, 1); try { if (!CanChangeLegacyBackground(allowBackgroundOperation: true)) { error = "배경은 IDLE 상태에서 다음 PREPARE 전에만 변경할 수 있습니다."; return; } var source = _legacyCompositionOptionsSource ?? throw new InvalidOperationException("Background composition is unavailable."); var current = source.Current; if (current.BackgroundKind == LegacySceneBackgroundKind.None) { try { source.Update(PlayoutSceneCompositionFactory.CreateLegacyDefault( _playoutOptions!, current.FadeDuration)); message = "기본 배경을 켰습니다. 다음 PREPARE부터 반영됩니다."; } catch (PlayoutConfigurationException) { error = "신뢰 배경 폴더의 기본.vrv를 확인할 수 없습니다. F2로 유효한 배경을 선택하세요."; return; } } else { source.Update(new LegacySceneCueCompositionOptions( current.FadeDuration, LegacySceneBackgroundKind.None)); message = "배경 사용을 껐습니다. 다음 PREPARE부터 반영됩니다."; } } catch { error = "배경 사용 상태를 변경하지 못했습니다."; } finally { Interlocked.Exchange(ref _backgroundSelectionInFlight, 0); _playoutCommandGate.Release(); if (error is null) { PostBackgroundStatus(requestId, message); } else { PostBackgroundError(requestId, error); } } } private LegacySceneCueCompositionOptions CreateOperatorBackgroundComposition( string selectedPath) { var options = _playoutOptions ?? throw new InvalidOperationException("Playout options are unavailable."); var source = _legacyCompositionOptionsSource ?? throw new InvalidOperationException("Background composition is unavailable."); return PlayoutSceneCompositionFactory.CreateOperatorSelection( options, source.Current.FadeDuration, selectedPath); } private bool CanChangeLegacyBackground(bool allowBackgroundOperation = false) { if (_playoutOptions is null || _legacyCompositionOptionsSource is null || !PlayoutSceneCompositionFactory.TryGetTrustedBackgroundDirectory( _playoutOptions, out _) || IsBrowserCorrelationQuarantined() || (!allowBackgroundOperation && Volatile.Read(ref _backgroundSelectionInFlight) != 0) || Volatile.Read(ref _playoutCommandInFlight) != 0 || Volatile.Read(ref _playoutShutdownStarted) != 0) { return false; } var status = _playoutEngine?.Status; var workflow = _playoutWorkflow?.State; return status is not null && workflow is not null && !status.IsPlayCompletionPending && string.IsNullOrWhiteSpace(status.PreparedSceneName) && string.IsNullOrWhiteSpace(status.OnAirSceneName) && string.IsNullOrWhiteSpace(workflow.PreparedCutCode) && string.IsNullOrWhiteSpace(workflow.OnAirCutCode) && (_legacyRefreshTask is null || _legacyRefreshTask.IsCompleted); } private static bool TryParseBackgroundRequest(JsonElement payload, out string requestId) { requestId = string.Empty; return payload.ValueKind == JsonValueKind.Object && HasOnlyProperties(payload, "requestId") && TryGetSafeToken( payload, "requestId", MaximumPlayoutRequestIdLength, out requestId); } private void PostBackgroundStatus(string requestId, string? message = null) { var current = _legacyCompositionOptionsSource?.Current ?? LegacySceneCueCompositionOptions.Default; var enabled = current.BackgroundKind != LegacySceneBackgroundKind.None; var rootAvailable = _playoutOptions is not null && PlayoutSceneCompositionFactory.TryGetTrustedBackgroundDirectory( _playoutOptions, out _); var defaultAvailable = false; if (rootAvailable && _playoutOptions is not null) { try { _ = PlayoutSceneCompositionFactory.CreateLegacyDefault( _playoutOptions, current.FadeDuration); defaultAvailable = true; } catch (PlayoutConfigurationException) { // F2 remains available for another valid file in the trusted root. } } PostMessage("background-status", new { requestId, enabled, kind = current.BackgroundKind switch { LegacySceneBackgroundKind.Texture => "texture", LegacySceneBackgroundKind.Video => "video", _ => "none" }, fileName = enabled ? Path.GetFileName(current.BackgroundAssetPath) : string.Empty, canChange = CanChangeLegacyBackground(), message = message ?? (!rootAvailable ? "신뢰 배경 폴더를 찾을 수 없어 F2/F3 배경 기능을 잠갔습니다." : enabled ? "선택한 배경은 다음 PREPARE부터 적용됩니다." : defaultAvailable ? "배경 사용 안 함" : "기본.vrv가 없습니다. F2로 신뢰 배경 폴더의 파일을 선택하세요.") }); } private void PostBackgroundError(string requestId, string message) { PostMessage("background-error", new { requestId, message }); } }