use std::str::FromStr;
use cyberex_macro::EnumRenames;
use derive_more::From;
use serde::Deserialize;
use serde_with::{DisplayFromStr, serde_as};
type Ofloat32 = ordered_float::OrderedFloat<f32>;
#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub struct OrderedFloat32(Ofloat32);
impl Into<f32> for OrderedFloat32 {
fn into(self) -> f32 {
self.as_f32()
}
}
impl From<f32> for OrderedFloat32 {
fn from(value: f32) -> Self {
Self(ordered_float::OrderedFloat::from(value))
}
}
impl From<u32> for OrderedFloat32 {
fn from(value: u32) -> Self {
Self(ordered_float::OrderedFloat::from(value as f32))
}
}
impl OrderedFloat32 {
pub fn new(value: impl Into<Ofloat32>) -> Self {
Self(value.into())
}
pub fn as_f32(&self) -> f32 {
self.0.into_inner()
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, From)]
#[serde(untagged)]
pub enum Actions {
Multi(Vec<Action>),
Uni(Action),
}
impl Actions {
pub fn as_muti(&self) -> Vec<Action> {
match self {
Actions::Multi(actions) => actions.to_owned(),
Actions::Uni(action) => vec![action.clone()],
}
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, From, EnumRenames)]
#[serde(tag = "command")]
pub enum MediaCtrl {
#[serde(rename = "playbackCtrl")]
PlaybackCtrl(PlaybackCtrl),
}
impl MediaCtrl {
pub fn command(&self) -> &str {
self.rename()
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct PlaybackCtrl {
#[serde(rename = "params")]
action: Actions,
}
impl PlaybackCtrl {
pub fn new(action: impl Into<Action>) -> Self {
Self {
action: Actions::from(action.into()),
}
}
pub fn new_muti_action<T, I>(actions: T) -> Self
where
T: IntoIterator<Item = I>,
I: Into<Action>,
{
let actions = actions.into_iter().map(|v| v.into()).collect::<Vec<Action>>();
Self {
action: Actions::from(actions),
}
}
pub fn as_muti_action(&self) -> Vec<Action> {
self.action.as_muti()
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, From)]
#[serde(rename_all = "lowercase")]
#[serde(tag = "action")]
pub enum Action {
Drag(Darg),
Speed(Speed),
Back(Back),
Pause,
Resume,
}
#[serde_as]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Darg {
#[serde_as(as = "DisplayFromStr")]
range: DargRange,
}
impl Darg {
pub fn new(range: DargRange) -> Self {
Self { range }
}
pub fn range(&self) -> &DargRange {
&self.range
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct DargRange {
from: i64,
to: i64,
}
impl DargRange {
pub fn new(from_sec: i64, to_sec: i64) -> Self {
Self {
from: from_sec,
to: to_sec,
}
}
pub fn range_from(&self) -> i64 {
self.from as _
}
pub fn range_to(&self) -> i64 {
self.to
}
}
impl FromStr for DargRange {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(from) = s.parse() {
return Ok(Self { from, to: 0 });
}
match s.split_once("-") {
Some((from_str, to_str)) => {
let from = from_str.parse()?;
let to = to_str.parse().unwrap_or(0);
Ok(Self { from, to })
},
None => {
let from = s.parse()?;
Ok(Self { from, to: 0 })
},
}
}
}
#[serde_as]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Speed {
#[serde_as(as = "DisplayFromStr")]
#[serde(rename = "speed")]
direction: SpeedDirection,
}
impl Speed {
pub fn new(speed: impl Into<SpeedDirection>) -> Self {
let direction = speed.into();
Self { direction }
}
pub fn direction(&self) -> &SpeedDirection {
&self.direction
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum SpeedDirection {
Up(OrderedFloat32),
Down(OrderedFloat32),
}
impl SpeedDirection {
pub fn new(speed: impl Into<OrderedFloat32>) -> Self {
let number: f32 = speed.into().into();
if number > 0.0 {
Self::up(number)
} else {
Self::down(number.abs())
}
}
pub fn up(speed: impl Into<OrderedFloat32>) -> Self {
Self::Up(speed.into())
}
pub fn down(speed: impl Into<OrderedFloat32>) -> Self {
Self::Down(speed.into())
}
pub fn as_f32(&self) -> f32 {
self.as_order_f32().into_inner()
}
fn as_order_f32(&self) -> Ofloat32 {
match self {
SpeedDirection::Up(v) => v.0,
SpeedDirection::Down(v) => Ofloat32::from(-v.as_f32()),
}
}
}
impl FromStr for SpeedDirection {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let number: f32 = s.parse()?;
Ok(Self::new(number))
}
}
#[serde_as]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Back {
#[serde_as(as = "DisplayFromStr")]
speed: usize,
}
impl Back {
pub fn new(speed: usize) -> Self {
Self { speed }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_case_mediactrl_drag_deserilize() {
assert_eq!(
serde_json::from_str::<MediaCtrl>(
r#"{"command":"playbackCtrl","params":{"action":"drag","range":"200"}}"#,
)
.unwrap(),
PlaybackCtrl::new(Darg::new(DargRange::new(200, 0))).into()
);
assert_eq!(
serde_json::from_str::<MediaCtrl>(
r#"{"command":"playbackCtrl","params":{"action":"drag","range":"200-400"}}"#,
)
.unwrap(),
PlaybackCtrl::new(Darg::new(DargRange::new(200, 400))).into()
);
assert_eq!(
serde_json::from_str::<MediaCtrl>(
r#"{"command":"playbackCtrl","params":{"action":"drag","range":"-400"}}"#,
)
.unwrap(),
PlaybackCtrl::new(Darg::new(DargRange::new(-400, 0))).into()
);
}
#[test]
fn test_case_mediactrl_speed_deserilize() {
assert_eq!(
serde_json::from_str::<MediaCtrl>(
r#"{"command":"playbackCtrl","params":[{"action":"speed","speed":"2"}]}"#
)
.unwrap(),
PlaybackCtrl::new_muti_action([Speed::new(SpeedDirection::up(2))]).into()
);
assert_eq!(
serde_json::from_str::<MediaCtrl>(
r#"{"command":"playbackCtrl","params":[{"action":"speed","speed":"2.1"}]}"#
)
.unwrap(),
PlaybackCtrl::new_muti_action([Speed::new(SpeedDirection::new(2.1))]).into()
);
assert_eq!(
serde_json::from_str::<MediaCtrl>(r#"{"command":"playbackCtrl","params":{"action":"speed","speed":"2"}}"#)
.unwrap(),
PlaybackCtrl::new(Speed::new(SpeedDirection::up(2))).into()
);
assert_eq!(
serde_json::from_str::<MediaCtrl>(
r#"{"command":"playbackCtrl","params":{"action":"speed","speed":"-2"}}"#,
)
.unwrap(),
PlaybackCtrl::new(Speed::new(SpeedDirection::down(2))).into()
);
assert_eq!(
serde_json::from_str::<MediaCtrl>(
r#"{"command":"playbackCtrl","params":{"action":"speed","speed":"-2.5"}}"#,
)
.unwrap(),
PlaybackCtrl::new(Speed::new(SpeedDirection::new(-2.5))).into()
);
}
#[test]
fn test_case_mediactrl_other_deserilize() {
assert_eq!(
serde_json::from_str::<MediaCtrl>(r#"{"command":"playbackCtrl","params":{"action":"pause"}}"#).unwrap(),
PlaybackCtrl::new(Action::Pause).into()
);
assert_eq!(
serde_json::from_str::<MediaCtrl>(r#"{"command":"playbackCtrl","params":{"action":"resume"}}"#,).unwrap(),
PlaybackCtrl::new(Action::Resume).into()
);
assert_eq!(
serde_json::from_str::<MediaCtrl>(
r#"{"command":"playbackCtrl","params":{"action":"back", "speed": "1"}}"#,
)
.unwrap(),
PlaybackCtrl::new(Action::Back(Back::new(1))).into()
);
}
#[test]
fn test_case_speed_direction() {
assert_eq!(SpeedDirection::new(1.0).as_order_f32(), Ofloat32::from(1.0));
assert_eq!(SpeedDirection::up(1.0).as_order_f32(), Ofloat32::from(1.0));
assert_eq!(SpeedDirection::new(-1.2).as_order_f32(), Ofloat32::from(-1.2));
assert_eq!(SpeedDirection::down(1.2).as_order_f32(), Ofloat32::from(-1.2));
}
#[test]
fn test_case_order_float() {
let f = OrderedFloat32::new(4.0);
let result = f.as_f32() / 3.0;
assert_eq!(result, 4.0 / 3.0);
}
}