Restart schedule from bottom active item

This commit is contained in:
2026-05-14 12:07:18 +09:00
parent 258b3ddaeb
commit 72afee11fc

View File

@@ -26,6 +26,7 @@ public sealed class ChannelScheduleEngine
private TaskCompletionSource<bool>? _advanceSignal; private TaskCompletionSource<bool>? _advanceSignal;
private Guid? _lastPlaybackItemId; private Guid? _lastPlaybackItemId;
private Guid? _skipCurrentItemId; private Guid? _skipCurrentItemId;
private Guid? _restartFromTopAfterItemId;
private ChannelScheduleItem? _directPlaybackItem; private ChannelScheduleItem? _directPlaybackItem;
private PreparedCutFrame? _preparedCutFrame; private PreparedCutFrame? _preparedCutFrame;
@@ -77,6 +78,12 @@ public sealed class ChannelScheduleEngine
{ {
if (IsRunning) if (IsRunning)
{ {
if (GetNextPlayableItem() is null)
{
await RestartFromTopAsync().ConfigureAwait(false);
return;
}
await AdvanceToNextAsync().ConfigureAwait(false); await AdvanceToNextAsync().ConfigureAwait(false);
return; return;
} }
@@ -87,7 +94,10 @@ public sealed class ChannelScheduleEngine
} }
_lastPlaybackItemId = null; _lastPlaybackItemId = null;
_skipCurrentItemId = null;
_restartFromTopAfterItemId = null;
ResetDataUnavailableItems(); ResetDataUnavailableItems();
ClearStalePlaybackStatesBeforeStart();
_playbackCts = new CancellationTokenSource(); _playbackCts = new CancellationTokenSource();
IsRunning = true; IsRunning = true;
RefreshQueueMarkers(); RefreshQueueMarkers();
@@ -112,6 +122,7 @@ public sealed class ChannelScheduleEngine
ClearPreparedFrame(resetState: true); ClearPreparedFrame(resetState: true);
_lastPlaybackItemId = null; _lastPlaybackItemId = null;
_skipCurrentItemId = null; _skipCurrentItemId = null;
_restartFromTopAfterItemId = null;
RefreshQueueMarkers(); RefreshQueueMarkers();
QueueChanged?.Invoke(this, EventArgs.Empty); QueueChanged?.Invoke(this, EventArgs.Empty);
return; return;
@@ -135,6 +146,7 @@ public sealed class ChannelScheduleEngine
_lastPlaybackItemId = null; _lastPlaybackItemId = null;
_skipCurrentItemId = null; _skipCurrentItemId = null;
_restartFromTopAfterItemId = null;
ClearPreparedFrame(resetState: false); ClearPreparedFrame(resetState: false);
IsRunning = false; IsRunning = false;
RefreshQueueMarkers(); RefreshQueueMarkers();
@@ -152,6 +164,8 @@ public sealed class ChannelScheduleEngine
try try
{ {
_lastPlaybackItemId = null; _lastPlaybackItemId = null;
_skipCurrentItemId = null;
_restartFromTopAfterItemId = null;
ResetDataUnavailableItems(); ResetDataUnavailableItems();
ClearPreparedFrame(resetState: true); ClearPreparedFrame(resetState: true);
RefreshQueueMarkers(); RefreshQueueMarkers();
@@ -260,6 +274,8 @@ public sealed class ChannelScheduleEngine
public void Reset() public void Reset()
{ {
_lastPlaybackItemId = null; _lastPlaybackItemId = null;
_skipCurrentItemId = null;
_restartFromTopAfterItemId = null;
ClearPreparedFrame(resetState: false); ClearPreparedFrame(resetState: false);
foreach (var item in Queue) foreach (var item in Queue)
{ {
@@ -327,6 +343,37 @@ public sealed class ChannelScheduleEngine
return Task.CompletedTask; 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) public bool Remove(ChannelScheduleItem? item)
{ {
if (item is null || !item.CanDelete) if (item is null || !item.CanDelete)
@@ -547,7 +594,7 @@ public sealed class ChannelScheduleEngine
if (regionTargets.Count == 0) if (regionTargets.Count == 0)
{ {
_lastPlaybackItemId = queueItem.Id; MarkLastPlaybackItem(queueItem);
MarkDataUnavailable(queueItem, "선택한 지역 조건에 송출 가능한 데이터가 없습니다."); MarkDataUnavailable(queueItem, "선택한 지역 조건에 송출 가능한 데이터가 없습니다.");
RefreshQueueMarkers(); RefreshQueueMarkers();
return; return;
@@ -637,7 +684,7 @@ public sealed class ChannelScheduleEngine
queueItem.CurrentRegionLabel = string.Empty; queueItem.CurrentRegionLabel = string.Empty;
queueItem.ClearInternalNextPreview(); queueItem.ClearInternalNextPreview();
queueItem.ClearPlaybackCountdown(); queueItem.ClearPlaybackCountdown();
_lastPlaybackItemId = queueItem.Id; MarkLastPlaybackItem(queueItem);
if (playedAny) if (playedAny)
{ {
queueItem.State = ScheduleQueueItemState.Queued; queueItem.State = ScheduleQueueItemState.Queued;
@@ -737,7 +784,7 @@ public sealed class ChannelScheduleEngine
queueItem.CurrentRegionLabel = string.Empty; queueItem.CurrentRegionLabel = string.Empty;
queueItem.ClearInternalNextPreview(); queueItem.ClearInternalNextPreview();
queueItem.ClearPlaybackCountdown(); queueItem.ClearPlaybackCountdown();
_lastPlaybackItemId = queueItem.Id; MarkLastPlaybackItem(queueItem);
if (playedAny) if (playedAny)
{ {
queueItem.State = ScheduleQueueItemState.Queued; queueItem.State = ScheduleQueueItemState.Queued;
@@ -1770,6 +1817,36 @@ public sealed class ChannelScheduleEngine
return item.State is ScheduleQueueItemState.Queued or ScheduleQueueItemState.Next or ScheduleQueueItemState.Completed; 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() private void ResetDataUnavailableItems()
{ {
foreach (var item in Queue.Where(item => item.State == ScheduleQueueItemState.DataUnavailable)) foreach (var item in Queue.Where(item => item.State == ScheduleQueueItemState.DataUnavailable))