182 lines
6.8 KiB
C#
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.");
|
|
}
|
|
}
|
|
}
|
|
}
|