feat: align legacy operator and playout behavior
This commit is contained in:
@@ -136,42 +136,44 @@ public sealed partial class MainWindow
|
||||
try
|
||||
{
|
||||
Interlocked.Exchange(ref _playoutBusy, 1);
|
||||
StopRefreshLoop();
|
||||
var nextKindBefore = workflow.State.NextKind;
|
||||
if (command == LegacyOperatorPlayoutCommand.Next &&
|
||||
var pageNavigationEnabledBefore = workflow.State.IsPageNavigationEnabled;
|
||||
if (engine.Status.IsTakeOutCompletionPending)
|
||||
{
|
||||
return _controller.ReportError(
|
||||
"Tornado TAKE OUT 완료 이벤트를 기다리는 동안에는 송출 명령을 실행할 수 없습니다.");
|
||||
}
|
||||
|
||||
if (command != LegacyOperatorPlayoutCommand.TakeOut &&
|
||||
engine.Status.IsPlayCompletionPending)
|
||||
{
|
||||
return _controller.ReportError(
|
||||
"Tornado 재생 완료 이벤트를 기다리는 중입니다. NEXT를 반복하지 마세요.");
|
||||
"Tornado 재생 완료 이벤트를 기다리는 중입니다. 결과가 확인될 때까지 TAKE OUT만 사용할 수 있습니다.");
|
||||
}
|
||||
|
||||
StopRefreshLoop();
|
||||
PlayoutResult result;
|
||||
switch (command)
|
||||
{
|
||||
case LegacyOperatorPlayoutCommand.Prepare:
|
||||
var request = _controller.CreatePlayoutRequest();
|
||||
var request = _controller.CreatePlayoutRequest(
|
||||
_compositionOptions!.Current.FadeDuration);
|
||||
result = await workflow.PrepareAsync(
|
||||
request.Playlist,
|
||||
request.SelectedIndexZeroBased,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case LegacyOperatorPlayoutCommand.TakeIn:
|
||||
// MainForm.btnTake_Click performed ONAirMode (load/prepare) and
|
||||
// Play in one visible F8 action. Keep the explicit PREPARE control
|
||||
// for engineering checks, but preserve the original one-click path
|
||||
// whenever no scene is already prepared.
|
||||
if (string.IsNullOrWhiteSpace(workflow.State.PreparedCutCode))
|
||||
{
|
||||
var takeInRequest = _controller.CreatePlayoutRequest();
|
||||
result = await workflow.PrepareAndTakeInAsync(
|
||||
takeInRequest.Playlist,
|
||||
takeInRequest.SelectedIndexZeroBased,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = await workflow.TakeInAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
// MainForm.btnTake_Click always copied m_rowidx (the operator's
|
||||
// candidate) to m_crow and ran ONAirMode + Play, even while another
|
||||
// row was already on air. Re-read the current selection on every F8;
|
||||
// a stale explicit PREPARE must never override a later row click.
|
||||
var takeInRequest = _controller.CreatePlayoutRequest(
|
||||
_compositionOptions!.Current.FadeDuration);
|
||||
result = await workflow.TakeSelectedAsync(
|
||||
takeInRequest.Playlist,
|
||||
takeInRequest.SelectedIndexZeroBased,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case LegacyOperatorPlayoutCommand.Next:
|
||||
result = await workflow.NextAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -185,6 +187,23 @@ public sealed partial class MainWindow
|
||||
throw new ArgumentOutOfRangeException(nameof(command));
|
||||
}
|
||||
|
||||
if (result.IsSuccess &&
|
||||
command != LegacyOperatorPlayoutCommand.TakeOut &&
|
||||
workflow.State.CurrentEntryId is { Length: > 0 } currentEntryId &&
|
||||
workflow.State.PageCount > 0)
|
||||
{
|
||||
// FarPoint column 5 was mutable operator state: F8/PREPARE reset a
|
||||
// paged cut to 1/N, while Page NEXT left 2/N, 3/N, ... in the row.
|
||||
// NEXT must not replace the independently selected F8 candidate.
|
||||
_controller.ReflectSuccessfulPlayout(
|
||||
currentEntryId,
|
||||
workflow.State.PageIndexZeroBased,
|
||||
workflow.State.PageCount,
|
||||
synchronizeOperatorSelection:
|
||||
command is LegacyOperatorPlayoutCommand.Prepare or
|
||||
LegacyOperatorPlayoutCommand.TakeIn);
|
||||
}
|
||||
|
||||
if (result.Code is PlayoutResultCode.OutcomeUnknown or PlayoutResultCode.TimedOut)
|
||||
{
|
||||
await QuarantinePlayoutAsync().ConfigureAwait(false);
|
||||
@@ -197,9 +216,15 @@ public sealed partial class MainWindow
|
||||
return _controller.ReportError(result.Message);
|
||||
}
|
||||
|
||||
if (command == LegacyOperatorPlayoutCommand.TakeOut)
|
||||
{
|
||||
ResetRefreshState();
|
||||
}
|
||||
|
||||
if (command == LegacyOperatorPlayoutCommand.TakeIn ||
|
||||
command == LegacyOperatorPlayoutCommand.Next &&
|
||||
nextKindBefore == LegacyWorkflowNextKind.PlaylistNext)
|
||||
nextKindBefore == LegacyWorkflowNextKind.PlaylistNext &&
|
||||
!pageNavigationEnabledBefore)
|
||||
{
|
||||
StartRefreshLoop(workflow);
|
||||
}
|
||||
@@ -237,9 +262,9 @@ public sealed partial class MainWindow
|
||||
int fadeDuration,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (fadeDuration is < 0 or > 60)
|
||||
if (fadeDuration is < 0 or > 19)
|
||||
{
|
||||
return _controller.ReportError("Fade 값이 올바르지 않습니다.");
|
||||
return _controller.ReportError("DissolveTime 값이 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
if (!await _playoutCommandGate.WaitAsync(0, cancellationToken).ConfigureAwait(false))
|
||||
@@ -251,13 +276,13 @@ public sealed partial class MainWindow
|
||||
{
|
||||
if (!CanChangeComposition())
|
||||
{
|
||||
return _controller.ReportError("Fade는 IDLE 상태에서만 변경할 수 있습니다.");
|
||||
return _controller.ReportError("DissolveTime은 IDLE 상태에서만 변경할 수 있습니다.");
|
||||
}
|
||||
|
||||
var source = _compositionOptions!;
|
||||
source.Update(source.Current with { FadeDuration = fadeDuration });
|
||||
return _controller.ReportInformation(
|
||||
$"Fade {fadeDuration}을 다음 PREPARE부터 적용합니다.");
|
||||
$"DissolveTime {fadeDuration + 1}을 다음 PREPARE부터 적용합니다.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -265,6 +290,14 @@ public sealed partial class MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
private LegacyOperatorSnapshot SelectPlaylistBoundaryAndStopRefresh(bool last)
|
||||
{
|
||||
// MainForm.ProcessCmdKey stopped timer1 before applying either Home or End.
|
||||
// Ordinary row selection deliberately does not use this path.
|
||||
StopRefreshLoop();
|
||||
return _controller.SelectPlaylistBoundary(last);
|
||||
}
|
||||
|
||||
private async Task<LegacyOperatorSnapshot> ToggleBackgroundAsync(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -390,6 +423,7 @@ public sealed partial class MainWindow
|
||||
Volatile.Read(ref _playoutBusy) == 0 &&
|
||||
Volatile.Read(ref _playoutQuarantined) == 0 &&
|
||||
!status.IsPlayCompletionPending &&
|
||||
!status.IsTakeOutCompletionPending &&
|
||||
string.IsNullOrWhiteSpace(status.PreparedSceneName) &&
|
||||
string.IsNullOrWhiteSpace(status.OnAirSceneName) &&
|
||||
string.IsNullOrWhiteSpace(workflow.PreparedCutCode) &&
|
||||
@@ -413,6 +447,7 @@ public sealed partial class MainWindow
|
||||
Volatile.Read(ref _playoutShutdownStarted) == 0 &&
|
||||
status.State != PlayoutConnectionState.OutcomeUnknown &&
|
||||
!status.IsPlayCompletionPending &&
|
||||
!status.IsTakeOutCompletionPending &&
|
||||
string.IsNullOrWhiteSpace(status.PreparedSceneName) &&
|
||||
string.IsNullOrWhiteSpace(status.OnAirSceneName) &&
|
||||
string.IsNullOrWhiteSpace(workflow.PreparedCutCode) &&
|
||||
@@ -421,6 +456,9 @@ public sealed partial class MainWindow
|
||||
}
|
||||
|
||||
private static bool IsOperatorMutationIntent(LegacyUiIntent intent) => intent is
|
||||
LegacyCutPointerDownIntent or
|
||||
LegacyCutKeyDownIntent or
|
||||
LegacyClearCutSelectionIntent or
|
||||
LegacyDropSelectedCutsIntent or
|
||||
LegacySetPlaylistEnabledIntent or
|
||||
LegacySetAllPlaylistEnabledIntent or
|
||||
@@ -521,7 +559,7 @@ public sealed partial class MainWindow
|
||||
: LegacyOperatorPlayoutPhase.Idle;
|
||||
var backgroundEnabled =
|
||||
composition.BackgroundKind != LegacySceneBackgroundKind.None;
|
||||
return new LegacyOperatorPlayoutSnapshot(
|
||||
var snapshot = new LegacyOperatorPlayoutSnapshot(
|
||||
status?.Mode ?? PlayoutMode.Disabled,
|
||||
quarantined
|
||||
? PlayoutConnectionState.OutcomeUnknown
|
||||
@@ -563,6 +601,10 @@ public sealed partial class MainWindow
|
||||
: _playoutInitializationWarning ?? status?.Message ??
|
||||
_playoutInitializationError ??
|
||||
"송출 어댑터를 사용할 수 없습니다.");
|
||||
return snapshot with
|
||||
{
|
||||
IsTakeOutCompletionPending = status?.IsTakeOutCompletionPending ?? false
|
||||
};
|
||||
}
|
||||
|
||||
private void StartRefreshLoop(LegacyPlayoutWorkflow workflow)
|
||||
@@ -599,6 +641,14 @@ public sealed partial class MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetRefreshState()
|
||||
{
|
||||
StopRefreshLoop();
|
||||
Interlocked.Exchange(ref _refreshCompletedCount, 0);
|
||||
_refreshLimitReached = false;
|
||||
_refreshMessage = string.Empty;
|
||||
}
|
||||
|
||||
private async Task RunRefreshLoopAsync(
|
||||
LegacyPlayoutWorkflow workflow,
|
||||
TimeSpan firstInterval,
|
||||
@@ -809,7 +859,8 @@ public sealed partial class MainWindow
|
||||
if (Volatile.Read(ref _playoutQuarantined) == 0 &&
|
||||
engine.Status.State != PlayoutConnectionState.OutcomeUnknown &&
|
||||
string.IsNullOrWhiteSpace(engine.Status.OnAirSceneName) &&
|
||||
!engine.Status.IsPlayCompletionPending)
|
||||
!engine.Status.IsPlayCompletionPending &&
|
||||
!engine.Status.IsTakeOutCompletionPending)
|
||||
{
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
await engine.DisconnectAsync(timeout.Token).ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user