wire interstitial insertion into schedule engine resolve_block
This commit is contained in:
@@ -9,7 +9,7 @@ use crate::models::{
|
||||
ScheduledSlot,
|
||||
};
|
||||
use crate::ports::{ChannelQuery, LibraryQuery, ScheduleCommand, ScheduleQuery};
|
||||
use crate::value_objects::{BlockId, ChannelId, FillStrategy, LibrarySearchFilter, MediaItemId, RotationPolicy, Weekday};
|
||||
use crate::value_objects::{BlockId, ChannelId, FillStrategy, InterstitialRule, LibrarySearchFilter, MediaItemId, MediaRole, RotationPolicy, Weekday};
|
||||
|
||||
mod fill;
|
||||
mod rotation;
|
||||
@@ -248,10 +248,10 @@ impl ScheduleEngineService {
|
||||
window: BlockTimeWindow,
|
||||
rotation: RotationContext<'_>,
|
||||
) -> DomainResult<Vec<ScheduledSlot>> {
|
||||
match block.content() {
|
||||
let program_slots = match block.content() {
|
||||
BlockContent::Manual { items } => {
|
||||
self.resolve_manual(items, window.start, window.end, block.id())
|
||||
.await
|
||||
.await?
|
||||
}
|
||||
BlockContent::Algorithmic {
|
||||
filter,
|
||||
@@ -265,12 +265,85 @@ impl ScheduleEngineService {
|
||||
loop_on_finish: block.loop_on_finish(),
|
||||
ignore_rotation_policy: block.ignore_rotation_policy(),
|
||||
},
|
||||
window,
|
||||
BlockTimeWindow {
|
||||
start: window.start,
|
||||
end: window.end,
|
||||
},
|
||||
rotation,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(rule) = block.interstitial_rule() {
|
||||
self.insert_interstitials(program_slots, rule, block.id(), window.end)
|
||||
.await
|
||||
} else {
|
||||
Ok(program_slots)
|
||||
}
|
||||
}
|
||||
|
||||
async fn insert_interstitials(
|
||||
&self,
|
||||
program_slots: Vec<ScheduledSlot>,
|
||||
rule: &InterstitialRule,
|
||||
block_id: BlockId,
|
||||
block_end: DateTime<Utc>,
|
||||
) -> DomainResult<Vec<ScheduledSlot>> {
|
||||
if program_slots.len() < 2 {
|
||||
return Ok(program_slots);
|
||||
}
|
||||
|
||||
let mut filter = media_filter_to_library_search(rule.pool_filter());
|
||||
filter = filter.with_role(MediaRole::Interstitial);
|
||||
let (interstitials, _) = self.library_query.search(&filter).await?;
|
||||
|
||||
if interstitials.is_empty() {
|
||||
return Ok(program_slots);
|
||||
}
|
||||
|
||||
let mut result: Vec<ScheduledSlot> = Vec::new();
|
||||
let mut cursor = program_slots[0].start_at();
|
||||
let mut interstitial_idx = 0;
|
||||
|
||||
for (i, slot) in program_slots.iter().enumerate() {
|
||||
if cursor >= block_end {
|
||||
break;
|
||||
}
|
||||
|
||||
let program_duration = slot.item().duration_secs();
|
||||
let program_end = (cursor + Duration::seconds(program_duration as i64)).min(block_end);
|
||||
result.push(ScheduledSlot::new(
|
||||
cursor,
|
||||
program_end,
|
||||
slot.item().clone(),
|
||||
block_id,
|
||||
));
|
||||
cursor = program_end;
|
||||
|
||||
let should_insert = i + 1 < program_slots.len()
|
||||
&& program_duration >= rule.min_gap_secs()
|
||||
&& cursor < block_end;
|
||||
|
||||
if should_insert {
|
||||
let interstitial = &interstitials[interstitial_idx % interstitials.len()];
|
||||
let interstitial_end =
|
||||
(cursor + Duration::seconds(interstitial.duration_secs() as i64))
|
||||
.min(block_end);
|
||||
if interstitial_end > cursor {
|
||||
result.push(ScheduledSlot::new(
|
||||
cursor,
|
||||
interstitial_end,
|
||||
interstitial.clone(),
|
||||
block_id,
|
||||
));
|
||||
cursor = interstitial_end;
|
||||
interstitial_idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn resolve_manual(
|
||||
@@ -304,7 +377,8 @@ impl ScheduleEngineService {
|
||||
window: BlockTimeWindow,
|
||||
rotation: RotationContext<'_>,
|
||||
) -> DomainResult<Vec<ScheduledSlot>> {
|
||||
let library_filter = media_filter_to_library_search(params.filter);
|
||||
let library_filter =
|
||||
media_filter_to_library_search(params.filter).with_role(MediaRole::Program);
|
||||
let (candidates, _total) = self.library_query.search(&library_filter).await?;
|
||||
|
||||
if candidates.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user