use std::{
collections::{HashMap, HashSet},
convert::Infallible,
fmt::{self, Write},
io::Read,
str::FromStr,
time::Duration,
};
use base64::prelude::*;
use flate2::{
read::{DeflateDecoder, DeflateEncoder},
Compression,
};
use protobuf::Message;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_repr::{Deserialize_repr, Serialize_repr};
use serde_with::{
json::JsonString, serde_as, DeserializeFromStr, DisplayFromStr, DurationSeconds,
NoneAsEmptyString, SerializeDisplay,
};
use uuid::Uuid;
use super::{channel::Ident, protos::queue};
use crate::{error::Error, track::TrackId, util::ToF32};
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Contents {
#[serde(rename = "APP")]
pub ident: Ident,
pub headers: Headers,
#[serde_as(as = "JsonString")]
pub body: Body,
}
impl fmt::Display for Contents {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:<16}", self.body.message_type())
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Headers {
pub from: DeviceId,
pub destination: Option<DeviceId>,
}
impl fmt::Display for Headers {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "from {}", self.from)?;
if let Some(destination) = &self.destination {
write!(f, " to {destination}")?;
}
Ok(())
}
}
#[derive(
Clone, Debug, SerializeDisplay, DeserializeFromStr, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
pub enum DeviceId {
Uuid(Uuid),
Other(String),
}
impl Default for DeviceId {
fn default() -> Self {
Self::Uuid(crate::Uuid::fast_v4().into())
}
}
impl From<Uuid> for DeviceId {
fn from(uuid: Uuid) -> Self {
Self::Uuid(uuid)
}
}
impl FromStr for DeviceId {
type Err = Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let device = match Uuid::try_parse(s) {
Ok(uuid) => Self::from(uuid),
Err(_) => Self::Other(s.to_owned()),
};
Ok(device)
}
}
impl fmt::Display for DeviceId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Uuid(uuid) => write!(f, "{uuid}"),
Self::Other(s) => write!(f, "{s}"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Body {
Acknowledgement {
message_id: String,
acknowledgement_id: String,
},
Close {
message_id: String,
},
Connect {
message_id: String,
from: DeviceId,
offer_id: Option<String>,
},
ConnectionOffer {
message_id: String,
from: DeviceId,
device_name: String,
device_type: DeviceType,
},
DiscoveryRequest {
message_id: String,
from: DeviceId,
discovery_session: String,
},
PlaybackProgress {
message_id: String,
track: QueueItem,
quality: AudioQuality,
duration: Duration,
buffered: Duration,
progress: Option<Percentage>,
volume: Percentage,
is_playing: bool,
is_shuffle: bool,
repeat_mode: RepeatMode,
},
PublishQueue {
message_id: String,
queue: queue::List,
},
Ping {
message_id: String,
},
Ready {
message_id: String,
},
RefreshQueue {
message_id: String,
},
Skip {
message_id: String,
queue_id: Option<String>,
track: Option<QueueItem>,
progress: Option<Percentage>,
should_play: Option<bool>,
set_repeat_mode: Option<RepeatMode>,
set_shuffle: Option<bool>,
set_volume: Option<Percentage>,
},
Status {
message_id: String,
command_id: String,
status: Status,
},
Stop {
message_id: String,
},
}
impl Body {
#[must_use]
pub fn message_type(&self) -> MessageType {
match self {
Self::Acknowledgement { .. } => MessageType::Acknowledgement,
Self::Close { .. } => MessageType::Close,
Self::Connect { .. } => MessageType::Connect,
Self::ConnectionOffer { .. } => MessageType::ConnectionOffer,
Self::DiscoveryRequest { .. } => MessageType::DiscoveryRequest,
Self::PlaybackProgress { .. } => MessageType::PlaybackProgress,
Self::PublishQueue { .. } => MessageType::PublishQueue,
Self::Ping { .. } => MessageType::Ping,
Self::Ready { .. } => MessageType::Ready,
Self::RefreshQueue { .. } => MessageType::RefreshQueue,
Self::Skip { .. } => MessageType::Skip,
Self::Status { .. } => MessageType::Status,
Self::Stop { .. } => MessageType::Stop,
}
}
#[must_use]
pub fn message_id(&self) -> &str {
match self {
Self::Acknowledgement { message_id, .. }
| Self::Close { message_id, .. }
| Self::Connect { message_id, .. }
| Self::ConnectionOffer { message_id, .. }
| Self::DiscoveryRequest { message_id, .. }
| Self::PlaybackProgress { message_id, .. }
| Self::PublishQueue { message_id, .. }
| Self::Ping { message_id, .. }
| Self::Ready { message_id, .. }
| Self::RefreshQueue { message_id, .. }
| Self::Skip { message_id, .. }
| Self::Status { message_id, .. }
| Self::Stop { message_id, .. } => message_id,
}
}
}
#[derive(
Copy,
Clone,
Debug,
Default,
Hash,
Serialize_repr,
Deserialize_repr,
PartialOrd,
Ord,
PartialEq,
Eq,
)]
#[repr(u64)]
pub enum Status {
OK = 0,
#[default]
Error = 1,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Status::OK => write!(f, "Ok"),
Status::Error => write!(f, "Err"),
}
}
}
#[derive(
Copy,
Clone,
Debug,
Default,
Hash,
Serialize_repr,
Deserialize_repr,
PartialOrd,
Ord,
PartialEq,
Eq,
)]
#[repr(i64)]
pub enum RepeatMode {
#[default]
None = 0,
All = 1,
One = 2,
Unrecognized = -1,
}
impl fmt::Display for RepeatMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RepeatMode::None => write!(f, "None"),
RepeatMode::All => write!(f, "All"),
RepeatMode::One => write!(f, "One"),
RepeatMode::Unrecognized => write!(f, "Unknown"),
}
}
}
#[expect(clippy::doc_markdown)]
#[derive(
Copy,
Clone,
Debug,
Default,
Hash,
Serialize_repr,
Deserialize_repr,
PartialEq,
Eq,
PartialOrd,
Ord,
)]
#[repr(i64)]
pub enum AudioQuality {
Basic = 0,
#[default]
Standard = 1,
High = 2,
#[expect(clippy::doc_markdown)]
Lossless = 3,
Unknown = -1,
}
impl fmt::Display for AudioQuality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AudioQuality::Basic => write!(f, "Basic"),
AudioQuality::Standard => write!(f, "Standard"),
AudioQuality::High => write!(f, "High Quality"),
AudioQuality::Lossless => write!(f, "High Fidelity"),
AudioQuality::Unknown => write!(f, "Unknown"),
}
}
}
impl FromStr for AudioQuality {
type Err = Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let variant = match s {
"low" => AudioQuality::Basic,
"standard" => AudioQuality::Standard,
"high" => AudioQuality::High,
"lossless" => AudioQuality::Lossless,
_ => AudioQuality::Unknown,
};
Ok(variant)
}
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialOrd)]
pub struct Percentage(f64);
impl Percentage {
pub const ZERO: Self = Self(0.0);
pub const ONE_HUNDRED: Self = Self(1.0);
#[must_use]
pub fn from_ratio_f32(ratio: f32) -> Self {
Self(ratio.into())
}
#[must_use]
pub fn from_ratio_f64(ratio: f64) -> Self {
Self(ratio)
}
#[must_use]
pub fn from_percent_f32(percent: f32) -> Self {
Self(f64::from(percent) / 100.0)
}
#[must_use]
pub fn from_percent_f64(percent: f64) -> Self {
Self(percent / 100.0)
}
#[must_use]
pub fn as_ratio_f32(&self) -> f32 {
self.0.to_f32_lossy()
}
#[must_use]
pub fn as_ratio_f64(&self) -> f64 {
self.0
}
#[must_use]
pub fn as_percent_f32(&self) -> f32 {
self.0.to_f32_lossy() * 100.0
}
#[must_use]
pub fn as_percent_f64(&self) -> f64 {
self.0 * 100.0
}
}
impl PartialEq for Percentage {
fn eq(&self, other: &Self) -> bool {
(self.0 - other.0).abs() < f64::EPSILON
}
}
impl fmt::Display for Percentage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}%", self.as_percent_f32())
}
}
#[derive(
Clone, Debug, SerializeDisplay, DeserializeFromStr, PartialOrd, Ord, PartialEq, Eq, Hash,
)]
pub struct QueueItem {
pub queue_id: String,
pub track_id: TrackId,
pub position: usize,
}
impl QueueItem {
const SEPARATOR: char = '-';
}
impl fmt::Display for QueueItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}{}",
self.queue_id,
Self::SEPARATOR,
self.track_id,
Self::SEPARATOR,
self.position
)
}
}
impl FromStr for QueueItem {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut parts = s.split(Self::SEPARATOR);
let mut queue_id = String::new();
for i in 0..5 {
match parts.next() {
Some(part) => {
write!(queue_id, "{part}")?;
if i < 4 {
write!(queue_id, "{}", Self::SEPARATOR)?;
}
}
None => {
return Err(Self::Err::invalid_argument(format!(
"list element string slice should hold five `queue_id` parts, found {i}"
)))
}
}
}
if let Err(e) = Uuid::try_parse(&queue_id) {
return Err(Self::Err::invalid_argument(format!("queue id: {e}")));
}
let track_id = parts.next().ok_or_else(|| {
Self::Err::invalid_argument(
"list element string slice should hold `track_id` part".to_string(),
)
})?;
let track_id = if track_id.is_empty() {
if let Some(user_uploaded_id) = parts.next() {
let negative_track_id = format!("-{user_uploaded_id}");
negative_track_id.parse::<TrackId>()?
} else {
return Err(Self::Err::invalid_argument(
"user-uploaded track id should not be empty".to_string(),
));
}
} else {
track_id.parse::<TrackId>()?
};
let position = parts.next().ok_or_else(|| {
Self::Err::invalid_argument(
"list element string slice should hold `position` part".to_string(),
)
})?;
let position = position.parse::<usize>()?;
Ok(Self {
queue_id,
track_id,
position,
})
}
}
impl Serialize for Body {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
let wire_body = WireBody::from(self.clone());
wire_body.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Body {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
let wire_body = WireBody::deserialize(deserializer)?;
Self::try_from(wire_body).map_err(serde::de::Error::custom)
}
}
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct WireBody {
message_id: String,
message_type: MessageType,
protocol_version: String,
#[serde_as(as = "DisplayFromStr")]
payload: Payload,
clock: HashMap<String, serde_json::Value>,
}
#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")]
pub enum MessageType {
#[serde(rename = "ack")]
Acknowledgement,
Close,
Connect,
ConnectionOffer,
DiscoveryRequest,
PlaybackProgress,
PublishQueue,
Ping,
Ready,
RefreshQueue,
Skip,
Status,
Stop,
}
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum Payload {
#[serde(rename_all = "camelCase")]
PlaybackProgress {
queue_id: String,
element_id: QueueItem,
#[serde_as(as = "DurationSeconds<u64>")]
duration: Duration,
#[serde_as(as = "DurationSeconds<u64>")]
buffered: Duration,
progress: Option<Percentage>,
volume: Percentage,
quality: AudioQuality,
is_playing: bool,
is_shuffle: bool,
repeat_mode: RepeatMode,
},
#[serde(rename_all = "camelCase")]
Acknowledgement {
acknowledgement_id: String,
},
#[serde(rename_all = "camelCase")]
Status {
command_id: String,
status: Status,
},
WithParams {
from: DeviceId,
params: Params,
},
#[serde(rename_all = "camelCase")]
Skip {
queue_id: Option<String>,
element_id: Option<QueueItem>,
progress: Option<Percentage>,
should_play: Option<bool>,
set_repeat_mode: Option<RepeatMode>,
set_shuffle: Option<bool>,
set_volume: Option<Percentage>,
},
String(#[serde_as(as = "NoneAsEmptyString")] Option<String>),
#[serde(skip)]
PublishQueue(queue::List),
}
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase", untagged)]
pub enum Params {
ConnectionOffer {
device_name: String,
device_type: DeviceType,
supported_control_versions: HashSet<String>,
},
DiscoveryRequest {
discovery_session: String,
},
Connect {
offer_id: Option<String>,
},
}
#[serde_as]
#[derive(
Copy,
Clone,
Default,
PartialEq,
Eq,
PartialOrd,
Debug,
SerializeDisplay,
DeserializeFromStr,
Hash,
)]
pub enum DeviceType {
Desktop,
Mobile,
Tablet,
#[default]
Web,
Unknown,
}
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DeviceType::Desktop => write!(f, "desktop"),
DeviceType::Mobile => write!(f, "mobile"),
DeviceType::Tablet => write!(f, "tablet"),
DeviceType::Web => write!(f, "web"),
DeviceType::Unknown => write!(f, "unknown"),
}
}
}
impl FromStr for DeviceType {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"desktop" => Ok(DeviceType::Desktop),
"mobile" => Ok(DeviceType::Mobile),
"tablet" => Ok(DeviceType::Tablet),
"web" => Ok(DeviceType::Web),
_ => Ok(DeviceType::Unknown),
}
}
}
impl fmt::Display for Payload {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut buffer: Vec<u8> = vec![];
if let Payload::PublishQueue(queue) = self {
match queue.write_to_bytes() {
Ok(protobuf) => {
let mut deflater = DeflateEncoder::new(&protobuf[..], Compression::fast());
if let Err(e) = deflater.read_to_end(&mut buffer) {
error!("{e}");
return Err(fmt::Error);
}
}
Err(e) => {
error!("{e}");
return Err(fmt::Error);
}
}
} else {
if let Payload::String(s) = self {
if s.as_ref().map_or(true, String::is_empty) {
return Ok(());
}
}
if let Err(e) = serde_json::to_writer(&mut buffer, self) {
error!("{e}");
return Err(fmt::Error);
}
}
write!(f, "{}", BASE64_STANDARD.encode(buffer))
}
}
impl FromStr for Payload {
type Err = Error;
fn from_str(encoded: &str) -> std::result::Result<Self, Self::Err> {
let decoded = BASE64_STANDARD.decode(encoded)?;
if let Ok(s) = std::str::from_utf8(&decoded) {
if s.is_empty() || s == "{}" {
return Ok(Self::String(None));
}
serde_json::from_str::<Self>(s).map_err(Into::into)
} else {
let mut inflater = DeflateDecoder::new(&decoded[..]);
let mut buffer: Vec<u8> = vec![];
inflater.read_to_end(&mut buffer)?;
if let Ok(queue) = queue::List::parse_from_bytes(&buffer) {
if !queue.id.is_empty() {
return Ok(Self::PublishQueue(queue));
}
}
Err(Self::Err::unimplemented(
"protobuf should match some variant".to_string(),
))
}
}
}
impl WireBody {
const COMMAND_VERSION: &'static str = "com.deezer.remote.command.proto1";
const DISCOVERY_VERSION: &'static str = "com.deezer.remote.discovery.proto1";
const QUEUE_VERSION: &'static str = "com.deezer.remote.queue.proto1";
const SUPPORTED_CONTROL_VERSIONS: [&'static str; 1] = ["1.0.0-beta2"];
#[must_use]
fn supports_control_versions(control_versions: &HashSet<String>) -> bool {
for version in control_versions {
if Self::SUPPORTED_CONTROL_VERSIONS.contains(&version.as_str()) {
return true;
}
}
false
}
#[must_use]
fn supported_protocol_version(&self) -> bool {
matches!(
self.protocol_version.as_ref(),
WireBody::COMMAND_VERSION | WireBody::DISCOVERY_VERSION | WireBody::QUEUE_VERSION
)
}
}
impl From<Body> for WireBody {
#[expect(clippy::too_many_lines)]
fn from(body: Body) -> Self {
let clock: HashMap<String, serde_json::Value> = HashMap::new();
match body {
Body::Acknowledgement {
message_id,
acknowledgement_id,
} => WireBody {
message_id,
message_type: MessageType::Acknowledgement,
protocol_version: Self::COMMAND_VERSION.to_string(),
payload: Payload::Acknowledgement { acknowledgement_id },
clock,
},
Body::Close { message_id } => WireBody {
message_id,
message_type: MessageType::Close,
protocol_version: Self::COMMAND_VERSION.to_string(),
payload: Payload::String(None),
clock,
},
Body::Connect {
message_id,
from,
offer_id,
} => WireBody {
message_id,
message_type: MessageType::Connect,
protocol_version: Self::DISCOVERY_VERSION.to_string(),
payload: Payload::WithParams {
from,
params: Params::Connect { offer_id },
},
clock,
},
Body::ConnectionOffer {
message_id,
from,
device_name,
device_type,
} => WireBody {
message_id,
message_type: MessageType::ConnectionOffer,
protocol_version: Self::DISCOVERY_VERSION.to_string(),
payload: Payload::WithParams {
from,
params: Params::ConnectionOffer {
device_name,
device_type,
supported_control_versions: Self::SUPPORTED_CONTROL_VERSIONS
.into_iter()
.map(ToString::to_string)
.collect(),
},
},
clock,
},
Body::DiscoveryRequest {
message_id,
from,
discovery_session,
} => WireBody {
message_id,
message_type: MessageType::DiscoveryRequest,
protocol_version: Self::DISCOVERY_VERSION.to_string(),
payload: Payload::WithParams {
from,
params: Params::DiscoveryRequest { discovery_session },
},
clock,
},
Body::Ping { message_id } => WireBody {
message_id,
message_type: MessageType::Ping,
protocol_version: Self::COMMAND_VERSION.to_string(),
payload: Payload::String(None),
clock,
},
Body::PlaybackProgress {
message_id,
track,
duration,
buffered,
progress,
volume,
quality,
is_playing,
is_shuffle,
repeat_mode,
} => WireBody {
message_id,
message_type: MessageType::PlaybackProgress,
protocol_version: Self::COMMAND_VERSION.to_string(),
payload: Payload::PlaybackProgress {
queue_id: track.queue_id.clone(),
element_id: track,
duration,
buffered,
progress,
volume,
quality,
is_playing,
is_shuffle,
repeat_mode,
},
clock,
},
Body::PublishQueue { message_id, queue } => WireBody {
message_id,
message_type: MessageType::PublishQueue,
protocol_version: Self::QUEUE_VERSION.to_string(),
payload: Payload::PublishQueue(queue),
clock,
},
Body::Ready { message_id } => WireBody {
message_id,
message_type: MessageType::Ready,
protocol_version: Self::COMMAND_VERSION.to_string(),
payload: Payload::String(None),
clock,
},
Body::RefreshQueue { message_id } => WireBody {
message_id,
message_type: MessageType::RefreshQueue,
protocol_version: Self::QUEUE_VERSION.to_string(),
payload: Payload::String(None),
clock,
},
Body::Skip {
message_id,
queue_id,
track,
progress,
should_play,
set_shuffle,
set_repeat_mode,
set_volume,
} => WireBody {
message_id,
message_type: MessageType::Skip,
protocol_version: Self::COMMAND_VERSION.to_string(),
payload: Payload::Skip {
queue_id,
element_id: track,
progress,
should_play,
set_shuffle,
set_repeat_mode,
set_volume,
},
clock,
},
Body::Status {
message_id,
command_id,
status,
} => WireBody {
message_id,
message_type: MessageType::Status,
protocol_version: Self::COMMAND_VERSION.to_string(),
payload: Payload::Status { command_id, status },
clock,
},
Body::Stop { message_id } => WireBody {
message_id,
message_type: MessageType::Stop,
protocol_version: Self::COMMAND_VERSION.to_string(),
payload: Payload::String(None),
clock,
},
}
}
}
impl TryFrom<WireBody> for Body {
type Error = Error;
#[expect(clippy::too_many_lines)]
fn try_from(wire_body: WireBody) -> std::result::Result<Self, Self::Error> {
if !wire_body.supported_protocol_version() {
warn!("protocol version {} is unknown", wire_body.protocol_version);
}
let message_id = wire_body.message_id;
let message_type = wire_body.message_type;
let body = match message_type {
MessageType::Acknowledgement => {
if let Payload::Acknowledgement { acknowledgement_id } = wire_body.payload {
Self::Acknowledgement {
message_id,
acknowledgement_id,
}
} else {
trace!("{:#?}", wire_body.payload);
return Err(Self::Error::failed_precondition(format!(
"payload should match message type {message_type}"
)));
}
}
MessageType::Close => Body::Close { message_id },
MessageType::Connect => {
if let Payload::WithParams { from, params } = wire_body.payload {
if let Params::Connect { offer_id } = params {
Self::Connect {
message_id,
from,
offer_id,
}
} else {
trace!("{params:#?}");
return Err(Self::Error::failed_precondition(format!(
"params should match message type {message_type}"
)));
}
} else {
trace!("{:#?}", wire_body.payload);
return Err(Self::Error::failed_precondition(format!(
"payload should match message type {message_type}"
)));
}
}
MessageType::ConnectionOffer => {
if let Payload::WithParams { from, params } = wire_body.payload {
if let Params::ConnectionOffer {
device_name,
device_type,
supported_control_versions,
..
} = params
{
if !WireBody::supports_control_versions(&supported_control_versions) {
warn!(
"control versions {:?} are unknown",
supported_control_versions
);
}
Self::ConnectionOffer {
message_id,
from,
device_name,
device_type,
}
} else {
trace!("{params:#?}");
return Err(Self::Error::failed_precondition(format!(
"params should match message type {message_type}"
)));
}
} else {
trace!("{:#?}", wire_body.payload);
return Err(Self::Error::failed_precondition(format!(
"payload should match message type {message_type}"
)));
}
}
MessageType::DiscoveryRequest => {
if let Payload::WithParams { from, params } = wire_body.payload {
if let Params::DiscoveryRequest { discovery_session } = params {
Self::DiscoveryRequest {
message_id,
from,
discovery_session,
}
} else {
trace!("{params:#?}");
return Err(Self::Error::failed_precondition(format!(
"params should match message type {message_type}"
)));
}
} else {
trace!("{:#?}", wire_body.payload);
return Err(Self::Error::failed_precondition(format!(
"payload should match message type {message_type}"
)));
}
}
MessageType::Ping => Self::Ping { message_id },
MessageType::PlaybackProgress => {
if let Payload::PlaybackProgress {
element_id,
buffered,
duration,
progress,
volume,
quality,
is_playing,
is_shuffle,
repeat_mode,
..
} = wire_body.payload
{
Self::PlaybackProgress {
message_id,
track: element_id,
buffered,
duration,
progress,
volume,
quality,
is_playing,
is_shuffle,
repeat_mode,
}
} else {
trace!("{:#?}", wire_body.payload);
return Err(Self::Error::failed_precondition(format!(
"payload should match message type {message_type}"
)));
}
}
MessageType::PublishQueue => {
if let Payload::PublishQueue(queue) = wire_body.payload {
Self::PublishQueue { message_id, queue }
} else {
trace!("{:#?}", wire_body.payload);
return Err(Self::Error::failed_precondition(format!(
"payload should match message type {message_type}"
)));
}
}
MessageType::Ready => Self::Ready { message_id },
MessageType::RefreshQueue => Self::RefreshQueue { message_id },
MessageType::Skip => {
if let Payload::Skip {
queue_id,
element_id,
progress,
should_play,
set_shuffle,
set_repeat_mode,
set_volume,
..
} = wire_body.payload
{
Self::Skip {
message_id,
queue_id,
track: element_id,
progress,
should_play,
set_shuffle,
set_repeat_mode,
set_volume,
}
} else {
trace!("{:#?}", wire_body.payload);
return Err(Self::Error::failed_precondition(format!(
"payload should match message type {message_type}"
)));
}
}
MessageType::Status => {
if let Payload::Status { command_id, status } = wire_body.payload {
Self::Status {
message_id,
command_id,
status,
}
} else {
trace!("{:#?}", wire_body.payload);
return Err(Self::Error::failed_precondition(format!(
"payload should match message type {message_type}"
)));
}
}
MessageType::Stop => Self::Stop { message_id },
};
Ok(body)
}
}
impl fmt::Display for MessageType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}