use std::collections::HashMap; use serde::{de, ser, Deserialize, Serialize}; use serde_json::{json, Value}; use serde_repr::{Deserialize_repr, Serialize_repr}; macro_rules! get_from_map { ($map:expr, $key:expr) => { $map.get($key).ok_or(de::Error::missing_field($key)) }; } #[derive(Debug, PartialEq, Clone)] pub enum MetadataObject { Generic { title: Option, thumbnail_url: Option, custom: Option, }, } impl Serialize for MetadataObject { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match self { MetadataObject::Generic { title, thumbnail_url, custom, } => { let mut map = serde_json::Map::new(); map.insert("type".to_owned(), json!(0u64)); map.insert( "title".to_owned(), match title { Some(t) => Value::String(t.to_owned()), None => Value::Null, }, ); map.insert( "thumbnailUrl".to_owned(), match thumbnail_url { Some(t) => Value::String(t.to_owned()), None => Value::Null, }, ); if let Some(custom) = custom { map.insert("custom".to_owned(), custom.clone()); } map.serialize(serializer) } } } } impl<'de> Deserialize<'de> for MetadataObject { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let mut map = serde_json::Map::deserialize(deserializer)?; let type_ = map .remove("type") .ok_or(de::Error::missing_field("type"))? .as_u64() .ok_or(de::Error::custom("`type` is not an integer"))?; let rest = Value::Object(map); match type_ { 0 => { let title = match rest.get("title") { Some(t) => Some( t.as_str() .ok_or(de::Error::custom("`title` is not a string"))? .to_owned(), ), None => None, }; let thumbnail_url = match rest.get("thumbnailUrl") { Some(t) => Some( t.as_str() .ok_or(de::Error::custom("`thumbnailUrl` is not a string"))? .to_owned(), ), None => None, }; Ok(Self::Generic { title, thumbnail_url, custom: rest .get("custom") .cloned(), }) } _ => Err(de::Error::custom(format!("Unknown metadata type {type_}"))), } } } #[derive(Serialize, Deserialize, Debug)] pub struct PlayMessage { /// The MIME type (video/mp4) pub container: String, // The URL to load (optional) pub url: Option, // The content to load (i.e. a DASH manifest, json content, optional) pub content: Option, // The time to start playing in seconds pub time: Option, // The desired volume (0-1) pub volume: Option, // The factor to multiply playback speed by (defaults to 1.0) pub speed: Option, // HTTP request headers to add to the play request Map pub headers: Option>, pub metadata: Option, } #[derive(Deserialize_repr, Serialize_repr, Debug)] #[repr(u8)] pub enum ContentType { Playlist = 0, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct MediaItem { /// The MIME type (video/mp4) pub container: String, /// The URL to load (optional) pub url: Option, /// The content to load (i.e. a DASH manifest, json content, optional) pub content: Option, /// The time to start playing in seconds pub time: Option, /// The desired volume (0-1) pub volume: Option, /// The factor to multiply playback speed by (defaults to 1.0) pub speed: Option, /// Indicates if the receiver should preload the media item pub cache: Option, /// Indicates how long the item content is presented on screen in seconds #[serde(rename = "showDuration")] pub show_duration: Option, /// HTTP request headers to add to the play request Map pub headers: Option>, pub metadata: Option, } #[derive(Serialize, Debug)] pub struct PlaylistContent { #[serde(rename = "type")] variant: ContentType, pub items: Vec, /// Start position of the first item to play from the playlist pub offset: Option, // int or float? /// The desired volume (0-1) pub volume: Option, /// The factor to multiply playback speed by (defaults to 1.0) pub speed: Option, /// Count of media items should be pre-loaded forward from the current view index #[serde(rename = "forwardCache")] pub forward_cache: Option, /// Count of media items should be pre-loaded backward from the current view index #[serde(rename = "backwardCache")] pub backward_cache: Option, pub metadata: Option, } #[derive(Serialize_repr, Deserialize_repr, Debug)] #[repr(u8)] pub enum PlaybackState { Idle = 0, Playing = 1, Paused = 2, } #[derive(Serialize, Deserialize, Debug)] pub struct PlaybackUpdateMessage { // The time the packet was generated (unix time milliseconds) #[serde(rename = "generationTime")] pub generation_time: u64, // The playback state pub state: PlaybackState, // The current time playing in seconds pub time: Option, // The duration in seconds pub duration: Option, // The playback speed factor pub speed: Option, // The playlist item index currently being played on receiver #[serde(rename = "itemIndex")] pub item_index: Option, } #[derive(Serialize, Debug)] pub struct InitialSenderMessage { #[serde(rename = "displayName")] pub display_name: Option, #[serde(rename = "appName")] pub app_name: Option, #[serde(rename = "appVersion")] pub app_version: Option, } #[derive(Deserialize, Debug)] pub struct InitialReceiverMessage { #[serde(rename = "displayName")] pub display_name: Option, #[serde(rename = "appName")] pub app_name: Option, #[serde(rename = "appVersion")] pub app_version: Option, #[serde(rename = "playData")] pub play_data: Option, } #[derive(Deserialize, Debug)] pub struct PlayUpdateMessage { #[serde(rename = "generationTime")] pub generation_time: Option, #[serde(rename = "playData")] pub play_data: Option, } #[derive(Serialize, Debug)] pub struct SetPlaylistItemMessage { #[serde(rename = "itemIndex")] pub item_index: u64, } #[derive(Deserialize, Debug)] pub enum KeyNames { ArrowLeft, ArrowRight, ArrowUp, ArrowDown, Enter, } impl KeyNames { pub fn all() -> Vec { vec![ "ArrowLeft".to_owned(), "ArrowRight".to_owned(), "ArrowUp".to_owned(), "ArrowDown".to_owned(), "Enter".to_owned(), ] } } #[derive(Debug, PartialEq)] pub enum EventSubscribeObject { MediaItemStart, MediaItemEnd, MediaItemChanged, KeyDown { keys: Vec }, KeyUp { keys: Vec }, } impl Serialize for EventSubscribeObject { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serde_json::Map::new(); let type_val: u64 = match self { EventSubscribeObject::MediaItemStart => 0, EventSubscribeObject::MediaItemEnd => 1, EventSubscribeObject::MediaItemChanged => 2, EventSubscribeObject::KeyDown { .. } => 3, EventSubscribeObject::KeyUp { .. } => 4, }; map.insert("type".to_owned(), json!(type_val)); let keys = match self { EventSubscribeObject::KeyDown { keys } => Some(keys), EventSubscribeObject::KeyUp { keys } => Some(keys), _ => None, }; if let Some(keys) = keys { map.insert("keys".to_owned(), json!(keys)); } map.serialize(serializer) } } impl<'de> Deserialize<'de> for EventSubscribeObject { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let mut map = serde_json::Map::deserialize(deserializer)?; let type_ = map .remove("type") .ok_or(de::Error::missing_field("type"))? .as_u64() .ok_or(de::Error::custom("`type` is not an integer"))?; let rest = Value::Object(map); match type_ { 0 => Ok(Self::MediaItemStart), 1 => Ok(Self::MediaItemEnd), 2 => Ok(Self::MediaItemChanged), 3 | 4 => { let keys = get_from_map!(rest, "keys")? .as_array() .ok_or(de::Error::custom("`type` is not an array"))? .iter() .map(|v| v.as_str().map(|s| s.to_owned())) .collect::>>() .ok_or(de::Error::custom("`type` is not an array of strings"))?; if type_ == 3 { Ok(Self::KeyDown { keys }) } else { Ok(Self::KeyUp { keys }) } } _ => Err(de::Error::custom(format!("Unknown event type {type_}"))), } } } #[derive(Serialize, Deserialize, Debug)] pub struct SubscribeEventMessage { pub event: EventSubscribeObject, } #[derive(Serialize, Deserialize, Debug)] pub struct UnsubscribeEventMessage { pub event: EventSubscribeObject, } #[derive(Debug, PartialEq, Copy, Clone)] #[repr(u8)] pub enum EventType { MediaItemStart = 0, MediaItemEnd = 1, MediaItemChange = 2, KeyDown = 3, KeyUp = 4, } #[derive(Debug, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum EventObject { MediaItem { variant: EventType, item: MediaItem, }, Key { variant: EventType, key: String, repeat: bool, handled: bool, }, } impl Serialize for EventObject { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serde_json::Map::new(); match self { EventObject::MediaItem { variant, item } => { map.insert("type".to_owned(), json!(*variant as u8)); map.insert( "item".to_owned(), serde_json::to_value(item).map_err(ser::Error::custom)?, ); } EventObject::Key { variant, key, repeat, handled, } => { map.insert("type".to_owned(), json!(*variant as u8)); map.insert( "key".to_owned(), serde_json::to_value(key).map_err(ser::Error::custom)?, ); map.insert( "repeat".to_owned(), serde_json::to_value(repeat).map_err(ser::Error::custom)?, ); map.insert( "handled".to_owned(), serde_json::to_value(handled).map_err(ser::Error::custom)?, ); } } map.serialize(serializer) } } impl<'de> Deserialize<'de> for EventObject { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { let mut map = serde_json::Map::deserialize(deserializer)?; let type_ = map .remove("type") .ok_or(de::Error::missing_field("type"))? .as_u64() .ok_or(de::Error::custom("`type` is not an integer"))?; let rest = Value::Object(map); match type_ { #[allow(clippy::manual_range_patterns)] 0 | 1 | 2 => { let variant = match type_ { 0 => EventType::MediaItemStart, 1 => EventType::MediaItemEnd, _ => EventType::MediaItemChange, }; let item = get_from_map!(rest, "item")?; Ok(Self::MediaItem { variant, item: MediaItem::deserialize(item).map_err(de::Error::custom)?, }) } 3 | 4 => { let variant = if type_ == 3 { EventType::KeyDown } else { EventType::KeyUp }; Ok(Self::Key { variant, key: get_from_map!(rest, "key")? .as_str() .ok_or(de::Error::custom("`key` is not a string"))? .to_owned(), repeat: get_from_map!(rest, "repeat")? .as_bool() .ok_or(de::Error::custom("`repeat` is not a bool"))?, handled: get_from_map!(rest, "handled")? .as_bool() .ok_or(de::Error::custom("`handled` is not a bool"))?, }) } _ => Err(de::Error::custom(format!("Unknown event type {type_}"))), } } } #[derive(Serialize, Deserialize, Debug)] pub struct EventMessage { #[serde(rename = "generationTime")] pub generation_time: u64, event: EventObject, } #[cfg(test)] mod tests { use super::*; macro_rules! s { ($s:expr) => { ($s).to_string() }; } #[test] fn serialize_metadata_object() { assert_eq!( &serde_json::to_string(&MetadataObject::Generic { title: Some(s!("abc")), thumbnail_url: Some(s!("def")), custom: Some(serde_json::Value::Null), }) .unwrap(), r#"{"custom":null,"thumbnailUrl":"def","title":"abc","type":0}"# ); assert_eq!( &serde_json::to_string(&MetadataObject::Generic { title: None, thumbnail_url: None, custom: Some(serde_json::Value::Null), }) .unwrap(), r#"{"custom":null,"thumbnailUrl":null,"title":null,"type":0}"# ); assert_eq!( &serde_json::to_string(&MetadataObject::Generic { title: Some(s!("abc")), thumbnail_url: Some(s!("def")), custom: None, }) .unwrap(), r#"{"thumbnailUrl":"def","title":"abc","type":0}"# ); } #[test] fn deserialize_metadata_object() { assert_eq!( serde_json::from_str::( r#"{"type":0,"title":"abc","thumbnailUrl":"def","custom":null}"# ) .unwrap(), MetadataObject::Generic { title: Some(s!("abc")), thumbnail_url: Some(s!("def")), custom: Some(serde_json::Value::Null), } ); assert_eq!( serde_json::from_str::(r#"{"type":0,"custom":null}"#).unwrap(), MetadataObject::Generic { title: None, thumbnail_url: None, custom: Some(serde_json::Value::Null), } ); assert_eq!( serde_json::from_str::(r#"{"type":0}"#).unwrap(), MetadataObject::Generic { title: None, thumbnail_url: None, custom: None, } ); assert!(serde_json::from_str::(r#"{"type":1"#).is_err()); } #[test] fn serialize_event_sub_obj() { assert_eq!( &serde_json::to_string(&EventSubscribeObject::MediaItemStart).unwrap(), r#"{"type":0}"# ); assert_eq!( &serde_json::to_string(&EventSubscribeObject::MediaItemEnd).unwrap(), r#"{"type":1}"# ); assert_eq!( &serde_json::to_string(&EventSubscribeObject::MediaItemChanged).unwrap(), r#"{"type":2}"# ); assert_eq!( &serde_json::to_string(&EventSubscribeObject::KeyDown { keys: vec![] }).unwrap(), r#"{"keys":[],"type":3}"# ); assert_eq!( &serde_json::to_string(&EventSubscribeObject::KeyDown { keys: vec![] }).unwrap(), r#"{"keys":[],"type":3}"# ); assert_eq!( &serde_json::to_string(&EventSubscribeObject::KeyUp { keys: vec![s!("abc"), s!("def")] }) .unwrap(), r#"{"keys":["abc","def"],"type":4}"# ); assert_eq!( &serde_json::to_string(&EventSubscribeObject::KeyDown { keys: vec![s!("abc"), s!("def")] }) .unwrap(), r#"{"keys":["abc","def"],"type":3}"# ); assert_eq!( &serde_json::to_string(&EventSubscribeObject::KeyDown { keys: vec![s!("\"\"")] }) .unwrap(), r#"{"keys":["\"\""],"type":3}"# ); } #[test] fn deserialize_event_sub_obj() { assert_eq!( serde_json::from_str::(r#"{"type":0}"#).unwrap(), EventSubscribeObject::MediaItemStart ); assert_eq!( serde_json::from_str::(r#"{"type":1}"#).unwrap(), EventSubscribeObject::MediaItemEnd ); assert_eq!( serde_json::from_str::(r#"{"type":2}"#).unwrap(), EventSubscribeObject::MediaItemChanged ); assert_eq!( serde_json::from_str::(r#"{"keys":[],"type":3}"#).unwrap(), EventSubscribeObject::KeyDown { keys: vec![] } ); assert_eq!( serde_json::from_str::(r#"{"keys":[],"type":4}"#).unwrap(), EventSubscribeObject::KeyUp { keys: vec![] } ); assert_eq!( serde_json::from_str::(r#"{"keys":["abc","def"],"type":3}"#) .unwrap(), EventSubscribeObject::KeyDown { keys: vec![s!("abc"), s!("def")] } ); assert_eq!( serde_json::from_str::(r#"{"keys":["abc","def"],"type":4}"#) .unwrap(), EventSubscribeObject::KeyUp { keys: vec![s!("abc"), s!("def")] } ); assert!(serde_json::from_str::(r#""type":5}"#).is_err()); } const EMPTY_TEST_MEDIA_ITEM: MediaItem = MediaItem { container: String::new(), url: None, content: None, time: None, volume: None, speed: None, cache: None, show_duration: None, headers: None, metadata: None, }; const TEST_MEDIA_ITEM_JSON: &str = r#"{"cache":null,"container":"","content":null,"headers":null,"metadata":null,"showDuration":null,"speed":null,"time":null,"url":null,"volume":null}"#; #[test] fn serialize_event_obj() { assert_eq!( serde_json::to_string(&EventObject::MediaItem { variant: EventType::MediaItemStart, item: EMPTY_TEST_MEDIA_ITEM.clone(), }) .unwrap(), format!(r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":0}}"#), ); assert_eq!( serde_json::to_string(&EventObject::MediaItem { variant: EventType::MediaItemEnd, item: EMPTY_TEST_MEDIA_ITEM.clone(), }) .unwrap(), format!(r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":1}}"#), ); assert_eq!( serde_json::to_string(&EventObject::MediaItem { variant: EventType::MediaItemChange, item: EMPTY_TEST_MEDIA_ITEM.clone(), }) .unwrap(), format!(r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":2}}"#), ); assert_eq!( &serde_json::to_string(&EventObject::Key { variant: EventType::KeyDown, key: s!(""), repeat: false, handled: false, }) .unwrap(), r#"{"handled":false,"key":"","repeat":false,"type":3}"# ); assert_eq!( &serde_json::to_string(&EventObject::Key { variant: EventType::KeyUp, key: s!(""), repeat: false, handled: false, }) .unwrap(), r#"{"handled":false,"key":"","repeat":false,"type":4}"# ); } #[test] fn deserialize_event_obj() { assert_eq!( serde_json::from_str::(&format!( r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":0}}"# )) .unwrap(), EventObject::MediaItem { variant: EventType::MediaItemStart, item: EMPTY_TEST_MEDIA_ITEM.clone(), } ); assert_eq!( serde_json::from_str::(&format!( r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":1}}"# )) .unwrap(), EventObject::MediaItem { variant: EventType::MediaItemEnd, item: EMPTY_TEST_MEDIA_ITEM.clone(), } ); assert_eq!( serde_json::from_str::(&format!( r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":2}}"# )) .unwrap(), EventObject::MediaItem { variant: EventType::MediaItemChange, item: EMPTY_TEST_MEDIA_ITEM.clone(), } ); assert_eq!( serde_json::from_str::( r#"{"handled":false,"key":"","repeat":false,"type":3}"# ) .unwrap(), EventObject::Key { variant: EventType::KeyDown, key: s!(""), repeat: false, handled: false, } ); assert_eq!( serde_json::from_str::( r#"{"handled":false,"key":"","repeat":false,"type":4}"# ) .unwrap(), EventObject::Key { variant: EventType::KeyUp, key: s!(""), repeat: false, handled: false, } ); assert!(serde_json::from_str::(r#"{"type":5}"#).is_err()); } }