refactor(qconnect): delegate session-apply + watchdog to qconnect-app; drop Tauri-side duplicate (slice 2+4)
The Tauri event sink's apply_session_management_event is now a thin delegator: it resolves this device's identity (resolve_local_identity) and calls QconnectApp::apply_session_management_event, then runs the post-lock CoreBridge work the returned SessionApplyOutcome asks for in the exact prior order (loop mode -> local-playback handoff -> projection -> freeze -> watchdog). Freeze + watchdog route through app.freeze_active_renderer_projection / app.arm_renderer_watchdog (same shared sync Mutex, same epoch guard). Deleted from the Tauri adapter (now solely in qconnect-app): the locked session-management match, bump_watchdog_generation, the inline 12s watchdog spawn, freeze_active_renderer_projection_to_unknown, and the duplicate ensure_session_renderer_state / sync_session_renderer_active_flags. ensure_* is re-exported from qconnect-app for service.rs; refresh_local_renderer_id wrapper is replaced by resolve_local_identity (returns the injected LocalIdentity), and the 3 renderer-id-resolution tests call the pure qconnect-app fn with it. Byte-identity fence holds: src-tauri qconnect 42 green, qconnect-app 38 green. cc
This commit is contained in:
4 files changed
+36
-413
No files matched your search
@@ -24,15 +24,9 @@ use super::corebridge::{
|
||||
use super::queue_resolution::is_valid_ordered_queue_shuffle_order;
|
||||
use super::service::emit_qconnect_diagnostic;
|
||||
use super::session::{
|
||||
build_session_renderer_snapshot, cache_renderer_snapshot, ensure_session_renderer_state,
|
||||
is_peer_renderer_active, normalize_active_renderer_id, refresh_local_renderer_id,
|
||||
sync_session_renderer_active_flags,
|
||||
};
|
||||
use super::{
|
||||
qconnect_now_ms, should_arm_renderer_watchdog, QconnectRemoteSyncState, QconnectRendererInfo,
|
||||
RendererStatus, BUFFER_STATE_OK, PLAYING_STATE_PLAYING, PLAYING_STATE_UNKNOWN,
|
||||
QCONNECT_RENDERER_LOST_TIMEOUT_MS,
|
||||
build_session_renderer_snapshot, cache_renderer_snapshot, is_peer_renderer_active,
|
||||
};
|
||||
use super::{QconnectRemoteSyncState, BUFFER_STATE_OK};
|
||||
|
||||
/// Concrete `QconnectApp` type used by the Tauri adapter.
|
||||
type TauriQconnectApp = QconnectApp<NativeWsTransport, TauriQconnectEventSink>;
|
||||
@@ -207,296 +201,24 @@ impl QconnectEventSink for TauriQconnectEventSink {
|
||||
}
|
||||
|
||||
impl TauriQconnectEventSink {
|
||||
/// Apply a server session-management event by delegating the locked critical
|
||||
/// section to qconnect-app, then running the post-lock CoreBridge work the
|
||||
/// returned `SessionApplyOutcome` asks for (renderer-engine seam stays
|
||||
/// adapter-side, slice 6). This device's identity is resolved here and
|
||||
/// injected so qconnect-app stays frontend-agnostic. The post-lock ordering
|
||||
/// (loop mode -> local-playback handoff -> projection -> freeze -> watchdog)
|
||||
/// is byte-for-byte the prior inline implementation; only the locked match,
|
||||
/// watchdog spawn, and freeze now live in qconnect-app.
|
||||
async fn apply_session_management_event(&self, message_type: &str, payload: &Value) {
|
||||
let mut remote_projection_renderer_id: Option<i32> = None;
|
||||
let mut sync_local_playback = false;
|
||||
let mut apply_loop_mode: Option<i32> = None;
|
||||
let mut disconnected_renderer_id: Option<i32> = None;
|
||||
let mut watchdog_arm: Option<(i32, u64)> = None;
|
||||
let mut state = self.sync_state.lock().await;
|
||||
match message_type {
|
||||
"MESSAGE_TYPE_SRVR_CTRL_SESSION_STATE" => {
|
||||
if let Some(uuid) = payload.get("session_uuid").and_then(Value::as_str) {
|
||||
state.session.session_uuid = Some(uuid.to_string());
|
||||
}
|
||||
state.session.active_renderer_id = normalize_active_renderer_id(
|
||||
payload.get("active_renderer_id").and_then(Value::as_i64),
|
||||
);
|
||||
if let Some(loop_mode) = payload
|
||||
.get("loop_mode")
|
||||
.and_then(Value::as_i64)
|
||||
.and_then(|value| i32::try_from(value).ok())
|
||||
{
|
||||
state.session_loop_mode = Some(loop_mode);
|
||||
apply_loop_mode = Some(loop_mode);
|
||||
}
|
||||
if let (Some(active_renderer_id), Some(loop_mode)) =
|
||||
(state.session.active_renderer_id, state.session_loop_mode)
|
||||
{
|
||||
let renderer_state =
|
||||
ensure_session_renderer_state(&mut state, active_renderer_id);
|
||||
renderer_state.loop_mode = Some(loop_mode);
|
||||
renderer_state.updated_at_ms = qconnect_now_ms();
|
||||
}
|
||||
sync_session_renderer_active_flags(&mut state);
|
||||
sync_local_playback = true;
|
||||
}
|
||||
"MESSAGE_TYPE_SRVR_CTRL_ADD_RENDERER" => {
|
||||
if let Some(renderer_id) = payload.get("renderer_id").and_then(Value::as_i64) {
|
||||
let renderer_id = renderer_id as i32;
|
||||
// Don't add duplicates
|
||||
if !state
|
||||
.session
|
||||
.renderers
|
||||
.iter()
|
||||
.any(|r| r.renderer_id == renderer_id)
|
||||
{
|
||||
let device_info = payload.get("device_info");
|
||||
state.session.renderers.push(QconnectRendererInfo {
|
||||
renderer_id,
|
||||
device_uuid: device_info
|
||||
.and_then(|d| d.get("device_uuid"))
|
||||
.and_then(Value::as_str)
|
||||
.map(String::from),
|
||||
friendly_name: device_info
|
||||
.and_then(|d| d.get("friendly_name"))
|
||||
.and_then(Value::as_str)
|
||||
.map(String::from),
|
||||
brand: device_info
|
||||
.and_then(|d| d.get("brand"))
|
||||
.and_then(Value::as_str)
|
||||
.map(String::from),
|
||||
model: device_info
|
||||
.and_then(|d| d.get("model"))
|
||||
.and_then(Value::as_str)
|
||||
.map(String::from),
|
||||
device_type: device_info
|
||||
.and_then(|d| d.get("device_type"))
|
||||
.and_then(Value::as_i64)
|
||||
.map(|v| v as i32),
|
||||
volume_remote_control: device_info
|
||||
.and_then(|d| d.get("capabilities"))
|
||||
.and_then(|c| c.get("volume_remote_control"))
|
||||
.and_then(Value::as_i64)
|
||||
.map(|v| v as i32),
|
||||
});
|
||||
refresh_local_renderer_id(&mut state.session);
|
||||
}
|
||||
let _ = ensure_session_renderer_state(&mut state, renderer_id);
|
||||
sync_session_renderer_active_flags(&mut state);
|
||||
}
|
||||
}
|
||||
"MESSAGE_TYPE_SRVR_CTRL_UPDATE_RENDERER" => {
|
||||
if let Some(renderer_id) = payload.get("renderer_id").and_then(Value::as_i64) {
|
||||
let renderer_id = renderer_id as i32;
|
||||
if let Some(existing) = state
|
||||
.session
|
||||
.renderers
|
||||
.iter_mut()
|
||||
.find(|r| r.renderer_id == renderer_id)
|
||||
{
|
||||
let device_info = payload.get("device_info");
|
||||
if let Some(device_uuid) = device_info
|
||||
.and_then(|d| d.get("device_uuid"))
|
||||
.and_then(Value::as_str)
|
||||
{
|
||||
existing.device_uuid = Some(device_uuid.to_string());
|
||||
}
|
||||
if let Some(name) = device_info
|
||||
.and_then(|d| d.get("friendly_name"))
|
||||
.and_then(Value::as_str)
|
||||
{
|
||||
existing.friendly_name = Some(name.to_string());
|
||||
}
|
||||
if let Some(brand) = device_info
|
||||
.and_then(|d| d.get("brand"))
|
||||
.and_then(Value::as_str)
|
||||
{
|
||||
existing.brand = Some(brand.to_string());
|
||||
}
|
||||
if let Some(model) = device_info
|
||||
.and_then(|d| d.get("model"))
|
||||
.and_then(Value::as_str)
|
||||
{
|
||||
existing.model = Some(model.to_string());
|
||||
}
|
||||
if let Some(device_type) = device_info
|
||||
.and_then(|d| d.get("device_type"))
|
||||
.and_then(Value::as_i64)
|
||||
{
|
||||
existing.device_type = Some(device_type as i32);
|
||||
}
|
||||
if let Some(vrc) = device_info
|
||||
.and_then(|d| d.get("capabilities"))
|
||||
.and_then(|c| c.get("volume_remote_control"))
|
||||
.and_then(Value::as_i64)
|
||||
{
|
||||
existing.volume_remote_control = Some(vrc as i32);
|
||||
}
|
||||
refresh_local_renderer_id(&mut state.session);
|
||||
}
|
||||
let _ = ensure_session_renderer_state(&mut state, renderer_id);
|
||||
sync_session_renderer_active_flags(&mut state);
|
||||
}
|
||||
}
|
||||
"MESSAGE_TYPE_SRVR_CTRL_REMOVE_RENDERER" => {
|
||||
if let Some(renderer_id) = payload.get("renderer_id").and_then(Value::as_i64) {
|
||||
let renderer_id = renderer_id as i32;
|
||||
state
|
||||
.session
|
||||
.renderers
|
||||
.retain(|r| r.renderer_id != renderer_id);
|
||||
state.session_renderer_states.remove(&renderer_id);
|
||||
refresh_local_renderer_id(&mut state.session);
|
||||
sync_session_renderer_active_flags(&mut state);
|
||||
}
|
||||
}
|
||||
"MESSAGE_TYPE_SRVR_CTRL_ACTIVE_RENDERER_CHANGED" => {
|
||||
state.session.active_renderer_id = normalize_active_renderer_id(
|
||||
payload.get("active_renderer_id").and_then(Value::as_i64),
|
||||
);
|
||||
if let (Some(active_renderer_id), Some(loop_mode)) =
|
||||
(state.session.active_renderer_id, state.session_loop_mode)
|
||||
{
|
||||
let renderer_state =
|
||||
ensure_session_renderer_state(&mut state, active_renderer_id);
|
||||
renderer_state.loop_mode = Some(loop_mode);
|
||||
renderer_state.updated_at_ms = qconnect_now_ms();
|
||||
}
|
||||
apply_loop_mode = state.session_loop_mode;
|
||||
sync_session_renderer_active_flags(&mut state);
|
||||
remote_projection_renderer_id = state.session.active_renderer_id;
|
||||
sync_local_playback = true;
|
||||
// P0-1: active-renderer change disarms any pending liveness task.
|
||||
Self::bump_watchdog_generation(&mut state);
|
||||
}
|
||||
"MESSAGE_TYPE_SRVR_CTRL_RENDERER_STATE_UPDATED" => {
|
||||
let Some(renderer_id) = payload.get("renderer_id").and_then(Value::as_i64) else {
|
||||
return;
|
||||
};
|
||||
let player_state = payload.get("player_state");
|
||||
let renderer_state = ensure_session_renderer_state(&mut state, renderer_id as i32);
|
||||
let Some(app) = self.app.get().and_then(Weak::upgrade) else {
|
||||
return;
|
||||
};
|
||||
let identity = super::session::resolve_local_identity();
|
||||
let outcome = app
|
||||
.apply_session_management_event(message_type, payload, &identity)
|
||||
.await;
|
||||
|
||||
if let Some(playing_state) = player_state
|
||||
.and_then(|value| value.get("playing_state"))
|
||||
.and_then(Value::as_i64)
|
||||
.and_then(|value| i32::try_from(value).ok())
|
||||
{
|
||||
renderer_state.playing_state = Some(playing_state);
|
||||
}
|
||||
|
||||
if let Some(current_position_ms) = player_state
|
||||
.and_then(|value| value.get("current_position"))
|
||||
.and_then(Value::as_i64)
|
||||
.and_then(|value| u64::try_from(value).ok())
|
||||
{
|
||||
renderer_state.current_position_ms = Some(current_position_ms);
|
||||
}
|
||||
|
||||
if let Some(current_queue_item_id) = player_state
|
||||
.and_then(|value| value.get("current_queue_item_id"))
|
||||
.and_then(Value::as_i64)
|
||||
{
|
||||
renderer_state.current_queue_item_id =
|
||||
u64::try_from(current_queue_item_id).ok();
|
||||
}
|
||||
|
||||
renderer_state.updated_at_ms = qconnect_now_ms();
|
||||
// Snapshot the just-written playing_state for the watchdog decision
|
||||
// (the &mut borrow ends here so we can re-read state.session below).
|
||||
let this_playing_state = renderer_state.playing_state;
|
||||
remote_projection_renderer_id = Some(renderer_id as i32);
|
||||
sync_local_playback = true;
|
||||
|
||||
let is_active_peer = state.session.active_renderer_id
|
||||
== Some(renderer_id as i32)
|
||||
&& is_peer_renderer_active(&state.session)
|
||||
&& renderer_id != -1;
|
||||
|
||||
// P0-2: graceful disconnect. status==ACTIVE_DISCONNECTED(2) for
|
||||
// the active remote renderer (id != -1) means the renderer left
|
||||
// cleanly — freeze the projection so the UI stops lying.
|
||||
let status =
|
||||
RendererStatus::from_wire(payload.get("status").and_then(Value::as_i64));
|
||||
if status == RendererStatus::ActiveDisconnected && is_active_peer {
|
||||
disconnected_renderer_id = Some(renderer_id as i32);
|
||||
}
|
||||
|
||||
// P0-1 liveness watchdog. Any RENDERER_STATE_UPDATED for the
|
||||
// active peer resets the timer (bump generation); arm a fresh 12s
|
||||
// task only while PLAYING. A non-playing update disarms (bump
|
||||
// only, no spawn).
|
||||
let new_generation = Self::bump_watchdog_generation(&mut state);
|
||||
if should_arm_renderer_watchdog(this_playing_state, is_active_peer) {
|
||||
watchdog_arm = Some((renderer_id as i32, new_generation));
|
||||
}
|
||||
}
|
||||
"MESSAGE_TYPE_SRVR_CTRL_VOLUME_CHANGED" => {
|
||||
let Some(renderer_id) = payload.get("renderer_id").and_then(Value::as_i64) else {
|
||||
return;
|
||||
};
|
||||
let Some(volume) = payload
|
||||
.get("volume")
|
||||
.and_then(Value::as_i64)
|
||||
.and_then(|value| i32::try_from(value).ok())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let renderer_state = ensure_session_renderer_state(&mut state, renderer_id as i32);
|
||||
renderer_state.volume = Some(volume);
|
||||
renderer_state.updated_at_ms = qconnect_now_ms();
|
||||
}
|
||||
"MESSAGE_TYPE_SRVR_CTRL_VOLUME_MUTED" => {
|
||||
let Some(renderer_id) = payload.get("renderer_id").and_then(Value::as_i64) else {
|
||||
return;
|
||||
};
|
||||
let Some(muted) = payload.get("value").and_then(Value::as_bool) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let renderer_state = ensure_session_renderer_state(&mut state, renderer_id as i32);
|
||||
renderer_state.muted = Some(muted);
|
||||
renderer_state.updated_at_ms = qconnect_now_ms();
|
||||
}
|
||||
"MESSAGE_TYPE_SRVR_CTRL_MAX_AUDIO_QUALITY_CHANGED" => {
|
||||
let Some(renderer_id) = payload.get("renderer_id").and_then(Value::as_i64) else {
|
||||
return;
|
||||
};
|
||||
let Some(max_audio_quality) = payload
|
||||
.get("max_audio_quality")
|
||||
.and_then(Value::as_i64)
|
||||
.and_then(|value| i32::try_from(value).ok())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let renderer_state = ensure_session_renderer_state(&mut state, renderer_id as i32);
|
||||
renderer_state.max_audio_quality = Some(max_audio_quality);
|
||||
renderer_state.updated_at_ms = qconnect_now_ms();
|
||||
}
|
||||
"MESSAGE_TYPE_SRVR_CTRL_LOOP_MODE_SET" => {
|
||||
let Some(loop_mode) = payload
|
||||
.get("loop_mode")
|
||||
.and_then(Value::as_i64)
|
||||
.and_then(|value| i32::try_from(value).ok())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
state.session_loop_mode = Some(loop_mode);
|
||||
apply_loop_mode = Some(loop_mode);
|
||||
if let Some(active_renderer_id) = state.session.active_renderer_id {
|
||||
let renderer_state =
|
||||
ensure_session_renderer_state(&mut state, active_renderer_id);
|
||||
renderer_state.loop_mode = Some(loop_mode);
|
||||
renderer_state.updated_at_ms = qconnect_now_ms();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
drop(state);
|
||||
|
||||
if let Some(loop_mode) = apply_loop_mode {
|
||||
if let Some(loop_mode) = outcome.apply_loop_mode {
|
||||
if let Err(err) =
|
||||
apply_remote_loop_mode_to_corebridge(&self.core_bridge, loop_mode).await
|
||||
{
|
||||
@@ -504,92 +226,27 @@ impl TauriQconnectEventSink {
|
||||
}
|
||||
}
|
||||
|
||||
if sync_local_playback {
|
||||
if outcome.sync_local_playback {
|
||||
self.sync_local_playback_for_renderer_ownership().await;
|
||||
}
|
||||
|
||||
if let Some(renderer_id) = remote_projection_renderer_id {
|
||||
if let Some(renderer_id) = outcome.remote_projection_renderer_id {
|
||||
self.sync_active_renderer_projection(renderer_id).await;
|
||||
}
|
||||
|
||||
if let Some(renderer_id) = disconnected_renderer_id {
|
||||
self.freeze_active_renderer_projection_to_unknown(
|
||||
if let Some(renderer_id) = outcome.disconnected_renderer_id {
|
||||
app.freeze_active_renderer_projection(
|
||||
renderer_id,
|
||||
QconnectAppEvent::RendererDisconnected { renderer_id },
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// P0-1: arm the generation-guarded 12s liveness watchdog. The spawned
|
||||
// task holds a clone of this sink (TauriQconnectEventSink: Clone). On
|
||||
// wake it re-locks and no-ops unless its captured epoch is still current
|
||||
// AND the renderer is still the active peer AND still nominally playing.
|
||||
if let Some((renderer_id, generation)) = watchdog_arm {
|
||||
let sink = self.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(
|
||||
QCONNECT_RENDERER_LOST_TIMEOUT_MS,
|
||||
))
|
||||
.await;
|
||||
let fire = {
|
||||
let state = sink.sync_state.lock().await;
|
||||
state.watchdog_generation == generation
|
||||
&& state.session.active_renderer_id == Some(renderer_id)
|
||||
&& is_peer_renderer_active(&state.session)
|
||||
&& state
|
||||
.session_renderer_states
|
||||
.get(&renderer_id)
|
||||
.and_then(|r| r.playing_state)
|
||||
== Some(PLAYING_STATE_PLAYING)
|
||||
};
|
||||
if fire {
|
||||
log::warn!(
|
||||
"[QConnect] Renderer {renderer_id} silent for {}ms — marking unreachable",
|
||||
QCONNECT_RENDERER_LOST_TIMEOUT_MS
|
||||
);
|
||||
sink.freeze_active_renderer_projection_to_unknown(
|
||||
renderer_id,
|
||||
QconnectAppEvent::RendererUnreachable { renderer_id },
|
||||
)
|
||||
.await;
|
||||
}
|
||||
});
|
||||
if let Some((renderer_id, generation)) = outcome.watchdog_arm {
|
||||
app.arm_renderer_watchdog(renderer_id, generation);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bump the watchdog epoch, invalidating any in-flight 12s liveness task.
|
||||
/// Returns the new generation (used when arming so the spawned task knows
|
||||
/// which epoch it belongs to).
|
||||
fn bump_watchdog_generation(state: &mut QconnectRemoteSyncState) -> u64 {
|
||||
state.watchdog_generation = state.watchdog_generation.wrapping_add(1);
|
||||
state.watchdog_generation
|
||||
}
|
||||
|
||||
/// Force the active renderer's cached projection to UNKNOWN/stopped and
|
||||
/// signal a freeze event through the event sink. Shared by
|
||||
/// ACTIVE_DISCONNECTED (P0-2) and the silence watchdog (P0-1). Caller passes
|
||||
/// the `QconnectAppEvent` freeze variant (`RendererUnreachable` /
|
||||
/// `RendererDisconnected`) to emit.
|
||||
///
|
||||
/// This is a pure-state mutation followed by `on_event(variant)`. The sink's
|
||||
/// mapper arm for these variants ONLY emits the dedicated Tauri channel (+
|
||||
/// the blanket `qconnect:event`); it does NOT route back through
|
||||
/// `apply_session_management_event` or call this helper again, so it cannot
|
||||
/// re-arm the watchdog or recurse.
|
||||
async fn freeze_active_renderer_projection_to_unknown(
|
||||
&self,
|
||||
renderer_id: i32,
|
||||
freeze_event: QconnectAppEvent,
|
||||
) {
|
||||
{
|
||||
let mut state = self.sync_state.lock().await;
|
||||
let renderer_state = ensure_session_renderer_state(&mut state, renderer_id);
|
||||
renderer_state.playing_state = Some(PLAYING_STATE_UNKNOWN);
|
||||
renderer_state.updated_at_ms = qconnect_now_ms();
|
||||
}
|
||||
self.on_event(freeze_event).await;
|
||||
}
|
||||
|
||||
/// Emit one renderer-freeze dedicated channel (`qconnect:renderer_unreachable`
|
||||
/// / `qconnect:renderer_disconnected`) with the legacy `{renderer_id}` payload.
|
||||
/// Called from the `on_event` mapper arms; kept as a small helper so both
|
||||
|
||||
@@ -39,13 +39,6 @@ pub(super) const PLAYING_STATE_PLAYING: i32 = 2;
|
||||
pub(super) const PLAYING_STATE_PAUSED: i32 = 3;
|
||||
pub(super) const BUFFER_STATE_OK: i32 = 2;
|
||||
|
||||
/// The renderer-liveness silence budget and the pure watchdog-arming predicate
|
||||
/// now live in the frontend-agnostic `qconnect_app::session` module. Re-exported
|
||||
/// here so existing `super::…` references inside this module compile unchanged.
|
||||
/// (The JoinSession `reason` consts also moved there; they are consumed only by
|
||||
/// `deferred_join_reason`, which moved too, so they are not re-exported here.)
|
||||
pub(super) use qconnect_app::{should_arm_renderer_watchdog, QCONNECT_RENDERER_LOST_TIMEOUT_MS};
|
||||
|
||||
// AudioQuality enum: 0=unknown, 1=mp3, 2=cd, 3=hires_l1, 4=hires_l2(192k), 5=hires_l3(384k)
|
||||
pub(super) const AUDIO_QUALITY_UNKNOWN: i32 = 0;
|
||||
pub(super) const AUDIO_QUALITY_MP3: i32 = 1;
|
||||
|
||||
@@ -35,7 +35,7 @@ pub use qconnect_app::{QconnectRendererInfo, QconnectSessionRendererState, Qconn
|
||||
/// qconnect-app (slice 2+4). Re-exported so existing `super::session::…` /
|
||||
/// `super::…` references compile unchanged.
|
||||
pub(super) use qconnect_app::{
|
||||
is_local_renderer_active, is_peer_renderer_active, normalize_active_renderer_id,
|
||||
ensure_session_renderer_state, is_local_renderer_active, is_peer_renderer_active,
|
||||
renderer_allows_remote_volume, QconnectFileAudioQualitySnapshot,
|
||||
};
|
||||
// find_unique_renderer_id's sole non-test caller (refresh_local_renderer_id)
|
||||
@@ -76,49 +76,22 @@ pub(crate) struct QconnectRendererReportDebugEvent {
|
||||
pub(super) resolution_strategy: String,
|
||||
}
|
||||
|
||||
/// Thin Tauri wrapper: resolves this device's identity (uuid + device-info)
|
||||
/// adapter-side, then delegates to the frontend-agnostic resolver in
|
||||
/// qconnect-app. qconnect-app stays identity-free. The eager device-info build
|
||||
/// is idempotent and side-effect-free (env reads + the cached uuid), so the
|
||||
/// resolved renderer id is identical to the prior in-place implementation.
|
||||
pub(super) fn refresh_local_renderer_id(session: &mut QconnectSessionState) {
|
||||
/// Resolve THIS device's identity (uuid + device-info) for injection into the
|
||||
/// frontend-agnostic session-apply logic in qconnect-app. The device-info build
|
||||
/// is idempotent and side-effect-free (env reads + the cached uuid), so
|
||||
/// resolving it here once per session event yields the exact renderer-id
|
||||
/// resolution the prior in-place `refresh_local_renderer_id` produced.
|
||||
pub(super) fn resolve_local_identity() -> qconnect_app::LocalIdentity {
|
||||
let info = default_qconnect_device_info();
|
||||
let identity = qconnect_app::LocalIdentity {
|
||||
qconnect_app::LocalIdentity {
|
||||
device_uuid: info.device_uuid.unwrap_or_default(),
|
||||
friendly_name: info.friendly_name,
|
||||
brand: info.brand,
|
||||
model: info.model,
|
||||
device_type: info.device_type,
|
||||
};
|
||||
qconnect_app::refresh_local_renderer_id(session, &identity);
|
||||
}
|
||||
|
||||
pub(super) fn sync_session_renderer_active_flags(state: &mut QconnectRemoteSyncState) {
|
||||
for (renderer_id, renderer_state) in &mut state.session_renderer_states {
|
||||
renderer_state.active = state
|
||||
.session
|
||||
.active_renderer_id
|
||||
.map(|active_renderer_id| active_renderer_id == *renderer_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn ensure_session_renderer_state(
|
||||
state: &mut QconnectRemoteSyncState,
|
||||
renderer_id: i32,
|
||||
) -> &mut QconnectSessionRendererState {
|
||||
let active = state
|
||||
.session
|
||||
.active_renderer_id
|
||||
.map(|active_renderer_id| active_renderer_id == renderer_id);
|
||||
state
|
||||
.session_renderer_states
|
||||
.entry(renderer_id)
|
||||
.or_insert_with(|| QconnectSessionRendererState {
|
||||
active,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn build_session_renderer_snapshot(
|
||||
queue: &QConnectQueueState,
|
||||
renderer_state: Option<&QconnectSessionRendererState>,
|
||||
|
||||
@@ -8,7 +8,7 @@ use super::queue_resolution::{
|
||||
QconnectRemoteSkipDirection,
|
||||
};
|
||||
use super::session::{
|
||||
find_unique_renderer_id, refresh_local_renderer_id, renderer_allows_remote_volume,
|
||||
find_unique_renderer_id, renderer_allows_remote_volume, resolve_local_identity,
|
||||
QconnectFileAudioQualitySnapshot,
|
||||
};
|
||||
use super::transport::{
|
||||
@@ -138,7 +138,7 @@ fn refreshes_local_renderer_id_from_exact_device_uuid_match() {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
refresh_local_renderer_id(&mut session);
|
||||
qconnect_app::refresh_local_renderer_id(&mut session, &resolve_local_identity());
|
||||
|
||||
assert_eq!(session.local_renderer_id, Some(6));
|
||||
}
|
||||
@@ -172,7 +172,7 @@ fn refreshes_local_renderer_id_from_unique_fingerprint_when_uuid_missing() {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
refresh_local_renderer_id(&mut session);
|
||||
qconnect_app::refresh_local_renderer_id(&mut session, &resolve_local_identity());
|
||||
|
||||
assert_eq!(session.local_renderer_id, Some(6));
|
||||
}
|
||||
@@ -203,7 +203,7 @@ fn does_not_guess_local_renderer_id_when_fingerprint_is_ambiguous() {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
refresh_local_renderer_id(&mut session);
|
||||
qconnect_app::refresh_local_renderer_id(&mut session, &resolve_local_identity());
|
||||
|
||||
assert_eq!(session.local_renderer_id, None);
|
||||
assert_eq!(
|
||||
|
||||
Reference in new issue
Block a user