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 = [
|
||||
"async-trait",
|
||||
"log",
|
||||
"prost",
|
||||
"qconnect-core",
|
||||
"qconnect-protocol",
|
||||
"qconnect-transport-ws",
|
||||
|
||||
@@ -19,3 +19,4 @@ log = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["test-util"] }
|
||||
prost = "0.13"
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user