feat(qconnect): add session management protobuf types and outbound reporting
Add missing SRVR_CTRL session management message types (81-87, 97-101) that were completely absent from the protobuf definitions. Without these, prost silently discarded all session topology messages from the server. - Add 12 new proto structs for session/renderer/volume/quality events - Add decoder branches and map functions for all new message types - Add SessionManagementEvent variant to bypass queue reducer - Add v2_qconnect_report_playback_state and v2_qconnect_report_volume - Add comprehensive logging for inbound message diagnostics
This commit is contained in:
13 files changed
+950
-106
No files matched your search
@@ -209,6 +209,18 @@ where
|
||||
}
|
||||
|
||||
async fn apply_server_event(&self, event: QueueServerEvent) -> Result<(), QconnectAppError> {
|
||||
// Session management events bypass the queue reducer entirely.
|
||||
// They provide session topology info (renderers, active renderer, etc.)
|
||||
if event.event_type.is_session_management() {
|
||||
self.sink
|
||||
.on_event(QconnectAppEvent::SessionManagementEvent {
|
||||
message_type: event.message_type().to_string(),
|
||||
payload: event.payload.clone(),
|
||||
})
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut completed_uuid: Option<String> = None;
|
||||
let mut canceled_uuid: Option<String> = None;
|
||||
let mut ignored_queue_error_uuid: Option<String> = None;
|
||||
@@ -601,6 +613,12 @@ fn map_server_event(event: &QueueServerEvent, current: &QConnectQueueState) -> Q
|
||||
.map(value_to_string)
|
||||
.unwrap_or_else(|| "queue_error_message".to_string()),
|
||||
},
|
||||
// Session management events are intercepted in apply_server_event()
|
||||
// and should never reach map_server_event(). Treat as no-op if they do.
|
||||
_ => QueueEvent::QueueCleared {
|
||||
action_uuid: None,
|
||||
version: current.version,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use qconnect_core::{QConnectQueueState, QConnectRendererState, RendererCommand};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum QconnectAppEvent {
|
||||
@@ -30,6 +31,12 @@ pub enum QconnectAppEvent {
|
||||
action_uuid: String,
|
||||
},
|
||||
QueueResyncTriggered,
|
||||
/// Session management event from server (types 81-87, 97-101).
|
||||
/// These don't affect the queue reducer but provide session topology info.
|
||||
SessionManagementEvent {
|
||||
message_type: String,
|
||||
payload: Value,
|
||||
},
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -17,7 +17,7 @@ pub use feature_flags::{
|
||||
};
|
||||
pub use qconnect_core::{
|
||||
evaluate_remote_queue_admission, resolve_handoff_intent, AdmissionDecision, HandoffIntent,
|
||||
QConnectQueueState, QConnectRendererState, RendererCommand, TrackOrigin,
|
||||
QConnectQueueState, QConnectRendererState, QueueVersion, RendererCommand, TrackOrigin,
|
||||
};
|
||||
pub use qconnect_protocol::{QueueCommandType, RendererReport, RendererReportType};
|
||||
pub use state::QconnectRuntimeState;
|
||||
@@ -6,12 +6,16 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
queue_command_proto::{
|
||||
AutoplayModeSetMessage, AutoplayTracksLoadedMessage, AutoplayTracksRemovedMessage,
|
||||
QConnectMessage, QConnectMessageType, QConnectMessages, QueueClearedMessage,
|
||||
QueueErrorMessage, QueueStateMessage, QueueTrack, QueueTrackWithContext,
|
||||
QueueTracksAddedFromAutoplayMessage, QueueTracksAddedMessage, QueueTracksInsertedMessage,
|
||||
QueueTracksLoadedMessage, QueueTracksRemovedMessage, QueueTracksReorderedMessage,
|
||||
QueueVersionRef, RendererMuteVolumeMessage, RendererSetActiveMessage,
|
||||
RendererSetLoopModeMessage, RendererSetMaxAudioQualityMessage,
|
||||
CtrlActiveRendererChangedMessage, CtrlAddRendererMessage,
|
||||
CtrlDeviceAudioQualityChangedMessage, CtrlFileAudioQualityChangedMessage,
|
||||
CtrlLoopModeSetMessage, CtrlMaxAudioQualityChangedMessage, CtrlRemoveRendererMessage,
|
||||
CtrlRendererStateUpdatedMessage, CtrlSessionStateMessage, CtrlUpdateRendererMessage,
|
||||
CtrlVolumeChangedMessage, CtrlVolumeMutedMessage, QConnectMessage, QConnectMessageType,
|
||||
QConnectMessages, QueueClearedMessage, QueueErrorMessage, QueueStateMessage, QueueTrack,
|
||||
QueueTrackWithContext, QueueTracksAddedFromAutoplayMessage, QueueTracksAddedMessage,
|
||||
QueueTracksInsertedMessage, QueueTracksLoadedMessage, QueueTracksRemovedMessage,
|
||||
QueueTracksReorderedMessage, QueueVersionRef, RendererMuteVolumeMessage,
|
||||
RendererSetActiveMessage, RendererSetLoopModeMessage, RendererSetMaxAudioQualityMessage,
|
||||
RendererSetShuffleModeMessage, RendererSetStateMessage, RendererSetVolumeMessage,
|
||||
ShuffleModeSetMessage,
|
||||
},
|
||||
@@ -120,6 +124,83 @@ fn decode_queue_server_event(
|
||||
};
|
||||
map_tracks_added_from_autoplay(payload)?
|
||||
}
|
||||
// Session management events (81-87, 97-101)
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlSessionState as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_session_state else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_session_state(payload)?
|
||||
}
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlRendererStateUpdated as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_renderer_state_updated else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_ctrl_renderer_state_updated(payload)?
|
||||
}
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlAddRenderer as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_add_renderer else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_add_renderer(payload)?
|
||||
}
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlUpdateRenderer as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_update_renderer else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_update_renderer(payload)?
|
||||
}
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlRemoveRenderer as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_remove_renderer else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_remove_renderer(payload)?
|
||||
}
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlActiveRendererChanged as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_active_renderer_changed else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_active_renderer_changed(payload)?
|
||||
}
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlVolumeChanged as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_volume_changed else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_ctrl_volume_changed(payload)?
|
||||
}
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlLoopModeSet as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_loop_mode_set else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_ctrl_loop_mode_set(payload)?
|
||||
}
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlVolumeMuted as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_volume_muted else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_ctrl_volume_muted(payload)?
|
||||
}
|
||||
code if code == QConnectMessageType::MessageTypeSrvrCtrlMaxAudioQualityChanged as i32 => {
|
||||
let Some(payload) = message.srvr_ctrl_max_audio_quality_changed else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_ctrl_max_audio_quality_changed(payload)?
|
||||
}
|
||||
code if code
|
||||
== QConnectMessageType::MessageTypeSrvrCtrlFileAudioQualityChanged as i32 =>
|
||||
{
|
||||
let Some(payload) = message.srvr_ctrl_file_audio_quality_changed else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_ctrl_file_audio_quality_changed(payload)?
|
||||
}
|
||||
code if code
|
||||
== QConnectMessageType::MessageTypeSrvrCtrlDeviceAudioQualityChanged as i32 =>
|
||||
{
|
||||
let Some(payload) = message.srvr_ctrl_device_audio_quality_changed else {
|
||||
return Ok(None);
|
||||
};
|
||||
map_ctrl_device_audio_quality_changed(payload)?
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
@@ -169,6 +250,45 @@ fn resolve_queue_message_type(message: &QConnectMessage) -> Option<i32> {
|
||||
QConnectMessageType::MessageTypeSrvrCtrlQueueTracksAddedFromAutoplay as i32,
|
||||
);
|
||||
}
|
||||
// Session management
|
||||
if message.srvr_ctrl_session_state.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlSessionState as i32);
|
||||
}
|
||||
if message.srvr_ctrl_renderer_state_updated.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlRendererStateUpdated as i32);
|
||||
}
|
||||
if message.srvr_ctrl_add_renderer.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlAddRenderer as i32);
|
||||
}
|
||||
if message.srvr_ctrl_update_renderer.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlUpdateRenderer as i32);
|
||||
}
|
||||
if message.srvr_ctrl_remove_renderer.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlRemoveRenderer as i32);
|
||||
}
|
||||
if message.srvr_ctrl_active_renderer_changed.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlActiveRendererChanged as i32);
|
||||
}
|
||||
if message.srvr_ctrl_volume_changed.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlVolumeChanged as i32);
|
||||
}
|
||||
if message.srvr_ctrl_loop_mode_set.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlLoopModeSet as i32);
|
||||
}
|
||||
if message.srvr_ctrl_volume_muted.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlVolumeMuted as i32);
|
||||
}
|
||||
if message.srvr_ctrl_max_audio_quality_changed.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlMaxAudioQualityChanged as i32);
|
||||
}
|
||||
if message.srvr_ctrl_file_audio_quality_changed.is_some() {
|
||||
return Some(QConnectMessageType::MessageTypeSrvrCtrlFileAudioQualityChanged as i32);
|
||||
}
|
||||
if message.srvr_ctrl_device_audio_quality_changed.is_some() {
|
||||
return Some(
|
||||
QConnectMessageType::MessageTypeSrvrCtrlDeviceAudioQualityChanged as i32,
|
||||
);
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
@@ -642,6 +762,207 @@ fn map_tracks_added_from_autoplay(
|
||||
})
|
||||
}
|
||||
|
||||
// --- Session management event mappers ---
|
||||
|
||||
fn map_session_state(payload: CtrlSessionStateMessage) -> Result<QueueServerEvent, ProtocolError> {
|
||||
let session_uuid = uuid_bytes_to_string_opt(payload.session_uuid, "session_state.session_uuid")?;
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlSessionState,
|
||||
action_uuid: None,
|
||||
queue_version: queue_version_opt(payload.queue_version)?,
|
||||
payload: json!({
|
||||
"session_uuid": session_uuid,
|
||||
"active_renderer_id": payload.active_renderer_id,
|
||||
"playing_state": payload.playing_state,
|
||||
"loop_mode": payload.loop_mode
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_ctrl_renderer_state_updated(
|
||||
payload: CtrlRendererStateUpdatedMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
let player_state = payload.player_state.map(|ps| {
|
||||
json!({
|
||||
"playing_state": ps.playing_state,
|
||||
"buffer_state": ps.buffer_state,
|
||||
"current_position": ps.current_position.as_ref().and_then(|pos| pos.value),
|
||||
"duration": ps.duration,
|
||||
"current_queue_item_id": ps.current_queue_item_id
|
||||
})
|
||||
});
|
||||
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlRendererStateUpdated,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"renderer_id": payload.renderer_id,
|
||||
"status": payload.status,
|
||||
"player_state": player_state
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_add_renderer(payload: CtrlAddRendererMessage) -> Result<QueueServerEvent, ProtocolError> {
|
||||
let device_info = payload.device_info.map(|di| {
|
||||
json!({
|
||||
"friendly_name": di.friendly_name,
|
||||
"brand": di.brand,
|
||||
"model": di.model,
|
||||
"device_type": di.device_type
|
||||
})
|
||||
});
|
||||
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlAddRenderer,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"renderer_id": payload.renderer_id,
|
||||
"device_info": device_info
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_update_renderer(
|
||||
payload: CtrlUpdateRendererMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
let device_info = payload.device_info.map(|di| {
|
||||
json!({
|
||||
"friendly_name": di.friendly_name,
|
||||
"brand": di.brand,
|
||||
"model": di.model,
|
||||
"device_type": di.device_type
|
||||
})
|
||||
});
|
||||
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlUpdateRenderer,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"renderer_id": payload.renderer_id,
|
||||
"device_info": device_info
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_remove_renderer(
|
||||
payload: CtrlRemoveRendererMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlRemoveRenderer,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"renderer_id": payload.renderer_id
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_active_renderer_changed(
|
||||
payload: CtrlActiveRendererChangedMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlActiveRendererChanged,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"active_renderer_id": payload.active_renderer_id
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_ctrl_volume_changed(
|
||||
payload: CtrlVolumeChangedMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlVolumeChanged,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"renderer_id": payload.renderer_id,
|
||||
"volume": payload.volume
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_ctrl_loop_mode_set(
|
||||
payload: CtrlLoopModeSetMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlLoopModeSet,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"loop_mode": payload.loop_mode
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_ctrl_volume_muted(
|
||||
payload: CtrlVolumeMutedMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlVolumeMuted,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"renderer_id": payload.renderer_id,
|
||||
"value": payload.value
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_ctrl_max_audio_quality_changed(
|
||||
payload: CtrlMaxAudioQualityChangedMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlMaxAudioQualityChanged,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"renderer_id": payload.renderer_id,
|
||||
"max_audio_quality": payload.max_audio_quality,
|
||||
"network_type": payload.network_type
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_ctrl_file_audio_quality_changed(
|
||||
payload: CtrlFileAudioQualityChangedMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlFileAudioQualityChanged,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"renderer_id": payload.renderer_id,
|
||||
"sampling_rate": payload.sampling_rate,
|
||||
"bit_depth": payload.bit_depth,
|
||||
"nb_channels": payload.nb_channels,
|
||||
"audio_quality": payload.audio_quality
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_ctrl_device_audio_quality_changed(
|
||||
payload: CtrlDeviceAudioQualityChangedMessage,
|
||||
) -> Result<QueueServerEvent, ProtocolError> {
|
||||
Ok(QueueServerEvent {
|
||||
event_type: QueueEventType::SrvrCtrlDeviceAudioQualityChanged,
|
||||
action_uuid: None,
|
||||
queue_version: None,
|
||||
payload: json!({
|
||||
"renderer_id": payload.renderer_id,
|
||||
"sampling_rate": payload.sampling_rate,
|
||||
"bit_depth": payload.bit_depth,
|
||||
"nb_channels": payload.nb_channels
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn queue_track_to_json(
|
||||
track: QueueTrack,
|
||||
context_uuid: Option<&str>,
|
||||
|
||||
@@ -17,6 +17,19 @@ pub enum QueueEventType {
|
||||
SrvrCtrlAutoplayTracksRemoved,
|
||||
SrvrCtrlQueueTracksAddedFromAutoplay,
|
||||
SrvrCtrlQueueErrorMessage,
|
||||
// Session management events (types 81-87, 97-101)
|
||||
SrvrCtrlSessionState,
|
||||
SrvrCtrlRendererStateUpdated,
|
||||
SrvrCtrlAddRenderer,
|
||||
SrvrCtrlUpdateRenderer,
|
||||
SrvrCtrlRemoveRenderer,
|
||||
SrvrCtrlActiveRendererChanged,
|
||||
SrvrCtrlVolumeChanged,
|
||||
SrvrCtrlLoopModeSet,
|
||||
SrvrCtrlVolumeMuted,
|
||||
SrvrCtrlMaxAudioQualityChanged,
|
||||
SrvrCtrlFileAudioQualityChanged,
|
||||
SrvrCtrlDeviceAudioQualityChanged,
|
||||
}
|
||||
|
||||
impl QueueEventType {
|
||||
@@ -37,10 +50,52 @@ impl QueueEventType {
|
||||
"MESSAGE_TYPE_SRVR_CTRL_QUEUE_TRACKS_ADDED_FROM_AUTOPLAY"
|
||||
}
|
||||
Self::SrvrCtrlQueueErrorMessage => "MESSAGE_TYPE_SRVR_CTRL_QUEUE_ERROR_MESSAGE",
|
||||
Self::SrvrCtrlSessionState => "MESSAGE_TYPE_SRVR_CTRL_SESSION_STATE",
|
||||
Self::SrvrCtrlRendererStateUpdated => "MESSAGE_TYPE_SRVR_CTRL_RENDERER_STATE_UPDATED",
|
||||
Self::SrvrCtrlAddRenderer => "MESSAGE_TYPE_SRVR_CTRL_ADD_RENDERER",
|
||||
Self::SrvrCtrlUpdateRenderer => "MESSAGE_TYPE_SRVR_CTRL_UPDATE_RENDERER",
|
||||
Self::SrvrCtrlRemoveRenderer => "MESSAGE_TYPE_SRVR_CTRL_REMOVE_RENDERER",
|
||||
Self::SrvrCtrlActiveRendererChanged => {
|
||||
"MESSAGE_TYPE_SRVR_CTRL_ACTIVE_RENDERER_CHANGED"
|
||||
}
|
||||
Self::SrvrCtrlVolumeChanged => "MESSAGE_TYPE_SRVR_CTRL_VOLUME_CHANGED",
|
||||
Self::SrvrCtrlLoopModeSet => "MESSAGE_TYPE_SRVR_CTRL_LOOP_MODE_SET",
|
||||
Self::SrvrCtrlVolumeMuted => "MESSAGE_TYPE_SRVR_CTRL_VOLUME_MUTED",
|
||||
Self::SrvrCtrlMaxAudioQualityChanged => {
|
||||
"MESSAGE_TYPE_SRVR_CTRL_MAX_AUDIO_QUALITY_CHANGED"
|
||||
}
|
||||
Self::SrvrCtrlFileAudioQualityChanged => {
|
||||
"MESSAGE_TYPE_SRVR_CTRL_FILE_AUDIO_QUALITY_CHANGED"
|
||||
}
|
||||
Self::SrvrCtrlDeviceAudioQualityChanged => {
|
||||
"MESSAGE_TYPE_SRVR_CTRL_DEVICE_AUDIO_QUALITY_CHANGED"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QueueEventType {
|
||||
/// Returns true for session management events that should NOT go through
|
||||
/// the queue reducer.
|
||||
pub const fn is_session_management(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::SrvrCtrlSessionState
|
||||
| Self::SrvrCtrlRendererStateUpdated
|
||||
| Self::SrvrCtrlAddRenderer
|
||||
| Self::SrvrCtrlUpdateRenderer
|
||||
| Self::SrvrCtrlRemoveRenderer
|
||||
| Self::SrvrCtrlActiveRendererChanged
|
||||
| Self::SrvrCtrlVolumeChanged
|
||||
| Self::SrvrCtrlLoopModeSet
|
||||
| Self::SrvrCtrlVolumeMuted
|
||||
| Self::SrvrCtrlMaxAudioQualityChanged
|
||||
| Self::SrvrCtrlFileAudioQualityChanged
|
||||
| Self::SrvrCtrlDeviceAudioQualityChanged
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct QueueServerEvent {
|
||||
pub event_type: QueueEventType,
|
||||
|
||||
@@ -37,6 +37,13 @@ pub enum QConnectMessageType {
|
||||
MessageTypeCtrlSrvrSetAutoplayMode = 78,
|
||||
MessageTypeCtrlSrvrAutoplayLoadTracks = 79,
|
||||
MessageTypeCtrlSrvrAutoplayRemoveTracks = 80,
|
||||
MessageTypeSrvrCtrlSessionState = 81,
|
||||
MessageTypeSrvrCtrlRendererStateUpdated = 82,
|
||||
MessageTypeSrvrCtrlAddRenderer = 83,
|
||||
MessageTypeSrvrCtrlUpdateRenderer = 84,
|
||||
MessageTypeSrvrCtrlRemoveRenderer = 85,
|
||||
MessageTypeSrvrCtrlActiveRendererChanged = 86,
|
||||
MessageTypeSrvrCtrlVolumeChanged = 87,
|
||||
MessageTypeSrvrCtrlQueueErrorMessage = 88,
|
||||
MessageTypeSrvrCtrlQueueCleared = 89,
|
||||
MessageTypeSrvrCtrlQueueState = 90,
|
||||
@@ -46,6 +53,11 @@ pub enum QConnectMessageType {
|
||||
MessageTypeSrvrCtrlQueueTracksRemoved = 94,
|
||||
MessageTypeSrvrCtrlQueueTracksReordered = 95,
|
||||
MessageTypeSrvrCtrlShuffleModeSet = 96,
|
||||
MessageTypeSrvrCtrlLoopModeSet = 97,
|
||||
MessageTypeSrvrCtrlVolumeMuted = 98,
|
||||
MessageTypeSrvrCtrlMaxAudioQualityChanged = 99,
|
||||
MessageTypeSrvrCtrlFileAudioQualityChanged = 100,
|
||||
MessageTypeSrvrCtrlDeviceAudioQualityChanged = 101,
|
||||
MessageTypeSrvrCtrlAutoplayModeSet = 102,
|
||||
MessageTypeSrvrCtrlAutoplayTracksLoaded = 103,
|
||||
MessageTypeSrvrCtrlAutoplayTracksRemoved = 104,
|
||||
@@ -709,6 +721,145 @@ pub struct RendererMaxAudioQualityChangedMessage {
|
||||
pub network_type: Option<i32>,
|
||||
}
|
||||
|
||||
// --- SRVR_CTRL session management messages (server → controller) ---
|
||||
|
||||
/// Type 81: Session state after joining
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlSessionStateMessage {
|
||||
#[prost(bytes = "vec", optional, tag = "1")]
|
||||
pub session_uuid: Option<Vec<u8>>,
|
||||
#[prost(int32, optional, tag = "2")]
|
||||
pub active_renderer_id: Option<i32>,
|
||||
#[prost(message, optional, tag = "3")]
|
||||
pub queue_version: Option<QueueVersionRef>,
|
||||
#[prost(int32, optional, tag = "4")]
|
||||
pub playing_state: Option<i32>,
|
||||
#[prost(int32, optional, tag = "5")]
|
||||
pub loop_mode: Option<i32>,
|
||||
}
|
||||
|
||||
/// Nested player state for CtrlRendererStateUpdatedMessage
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlRendererPlayerState {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub playing_state: Option<i32>,
|
||||
#[prost(int32, optional, tag = "2")]
|
||||
pub buffer_state: Option<i32>,
|
||||
#[prost(message, optional, tag = "3")]
|
||||
pub current_position: Option<PlaybackPositionMessage>,
|
||||
#[prost(uint32, optional, tag = "4")]
|
||||
pub duration: Option<u32>,
|
||||
#[prost(int32, optional, tag = "5")]
|
||||
pub current_queue_item_id: Option<i32>,
|
||||
}
|
||||
|
||||
/// Type 82: Renderer state updated (controller view)
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlRendererStateUpdatedMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub renderer_id: Option<i32>,
|
||||
#[prost(int32, optional, tag = "2")]
|
||||
pub status: Option<i32>,
|
||||
#[prost(message, optional, tag = "3")]
|
||||
pub player_state: Option<CtrlRendererPlayerState>,
|
||||
}
|
||||
|
||||
/// Type 83: New renderer added to session
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlAddRendererMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub renderer_id: Option<i32>,
|
||||
#[prost(message, optional, tag = "2")]
|
||||
pub device_info: Option<DeviceInfoMessage>,
|
||||
}
|
||||
|
||||
/// Type 84: Renderer info updated
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlUpdateRendererMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub renderer_id: Option<i32>,
|
||||
#[prost(message, optional, tag = "2")]
|
||||
pub device_info: Option<DeviceInfoMessage>,
|
||||
}
|
||||
|
||||
/// Type 85: Renderer removed from session
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlRemoveRendererMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub renderer_id: Option<i32>,
|
||||
}
|
||||
|
||||
/// Type 86: Active renderer changed
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlActiveRendererChangedMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub active_renderer_id: Option<i32>,
|
||||
}
|
||||
|
||||
/// Type 87: Volume changed on a renderer (controller view)
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlVolumeChangedMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub renderer_id: Option<i32>,
|
||||
#[prost(uint32, optional, tag = "2")]
|
||||
pub volume: Option<u32>,
|
||||
}
|
||||
|
||||
/// Type 97: Loop mode set
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlLoopModeSetMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub loop_mode: Option<i32>,
|
||||
}
|
||||
|
||||
/// Type 98: Volume muted (controller view)
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlVolumeMutedMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub renderer_id: Option<i32>,
|
||||
#[prost(bool, optional, tag = "2")]
|
||||
pub value: Option<bool>,
|
||||
}
|
||||
|
||||
/// Type 99: Max audio quality changed (controller view)
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlMaxAudioQualityChangedMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub renderer_id: Option<i32>,
|
||||
#[prost(int32, optional, tag = "2")]
|
||||
pub max_audio_quality: Option<i32>,
|
||||
#[prost(int32, optional, tag = "3")]
|
||||
pub network_type: Option<i32>,
|
||||
}
|
||||
|
||||
/// Type 100: File audio quality changed (controller view)
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlFileAudioQualityChangedMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub renderer_id: Option<i32>,
|
||||
#[prost(uint32, optional, tag = "2")]
|
||||
pub sampling_rate: Option<u32>,
|
||||
#[prost(uint32, optional, tag = "3")]
|
||||
pub bit_depth: Option<u32>,
|
||||
#[prost(uint32, optional, tag = "4")]
|
||||
pub nb_channels: Option<u32>,
|
||||
#[prost(int32, optional, tag = "5")]
|
||||
pub audio_quality: Option<i32>,
|
||||
}
|
||||
|
||||
/// Type 101: Device audio quality changed (controller view)
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct CtrlDeviceAudioQualityChangedMessage {
|
||||
#[prost(int32, optional, tag = "1")]
|
||||
pub renderer_id: Option<i32>,
|
||||
#[prost(uint32, optional, tag = "2")]
|
||||
pub sampling_rate: Option<u32>,
|
||||
#[prost(uint32, optional, tag = "3")]
|
||||
pub bit_depth: Option<u32>,
|
||||
#[prost(uint32, optional, tag = "4")]
|
||||
pub nb_channels: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct QConnectMessages {
|
||||
#[prost(uint64, optional, tag = "1")]
|
||||
@@ -791,6 +942,20 @@ pub struct QConnectMessage {
|
||||
pub ctrl_srvr_autoplay_load_tracks: Option<AutoplayLoadTracksMessage>,
|
||||
#[prost(message, optional, tag = "80")]
|
||||
pub ctrl_srvr_autoplay_remove_tracks: Option<AutoplayRemoveTracksMessage>,
|
||||
#[prost(message, optional, tag = "81")]
|
||||
pub srvr_ctrl_session_state: Option<CtrlSessionStateMessage>,
|
||||
#[prost(message, optional, tag = "82")]
|
||||
pub srvr_ctrl_renderer_state_updated: Option<CtrlRendererStateUpdatedMessage>,
|
||||
#[prost(message, optional, tag = "83")]
|
||||
pub srvr_ctrl_add_renderer: Option<CtrlAddRendererMessage>,
|
||||
#[prost(message, optional, tag = "84")]
|
||||
pub srvr_ctrl_update_renderer: Option<CtrlUpdateRendererMessage>,
|
||||
#[prost(message, optional, tag = "85")]
|
||||
pub srvr_ctrl_remove_renderer: Option<CtrlRemoveRendererMessage>,
|
||||
#[prost(message, optional, tag = "86")]
|
||||
pub srvr_ctrl_active_renderer_changed: Option<CtrlActiveRendererChangedMessage>,
|
||||
#[prost(message, optional, tag = "87")]
|
||||
pub srvr_ctrl_volume_changed: Option<CtrlVolumeChangedMessage>,
|
||||
#[prost(message, optional, tag = "88")]
|
||||
pub srvr_ctrl_queue_error_message: Option<QueueErrorMessage>,
|
||||
#[prost(message, optional, tag = "89")]
|
||||
@@ -809,6 +974,16 @@ pub struct QConnectMessage {
|
||||
pub srvr_ctrl_queue_tracks_reordered: Option<QueueTracksReorderedMessage>,
|
||||
#[prost(message, optional, tag = "96")]
|
||||
pub srvr_ctrl_shuffle_mode_set: Option<ShuffleModeSetMessage>,
|
||||
#[prost(message, optional, tag = "97")]
|
||||
pub srvr_ctrl_loop_mode_set: Option<CtrlLoopModeSetMessage>,
|
||||
#[prost(message, optional, tag = "98")]
|
||||
pub srvr_ctrl_volume_muted: Option<CtrlVolumeMutedMessage>,
|
||||
#[prost(message, optional, tag = "99")]
|
||||
pub srvr_ctrl_max_audio_quality_changed: Option<CtrlMaxAudioQualityChangedMessage>,
|
||||
#[prost(message, optional, tag = "100")]
|
||||
pub srvr_ctrl_file_audio_quality_changed: Option<CtrlFileAudioQualityChangedMessage>,
|
||||
#[prost(message, optional, tag = "101")]
|
||||
pub srvr_ctrl_device_audio_quality_changed: Option<CtrlDeviceAudioQualityChangedMessage>,
|
||||
#[prost(message, optional, tag = "102")]
|
||||
pub srvr_ctrl_autoplay_mode_set: Option<AutoplayModeSetMessage>,
|
||||
#[prost(message, optional, tag = "103")]
|
||||
|
||||
Reference in new issue
Block a user