From 72afee11fcc873cbe1f03c7a48270af4c880e52c Mon Sep 17 00:00:00 2001 From: y2keui Date: Thu, 14 May 2026 12:07:18 +0900 Subject: [PATCH] Restart schedule from bottom active item --- .../Services/ChannelScheduleEngine.cs | 83 ++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/Tornado3_2026Election/Services/ChannelScheduleEngine.cs b/Tornado3_2026Election/Services/ChannelScheduleEngine.cs index b84dbdf..edc73e7 100644 --- a/Tornado3_2026Election/Services/ChannelScheduleEngine.cs +++ b/Tornado3_2026Election/Services/ChannelScheduleEngine.cs @@ -26,6 +26,7 @@ public sealed class ChannelScheduleEngine private TaskCompletionSource? _advanceSignal; private Guid? _lastPlaybackItemId; private Guid? _skipCurrentItemId; + private Guid? _restartFromTopAfterItemId; private ChannelScheduleItem? _directPlaybackItem; private PreparedCutFrame? _preparedCutFrame; @@ -77,6 +78,12 @@ public sealed class ChannelScheduleEngine { if (IsRunning) { + if (GetNextPlayableItem() is null) + { + await RestartFromTopAsync().ConfigureAwait(false); + return; + } + await AdvanceToNextAsync().ConfigureAwait(false); return; } @@ -87,7 +94,10 @@ public sealed class ChannelScheduleEngine } _lastPlaybackItemId = null; + _skipCurrentItemId = null; + _restartFromTopAfterItemId = null; ResetDataUnavailableItems(); + ClearStalePlaybackStatesBeforeStart(); _playbackCts = new CancellationTokenSource(); IsRunning = true; RefreshQueueMarkers(); @@ -112,6 +122,7 @@ public sealed class ChannelScheduleEngine ClearPreparedFrame(resetState: true); _lastPlaybackItemId = null; _skipCurrentItemId = null; + _restartFromTopAfterItemId = null; RefreshQueueMarkers(); QueueChanged?.Invoke(this, EventArgs.Empty); return; @@ -135,6 +146,7 @@ public sealed class ChannelScheduleEngine _lastPlaybackItemId = null; _skipCurrentItemId = null; + _restartFromTopAfterItemId = null; ClearPreparedFrame(resetState: false); IsRunning = false; RefreshQueueMarkers(); @@ -152,6 +164,8 @@ public sealed class ChannelScheduleEngine try { _lastPlaybackItemId = null; + _skipCurrentItemId = null; + _restartFromTopAfterItemId = null; ResetDataUnavailableItems(); ClearPreparedFrame(resetState: true); RefreshQueueMarkers(); @@ -260,6 +274,8 @@ public sealed class ChannelScheduleEngine public void Reset() { _lastPlaybackItemId = null; + _skipCurrentItemId = null; + _restartFromTopAfterItemId = null; ClearPreparedFrame(resetState: false); foreach (var item in Queue) { @@ -327,6 +343,37 @@ public sealed class ChannelScheduleEngine return Task.CompletedTask; } + private Task RestartFromTopAsync() + { + if (!IsRunning) + { + return Task.CompletedTask; + } + + var activeItem = Queue.FirstOrDefault(item => item.State is ScheduleQueueItemState.OnAir or ScheduleQueueItemState.Sending); + if (activeItem is not null) + { + _skipCurrentItemId = activeItem.Id; + _restartFromTopAfterItemId = activeItem.Id; + _lastPlaybackItemId = null; + activeItem.State = ScheduleQueueItemState.Queued; + activeItem.LastError = string.Empty; + activeItem.CurrentRegionLabel = string.Empty; + activeItem.ClearInternalNextPreview(); + activeItem.ClearPlaybackCountdown(); + } + else + { + _lastPlaybackItemId = null; + _restartFromTopAfterItemId = null; + } + + RefreshQueueMarkers(); + QueueChanged?.Invoke(this, EventArgs.Empty); + _advanceSignal?.TrySetResult(true); + return Task.CompletedTask; + } + public bool Remove(ChannelScheduleItem? item) { if (item is null || !item.CanDelete) @@ -547,7 +594,7 @@ public sealed class ChannelScheduleEngine if (regionTargets.Count == 0) { - _lastPlaybackItemId = queueItem.Id; + MarkLastPlaybackItem(queueItem); MarkDataUnavailable(queueItem, "선택한 지역 조건에 송출 가능한 데이터가 없습니다."); RefreshQueueMarkers(); return; @@ -637,7 +684,7 @@ public sealed class ChannelScheduleEngine queueItem.CurrentRegionLabel = string.Empty; queueItem.ClearInternalNextPreview(); queueItem.ClearPlaybackCountdown(); - _lastPlaybackItemId = queueItem.Id; + MarkLastPlaybackItem(queueItem); if (playedAny) { queueItem.State = ScheduleQueueItemState.Queued; @@ -737,7 +784,7 @@ public sealed class ChannelScheduleEngine queueItem.CurrentRegionLabel = string.Empty; queueItem.ClearInternalNextPreview(); queueItem.ClearPlaybackCountdown(); - _lastPlaybackItemId = queueItem.Id; + MarkLastPlaybackItem(queueItem); if (playedAny) { queueItem.State = ScheduleQueueItemState.Queued; @@ -1770,6 +1817,36 @@ public sealed class ChannelScheduleEngine return item.State is ScheduleQueueItemState.Queued or ScheduleQueueItemState.Next or ScheduleQueueItemState.Completed; } + private void MarkLastPlaybackItem(ChannelScheduleItem queueItem) + { + if (_restartFromTopAfterItemId == queueItem.Id) + { + _lastPlaybackItemId = null; + _restartFromTopAfterItemId = null; + return; + } + + _lastPlaybackItemId = queueItem.Id; + } + + private void ClearStalePlaybackStatesBeforeStart() + { + foreach (var item in Queue.Where(item => item.State is ScheduleQueueItemState.OnAir or ScheduleQueueItemState.Sending)) + { + if (_preparedCutFrame?.Item.Id == item.Id) + { + continue; + } + + item.State = ScheduleQueueItemState.Queued; + item.LastError = string.Empty; + item.CurrentRegionLabel = string.Empty; + item.ClearRenderedPreview(); + item.ClearInternalNextPreview(); + item.ClearPlaybackCountdown(); + } + } + private void ResetDataUnavailableItems() { foreach (var item in Queue.Where(item => item.State == ScheduleQueueItemState.DataUnavailable))