diff --git a/crates/qconnect-app/src/lib.rs b/crates/qconnect-app/src/lib.rs index b24112974..9715e0804 100644 --- a/crates/qconnect-app/src/lib.rs +++ b/crates/qconnect-app/src/lib.rs @@ -17,7 +17,8 @@ pub use events::{NoOpEventSink, QconnectAppEvent, QconnectEventSink}; pub use session::{ compute_connection_state, deferred_join_reason, quality_from_max_audio_quality, should_arm_renderer_watchdog, should_reask_queue_state, ConnectionDecision, - QconnectLifecycleState, RendererStatus, ServerActiveState, + QconnectLifecycleState, QconnectRendererInfo, QconnectSessionRendererState, + QconnectSessionState, RendererStatus, ServerActiveState, JOIN_SESSION_REASON_CONTROLLER_REQUEST, JOIN_SESSION_REASON_RECONNECTION, QCONNECT_RENDERER_LOST_TIMEOUT_MS, }; diff --git a/crates/qconnect-app/src/session.rs b/crates/qconnect-app/src/session.rs index 93b0a2a62..7e69f780f 100644 --- a/crates/qconnect-app/src/session.rs +++ b/crates/qconnect-app/src/session.rs @@ -186,6 +186,57 @@ pub fn quality_from_max_audio_quality(level: Option) -> Quality { } } +// --------------------------------------------------------------------------- +// Session topology types (relocated from the Tauri adapter — slice 2+4). +// +// These are pure data describing who is in a QConnect session and the cloud's +// per-renderer view. They move here (frontend-agnostic) so both the Tauri and +// Slint adapters share one session-topology model under a single lock. Field +// visibility is widened to `pub` because the adapters access these fields +// across the crate boundary; serialized shape is unchanged. +// --------------------------------------------------------------------------- + +/// Per-renderer cached state derived from session-management events +/// (RENDERER_STATE_UPDATED etc.): the cloud's latest view of one renderer. +#[derive(Debug, Clone, Default)] +pub struct QconnectSessionRendererState { + pub active: Option, + pub playing_state: Option, + pub current_position_ms: Option, + pub current_queue_item_id: Option, + pub volume: Option, + pub muted: Option, + pub max_audio_quality: Option, + pub loop_mode: Option, + pub shuffle_mode: Option, + pub updated_at_ms: u64, +} + +/// Session topology — stored from session management events (types 81-87): +/// who is in the session, who is active, and which renderer is us. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct QconnectSessionState { + pub session_uuid: Option, + pub active_renderer_id: Option, + pub local_renderer_id: Option, + pub renderers: Vec, +} + +/// One renderer registered in the QConnect session. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QconnectRendererInfo { + pub renderer_id: i32, + pub device_uuid: Option, + pub friendly_name: Option, + pub brand: Option, + pub model: Option, + pub device_type: Option, + /// capabilities.volume_remote_control. 1 == ALLOWED. None == not advertised + /// (treated as allowed to avoid regressing renderers that omit it). + #[serde(default)] + pub volume_remote_control: Option, +} + #[cfg(test)] mod tests { use super::*; diff --git a/src-tauri/src/qconnect/session.rs b/src-tauri/src/qconnect/session.rs index aedc9768d..a59f36b38 100644 --- a/src-tauri/src/qconnect/session.rs +++ b/src-tauri/src/qconnect/session.rs @@ -7,7 +7,7 @@ use qconnect_app::{QConnectQueueState, QConnectRendererState}; use qconnect_core::QueueItem; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use super::queue_resolution::{ find_cursor_index_by_queue_item_id, normalize_current_queue_item_id_from_queue_state, @@ -25,6 +25,12 @@ use super::{ /// is not named directly on the Tauri side; it lives in `qconnect_app`.) pub(super) use qconnect_app::{compute_connection_state, ServerActiveState}; +/// Session topology types now live in the frontend-agnostic `qconnect_app::session` +/// module (slice 2+4). Re-exported here so existing `super::session::…` / +/// `super::…` references inside this module compile unchanged, and so the Tauri +/// command surface keeps the same serialized shape. +pub use qconnect_app::{QconnectRendererInfo, QconnectSessionRendererState, QconnectSessionState}; + pub(super) fn queue_item_snapshot_for_cursor( queue: &QConnectQueueState, cursor: QconnectOrderedQueueCursor, @@ -40,20 +46,6 @@ pub(super) fn queue_item_snapshot_for_cursor( } } -#[derive(Debug, Clone, Default)] -pub(crate) struct QconnectSessionRendererState { - pub(super) active: Option, - pub(super) playing_state: Option, - pub(super) current_position_ms: Option, - pub(super) current_queue_item_id: Option, - pub(super) volume: Option, - pub(super) muted: Option, - pub(super) max_audio_quality: Option, - pub(super) loop_mode: Option, - pub(super) shuffle_mode: Option, - pub(super) updated_at_ms: u64, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct QconnectFileAudioQualitySnapshot { pub(super) sampling_rate: i32, @@ -62,14 +54,6 @@ pub(crate) struct QconnectFileAudioQualitySnapshot { pub(super) audio_quality: i32, } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct QconnectSessionState { - pub session_uuid: Option, - pub active_renderer_id: Option, - pub local_renderer_id: Option, - pub renderers: Vec, -} - #[derive(Debug, Clone, Serialize)] pub(crate) struct QconnectRendererReportDebugEvent { pub(super) requested_current_queue_item_id: Option, @@ -87,20 +71,6 @@ pub(crate) struct QconnectRendererReportDebugEvent { pub(super) resolution_strategy: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct QconnectRendererInfo { - pub renderer_id: i32, - pub device_uuid: Option, - pub friendly_name: Option, - pub brand: Option, - pub model: Option, - pub device_type: Option, - /// capabilities.volume_remote_control. 1 == ALLOWED. None == not advertised - /// (treated as allowed to avoid regressing renderers that omit it). - #[serde(default)] - pub volume_remote_control: Option, -} - /// Whether the active renderer permits remote volume control. Absent capability /// (None) defaults to allowed; only an explicit non-ALLOWED value disables. pub(super) fn renderer_allows_remote_volume(info: &QconnectRendererInfo) -> bool {