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:
5 files changed
+79
-3
No files matched your search
Generated
+1
@@ -5309,6 +5309,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"log",
|
"log",
|
||||||
|
"prost",
|
||||||
"qconnect-core",
|
"qconnect-core",
|
||||||
"qconnect-protocol",
|
"qconnect-protocol",
|
||||||
"qconnect-transport-ws",
|
"qconnect-transport-ws",
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ log = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["test-util"] }
|
tokio = { workspace = true, features = ["test-util"] }
|
||||||
|
prost = "0.13"
|
||||||
@@ -9,8 +9,8 @@ use qconnect_core::{
|
|||||||
};
|
};
|
||||||
use qconnect_protocol::{
|
use qconnect_protocol::{
|
||||||
build_qconnect_outbound_envelope, build_qconnect_renderer_outbound_envelope,
|
build_qconnect_outbound_envelope, build_qconnect_renderer_outbound_envelope,
|
||||||
parse_inbound_event, InboundEnvelope, QueueCommand, QueueCommandType, QueueEventType,
|
decode_playback_error, parse_inbound_event, InboundEnvelope, QueueCommand, QueueCommandType,
|
||||||
QueueServerEvent, RendererCommandType, RendererReport, RendererReportType,
|
QueueEventType, QueueServerEvent, RendererCommandType, RendererReport, RendererReportType,
|
||||||
RendererServerCommand,
|
RendererServerCommand,
|
||||||
};
|
};
|
||||||
use qconnect_transport_ws::{TransportEvent, WsTransport, WsTransportConfig};
|
use qconnect_transport_ws::{TransportEvent, WsTransport, WsTransportConfig};
|
||||||
@@ -213,6 +213,20 @@ where
|
|||||||
TransportEvent::InboundRendererServerCommand(command) => {
|
TransportEvent::InboundRendererServerCommand(command) => {
|
||||||
self.apply_renderer_server_command(command).await?;
|
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::Authenticated
|
||||||
| TransportEvent::Subscribed
|
| TransportEvent::Subscribed
|
||||||
| TransportEvent::SessionEstablished
|
| TransportEvent::SessionEstablished
|
||||||
@@ -223,7 +237,6 @@ where
|
|||||||
| TransportEvent::TransportError { .. }
|
| TransportEvent::TransportError { .. }
|
||||||
| TransportEvent::CloudError { .. }
|
| TransportEvent::CloudError { .. }
|
||||||
| TransportEvent::InboundFrameDecoded { .. }
|
| TransportEvent::InboundFrameDecoded { .. }
|
||||||
| TransportEvent::InboundPayloadBytes { .. }
|
|
||||||
| TransportEvent::OutboundSent { .. } => {}
|
| TransportEvent::OutboundSent { .. } => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
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]
|
#[tokio::test]
|
||||||
async fn autoplay_growth_triggers_queue_state_resync() {
|
async fn autoplay_growth_triggers_queue_state_resync() {
|
||||||
let (app, sink, transport, _events_rx) = build_connected_app().await;
|
let (app, sink, transport, _events_rx) = build_connected_app().await;
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ pub enum QconnectAppEvent {
|
|||||||
action_uuid: String,
|
action_uuid: String,
|
||||||
},
|
},
|
||||||
QueueResyncTriggered,
|
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).
|
/// Session management event from server (types 81-87, 97-101).
|
||||||
/// These don't affect the queue reducer but provide session topology info.
|
/// These don't affect the queue reducer but provide session topology info.
|
||||||
SessionManagementEvent {
|
SessionManagementEvent {
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ pub use decoder::decode_queue_server_events;
|
|||||||
pub use decoder::decode_renderer_server_commands;
|
pub use decoder::decode_renderer_server_commands;
|
||||||
pub use error::ProtocolError;
|
pub use error::ProtocolError;
|
||||||
pub use event::{ErrorType, PlaybackErrorEvent, QueueEventType, QueueServerEvent};
|
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::{
|
pub use mapper::{
|
||||||
build_qconnect_outbound_envelope, build_qconnect_renderer_outbound_envelope,
|
build_qconnect_outbound_envelope, build_qconnect_renderer_outbound_envelope,
|
||||||
encode_queue_command_batch, encode_renderer_report_batch,
|
encode_queue_command_batch, encode_renderer_report_batch,
|
||||||
|
|||||||
Reference in new issue
Block a user