Files
MBN_STOCK_WEBVIEW/MainWindow.Background.cs

281 lines
11 KiB
C#

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 static readonly IReadOnlySet<string> OperatorBackgroundExtensions =
new HashSet<string>([".vrv", ".jpg", ".jpeg", ".png"],
StringComparer.OrdinalIgnoreCase);
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 picker = new FileOpenPicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
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);
_legacyCompositionOptionsSource!.Update(composition);
_lastEnabledLegacyComposition = composition;
message = "선택한 배경을 다음 PREPARE부터 사용합니다.";
}
catch (Exception exception) when (
exception is ArgumentException or IOException or UnauthorizedAccessException)
{
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)
{
if (_lastEnabledLegacyComposition is null)
{
error = "켜 둘 배경 파일이 없습니다. 먼저 F2로 배경 파일을 선택하세요.";
return;
}
source.Update(_lastEnabledLegacyComposition);
message = "배경 사용을 켰습니다. 다음 PREPARE부터 반영됩니다.";
}
else
{
_lastEnabledLegacyComposition = current;
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.");
var rootText = options.SceneDirectory?.Trim();
if (string.IsNullOrWhiteSpace(rootText) || !Path.IsPathFullyQualified(rootText) ||
string.IsNullOrWhiteSpace(selectedPath) || !Path.IsPathFullyQualified(selectedPath))
{
throw new ArgumentException("A trusted scene root and background are required.");
}
var root = Path.TrimEndingDirectorySeparator(Path.GetFullPath(rootText));
var candidate = Path.GetFullPath(selectedPath);
var rootPrefix = root + Path.DirectorySeparatorChar;
var extension = Path.GetExtension(candidate);
if (!candidate.StartsWith(rootPrefix, StringComparison.OrdinalIgnoreCase) ||
!OperatorBackgroundExtensions.Contains(extension))
{
throw new ArgumentException("The selected background is outside the trusted scene root.");
}
var kind = string.Equals(extension, ".vrv", StringComparison.OrdinalIgnoreCase)
? LegacySceneBackgroundKind.Video
: LegacySceneBackgroundKind.Texture;
var validationOptions = new PlayoutOptions
{
SceneDirectory = root,
LegacySceneFadeDuration = source.Current.FadeDuration,
LegacySceneBackgroundKind = kind,
LegacySceneBackgroundAssetPath = Path.GetRelativePath(root, candidate),
LegacySceneBackgroundVideoLoopCount = options.LegacySceneBackgroundVideoLoopCount,
LegacySceneBackgroundVideoLoopInfinite = options.LegacySceneBackgroundVideoLoopInfinite
};
return PlayoutSceneCompositionFactory.Create(validationOptions);
}
private bool CanChangeLegacyBackground(bool allowBackgroundOperation = false)
{
if (_playoutOptions is null || _legacyCompositionOptionsSource is null ||
string.IsNullOrWhiteSpace(_playoutOptions.SceneDirectory) ||
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;
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 ?? (enabled
? "선택한 배경은 다음 PREPARE부터 적용됩니다."
: "배경 사용 안 함")
});
}
private void PostBackgroundError(string requestId, string message)
{
PostMessage("background-error", new
{
requestId,
message
});
}
}