refactor(qconnect): extract queue cursor and order resolution to queue_resolution.rs

Move 18 items that own the protocol-side queue/cursor/order math from
mod.rs into a dedicated queue_resolution submodule:

Types (pub(super)):
- QconnectOrderedQueueCursor — Queue/Autoplay cursor abstraction
- QconnectRemoteSkipDirection — controller next/previous direction
- QconnectControllerQueueItemResolution — skip resolution outcome

Helpers (pub(super) when used by other submodules, fn otherwise):
- is_valid_ordered_queue_shuffle_order
- ordered_queue_cursors, queue_item_track_id_for_cursor,
  normalized_queue_item_id_for_cursor
- find_cursor_index_by_queue_item_id, find_cursor_index_by_track_id,
  find_cursor_index_by_track_id_before
- resolve_current_cursor_index_from_snapshots,
  resolve_controller_queue_item_from_snapshots,
  resolve_queue_item_ids_from_queue_state
- dedupe_track_ids
- resolve_remote_start_index, resolve_core_shuffle_order
- is_cloud_placeholder_current_queue_item,
  normalize_current_queue_item_id_from_queue_state

session.rs now imports cursor helpers directly from
super::queue_resolution::* instead of going through mod.rs.

Pure relocation. mod.rs: 5766 → 5297 lines. queue_resolution.rs: 460 lines.

cc
This commit is contained in:
vicrodh committed 2026-04-27 14:57:54 -06:00
1 parent a608ba2e06
commit 7ea9511c26
3 files changed
+493 -472

No files matched your search

+7 -469
View File
@@ -1,7 +1,14 @@
mod queue_resolution;
mod session;
mod types;
pub use session::{QconnectRendererInfo, QconnectSessionState};
pub use types::*;
use queue_resolution::{
dedupe_track_ids, find_cursor_index_by_track_id, is_valid_ordered_queue_shuffle_order,
resolve_controller_queue_item_from_snapshots, resolve_core_shuffle_order,
resolve_queue_item_ids_from_queue_state, resolve_remote_start_index,
QconnectRemoteSkipDirection,
};
use session::{
build_effective_renderer_snapshot, build_session_renderer_snapshot,
build_visible_queue_projection, cache_renderer_snapshot, ensure_session_renderer_state,
@@ -2051,349 +2058,6 @@ impl QconnectServiceState {
}
}
fn resolve_queue_item_ids_from_queue_state(
queue: &QConnectQueueState,
track_id: u64,
) -> (Option<u64>, Option<u64>, Option<u64>) {
if let Some(current_index) = queue
.queue_items
.iter()
.position(|item| item.track_id == track_id)
{
let current_qid = normalize_current_queue_item_id_from_queue_state(queue, current_index);
let next_item = if queue.shuffle_mode {
queue
.shuffle_order
.as_ref()
.and_then(|order| {
order
.iter()
.position(|queue_index| *queue_index == current_index)
.and_then(|order_index| order.get(order_index + 1))
.and_then(|queue_index| queue.queue_items.get(*queue_index))
})
.or_else(|| queue.queue_items.get(current_index + 1))
.or_else(|| queue.autoplay_items.first())
} else {
queue
.queue_items
.get(current_index + 1)
.or_else(|| queue.autoplay_items.first())
};
return (
Some(current_qid),
next_item.map(|item| item.queue_item_id),
next_item.map(|item| item.track_id),
);
}
if let Some(current_index) = queue
.autoplay_items
.iter()
.position(|item| item.track_id == track_id)
{
let current_item = &queue.autoplay_items[current_index];
let next_item = queue.autoplay_items.get(current_index + 1);
return (
Some(current_item.queue_item_id),
next_item.map(|item| item.queue_item_id),
next_item.map(|item| item.track_id),
);
}
(None, None, None)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum QconnectOrderedQueueCursor {
Queue(usize),
Autoplay(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum QconnectRemoteSkipDirection {
Next,
Previous,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct QconnectControllerQueueItemResolution {
target_queue_item_id: Option<u64>,
strategy: &'static str,
queue_index: Option<usize>,
matched_track_id: Option<u64>,
matched_queue_item_id: Option<u64>,
}
fn is_valid_ordered_queue_shuffle_order(order: &[usize], track_count: usize) -> bool {
if order.len() != track_count {
return false;
}
let mut seen = vec![false; track_count];
for &index in order {
if index >= track_count || seen[index] {
return false;
}
seen[index] = true;
}
true
}
pub(super) fn ordered_queue_cursors(queue: &QConnectQueueState) -> Vec<QconnectOrderedQueueCursor> {
let mut cursors = if queue.shuffle_mode {
queue
.shuffle_order
.as_ref()
.filter(|order| is_valid_ordered_queue_shuffle_order(order, queue.queue_items.len()))
.map(|order| {
order
.iter()
.copied()
.map(QconnectOrderedQueueCursor::Queue)
.collect::<Vec<_>>()
})
.unwrap_or_else(|| {
queue
.queue_items
.iter()
.enumerate()
.map(|(index, _)| QconnectOrderedQueueCursor::Queue(index))
.collect::<Vec<_>>()
})
} else {
queue
.queue_items
.iter()
.enumerate()
.map(|(index, _)| QconnectOrderedQueueCursor::Queue(index))
.collect::<Vec<_>>()
};
cursors.extend(
queue
.autoplay_items
.iter()
.enumerate()
.map(|(index, _)| QconnectOrderedQueueCursor::Autoplay(index)),
);
cursors
}
fn queue_item_track_id_for_cursor(
queue: &QConnectQueueState,
cursor: QconnectOrderedQueueCursor,
) -> Option<u64> {
match cursor {
QconnectOrderedQueueCursor::Queue(index) => {
queue.queue_items.get(index).map(|item| item.track_id)
}
QconnectOrderedQueueCursor::Autoplay(index) => {
queue.autoplay_items.get(index).map(|item| item.track_id)
}
}
}
fn normalized_queue_item_id_for_cursor(
queue: &QConnectQueueState,
cursor: QconnectOrderedQueueCursor,
) -> Option<u64> {
match cursor {
QconnectOrderedQueueCursor::Queue(index) => Some(
normalize_current_queue_item_id_from_queue_state(queue, index),
),
QconnectOrderedQueueCursor::Autoplay(index) => queue
.autoplay_items
.get(index)
.map(|item| item.queue_item_id),
}
}
pub(super) fn find_cursor_index_by_queue_item_id(
cursors: &[QconnectOrderedQueueCursor],
queue: &QConnectQueueState,
queue_item_id: Option<u64>,
) -> Option<usize> {
let queue_item_id = queue_item_id?;
cursors.iter().position(|cursor| {
normalized_queue_item_id_for_cursor(queue, *cursor) == Some(queue_item_id)
|| match cursor {
QconnectOrderedQueueCursor::Queue(index) => queue
.queue_items
.get(*index)
.map(|item| item.queue_item_id == queue_item_id)
.unwrap_or(false),
QconnectOrderedQueueCursor::Autoplay(index) => queue
.autoplay_items
.get(*index)
.map(|item| item.queue_item_id == queue_item_id)
.unwrap_or(false),
}
})
}
pub(super) fn find_cursor_index_by_track_id(
cursors: &[QconnectOrderedQueueCursor],
queue: &QConnectQueueState,
track_id: Option<u64>,
) -> Option<usize> {
let track_id = track_id?;
cursors
.iter()
.position(|cursor| queue_item_track_id_for_cursor(queue, *cursor) == Some(track_id))
}
fn find_cursor_index_by_track_id_before(
cursors: &[QconnectOrderedQueueCursor],
queue: &QConnectQueueState,
track_id: Option<u64>,
end_exclusive: usize,
) -> Option<usize> {
let track_id = track_id?;
if end_exclusive == 0 {
return None;
}
for index in (0..end_exclusive).rev() {
if queue_item_track_id_for_cursor(queue, cursors[index]) == Some(track_id) {
return Some(index);
}
}
None
}
fn resolve_current_cursor_index_from_snapshots(
queue: &QConnectQueueState,
renderer: &QConnectRendererState,
cursors: &[QconnectOrderedQueueCursor],
) -> (Option<usize>, &'static str) {
let current_queue_index = find_cursor_index_by_queue_item_id(
cursors,
queue,
renderer
.current_track
.as_ref()
.map(|item| item.queue_item_id),
);
if current_queue_index.is_some() {
return (
current_queue_index,
"renderer_current_queue_item_id_verified",
);
}
let next_queue_index = find_cursor_index_by_queue_item_id(
cursors,
queue,
renderer.next_track.as_ref().map(|item| item.queue_item_id),
);
let track_index_before_next = next_queue_index.and_then(|next_index| {
find_cursor_index_by_track_id_before(
cursors,
queue,
renderer.current_track.as_ref().map(|item| item.track_id),
next_index,
)
});
if track_index_before_next.is_some() {
return (
track_index_before_next,
"queue_track_id_before_renderer_next",
);
}
let current_track_index = find_cursor_index_by_track_id(
cursors,
queue,
renderer.current_track.as_ref().map(|item| item.track_id),
);
if current_track_index.is_some() {
return (current_track_index, "queue_track_id_match");
}
if let Some(next_index) = next_queue_index {
if next_index > 0 {
return (Some(next_index - 1), "queue_item_before_renderer_next");
}
}
(None, "no_current_queue_item")
}
fn resolve_controller_queue_item_from_snapshots(
queue: &QConnectQueueState,
renderer: &QConnectRendererState,
direction: QconnectRemoteSkipDirection,
) -> QconnectControllerQueueItemResolution {
let cursors = ordered_queue_cursors(queue);
if cursors.is_empty() {
return QconnectControllerQueueItemResolution {
target_queue_item_id: None,
strategy: "no_queue_items",
queue_index: None,
matched_track_id: None,
matched_queue_item_id: None,
};
}
let (current_index, _current_strategy) =
resolve_current_cursor_index_from_snapshots(queue, renderer, &cursors);
let (target_index, strategy) = match direction {
QconnectRemoteSkipDirection::Next => {
let next_index = find_cursor_index_by_queue_item_id(
&cursors,
queue,
renderer.next_track.as_ref().map(|item| item.queue_item_id),
);
if let Some(next_index) = next_index {
(Some(next_index), "renderer_next_queue_item_id_verified")
} else if let Some(current_index) = current_index {
if current_index + 1 < cursors.len() {
(Some(current_index + 1), "queue_item_after_current")
} else {
(None, "no_next_queue_item")
}
} else {
(None, "no_next_queue_item")
}
}
QconnectRemoteSkipDirection::Previous => {
if let Some(current_index) = current_index {
if current_index > 0 {
(Some(current_index - 1), "queue_item_before_current")
} else {
(Some(current_index), "restart_current_queue_item")
}
} else {
(None, "no_previous_queue_item")
}
}
};
let Some(target_index) = target_index else {
return QconnectControllerQueueItemResolution {
target_queue_item_id: None,
strategy,
queue_index: None,
matched_track_id: None,
matched_queue_item_id: None,
};
};
let cursor = cursors[target_index];
let matched_track_id = queue_item_track_id_for_cursor(queue, cursor);
let matched_queue_item_id = normalized_queue_item_id_for_cursor(queue, cursor);
QconnectControllerQueueItemResolution {
target_queue_item_id: matched_queue_item_id,
strategy,
queue_index: Some(target_index),
matched_track_id,
matched_queue_item_id,
}
}
impl Default for QconnectServiceState {
fn default() -> Self {
@@ -2875,104 +2539,6 @@ async fn materialize_remote_queue_to_corebridge(
Ok(())
}
fn dedupe_track_ids(queue_state: &QConnectQueueState) -> Vec<u64> {
let mut unique = Vec::with_capacity(queue_state.queue_items.len());
for item in &queue_state.queue_items {
if !unique.contains(&item.track_id) {
unique.push(item.track_id);
}
}
unique
}
fn resolve_remote_start_index(
queue_state: &QConnectQueueState,
renderer_queue_item_id: Option<u64>,
renderer_track_id: Option<u64>,
) -> Option<usize> {
if let Some(queue_item_id) = renderer_queue_item_id {
if let Some(index) = queue_state
.queue_items
.iter()
.position(|item| item.queue_item_id == queue_item_id)
{
return Some(index);
}
}
if let Some(track_id) = renderer_track_id {
if let Some(index) = queue_state
.queue_items
.iter()
.position(|item| item.track_id == track_id)
{
return Some(index);
}
}
None
}
fn resolve_core_shuffle_order(
queue_state: &QConnectQueueState,
renderer_queue_item_id: Option<u64>,
renderer_track_id: Option<u64>,
renderer_next_queue_item_id: Option<u64>,
renderer_next_track_id: Option<u64>,
) -> Option<Vec<usize>> {
if !queue_state.shuffle_mode {
return None;
}
let raw_order = queue_state.shuffle_order.as_ref().filter(|order| {
is_valid_ordered_queue_shuffle_order(order, queue_state.queue_items.len())
});
if raw_order.is_none() {
log::debug!(
"[QConnect] resolve_core_shuffle_order: raw_order invalid or absent, items={} order={:?}",
queue_state.queue_items.len(),
queue_state.shuffle_order,
);
return None;
}
let raw_order = raw_order.unwrap();
let current_index =
resolve_remote_start_index(queue_state, renderer_queue_item_id, renderer_track_id);
let next_index = resolve_remote_start_index(
queue_state,
renderer_next_queue_item_id,
renderer_next_track_id,
);
let mut ordered = Vec::with_capacity(queue_state.queue_items.len());
if let Some(index) = current_index {
ordered.push(index);
}
if let Some(index) = next_index {
if !ordered.contains(&index) {
ordered.push(index);
}
}
for &index in raw_order {
if !ordered.contains(&index) {
ordered.push(index);
}
}
for index in 0..queue_state.queue_items.len() {
if !ordered.contains(&index) {
ordered.push(index);
}
}
log::debug!(
"[QConnect] resolve_core_shuffle_order: result={:?} current={:?} next={:?}",
ordered, current_index, next_index,
);
Some(ordered)
}
async fn align_corebridge_queue_cursor(bridge: &CoreBridge, track_id: u64) -> Result<(), String> {
let (tracks, current_index) = bridge.get_all_queue_tracks().await;
@@ -3371,34 +2937,6 @@ fn normalize_volume_to_fraction(volume: i32) -> f32 {
volume.clamp(0, 100) as f32 / 100.0
}
fn is_cloud_placeholder_current_queue_item(
queue: &QConnectQueueState,
current_index: usize,
) -> bool {
let Some(current_item) = queue.queue_items.get(current_index) else {
return false;
};
current_index == 0
&& current_item.queue_item_id == current_item.track_id
&& queue
.queue_items
.iter()
.skip(1)
.any(|item| item.queue_item_id < current_item.queue_item_id)
}
pub(super) fn normalize_current_queue_item_id_from_queue_state(
queue: &QConnectQueueState,
current_index: usize,
) -> u64 {
if is_cloud_placeholder_current_queue_item(queue, current_index) {
0
} else {
queue.queue_items[current_index].queue_item_id
}
}
fn classify_qconnect_audio_quality(sample_rate: u32, bit_depth: u32) -> i32 {
if sample_rate == 0 || bit_depth == 0 {
AUDIO_QUALITY_UNKNOWN
+481
View File
@@ -0,0 +1,481 @@
//! Queue cursor abstractions and index/order resolution helpers used to
//! map between cloud-side queue/renderer state and local queue indices.
//!
//! The QConnect protocol mixes queue_item_id, track_id, and ordering
//! data across multiple separate frames; this module owns the lookups
//! that reconcile those signals into a single coherent cursor.
use qconnect_app::{QConnectQueueState, QConnectRendererState};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum QconnectOrderedQueueCursor {
Queue(usize),
Autoplay(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum QconnectRemoteSkipDirection {
Next,
Previous,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct QconnectControllerQueueItemResolution {
pub(super) target_queue_item_id: Option<u64>,
pub(super) strategy: &'static str,
pub(super) queue_index: Option<usize>,
pub(super) matched_track_id: Option<u64>,
pub(super) matched_queue_item_id: Option<u64>,
}
pub(super) fn is_valid_ordered_queue_shuffle_order(order: &[usize], track_count: usize) -> bool {
if order.len() != track_count {
return false;
}
let mut seen = vec![false; track_count];
for &index in order {
if index >= track_count || seen[index] {
return false;
}
seen[index] = true;
}
true
}
pub(super) fn ordered_queue_cursors(
queue: &QConnectQueueState,
) -> Vec<QconnectOrderedQueueCursor> {
let mut cursors = if queue.shuffle_mode {
queue
.shuffle_order
.as_ref()
.filter(|order| is_valid_ordered_queue_shuffle_order(order, queue.queue_items.len()))
.map(|order| {
order
.iter()
.copied()
.map(QconnectOrderedQueueCursor::Queue)
.collect::<Vec<_>>()
})
.unwrap_or_else(|| {
queue
.queue_items
.iter()
.enumerate()
.map(|(index, _)| QconnectOrderedQueueCursor::Queue(index))
.collect::<Vec<_>>()
})
} else {
queue
.queue_items
.iter()
.enumerate()
.map(|(index, _)| QconnectOrderedQueueCursor::Queue(index))
.collect::<Vec<_>>()
};
cursors.extend(
queue
.autoplay_items
.iter()
.enumerate()
.map(|(index, _)| QconnectOrderedQueueCursor::Autoplay(index)),
);
cursors
}
pub(super) fn queue_item_track_id_for_cursor(
queue: &QConnectQueueState,
cursor: QconnectOrderedQueueCursor,
) -> Option<u64> {
match cursor {
QconnectOrderedQueueCursor::Queue(index) => {
queue.queue_items.get(index).map(|item| item.track_id)
}
QconnectOrderedQueueCursor::Autoplay(index) => {
queue.autoplay_items.get(index).map(|item| item.track_id)
}
}
}
pub(super) fn normalized_queue_item_id_for_cursor(
queue: &QConnectQueueState,
cursor: QconnectOrderedQueueCursor,
) -> Option<u64> {
match cursor {
QconnectOrderedQueueCursor::Queue(index) => Some(
normalize_current_queue_item_id_from_queue_state(queue, index),
),
QconnectOrderedQueueCursor::Autoplay(index) => queue
.autoplay_items
.get(index)
.map(|item| item.queue_item_id),
}
}
pub(super) fn find_cursor_index_by_queue_item_id(
cursors: &[QconnectOrderedQueueCursor],
queue: &QConnectQueueState,
queue_item_id: Option<u64>,
) -> Option<usize> {
let queue_item_id = queue_item_id?;
cursors.iter().position(|cursor| {
normalized_queue_item_id_for_cursor(queue, *cursor) == Some(queue_item_id)
|| match cursor {
QconnectOrderedQueueCursor::Queue(index) => queue
.queue_items
.get(*index)
.map(|item| item.queue_item_id == queue_item_id)
.unwrap_or(false),
QconnectOrderedQueueCursor::Autoplay(index) => queue
.autoplay_items
.get(*index)
.map(|item| item.queue_item_id == queue_item_id)
.unwrap_or(false),
}
})
}
pub(super) fn find_cursor_index_by_track_id(
cursors: &[QconnectOrderedQueueCursor],
queue: &QConnectQueueState,
track_id: Option<u64>,
) -> Option<usize> {
let track_id = track_id?;
cursors
.iter()
.position(|cursor| queue_item_track_id_for_cursor(queue, *cursor) == Some(track_id))
}
fn find_cursor_index_by_track_id_before(
cursors: &[QconnectOrderedQueueCursor],
queue: &QConnectQueueState,
track_id: Option<u64>,
end_exclusive: usize,
) -> Option<usize> {
let track_id = track_id?;
if end_exclusive == 0 {
return None;
}
for index in (0..end_exclusive).rev() {
if queue_item_track_id_for_cursor(queue, cursors[index]) == Some(track_id) {
return Some(index);
}
}
None
}
fn resolve_current_cursor_index_from_snapshots(
queue: &QConnectQueueState,
renderer: &QConnectRendererState,
cursors: &[QconnectOrderedQueueCursor],
) -> (Option<usize>, &'static str) {
let current_queue_index = find_cursor_index_by_queue_item_id(
cursors,
queue,
renderer
.current_track
.as_ref()
.map(|item| item.queue_item_id),
);
if current_queue_index.is_some() {
return (
current_queue_index,
"renderer_current_queue_item_id_verified",
);
}
let next_queue_index = find_cursor_index_by_queue_item_id(
cursors,
queue,
renderer.next_track.as_ref().map(|item| item.queue_item_id),
);
let track_index_before_next = next_queue_index.and_then(|next_index| {
find_cursor_index_by_track_id_before(
cursors,
queue,
renderer.current_track.as_ref().map(|item| item.track_id),
next_index,
)
});
if track_index_before_next.is_some() {
return (
track_index_before_next,
"queue_track_id_before_renderer_next",
);
}
let current_track_index = find_cursor_index_by_track_id(
cursors,
queue,
renderer.current_track.as_ref().map(|item| item.track_id),
);
if current_track_index.is_some() {
return (current_track_index, "queue_track_id_match");
}
if let Some(next_index) = next_queue_index {
if next_index > 0 {
return (Some(next_index - 1), "queue_item_before_renderer_next");
}
}
(None, "no_current_queue_item")
}
pub(super) fn resolve_controller_queue_item_from_snapshots(
queue: &QConnectQueueState,
renderer: &QConnectRendererState,
direction: QconnectRemoteSkipDirection,
) -> QconnectControllerQueueItemResolution {
let cursors = ordered_queue_cursors(queue);
if cursors.is_empty() {
return QconnectControllerQueueItemResolution {
target_queue_item_id: None,
strategy: "no_queue_items",
queue_index: None,
matched_track_id: None,
matched_queue_item_id: None,
};
}
let (current_index, _current_strategy) =
resolve_current_cursor_index_from_snapshots(queue, renderer, &cursors);
let (target_index, strategy) = match direction {
QconnectRemoteSkipDirection::Next => {
let next_index = find_cursor_index_by_queue_item_id(
&cursors,
queue,
renderer.next_track.as_ref().map(|item| item.queue_item_id),
);
if let Some(next_index) = next_index {
(Some(next_index), "renderer_next_queue_item_id_verified")
} else if let Some(current_index) = current_index {
if current_index + 1 < cursors.len() {
(Some(current_index + 1), "queue_item_after_current")
} else {
(None, "no_next_queue_item")
}
} else {
(None, "no_next_queue_item")
}
}
QconnectRemoteSkipDirection::Previous => {
if let Some(current_index) = current_index {
if current_index > 0 {
(Some(current_index - 1), "queue_item_before_current")
} else {
(Some(current_index), "restart_current_queue_item")
}
} else {
(None, "no_previous_queue_item")
}
}
};
let Some(target_index) = target_index else {
return QconnectControllerQueueItemResolution {
target_queue_item_id: None,
strategy,
queue_index: None,
matched_track_id: None,
matched_queue_item_id: None,
};
};
let cursor = cursors[target_index];
let matched_track_id = queue_item_track_id_for_cursor(queue, cursor);
let matched_queue_item_id = normalized_queue_item_id_for_cursor(queue, cursor);
QconnectControllerQueueItemResolution {
target_queue_item_id: matched_queue_item_id,
strategy,
queue_index: Some(target_index),
matched_track_id,
matched_queue_item_id,
}
}
pub(super) fn resolve_queue_item_ids_from_queue_state(
queue: &QConnectQueueState,
track_id: u64,
) -> (Option<u64>, Option<u64>, Option<u64>) {
if let Some(current_index) = queue
.queue_items
.iter()
.position(|item| item.track_id == track_id)
{
let current_qid = normalize_current_queue_item_id_from_queue_state(queue, current_index);
let next_item = if queue.shuffle_mode {
queue
.shuffle_order
.as_ref()
.and_then(|order| {
order
.iter()
.position(|queue_index| *queue_index == current_index)
.and_then(|order_index| order.get(order_index + 1))
.and_then(|queue_index| queue.queue_items.get(*queue_index))
})
.or_else(|| queue.queue_items.get(current_index + 1))
.or_else(|| queue.autoplay_items.first())
} else {
queue
.queue_items
.get(current_index + 1)
.or_else(|| queue.autoplay_items.first())
};
return (
Some(current_qid),
next_item.map(|item| item.queue_item_id),
next_item.map(|item| item.track_id),
);
}
if let Some(current_index) = queue
.autoplay_items
.iter()
.position(|item| item.track_id == track_id)
{
let current_item = &queue.autoplay_items[current_index];
let next_item = queue.autoplay_items.get(current_index + 1);
return (
Some(current_item.queue_item_id),
next_item.map(|item| item.queue_item_id),
next_item.map(|item| item.track_id),
);
}
(None, None, None)
}
pub(super) fn dedupe_track_ids(queue_state: &QConnectQueueState) -> Vec<u64> {
let mut unique = Vec::with_capacity(queue_state.queue_items.len());
for item in &queue_state.queue_items {
if !unique.contains(&item.track_id) {
unique.push(item.track_id);
}
}
unique
}
pub(super) fn resolve_remote_start_index(
queue_state: &QConnectQueueState,
renderer_queue_item_id: Option<u64>,
renderer_track_id: Option<u64>,
) -> Option<usize> {
if let Some(queue_item_id) = renderer_queue_item_id {
if let Some(index) = queue_state
.queue_items
.iter()
.position(|item| item.queue_item_id == queue_item_id)
{
return Some(index);
}
}
if let Some(track_id) = renderer_track_id {
if let Some(index) = queue_state
.queue_items
.iter()
.position(|item| item.track_id == track_id)
{
return Some(index);
}
}
None
}
pub(super) fn resolve_core_shuffle_order(
queue_state: &QConnectQueueState,
renderer_queue_item_id: Option<u64>,
renderer_track_id: Option<u64>,
renderer_next_queue_item_id: Option<u64>,
renderer_next_track_id: Option<u64>,
) -> Option<Vec<usize>> {
if !queue_state.shuffle_mode {
return None;
}
let raw_order = queue_state.shuffle_order.as_ref().filter(|order| {
is_valid_ordered_queue_shuffle_order(order, queue_state.queue_items.len())
});
if raw_order.is_none() {
log::debug!(
"[QConnect] resolve_core_shuffle_order: raw_order invalid or absent, items={} order={:?}",
queue_state.queue_items.len(),
queue_state.shuffle_order,
);
return None;
}
let raw_order = raw_order.unwrap();
let current_index =
resolve_remote_start_index(queue_state, renderer_queue_item_id, renderer_track_id);
let next_index = resolve_remote_start_index(
queue_state,
renderer_next_queue_item_id,
renderer_next_track_id,
);
let mut ordered = Vec::with_capacity(queue_state.queue_items.len());
if let Some(index) = current_index {
ordered.push(index);
}
if let Some(index) = next_index {
if !ordered.contains(&index) {
ordered.push(index);
}
}
for &index in raw_order {
if !ordered.contains(&index) {
ordered.push(index);
}
}
for index in 0..queue_state.queue_items.len() {
if !ordered.contains(&index) {
ordered.push(index);
}
}
log::debug!(
"[QConnect] resolve_core_shuffle_order: result={:?} current={:?} next={:?}",
ordered, current_index, next_index,
);
Some(ordered)
}
fn is_cloud_placeholder_current_queue_item(
queue: &QConnectQueueState,
current_index: usize,
) -> bool {
let Some(current_item) = queue.queue_items.get(current_index) else {
return false;
};
current_index == 0
&& current_item.queue_item_id == current_item.track_id
&& queue
.queue_items
.iter()
.skip(1)
.any(|item| item.queue_item_id < current_item.queue_item_id)
}
pub(super) fn normalize_current_queue_item_id_from_queue_state(
queue: &QConnectQueueState,
current_index: usize,
) -> u64 {
if is_cloud_placeholder_current_queue_item(queue, current_index) {
0
} else {
queue.queue_items[current_index].queue_item_id
}
}
+5 -3
View File
@@ -9,10 +9,12 @@ use qconnect_app::{QConnectQueueState, QConnectRendererState};
use qconnect_core::QueueItem;
use serde::{Deserialize, Serialize};
use super::queue_resolution::{
find_cursor_index_by_queue_item_id, normalize_current_queue_item_id_from_queue_state,
ordered_queue_cursors, QconnectOrderedQueueCursor,
};
use super::{
default_qconnect_device_info, find_cursor_index_by_queue_item_id,
normalize_current_queue_item_id_from_queue_state, ordered_queue_cursors,
resolve_qconnect_device_uuid, QconnectOrderedQueueCursor, QconnectQueueVersionPayload,
default_qconnect_device_info, resolve_qconnect_device_uuid, QconnectQueueVersionPayload,
QconnectRemoteSyncState, QconnectVisibleQueueProjection,
};