Files
MBN_STOCK_WEBVIEW/src/MBN_STOCK_WEBVIEW.Playout/PlayoutEngineFactory.cs

182 lines
6.8 KiB
C#

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<bool>? 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.");
}
}
/// <summary>
/// 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.
/// </summary>
public static void ValidateIsolatedTestCommand(
PlayoutOptions options,
IEnumerable<PlayoutCue> 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.");
}
}
}
}