use mineral_model::SongId;
use mineral_protocol::{AdvanceKind, PlayCursor};
use crate::queue::next_index;
use crate::state::State;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum Advance {
None,
Adopt,
Fallback,
}
pub(crate) fn decide_advance(finished_advanced: bool, playing: bool, has_queued: bool) -> Advance {
if !finished_advanced {
return Advance::None;
}
if playing && has_queued {
Advance::Adopt
} else {
Advance::Fallback
}
}
pub(crate) fn adopt_queued(st: &mut State) -> Option<SongId> {
let (slot, queued) = st.prefetch.take_armed()?;
let old_id = st.current_song.as_ref().map(|s| s.id.clone());
if let Some(current) = st.current_slot.take() {
current.cancel();
}
if let Some(idx) = next_index(st) {
st.cursor = PlayCursor::InQueue(idx);
}
st.advance = Some((queued.song.id.clone(), AdvanceKind::Next));
st.current_song = Some(queued.song);
st.current_slot = Some(slot);
st.media_info = Some(queued.media_info);
st.direct_media = queued.direct_media;
st.play_origin = Some(queued.origin);
st.current_lyrics = None;
st.current_lyrics_song_id = None;
st.prefetch_vetoed.clear();
st.bump_current();
old_id
}
#[cfg(test)]
mod tests {
use mineral_model::PlaybackMediaInfo;
use mineral_protocol::{AdvanceKind, PlayCursor, PlaybackOrigin};
use mineral_test::song;
use super::{Advance, adopt_queued, decide_advance};
use crate::gapless::Queued;
use crate::playback_instance::PlaybackSlot;
use crate::state::State;
#[test]
fn decide_advance_branches() {
assert_eq!(
decide_advance(
false, true, true
),
Advance::None,
"无曲终不动"
);
assert_eq!(
decide_advance(
true, true, true
),
Advance::Adopt,
"仍出声 + 有预排 → 无缝采纳"
);
assert_eq!(
decide_advance(
true, false, true
),
Advance::Fallback,
"停了(队尾静音)即便有预排也要兜底"
);
assert_eq!(
decide_advance(
true, true, false
),
Advance::Fallback,
"无预排 → 兜底"
);
}
#[test]
fn adopt_rotates_queued_into_current() {
let mut st = State::empty();
st.queue = vec![song("a"), song("b")];
st.cursor = PlayCursor::InQueue(0);
st.current_song = Some(song("a"));
st.current_slot = Some(PlaybackSlot::new(song("a").id));
let next_slot = PlaybackSlot::new(song("b").id);
st.prefetch.arm(
next_slot,
Queued {
song: song("b"),
media_info: PlaybackMediaInfo {
song_id: song("b").id,
bitrate_bps: None,
size: None,
format: None,
bit_depth: None,
substituted: false,
},
direct_media: None,
origin: PlaybackOrigin::Remote,
},
);
let old = adopt_queued(&mut st);
assert_eq!(old, Some(song("a").id), "应返回被顶替的旧当前歌 id");
assert_eq!(
st.current_song.as_ref().map(|s| s.id.clone()),
Some(song("b").id),
"current 应变成 queued"
);
assert_eq!(st.cursor, PlayCursor::InQueue(1), "游标应定位到 b");
assert_eq!(st.play_origin, Some(PlaybackOrigin::Remote));
assert_eq!(
st.advance,
Some((song("b").id, AdvanceKind::Next)),
"无缝接续也是一次下一首推进(client 转场据此定向)"
);
assert!(!st.prefetch.is_armed(), "armed prefetch 应被取走");
}
#[test]
fn adopt_without_queued_is_noop() {
let mut st = State::empty();
st.current_song = Some(song("a"));
assert!(adopt_queued(&mut st).is_none());
assert_eq!(
st.current_song.as_ref().map(|s| s.id.clone()),
Some(song("a").id),
"无 queued 不应改动当前歌"
);
}
}