feat: align legacy operator and playout behavior
This commit is contained in:
@@ -12,12 +12,12 @@ public sealed record LegacyParityStartupCompositionResolution(
|
||||
/// <summary>
|
||||
/// Resolves the startup-only MainForm background behavior used by LegacyParityApp.
|
||||
/// An explicit local file remains authoritative; only an absent file enables the
|
||||
/// validated legacy 기본.vrv fallback.
|
||||
/// validated legacy Cuts\video\기본.vrv fallback.
|
||||
/// </summary>
|
||||
public static class LegacyParityStartupCompositionResolver
|
||||
{
|
||||
public const string MissingLegacyDefaultWarning =
|
||||
"신뢰 배경 폴더에서 기본.vrv를 확인할 수 없어 배경없음으로 시작했습니다.";
|
||||
"신뢰 Cuts 경로에서 video\\기본.vrv를 확인할 수 없어 배경없음으로 시작했습니다.";
|
||||
|
||||
public static LegacyParityStartupCompositionResolution Resolve(
|
||||
PlayoutOptions options,
|
||||
@@ -34,7 +34,7 @@ public static class LegacyParityStartupCompositionResolver
|
||||
try
|
||||
{
|
||||
return new LegacyParityStartupCompositionResolution(
|
||||
PlayoutSceneCompositionFactory.CreateLegacyDefault(
|
||||
PlayoutSceneCompositionFactory.CreateLegacyStartupDefault(
|
||||
options,
|
||||
configured.FadeDuration),
|
||||
string.Empty);
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed class PlayoutOptions
|
||||
|
||||
public int LayoutIndex { get; set; } = 10;
|
||||
|
||||
/// <summary>MainForm ComboDi.SelectedIndex default passed to SetSceneEffectType.</summary>
|
||||
/// <summary>MainForm ComboDi.SelectedIndex (0..19) passed to SetSceneEffectType.</summary>
|
||||
public int LegacySceneFadeDuration { get; set; } = 6;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,12 +4,14 @@ using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
|
||||
namespace MBN_STOCK_WEBVIEW.Playout.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Validates the trusted MainForm-level fade/background configuration before the
|
||||
/// first DryRun or actual PREPARE. Web content never supplies these values.
|
||||
/// Validates the MainForm-level fade/background composition before the first DryRun
|
||||
/// or actual PREPARE. Background paths stay native/trusted; fade is restricted to the
|
||||
/// original closed DissolveTime selector index (0..19).
|
||||
/// </summary>
|
||||
public static class PlayoutSceneCompositionFactory
|
||||
{
|
||||
public const string LegacyDefaultBackgroundFileName = "기본.vrv";
|
||||
public const string LegacyStartupDefaultBackgroundAssetPath = @"video\기본.vrv";
|
||||
|
||||
private static readonly IReadOnlySet<string> AllowedExtensions = new HashSet<string>(
|
||||
[
|
||||
@@ -116,6 +118,38 @@ public static class PlayoutSceneCompositionFactory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the background assigned by MainForm at startup, Cuts\video\기본.vrv.
|
||||
/// This is intentionally distinct from the F2/F3 default in the sibling 배경 root.
|
||||
/// </summary>
|
||||
public static LegacySceneCueCompositionOptions CreateLegacyStartupDefault(
|
||||
PlayoutOptions options,
|
||||
int fadeDuration)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ValidateCommon(fadeDuration, options);
|
||||
|
||||
try
|
||||
{
|
||||
var root = TrustedPlayoutAssetPath.ValidateRoot(
|
||||
options.SceneDirectory?.Trim() ?? string.Empty);
|
||||
var candidate = TrustedPlayoutAssetPath.ResolveRelativeFile(
|
||||
root,
|
||||
LegacyStartupDefaultBackgroundAssetPath,
|
||||
AllowedExtensions);
|
||||
return CreateResult(
|
||||
options,
|
||||
fadeDuration,
|
||||
LegacySceneBackgroundKind.Video,
|
||||
Path.GetRelativePath(root, candidate),
|
||||
PlayoutAssetRoot.SceneDirectory);
|
||||
}
|
||||
catch (TrustedPlayoutAssetException)
|
||||
{
|
||||
throw Invalid();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetTrustedBackgroundDirectory(
|
||||
PlayoutOptions options,
|
||||
out string directory)
|
||||
@@ -190,17 +224,18 @@ public static class PlayoutSceneCompositionFactory
|
||||
PlayoutOptions options,
|
||||
int fadeDuration,
|
||||
LegacySceneBackgroundKind kind,
|
||||
string relativePath) => new(
|
||||
string relativePath,
|
||||
PlayoutAssetRoot assetRoot = PlayoutAssetRoot.OperatorBackgroundDirectory) => new(
|
||||
fadeDuration,
|
||||
kind,
|
||||
relativePath,
|
||||
options.LegacySceneBackgroundVideoLoopCount,
|
||||
options.LegacySceneBackgroundVideoLoopInfinite,
|
||||
PlayoutAssetRoot.OperatorBackgroundDirectory);
|
||||
assetRoot);
|
||||
|
||||
private static void ValidateCommon(int fadeDuration, PlayoutOptions options)
|
||||
{
|
||||
if (fadeDuration is < 0 or > 60 ||
|
||||
if (fadeDuration is < 0 or > 19 ||
|
||||
!Enum.IsDefined(options.LegacySceneBackgroundKind) ||
|
||||
options.LegacySceneBackgroundVideoLoopCount is < 0 or > 10_000)
|
||||
{
|
||||
|
||||
@@ -208,7 +208,7 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
throw new PlayoutRequestException("장면 이름이 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
if (cue.FadeDuration is < 0 or > 60)
|
||||
if (cue.FadeDuration is < 0 or > 19)
|
||||
{
|
||||
throw new PlayoutRequestException("장면 전환 시간이 올바르지 않습니다.");
|
||||
}
|
||||
@@ -388,6 +388,14 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
throw new PlayoutRequestException("장면 배경 영상 반복 값이 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
// Installed K3D Interop declares LoopInfinite as Int32. The source
|
||||
// application uses only 0, 1 (s5006), and 10 (common background).
|
||||
if (video.LoopInfinite is not (0 or 1 or 10))
|
||||
{
|
||||
throw new PlayoutRequestException(
|
||||
"장면 배경 영상 무한 반복 값이 허용된 원본 값이 아닙니다.");
|
||||
}
|
||||
|
||||
return video with
|
||||
{
|
||||
AssetPath = ResolveAssetPath(video.AssetPath, video.AssetRoot)
|
||||
|
||||
@@ -22,6 +22,8 @@ internal interface IK3dSession : IDisposable
|
||||
|
||||
bool HasPendingPlayCallbacks { get; }
|
||||
|
||||
bool HasPendingTakeOutCallbacks { get; }
|
||||
|
||||
bool TryPreventKtapConnect();
|
||||
|
||||
void Connect(ValidatedPlayoutOptions options);
|
||||
@@ -230,6 +232,9 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
public bool HasPendingPlayCallbacks =>
|
||||
SupportsLifecycleCallbacks && _pendingPlayCallbacks != 0;
|
||||
|
||||
public bool HasPendingTakeOutCallbacks => SupportsLifecycleCallbacks &&
|
||||
(_pendingCutOutCallbacks != 0 || _pendingStopAllCallbacks != 0);
|
||||
|
||||
public bool TryPreventKtapConnect()
|
||||
{
|
||||
var previous = Interlocked.CompareExchange(ref _ktapDispatchGate, 2, 0);
|
||||
@@ -855,7 +860,7 @@ internal sealed class DynamicK3dSession : IK3dGuardedConnectSession
|
||||
"SetBackgroundVideo",
|
||||
video.AssetPath,
|
||||
video.LoopCount,
|
||||
video.LoopInfinite ? 1 : 0);
|
||||
video.LoopInfinite);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("The scene setup mutation is not allowlisted.");
|
||||
|
||||
@@ -15,6 +15,7 @@ public static class K3dComConstants
|
||||
|
||||
public const string ApartmentThreadingModel = "Apartment";
|
||||
public const int LegacyLayoutIndex = 10;
|
||||
public const int SceneEffectInEnabled = 1;
|
||||
// MainForm passes the vendor API's raw bInEffect value 10. It is not a Boolean.
|
||||
public const int SceneEffectInEnabled = 10;
|
||||
public const int SceneChangeEffectFade = 7;
|
||||
}
|
||||
|
||||
@@ -1204,6 +1204,18 @@ internal sealed class TornadoPlayoutEngine : IPlayoutEngine
|
||||
return MarkOutcomeUnknown(operation);
|
||||
}
|
||||
|
||||
if (_session?.HasPendingTakeOutCallbacks == true)
|
||||
{
|
||||
const string pendingTakeOutMessage =
|
||||
"The previous SDK CutOut/StopAll completion callback is still pending. " +
|
||||
"No further scene command is allowed until Tornado confirms the result.";
|
||||
PublishStatus(pendingTakeOutMessage);
|
||||
return Result(
|
||||
operation,
|
||||
PlayoutResultCode.Unavailable,
|
||||
pendingTakeOutMessage);
|
||||
}
|
||||
|
||||
if (RequiresCompletedPlay(operation) &&
|
||||
_session?.HasPendingPlayCallbacks == true)
|
||||
{
|
||||
@@ -1810,8 +1822,9 @@ internal sealed class TornadoPlayoutEngine : IPlayoutEngine
|
||||
private PlayoutStatus BuildStatus(string message)
|
||||
{
|
||||
var connected = _session is not null && !_outcomeUnknown;
|
||||
var commandAvailable = _options.Mode == PlayoutMode.DryRun ||
|
||||
(connected && IsProcessEligible(_processSnapshot));
|
||||
var commandAvailable = (_options.Mode == PlayoutMode.DryRun ||
|
||||
(connected && IsProcessEligible(_processSnapshot))) &&
|
||||
_session?.HasPendingTakeOutCallbacks != true;
|
||||
var liveAllowed = connected &&
|
||||
IsProcessEligible(_processSnapshot) &&
|
||||
CanTakeIn();
|
||||
@@ -1830,6 +1843,7 @@ internal sealed class TornadoPlayoutEngine : IPlayoutEngine
|
||||
{
|
||||
OperationTimeoutMilliseconds = (int)_options.OperationTimeout.TotalMilliseconds,
|
||||
IsPlayCompletionPending = _session?.HasPendingPlayCallbacks == true,
|
||||
IsTakeOutCompletionPending = _session?.HasPendingTakeOutCallbacks == true,
|
||||
LastKtapConnectState = (PlayoutKtapConnectState)Volatile.Read(
|
||||
ref _lastKtapConnectState),
|
||||
KtapHelloObserved = Volatile.Read(ref _ktapHelloObservedState) switch
|
||||
|
||||
Reference in New Issue
Block a user