using MBN_STOCK_WEBVIEW.Core.Playout; using MBN_STOCK_WEBVIEW.Core.Playout.Scenes; using MBN_STOCK_WEBVIEW.Playout.Configuration; using MBN_STOCK_WEBVIEW.Playout.Diagnostics; using MBN_STOCK_WEBVIEW.Playout.Interop; using MBN_STOCK_WEBVIEW.Playout.Registration; using MBN_STOCK_WEBVIEW.Playout.Runtime; using MBN_STOCK_WEBVIEW.Playout.Safety; using System.Net; namespace MBN_STOCK_WEBVIEW.Playout; public static class PlayoutEngineFactory { public static IPlayoutEngine CreateDefault() => Create( PlayoutOptionsLoader.Load(), PlayoutLaunchAuthorization.CaptureFromEnvironment()); public static IPlayoutEngine Create(PlayoutOptions options) => Create(options, PlayoutLaunchAuthorization.CaptureFromEnvironment()); public static IPlayoutEngine Create( PlayoutOptions options, PlayoutLaunchAuthorization launchAuthorization) { ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(launchAuthorization); ValidateGateAOptions(options, launchAuthorization); var validated = ValidatedPlayoutOptions.Create(options); ITornadoProcessProbe processProbe = new TornadoProcessProbe(); Func? finalConnectSafetyCheck = null; var abandonOnTargetMismatch = false; var startMonitor = validated.Mode != PlayoutMode.Disabled; if (launchAuthorization.IsGateA) { var safetyProbe = new WindowsPgmDiagnosticSafetyProbe(); var request = new PgmConnectDiagnosticRequest( validated.Host, validated.Port, "PGM"); var parsedHost = IPAddress.Parse(validated.Host); var baseline = safetyProbe.Capture(request, parsedHost); if (!baseline.IsEligible || baseline.Identity is not { } identity || identity.ProcessId != launchAuthorization.ExpectedPgmProcessId || identity.StartTimeUtcTicks != launchAuthorization.ExpectedPgmStartTimeUtcTicks) { throw new PlayoutConfigurationException( "Gate A could not confirm the exact approved PGM process and listener."); } bool HasExactTarget() { try { var current = safetyProbe.Capture(request, parsedHost); return current.IsEligible && baseline.HasSameTarget(current); } catch { return false; } } processProbe = new PgmCutsSequenceProcessProbe( safetyProbe, request, parsedHost, baseline); finalConnectSafetyCheck = HasExactTarget; abandonOnTargetMismatch = true; startMonitor = false; } IStaDispatcher? dispatcher = validated.Mode is PlayoutMode.Test or PlayoutMode.Live ? new StaDispatcher(validated.QueueCapacity) : null; return new TornadoPlayoutEngine( validated, new DynamicK3dSessionFactory( rollbackOnPrepareFailure: !launchAuthorization.IsGateA), new K3dRegistrationProbe(), processProbe, dispatcher, launchAuthorization, TimeProvider.System, startMonitor, finalConnectSafetyCheck, abandonOnTargetMismatch, beforeSdkDispatchClaim: launchAuthorization.IsGateA ? launchAuthorization.EnsureGateASdkDispatchAuthorized : null, launchAuthorization: launchAuthorization); } internal static void ValidateGateAOptions( PlayoutOptions options, PlayoutLaunchAuthorization launchAuthorization) { if (!launchAuthorization.IsGateA) { return; } var allowlist = (options.TestSceneAllowlist ?? []) .Select(static value => value?.Trim()) .ToArray(); if (options.Mode != PlayoutMode.Live || !options.TrustedLiveOutputEnabled || !string.Equals(options.Host?.Trim(), "127.0.0.1", StringComparison.Ordinal) || options.Port != 30001 || options.TcpMode != 1 || options.ClientPort != 0 || options.OutputChannel is not null || options.LayoutIndex != 10 || !PlayoutLaunchAuthorization.IsExactGateASceneDirectory( options.SceneDirectory) || allowlist.Length != 1 || !string.Equals(allowlist[0], "5001", StringComparison.Ordinal) || options.ReconnectEnabled || options.MaximumReconnectAttempts != 0 || options.MaximumAutomaticRefreshesPerTakeIn != 0 || options.LegacySceneFadeDuration != 6 || options.LegacySceneBackgroundKind != LegacySceneBackgroundKind.None || !string.IsNullOrWhiteSpace(options.LegacySceneBackgroundAssetPath)) { throw new PlayoutConfigurationException( "Gate A requires the exact closed 5001-only Live configuration."); } } /// /// Applies the same fail-closed Test-mode configuration, scene-root, file-name and /// allowlist validation used by the runtime before a diagnostic tool creates an engine. /// This method never creates a dispatcher or activates COM. /// public static void ValidateIsolatedTestCommand( PlayoutOptions options, IEnumerable cues) { ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(cues); if (options.Mode != PlayoutMode.Test || options.TrustedLiveOutputEnabled) { throw new PlayoutConfigurationException( "The isolated test command requires Test mode with live output disabled."); } var validated = ValidatedPlayoutOptions.Create(options); foreach (var cue in cues) { if (cue is null) { throw new PlayoutConfigurationException( "The isolated test command contains an invalid scene."); } PlayoutCue resolved; try { resolved = validated.ResolveCue(cue); } catch (PlayoutRequestException exception) { throw new PlayoutConfigurationException( "The isolated test command contains an invalid scene.", exception); } if (!validated.IsTestSceneAllowed(resolved)) { throw new PlayoutConfigurationException( "The isolated test command contains a scene that is not allowlisted."); } } } }