Complete legacy operator UI and playout migration
This commit is contained in:
251
MainWindow.PlaylistPagePlans.cs
Normal file
251
MainWindow.PlaylistPagePlans.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System.Text.Json;
|
||||
using MBN_STOCK_WEBVIEW.Core.Playout;
|
||||
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
|
||||
using MBN_STOCK_WEBVIEW.Infrastructure;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW;
|
||||
|
||||
public sealed partial class MainWindow
|
||||
{
|
||||
private async Task HandlePlaylistPagePlansRequestAsync(JsonElement payload)
|
||||
{
|
||||
var parsed = PlayoutBridgeProtocol.ParsePagePlanRequest(payload);
|
||||
var request = parsed.Request;
|
||||
if (!parsed.IsValid)
|
||||
{
|
||||
PostPlaylistPagePlansError(
|
||||
request.RequestId,
|
||||
"INVALID_REQUEST",
|
||||
parsed.Error,
|
||||
retryable: false);
|
||||
return;
|
||||
}
|
||||
|
||||
var workflow = _playoutWorkflow;
|
||||
var engine = _playoutEngine;
|
||||
if (workflow is null || engine is null)
|
||||
{
|
||||
PostPlaylistPagePlansError(
|
||||
request.RequestId,
|
||||
"PLAYOUT_UNAVAILABLE",
|
||||
_playoutInitializationError ??
|
||||
"The playout workflow is not available for page preflight.",
|
||||
retryable: false);
|
||||
return;
|
||||
}
|
||||
|
||||
var requestCancellation = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
_lifetimeCancellation.Token);
|
||||
var requestToken = requestCancellation.Token;
|
||||
var browserGeneration = Volatile.Read(ref _playlistPagePlanBrowserGeneration);
|
||||
var previousCancellation = Interlocked.Exchange(
|
||||
ref _playlistPagePlanCancellation,
|
||||
requestCancellation);
|
||||
CancelRequest(previousCancellation);
|
||||
|
||||
var databaseActivityEntered = false;
|
||||
try
|
||||
{
|
||||
if (Volatile.Read(ref _playoutCommandInFlight) != 0)
|
||||
{
|
||||
PostPlaylistPagePlansErrorIfCurrent(
|
||||
request.RequestId,
|
||||
"PLAYOUT_PRIORITY",
|
||||
"A playout command has priority over page preflight.",
|
||||
retryable: true,
|
||||
requestCancellation,
|
||||
browserGeneration);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsPlaylistPagePlanIdle(workflow, engine))
|
||||
{
|
||||
PostPlaylistPagePlansErrorIfCurrent(
|
||||
request.RequestId,
|
||||
"PLAYOUT_NOT_IDLE",
|
||||
"Page preflight is available only while no scene is prepared or on air.",
|
||||
retryable: true,
|
||||
requestCancellation,
|
||||
browserGeneration);
|
||||
return;
|
||||
}
|
||||
|
||||
await _databaseActivityGate.WaitAsync(requestToken);
|
||||
databaseActivityEntered = true;
|
||||
requestToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (Volatile.Read(ref _playoutCommandInFlight) != 0)
|
||||
{
|
||||
PostPlaylistPagePlansErrorIfCurrent(
|
||||
request.RequestId,
|
||||
"PLAYOUT_PRIORITY",
|
||||
"A playout command cancelled page preflight before the database read.",
|
||||
retryable: true,
|
||||
requestCancellation,
|
||||
browserGeneration);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsPlaylistPagePlanIdle(workflow, engine))
|
||||
{
|
||||
PostPlaylistPagePlansErrorIfCurrent(
|
||||
request.RequestId,
|
||||
"PLAYOUT_NOT_IDLE",
|
||||
"Playout became active before page preflight could start.",
|
||||
retryable: true,
|
||||
requestCancellation,
|
||||
browserGeneration);
|
||||
return;
|
||||
}
|
||||
|
||||
var plans = await workflow.PreflightPagePlansAsync(
|
||||
request.Entries!,
|
||||
requestToken);
|
||||
requestToken.ThrowIfCancellationRequested();
|
||||
if (!IsCurrentPlaylistPagePlanRequest(requestCancellation, browserGeneration))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PostMessage("playlist-page-plans-results", new
|
||||
{
|
||||
requestId = request.RequestId,
|
||||
calculatedAt = DateTimeOffset.UtcNow,
|
||||
plans = plans.Select(plan => new
|
||||
{
|
||||
entryId = plan.EntryId,
|
||||
itemCount = plan.ItemCount,
|
||||
pageSize = plan.PageSize,
|
||||
pageCount = plan.PageCount,
|
||||
accessibleItemCount = plan.AccessibleItemCount,
|
||||
isTruncated = plan.IsTruncated,
|
||||
builderKey = plan.BuilderKey
|
||||
})
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException) when (requestToken.IsCancellationRequested)
|
||||
{
|
||||
if (Volatile.Read(ref _playoutCommandInFlight) != 0)
|
||||
{
|
||||
PostPlaylistPagePlansErrorIfCurrent(
|
||||
request.RequestId,
|
||||
"PLAYOUT_PRIORITY",
|
||||
"A playout command cancelled the read-only page preflight.",
|
||||
retryable: true,
|
||||
requestCancellation,
|
||||
browserGeneration);
|
||||
}
|
||||
}
|
||||
catch (DatabaseOperationException exception)
|
||||
{
|
||||
PostPlaylistPagePlansErrorIfCurrent(
|
||||
request.RequestId,
|
||||
"DATABASE_UNAVAILABLE",
|
||||
exception.Message,
|
||||
retryable: true,
|
||||
requestCancellation,
|
||||
browserGeneration);
|
||||
}
|
||||
catch (LegacySceneDataException exception)
|
||||
{
|
||||
PostPlaylistPagePlansErrorIfCurrent(
|
||||
request.RequestId,
|
||||
"SCENE_DATA_INVALID",
|
||||
exception.Message,
|
||||
retryable: false,
|
||||
requestCancellation,
|
||||
browserGeneration);
|
||||
}
|
||||
catch (InvalidOperationException exception)
|
||||
{
|
||||
PostPlaylistPagePlansErrorIfCurrent(
|
||||
request.RequestId,
|
||||
"PLAYOUT_NOT_IDLE",
|
||||
exception.Message,
|
||||
retryable: true,
|
||||
requestCancellation,
|
||||
browserGeneration);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
PostPlaylistPagePlansErrorIfCurrent(
|
||||
request.RequestId,
|
||||
"PAGE_PREFLIGHT_FAILED",
|
||||
exception.Message,
|
||||
retryable: false,
|
||||
requestCancellation,
|
||||
browserGeneration);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (databaseActivityEntered)
|
||||
{
|
||||
_databaseActivityGate.Release();
|
||||
}
|
||||
|
||||
Interlocked.CompareExchange(
|
||||
ref _playlistPagePlanCancellation,
|
||||
null,
|
||||
requestCancellation);
|
||||
requestCancellation.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPlaylistPagePlanIdle(
|
||||
LegacyPlayoutWorkflow workflow,
|
||||
IPlayoutEngine engine)
|
||||
{
|
||||
var workflowState = workflow.State;
|
||||
var engineStatus = engine.Status;
|
||||
return string.IsNullOrWhiteSpace(workflowState.PreparedCutCode) &&
|
||||
string.IsNullOrWhiteSpace(workflowState.OnAirCutCode) &&
|
||||
string.IsNullOrWhiteSpace(engineStatus.PreparedSceneName) &&
|
||||
string.IsNullOrWhiteSpace(engineStatus.OnAirSceneName);
|
||||
}
|
||||
|
||||
private bool IsCurrentPlaylistPagePlanRequest(
|
||||
CancellationTokenSource requestCancellation,
|
||||
int browserGeneration) =>
|
||||
ReferenceEquals(
|
||||
Volatile.Read(ref _playlistPagePlanCancellation),
|
||||
requestCancellation) &&
|
||||
browserGeneration == Volatile.Read(ref _playlistPagePlanBrowserGeneration) &&
|
||||
!_lifetimeCancellation.IsCancellationRequested;
|
||||
|
||||
private void PostPlaylistPagePlansErrorIfCurrent(
|
||||
string requestId,
|
||||
string code,
|
||||
string message,
|
||||
bool retryable,
|
||||
CancellationTokenSource requestCancellation,
|
||||
int browserGeneration)
|
||||
{
|
||||
if (!IsCurrentPlaylistPagePlanRequest(requestCancellation, browserGeneration))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PostPlaylistPagePlansError(requestId, code, message, retryable);
|
||||
}
|
||||
|
||||
private void PostPlaylistPagePlansError(
|
||||
string requestId,
|
||||
string code,
|
||||
string message,
|
||||
bool retryable)
|
||||
{
|
||||
PostMessage("playlist-page-plans-error", new
|
||||
{
|
||||
requestId,
|
||||
code,
|
||||
message,
|
||||
retryable
|
||||
});
|
||||
}
|
||||
|
||||
private void InvalidatePlaylistPagePlanBrowserRequest()
|
||||
{
|
||||
Interlocked.Increment(ref _playlistPagePlanBrowserGeneration);
|
||||
CancelRequest(Interlocked.Exchange(ref _playlistPagePlanCancellation, null));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user