feat(schedule): add loop and recycle policy options to programming blocks
This commit is contained in:
@@ -11,10 +11,13 @@ pub(super) fn fill_block<'a>(
|
||||
target_secs: u32,
|
||||
strategy: &FillStrategy,
|
||||
last_item_id: Option<&MediaItemId>,
|
||||
loop_on_finish: bool,
|
||||
) -> Vec<&'a MediaItem> {
|
||||
match strategy {
|
||||
FillStrategy::BestFit => fill_best_fit(pool, target_secs),
|
||||
FillStrategy::Sequential => fill_sequential(candidates, pool, target_secs, last_item_id),
|
||||
FillStrategy::Sequential => {
|
||||
fill_sequential(candidates, pool, target_secs, last_item_id, loop_on_finish)
|
||||
}
|
||||
FillStrategy::Random => {
|
||||
let mut indices: Vec<usize> = (0..pool.len()).collect();
|
||||
indices.shuffle(&mut rand::thread_rng());
|
||||
@@ -84,6 +87,7 @@ pub(super) fn fill_sequential<'a>(
|
||||
pool: &'a [MediaItem],
|
||||
target_secs: u32,
|
||||
last_item_id: Option<&MediaItemId>,
|
||||
loop_on_finish: bool,
|
||||
) -> Vec<&'a MediaItem> {
|
||||
if pool.is_empty() {
|
||||
return vec![];
|
||||
@@ -92,19 +96,35 @@ pub(super) fn fill_sequential<'a>(
|
||||
// Set of item IDs currently eligible to air.
|
||||
let available: HashSet<&MediaItemId> = pool.iter().map(|i| &i.id).collect();
|
||||
|
||||
// Find where in the full ordered list to resume.
|
||||
// Falls back to index 0 if last_item_id is absent or was removed from the library.
|
||||
let start_idx = last_item_id
|
||||
.and_then(|id| candidates.iter().position(|c| &c.id == id))
|
||||
.map(|pos| (pos + 1) % candidates.len())
|
||||
.unwrap_or(0);
|
||||
let ordered: Vec<&MediaItem> = if loop_on_finish {
|
||||
// Find where in the full ordered list to resume, wrapping around.
|
||||
// Falls back to index 0 if last_item_id is absent or was removed from the library.
|
||||
let start_idx = last_item_id
|
||||
.and_then(|id| candidates.iter().position(|c| &c.id == id))
|
||||
.map(|pos| (pos + 1) % candidates.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
// Walk candidates in order from start_idx, wrapping around once,
|
||||
// skipping any that are on cooldown (not in `available`).
|
||||
let ordered: Vec<&MediaItem> = (0..candidates.len())
|
||||
.map(|i| &candidates[(start_idx + i) % candidates.len()])
|
||||
.filter(|item| available.contains(&item.id))
|
||||
.collect();
|
||||
(0..candidates.len())
|
||||
.map(|i| &candidates[(start_idx + i) % candidates.len()])
|
||||
.filter(|item| available.contains(&item.id))
|
||||
.collect()
|
||||
} else {
|
||||
// No wrap: compute raw next position without modulo.
|
||||
// If the series has finished (next_pos >= len), return dead air.
|
||||
let next_pos = last_item_id
|
||||
.and_then(|id| candidates.iter().position(|c| &c.id == id))
|
||||
.map(|pos| pos + 1)
|
||||
.unwrap_or(0);
|
||||
|
||||
if next_pos >= candidates.len() {
|
||||
return vec![]; // series finished — dead air
|
||||
}
|
||||
|
||||
candidates[next_pos..]
|
||||
.iter()
|
||||
.filter(|item| available.contains(&item.id))
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Greedily fill the block's time budget in episode order.
|
||||
let mut remaining = target_secs;
|
||||
|
||||
@@ -255,6 +255,8 @@ impl ScheduleEngineService {
|
||||
self.resolve_algorithmic(
|
||||
filter, strategy, start, end, history, policy, generation,
|
||||
block.id, last_item_id,
|
||||
block.loop_on_finish,
|
||||
block.ignore_recycle_policy,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -311,6 +313,8 @@ impl ScheduleEngineService {
|
||||
generation: u32,
|
||||
block_id: BlockId,
|
||||
last_item_id: Option<&MediaItemId>,
|
||||
loop_on_finish: bool,
|
||||
ignore_recycle_policy: bool,
|
||||
) -> DomainResult<Vec<ScheduledSlot>> {
|
||||
// `candidates` — all items matching the filter, in provider order.
|
||||
// Kept separate from `pool` so Sequential can rotate through the full
|
||||
@@ -321,9 +325,14 @@ impl ScheduleEngineService {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let pool = recycle::apply_recycle_policy(&candidates, history, policy, generation);
|
||||
let pool = if ignore_recycle_policy {
|
||||
candidates.clone()
|
||||
} else {
|
||||
recycle::apply_recycle_policy(&candidates, history, policy, generation)
|
||||
};
|
||||
let target_secs = (end - start).num_seconds() as u32;
|
||||
let selected = fill::fill_block(&candidates, &pool, target_secs, strategy, last_item_id);
|
||||
let selected =
|
||||
fill::fill_block(&candidates, &pool, target_secs, strategy, last_item_id, loop_on_finish);
|
||||
|
||||
let mut slots = Vec::new();
|
||||
let mut cursor = start;
|
||||
|
||||
Reference in New Issue
Block a user