feat(qconnect-app): emit PlaybackError app event from renderer failure

Decodes PlaybackErrorMessage off the InboundPayloadBytes batch and emits a
PlaybackError app event; the Tauri event sink forwards it to qconnect:event
via its existing catch-all emit.

cc
This commit is contained in:
vicrodh committed 2026-06-01 03:23:03 -06:00
1 parent d865097a4c
commit fea9dbe1d4
5 files changed
+79 -3

No files matched your search

+1
View File
@@ -5309,6 +5309,7 @@ version = "0.1.0"
dependencies = [
"async-trait",
"log",
"prost",
"qconnect-core",
"qconnect-protocol",
"qconnect-transport-ws",
+1
View File
@@ -19,3 +19,4 @@ log = { workspace = true }
[dev-dependencies]
tokio = { workspace = true, features = ["test-util"] }
prost = "0.13"
+64 -3
View File
@@ -9,8 +9,8 @@ use qconnect_core::{
};
use qconnect_protocol::{
build_qconnect_outbound_envelope, build_qconnect_renderer_outbound_envelope,
parse_inbound_event, InboundEnvelope, QueueCommand, QueueCommandType, QueueEventType,
QueueServerEvent, RendererCommandType, RendererReport, RendererReportType,
decode_playback_error, parse_inbound_event, InboundEnvelope, QueueCommand, QueueCommandType,
QueueEventType, QueueServerEvent, RendererCommandType, RendererReport, RendererReportType,
RendererServerCommand,
};
use qconnect_transport_ws::{TransportEvent, WsTransport, WsTransportConfig};
@@ -213,6 +213,20 @@ where
TransportEvent::InboundRendererServerCommand(command) => {
self.apply_renderer_server_command(command).await?;
}
TransportEvent::InboundPayloadBytes { payload, .. } => {
// The batch carries renderer per-track failures as a tag-3
// playback_error on a messages[] entry. The queue/renderer
// decoders ignore it, so decode it here off the raw batch bytes.
if let Some(error) = decode_playback_error(&payload) {
self.sink
.on_event(QconnectAppEvent::PlaybackError {
queue_item_id: error.queue_item_id,
error_type: error.error_type,
queue_version: error.queue_version,
})
.await;
}
}
TransportEvent::Authenticated
| TransportEvent::Subscribed
| TransportEvent::SessionEstablished
@@ -223,7 +237,6 @@ where
| TransportEvent::TransportError { .. }
| TransportEvent::CloudError { .. }
| TransportEvent::InboundFrameDecoded { .. }
| TransportEvent::InboundPayloadBytes { .. }
| TransportEvent::OutboundSent { .. } => {}
}
Ok(())
@@ -1252,6 +1265,54 @@ mod tests {
);
}
#[tokio::test]
async fn inbound_playback_error_emits_playback_error_app_event() {
use prost::Message as _;
use qconnect_protocol::{
ErrorType, PlaybackErrorMessage, QConnectMessage, QConnectMessages, QueueVersionRef,
};
use qconnect_transport_ws::TransportEvent;
let (app, sink, _transport, _events_rx) = build_connected_app().await;
let batch = QConnectMessages {
messages_time: None,
messages_id: None,
messages: vec![QConnectMessage {
message_type: Some(2), // PLAYBACK_ERROR
playback_error: Some(PlaybackErrorMessage {
queue_version: Some(QueueVersionRef {
major: Some(4),
minor: Some(2),
}),
queue_item_id: Some(77),
error_type: Some(5), // NETWORK_ERROR
}),
..Default::default()
}],
};
app.handle_transport_event(TransportEvent::InboundPayloadBytes {
cloud_message_type: 0,
payload: batch.encode_to_vec(),
})
.await
.expect("handle playback-error frame");
let events = sink.snapshot().await;
assert!(
events.iter().any(|event| matches!(
event,
QconnectAppEvent::PlaybackError {
queue_item_id: 77,
error_type: ErrorType::NetworkError,
..
}
)),
"expected a PlaybackError app event for queue item 77 / NetworkError"
);
}
#[tokio::test]
async fn autoplay_growth_triggers_queue_state_resync() {
let (app, sink, transport, _events_rx) = build_connected_app().await;
+8
View File
@@ -31,6 +31,14 @@ pub enum QconnectAppEvent {
action_uuid: String,
},
QueueResyncTriggered,
/// Renderer per-track playback failure (PlaybackErrorMessage). The frontend
/// maps streamable/network/not-found failures on the current item to an
/// auto-skip; other types surface a toast only.
PlaybackError {
queue_item_id: u64,
error_type: qconnect_protocol::ErrorType,
queue_version: Option<qconnect_core::QueueVersion>,
},
/// Session management event from server (types 81-87, 97-101).
/// These don't affect the queue reducer but provide session topology info.
SessionManagementEvent {
+5
View File
@@ -17,6 +17,11 @@ pub use decoder::decode_queue_server_events;
pub use decoder::decode_renderer_server_commands;
pub use error::ProtocolError;
pub use event::{ErrorType, PlaybackErrorEvent, QueueEventType, QueueServerEvent};
// Proto message types needed by downstream crates to construct/encode frames
// (e.g. PlaybackErrorMessage for tests and any future re-encoding path).
pub use queue_command_proto::{
PlaybackErrorMessage, QConnectMessage, QConnectMessages, QueueVersionRef,
};
pub use mapper::{
build_qconnect_outbound_envelope, build_qconnect_renderer_outbound_envelope,
encode_queue_command_batch, encode_renderer_report_batch,