feat: advance legacy UI behavior parity
This commit is contained in:
@@ -20,15 +20,13 @@ public sealed partial class MainWindow
|
||||
private MutableLegacySceneCueCompositionOptionsSource? _compositionOptions;
|
||||
private string? _playoutInitializationError;
|
||||
private string? _playoutInitializationWarning;
|
||||
private CancellationTokenSource? _refreshCancellation;
|
||||
private Task? _refreshTask;
|
||||
private readonly LegacyRefreshEpoch<LegacyRefreshRuntimeStatus> _refreshEpoch =
|
||||
new(LegacyRefreshRuntimeStatus.Empty);
|
||||
private int _playoutBusy;
|
||||
private int _playoutQuarantined;
|
||||
private int _playoutShutdownStarted;
|
||||
private int _operatorMutationQuarantined;
|
||||
private int _refreshCompletedCount;
|
||||
private bool _refreshLimitReached;
|
||||
private string _refreshMessage = string.Empty;
|
||||
|
||||
private void InitializePlayoutRuntime()
|
||||
{
|
||||
@@ -37,6 +35,7 @@ public sealed partial class MainWindow
|
||||
var explicitLocalConfigurationExists = File.Exists(
|
||||
PlayoutOptionsLoader.DefaultPath);
|
||||
_playoutOptions = PlayoutOptionsLoader.Load();
|
||||
ResetRefreshState();
|
||||
var startupComposition = LegacyParityStartupCompositionResolver.Resolve(
|
||||
_playoutOptions,
|
||||
explicitLocalConfigurationExists);
|
||||
@@ -103,7 +102,15 @@ public sealed partial class MainWindow
|
||||
if (string.IsNullOrWhiteSpace(args.Current.PreparedSceneName) &&
|
||||
string.IsNullOrWhiteSpace(args.Current.OnAirSceneName))
|
||||
{
|
||||
StopRefreshLoop();
|
||||
if (args.Current.State == PlayoutConnectionState.OutcomeUnknown ||
|
||||
Volatile.Read(ref _playoutQuarantined) != 0)
|
||||
{
|
||||
StopRefreshLoop();
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetRefreshState();
|
||||
}
|
||||
}
|
||||
|
||||
QueueOperatorState();
|
||||
@@ -418,6 +425,7 @@ public sealed partial class MainWindow
|
||||
{
|
||||
var status = _playoutEngine?.Status;
|
||||
var workflow = _playoutWorkflow?.State;
|
||||
var refresh = _refreshEpoch.ReadState();
|
||||
return _playoutOptions is not null && _compositionOptions is not null &&
|
||||
status is not null && workflow is not null &&
|
||||
Volatile.Read(ref _playoutBusy) == 0 &&
|
||||
@@ -428,6 +436,7 @@ public sealed partial class MainWindow
|
||||
string.IsNullOrWhiteSpace(status.OnAirSceneName) &&
|
||||
string.IsNullOrWhiteSpace(workflow.PreparedCutCode) &&
|
||||
string.IsNullOrWhiteSpace(workflow.OnAirCutCode) &&
|
||||
!refresh.IsActive &&
|
||||
(_refreshTask is null || _refreshTask.IsCompleted);
|
||||
}
|
||||
|
||||
@@ -435,6 +444,7 @@ public sealed partial class MainWindow
|
||||
{
|
||||
var status = _playoutEngine?.Status;
|
||||
var workflow = _playoutWorkflow?.State;
|
||||
var refresh = _refreshEpoch.ReadState();
|
||||
if (status is null || workflow is null)
|
||||
{
|
||||
// A disabled or unavailable playout runtime must not prevent the operator
|
||||
@@ -452,6 +462,7 @@ public sealed partial class MainWindow
|
||||
string.IsNullOrWhiteSpace(status.OnAirSceneName) &&
|
||||
string.IsNullOrWhiteSpace(workflow.PreparedCutCode) &&
|
||||
string.IsNullOrWhiteSpace(workflow.OnAirCutCode) &&
|
||||
!refresh.IsActive &&
|
||||
(_refreshTask is null || _refreshTask.IsCompleted);
|
||||
}
|
||||
|
||||
@@ -495,9 +506,11 @@ public sealed partial class MainWindow
|
||||
LegacyMoveOperatorCatalogDraftRowIntent or
|
||||
LegacyReorderOperatorCatalogDraftRowIntent or
|
||||
LegacyRemoveOperatorCatalogDraftRowIntent or
|
||||
LegacyRemoveActiveOperatorCatalogDraftRowIntent or
|
||||
LegacyClearOperatorCatalogDraftRowsIntent or
|
||||
LegacySetOperatorCatalogDraftBuyAmountIntent or
|
||||
LegacySaveOperatorCatalogIntent or
|
||||
LegacyDeleteOperatorCatalogIntent or
|
||||
LegacyConfirmNativeDialogIntent or
|
||||
LegacyLoadSelectedNamedPlaylistIntent or
|
||||
LegacyLoadNamedPlaylistByIdIntent or
|
||||
LegacyCreateNamedPlaylistIntent or
|
||||
@@ -548,6 +561,7 @@ public sealed partial class MainWindow
|
||||
{
|
||||
var status = _playoutEngine?.Status;
|
||||
var workflow = _playoutWorkflow?.State;
|
||||
var refresh = _refreshEpoch.ReadState();
|
||||
var composition = _compositionOptions?.Current ??
|
||||
LegacySceneCueCompositionOptions.Default;
|
||||
var quarantined = Volatile.Read(ref _playoutQuarantined) != 0 ||
|
||||
@@ -584,10 +598,10 @@ public sealed partial class MainWindow
|
||||
workflow?.IsLastPage ?? true,
|
||||
workflow?.NextKind ?? LegacyWorkflowNextKind.None,
|
||||
Volatile.Read(ref _playoutBusy) != 0,
|
||||
_refreshTask is { IsCompleted: false },
|
||||
Volatile.Read(ref _refreshCompletedCount),
|
||||
_playoutOptions?.MaximumAutomaticRefreshesPerTakeIn,
|
||||
_refreshLimitReached,
|
||||
refresh.IsActive,
|
||||
refresh.CompletedRefreshCount,
|
||||
refresh.MaximumRefreshCount,
|
||||
refresh.IsLimitReached,
|
||||
composition.FadeDuration,
|
||||
backgroundEnabled,
|
||||
backgroundEnabled
|
||||
@@ -596,125 +610,310 @@ public sealed partial class MainWindow
|
||||
CanChangeComposition(),
|
||||
quarantined
|
||||
? "결과 불명확 상태입니다. 명령을 반복하지 마세요."
|
||||
: _refreshMessage.Length > 0
|
||||
? _refreshMessage
|
||||
: refresh.Message.Length > 0
|
||||
? refresh.Message
|
||||
: _playoutInitializationWarning ?? status?.Message ??
|
||||
_playoutInitializationError ??
|
||||
"송출 어댑터를 사용할 수 없습니다.");
|
||||
return snapshot with
|
||||
{
|
||||
IsTakeOutCompletionPending = status?.IsTakeOutCompletionPending ?? false
|
||||
IsTakeOutCompletionPending = status?.IsTakeOutCompletionPending ?? false,
|
||||
RefreshNextAtUtc = refresh.NextAtUtc,
|
||||
RefreshLastSuccessAtUtc = refresh.LastSuccessAtUtc,
|
||||
RefreshFaultCode = refresh.FaultCode
|
||||
};
|
||||
}
|
||||
|
||||
private void StartRefreshLoop(LegacyPlayoutWorkflow workflow)
|
||||
{
|
||||
StopRefreshLoop();
|
||||
Interlocked.Exchange(ref _refreshCompletedCount, 0);
|
||||
_refreshLimitReached = false;
|
||||
_refreshMessage = string.Empty;
|
||||
ResetRefreshState();
|
||||
var cutCode = workflow.State.OnAirCutCode;
|
||||
if (!LegacySceneRefreshPolicy.TryGetInterval(cutCode, out var firstInterval) ||
|
||||
firstInterval <= TimeSpan.Zero)
|
||||
firstInterval <= TimeSpan.Zero ||
|
||||
Volatile.Read(ref _playoutShutdownStarted) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scheduler = new LegacyRefreshScheduler(
|
||||
firstInterval,
|
||||
LegacySceneRefreshPolicy.RecurringInterval,
|
||||
_playoutOptions?.MaximumAutomaticRefreshesPerTakeIn);
|
||||
if (scheduler.HasReachedMaximum)
|
||||
{
|
||||
_refreshTask = Task.CompletedTask;
|
||||
_refreshEpoch.Stop(_ => LegacyRefreshRuntimeStatus.Empty with
|
||||
{
|
||||
MaximumRefreshCount = scheduler.MaximumRefreshCount,
|
||||
IsLimitReached = true,
|
||||
Message = $"자동 갱신 상한 {scheduler.MaximumRefreshCount}회가 설정되어 자동 갱신을 시작하지 않았습니다."
|
||||
});
|
||||
QueueOperatorState();
|
||||
return;
|
||||
}
|
||||
|
||||
var cancellation = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
_lifetimeCancellation.Token);
|
||||
_refreshCancellation = cancellation;
|
||||
_refreshTask = RunRefreshLoopAsync(workflow, firstInterval, cancellation);
|
||||
_refreshEpoch.Replace(
|
||||
cancellation,
|
||||
_ => new LegacyRefreshRuntimeStatus(
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
string.Empty,
|
||||
scheduler.CompletedRefreshCount,
|
||||
scheduler.MaximumRefreshCount,
|
||||
false));
|
||||
_refreshTask = RunRefreshLoopAsync(workflow, scheduler, cancellation);
|
||||
}
|
||||
|
||||
private void StopRefreshLoop()
|
||||
{
|
||||
var cancellation = Interlocked.Exchange(ref _refreshCancellation, null);
|
||||
if (cancellation is not null)
|
||||
private void StopRefreshLoop() =>
|
||||
_refreshEpoch.Stop(status => status with
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellation.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
IsActive = false,
|
||||
NextAtUtc = null
|
||||
});
|
||||
|
||||
private void ResetRefreshState()
|
||||
{
|
||||
StopRefreshLoop();
|
||||
Interlocked.Exchange(ref _refreshCompletedCount, 0);
|
||||
_refreshLimitReached = false;
|
||||
_refreshMessage = string.Empty;
|
||||
}
|
||||
private void ResetRefreshState() =>
|
||||
_refreshEpoch.Stop(_ => LegacyRefreshRuntimeStatus.Empty with
|
||||
{
|
||||
MaximumRefreshCount = _playoutOptions?.MaximumAutomaticRefreshesPerTakeIn
|
||||
});
|
||||
|
||||
private async Task RunRefreshLoopAsync(
|
||||
LegacyPlayoutWorkflow workflow,
|
||||
TimeSpan firstInterval,
|
||||
LegacyRefreshScheduler scheduler,
|
||||
CancellationTokenSource cancellation)
|
||||
{
|
||||
var token = cancellation.Token;
|
||||
var delay = firstInterval;
|
||||
var maximum = _playoutOptions?.MaximumAutomaticRefreshesPerTakeIn;
|
||||
try
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
while (true)
|
||||
{
|
||||
var engine = _playoutEngine;
|
||||
if (engine is null)
|
||||
if (engine is null ||
|
||||
!ReferenceEquals(_playoutWorkflow, workflow) ||
|
||||
!_refreshEpoch.IsCurrent(cancellation) ||
|
||||
Volatile.Read(ref _playoutShutdownStarted) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (maximum is { } cap && Volatile.Read(ref _refreshCompletedCount) >= cap)
|
||||
if (engine.Status.State == PlayoutConnectionState.OutcomeUnknown)
|
||||
{
|
||||
if (!await WaitForPlayCompletionAsync(engine, token)
|
||||
.ConfigureAwait(false))
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"PLAYOUT_OUTCOME_UNKNOWN",
|
||||
"송출 상태가 불명확하여 자동 갱신을 시작하지 않았습니다.");
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheduler.HasReachedMaximum)
|
||||
{
|
||||
if (!await scheduler.DrainMaximumCallbackAsync(
|
||||
callbackToken => WaitForPlayCompletionAsync(
|
||||
engine,
|
||||
callbackToken),
|
||||
token).ConfigureAwait(false))
|
||||
{
|
||||
_refreshMessage =
|
||||
"자동 갱신 상한에 도달했지만 마지막 재생 완료 이벤트를 확인하지 못했습니다. PGM을 확인하세요.";
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"PLAY_CALLBACK_TIMEOUT",
|
||||
"자동 갱신 상한에 도달했지만 마지막 재생 완료 이벤트를 확인하지 못했습니다. 결과가 불명확하므로 PGM을 확인하세요.");
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_refreshLimitReached = true;
|
||||
_refreshMessage = $"자동 갱신 상한 {cap}회에 도달했습니다.";
|
||||
if (engine.Status.State == PlayoutConnectionState.OutcomeUnknown)
|
||||
{
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"PLAY_CALLBACK_OUTCOME_UNKNOWN",
|
||||
"마지막 재생 완료 상태가 불명확합니다. PGM과 Network Monitoring을 확인하세요.");
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_refreshEpoch.TryUpdate(
|
||||
cancellation,
|
||||
status => status with
|
||||
{
|
||||
IsActive = false,
|
||||
NextAtUtc = null,
|
||||
CompletedRefreshCount = scheduler.CompletedRefreshCount,
|
||||
MaximumRefreshCount = scheduler.MaximumRefreshCount,
|
||||
IsLimitReached = true,
|
||||
Message = $"자동 갱신 상한 {scheduler.MaximumRefreshCount}회에 도달했습니다."
|
||||
});
|
||||
QueueOperatorState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await WaitForPlayCompletionAsync(engine, token)
|
||||
.ConfigureAwait(false))
|
||||
if (!await scheduler.WaitForNextRefreshAsync(
|
||||
callbackToken => WaitForPlayCompletionAsync(
|
||||
engine,
|
||||
callbackToken),
|
||||
scheduledDelay =>
|
||||
{
|
||||
if (_refreshEpoch.TryUpdate(
|
||||
cancellation,
|
||||
status => status.IsActive
|
||||
? status with
|
||||
{
|
||||
NextAtUtc = DateTimeOffset.UtcNow.Add(scheduledDelay)
|
||||
}
|
||||
: status))
|
||||
{
|
||||
QueueOperatorState();
|
||||
}
|
||||
},
|
||||
token).ConfigureAwait(false))
|
||||
{
|
||||
_refreshMessage = "재생 완료 이벤트를 확인하지 못해 자동 갱신을 중단했습니다.";
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"PLAY_CALLBACK_TIMEOUT",
|
||||
"재생 완료 이벤트를 확인하지 못해 자동 갱신을 중단했습니다. 결과가 불명확하므로 PGM을 확인하세요.");
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(delay, token).ConfigureAwait(false);
|
||||
await _playoutCommandGate.WaitAsync(token).ConfigureAwait(false);
|
||||
if (!ReferenceEquals(_playoutWorkflow, workflow) ||
|
||||
!_refreshEpoch.IsCurrent(cancellation) ||
|
||||
Volatile.Read(ref _playoutShutdownStarted) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (engine.Status.State == PlayoutConnectionState.OutcomeUnknown)
|
||||
{
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"PLAY_CALLBACK_OUTCOME_UNKNOWN",
|
||||
"재생 완료 상태가 불명확하여 자동 갱신을 시작하지 않았습니다.");
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var intentGateEntered = false;
|
||||
var playoutGateEntered = false;
|
||||
try
|
||||
{
|
||||
// Match MainForm's single UI thread: Web DB work and the timer
|
||||
// refresh are serialized in the same order as ProcessIntentAsync.
|
||||
await _intentGate.WaitAsync(token).ConfigureAwait(false);
|
||||
intentGateEntered = true;
|
||||
_intentBusy = true;
|
||||
QueueOperatorState();
|
||||
|
||||
await _playoutCommandGate.WaitAsync(token).ConfigureAwait(false);
|
||||
playoutGateEntered = true;
|
||||
Interlocked.Exchange(ref _playoutBusy, 1);
|
||||
var result = await workflow.RefreshOnAirAsync(token).ConfigureAwait(false);
|
||||
QueueOperatorState();
|
||||
|
||||
if (!_refreshEpoch.IsCurrent(cancellation) ||
|
||||
Volatile.Read(ref _playoutShutdownStarted) != 0 ||
|
||||
engine.Status.State == PlayoutConnectionState.OutcomeUnknown)
|
||||
{
|
||||
if (engine.Status.State == PlayoutConnectionState.OutcomeUnknown)
|
||||
{
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"REFRESH_OUTCOME_UNKNOWN",
|
||||
"송출 상태가 불명확하여 자동 갱신을 시작하지 않았습니다.");
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await scheduler.ExecuteRefreshAndTrackSuccessAsync(
|
||||
workflow.RefreshOnAirAsync,
|
||||
static refreshResult => refreshResult.IsSuccess,
|
||||
token).ConfigureAwait(false);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
RefreshFaultCode(result.Code),
|
||||
"자동 장면 갱신을 중단했습니다. TAKE OUT 전 실제 데이터와 PGM을 확인하세요.");
|
||||
if (result.Code is PlayoutResultCode.OutcomeUnknown or
|
||||
PlayoutResultCode.TimedOut)
|
||||
{
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_refreshMessage =
|
||||
"자동 장면 갱신을 중단했습니다. TAKE OUT 전 PGM을 확인하세요.";
|
||||
return;
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref _refreshCompletedCount);
|
||||
delay = LegacySceneRefreshPolicy.RecurringInterval;
|
||||
if (!_refreshEpoch.TryUpdate(
|
||||
cancellation,
|
||||
_ => new LegacyRefreshRuntimeStatus(
|
||||
true,
|
||||
null,
|
||||
DateTimeOffset.UtcNow,
|
||||
false,
|
||||
null,
|
||||
string.Empty,
|
||||
scheduler.CompletedRefreshCount,
|
||||
scheduler.MaximumRefreshCount,
|
||||
false)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (DatabaseInfrastructureException)
|
||||
{
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"DATABASE_UNAVAILABLE",
|
||||
"자동 장면 갱신 중 데이터베이스 조회가 실패했습니다. TAKE OUT 후 확인하세요.");
|
||||
return;
|
||||
}
|
||||
catch (LegacySceneDataException)
|
||||
{
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"SCENE_DATA_INVALID",
|
||||
"자동 장면 갱신 데이터가 유효하지 않습니다. TAKE OUT 후 확인하세요.");
|
||||
return;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"SCENE_ARGUMENT_INVALID",
|
||||
"자동 장면 갱신 요청 데이터가 유효하지 않습니다. TAKE OUT 후 확인하세요.");
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"REFRESH_OUTCOME_UNKNOWN",
|
||||
"자동 갱신 결과가 불명확합니다. 명령을 반복하지 마세요.");
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _playoutBusy, 0);
|
||||
_playoutCommandGate.Release();
|
||||
if (playoutGateEntered)
|
||||
{
|
||||
Interlocked.Exchange(ref _playoutBusy, 0);
|
||||
_playoutCommandGate.Release();
|
||||
}
|
||||
|
||||
if (intentGateEntered)
|
||||
{
|
||||
_intentBusy = false;
|
||||
_intentGate.Release();
|
||||
}
|
||||
|
||||
QueueOperatorState();
|
||||
}
|
||||
}
|
||||
@@ -724,21 +923,55 @@ public sealed partial class MainWindow
|
||||
}
|
||||
catch
|
||||
{
|
||||
TrySetRefreshFault(
|
||||
cancellation,
|
||||
"REFRESH_SCHEDULER_UNKNOWN",
|
||||
"자동 갱신 예약 상태를 확인할 수 없습니다. 명령을 반복하지 마세요.");
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
_refreshMessage = "자동 갱신 결과가 불명확합니다. 명령을 반복하지 마세요.";
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ReferenceEquals(_refreshCancellation, cancellation))
|
||||
{
|
||||
Interlocked.CompareExchange(ref _refreshCancellation, null, cancellation);
|
||||
}
|
||||
|
||||
var completedCurrentEpoch = _refreshEpoch.TryComplete(
|
||||
cancellation,
|
||||
status => status with
|
||||
{
|
||||
IsActive = false,
|
||||
NextAtUtc = null
|
||||
});
|
||||
cancellation.Dispose();
|
||||
QueueOperatorState();
|
||||
if (completedCurrentEpoch)
|
||||
{
|
||||
QueueOperatorState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySetRefreshFault(
|
||||
CancellationTokenSource cancellation,
|
||||
string code,
|
||||
string message) =>
|
||||
_refreshEpoch.TryUpdate(
|
||||
cancellation,
|
||||
status => status with
|
||||
{
|
||||
IsActive = false,
|
||||
NextAtUtc = null,
|
||||
IsFaulted = true,
|
||||
FaultCode = code,
|
||||
Message = message
|
||||
});
|
||||
|
||||
private static string RefreshFaultCode(PlayoutResultCode code) => code switch
|
||||
{
|
||||
PlayoutResultCode.OutcomeUnknown => "OUTCOME_UNKNOWN",
|
||||
PlayoutResultCode.TimedOut => "TIMED_OUT",
|
||||
PlayoutResultCode.Rejected => "REJECTED",
|
||||
PlayoutResultCode.Cancelled => "CANCELLED",
|
||||
PlayoutResultCode.Unavailable => "UNAVAILABLE",
|
||||
PlayoutResultCode.Failed => "FAILED",
|
||||
_ => "REFRESH_FAILED"
|
||||
};
|
||||
|
||||
private static async Task<bool> WaitForPlayCompletionAsync(
|
||||
IPlayoutEngine engine,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -886,4 +1119,27 @@ public sealed partial class MainWindow
|
||||
// outcome-unknown session quarantined and will not send a duplicate BYE.
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record LegacyRefreshRuntimeStatus(
|
||||
bool IsActive,
|
||||
DateTimeOffset? NextAtUtc,
|
||||
DateTimeOffset? LastSuccessAtUtc,
|
||||
bool IsFaulted,
|
||||
string? FaultCode,
|
||||
string Message,
|
||||
int CompletedRefreshCount,
|
||||
int? MaximumRefreshCount,
|
||||
bool IsLimitReached)
|
||||
{
|
||||
internal static LegacyRefreshRuntimeStatus Empty { get; } = new(
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
string.Empty,
|
||||
0,
|
||||
null,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user