refactor(qconnect-app): hoist renderer-projection helpers from the Tauri adapter
Move build_session_renderer_snapshot, queue_item_snapshot_for_cursor and cache_renderer_snapshot into qconnect-app (session.rs / sync_state.rs) and re-export them; repoint the Tauri qconnect adapter to consume them. This lets the Slint adapter share one definition (ADR-006) instead of duplicating the projection logic. Behavior is byte-identical. cc
This commit is contained in:
4 files changed
+115
-83
No files matched your search
@@ -22,12 +22,13 @@ pub use startup::{compute_effective_startup, QconnectStartupMode};
|
||||
pub use error::QconnectAppError;
|
||||
pub use events::{NoOpEventSink, QconnectAppEvent, QconnectEventSink};
|
||||
pub use session::{
|
||||
compute_connection_state, deferred_join_reason, find_unique_renderer_id,
|
||||
is_local_renderer_active, is_peer_renderer_active, normalize_active_renderer_id,
|
||||
quality_from_max_audio_quality, refresh_local_renderer_id, renderer_allows_remote_volume,
|
||||
should_arm_renderer_watchdog, should_reask_queue_state, ConnectionDecision, LocalIdentity,
|
||||
QconnectFileAudioQualitySnapshot, QconnectLifecycleState, QconnectRendererInfo,
|
||||
QconnectSessionRendererState, QconnectSessionState, RendererStatus, ServerActiveState,
|
||||
build_session_renderer_snapshot, compute_connection_state, deferred_join_reason,
|
||||
find_unique_renderer_id, is_local_renderer_active, is_peer_renderer_active,
|
||||
normalize_active_renderer_id, quality_from_max_audio_quality, queue_item_snapshot_for_cursor,
|
||||
refresh_local_renderer_id, renderer_allows_remote_volume, should_arm_renderer_watchdog,
|
||||
should_reask_queue_state, ConnectionDecision, LocalIdentity, QconnectFileAudioQualitySnapshot,
|
||||
QconnectLifecycleState, QconnectRendererInfo, QconnectSessionRendererState,
|
||||
QconnectSessionState, RendererStatus, ServerActiveState,
|
||||
JOIN_SESSION_REASON_CONTROLLER_REQUEST, JOIN_SESSION_REASON_RECONNECTION,
|
||||
QCONNECT_RENDERER_LOST_TIMEOUT_MS,
|
||||
};
|
||||
@@ -44,5 +45,6 @@ pub use qconnect_protocol::{QueueCommandType, RendererReport, RendererReportType
|
||||
pub use renderer_engine::QconnectRendererEngine;
|
||||
pub use state::QconnectRuntimeState;
|
||||
pub use sync_state::{
|
||||
ensure_session_renderer_state, sync_session_renderer_active_flags, QconnectRemoteSyncState,
|
||||
cache_renderer_snapshot, ensure_session_renderer_state, sync_session_renderer_active_flags,
|
||||
QconnectRemoteSyncState,
|
||||
};
|
||||
@@ -6,8 +6,14 @@
|
||||
//! byte-for-byte identical to the prior Tauri-side definitions.
|
||||
|
||||
use qbz_models::Quality;
|
||||
use qconnect_core::{QConnectQueueState, QConnectRendererState, QueueItem};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::queue_resolution::{
|
||||
find_cursor_index_by_queue_item_id, normalize_current_queue_item_id_from_queue_state,
|
||||
ordered_queue_cursors, QconnectOrderedQueueCursor,
|
||||
};
|
||||
|
||||
/// Lifecycle state surfaced to the UI so the toggle can reflect what the user
|
||||
/// asked for (`running`) separately from what the transport currently has
|
||||
/// (`Connecting`/`Connected`/`Reconnecting`). Without this distinction the UI
|
||||
@@ -360,6 +366,66 @@ pub fn find_unique_renderer_id(
|
||||
Some(first)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Session-projection helpers (relocated from the Tauri adapter — slice 6, Slint
|
||||
// port). Pure functions over the cloud queue + cached per-renderer state. They
|
||||
// move here (frontend-agnostic) so the Tauri event sink AND the Slint event sink
|
||||
// build the same renderer projection from one definition. Byte-for-byte
|
||||
// identical to the prior Tauri-side bodies.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Snapshot the queue item a cursor points at, normalizing the queue_item_id for
|
||||
/// in-queue cursors (autoplay items carry their own id verbatim).
|
||||
pub fn queue_item_snapshot_for_cursor(
|
||||
queue: &QConnectQueueState,
|
||||
cursor: QconnectOrderedQueueCursor,
|
||||
) -> Option<QueueItem> {
|
||||
match cursor {
|
||||
QconnectOrderedQueueCursor::Queue(index) => {
|
||||
queue.queue_items.get(index).cloned().map(|mut item| {
|
||||
item.queue_item_id = normalize_current_queue_item_id_from_queue_state(queue, index);
|
||||
item
|
||||
})
|
||||
}
|
||||
QconnectOrderedQueueCursor::Autoplay(index) => queue.autoplay_items.get(index).cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a renderer-state projection from the cloud queue + a cached per-renderer
|
||||
/// state, resolving the current/next track by the renderer's current_queue_item_id
|
||||
/// against the ordered cursors. `session_loop_mode` is the fallback loop mode when
|
||||
/// the renderer state omits one.
|
||||
pub fn build_session_renderer_snapshot(
|
||||
queue: &QConnectQueueState,
|
||||
renderer_state: Option<&QconnectSessionRendererState>,
|
||||
session_loop_mode: Option<i32>,
|
||||
) -> QConnectRendererState {
|
||||
let renderer_state = renderer_state.cloned().unwrap_or_default();
|
||||
let cursors = ordered_queue_cursors(queue);
|
||||
let current_index =
|
||||
find_cursor_index_by_queue_item_id(&cursors, queue, renderer_state.current_queue_item_id);
|
||||
let current_track =
|
||||
current_index.and_then(|index| queue_item_snapshot_for_cursor(queue, cursors[index]));
|
||||
let next_track = current_index
|
||||
.and_then(|index| cursors.get(index + 1).copied())
|
||||
.and_then(|cursor| queue_item_snapshot_for_cursor(queue, cursor));
|
||||
|
||||
QConnectRendererState {
|
||||
active: renderer_state.active,
|
||||
playing_state: renderer_state.playing_state,
|
||||
current_position_ms: renderer_state.current_position_ms,
|
||||
current_track,
|
||||
next_track,
|
||||
volume: renderer_state.volume,
|
||||
volume_delta: None,
|
||||
muted: renderer_state.muted,
|
||||
max_audio_quality: renderer_state.max_audio_quality,
|
||||
loop_mode: renderer_state.loop_mode.or(session_loop_mode),
|
||||
shuffle_mode: renderer_state.shuffle_mode,
|
||||
updated_at_ms: renderer_state.updated_at_ms,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
|
||||
use qconnect_core::QConnectQueueState;
|
||||
use qconnect_core::{QConnectQueueState, QConnectRendererState};
|
||||
|
||||
use crate::session::{
|
||||
QconnectFileAudioQualitySnapshot, QconnectSessionRendererState, QconnectSessionState,
|
||||
@@ -94,3 +94,31 @@ pub fn ensure_session_renderer_state(
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Cache the queue_item/track ids + playing_state derived from a renderer
|
||||
/// snapshot into the accumulator, so subsequent outbound reports and visible
|
||||
/// projections reuse them. Pure mutation; relocated from the Tauri adapter
|
||||
/// (slice 6, Slint port) so both the Tauri and Slint event sinks share one
|
||||
/// definition. Byte-identical behavior.
|
||||
pub fn cache_renderer_snapshot(
|
||||
state: &mut QconnectRemoteSyncState,
|
||||
renderer_snapshot: &QConnectRendererState,
|
||||
) {
|
||||
state.last_renderer_queue_item_id = renderer_snapshot
|
||||
.current_track
|
||||
.as_ref()
|
||||
.map(|item| item.queue_item_id);
|
||||
state.last_renderer_next_queue_item_id = renderer_snapshot
|
||||
.next_track
|
||||
.as_ref()
|
||||
.map(|item| item.queue_item_id);
|
||||
state.last_renderer_track_id = renderer_snapshot
|
||||
.current_track
|
||||
.as_ref()
|
||||
.map(|item| item.track_id);
|
||||
state.last_renderer_next_track_id = renderer_snapshot
|
||||
.next_track
|
||||
.as_ref()
|
||||
.map(|item| item.track_id);
|
||||
state.last_renderer_playing_state = renderer_snapshot.playing_state;
|
||||
}
|
||||
Reference in new issue
Block a user