Skip to main content

fcast_protocol/
v3.rs

1use std::collections::HashMap;
2
3use serde::{de, ser, Deserialize, Serialize};
4use serde_json::{json, Value};
5use serde_repr::{Deserialize_repr, Serialize_repr};
6use serde_with::skip_serializing_none;
7
8macro_rules! get_from_map {
9    ($map:expr, $key:expr) => {
10        $map.get($key).ok_or(de::Error::missing_field($key))
11    };
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum MetadataObject {
16    Generic {
17        title: Option<String>,
18        thumbnail_url: Option<String>,
19        custom: Option<Value>,
20    },
21}
22
23impl Serialize for MetadataObject {
24    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
25    where
26        S: serde::Serializer,
27    {
28        match self {
29            MetadataObject::Generic {
30                title,
31                thumbnail_url,
32                custom,
33            } => {
34                let mut map = serde_json::Map::new();
35                map.insert("type".to_owned(), json!(0u64));
36                map.insert(
37                    "title".to_owned(),
38                    match title {
39                        Some(t) => Value::String(t.to_owned()),
40                        None => Value::Null,
41                    },
42                );
43                map.insert(
44                    "thumbnailUrl".to_owned(),
45                    match thumbnail_url {
46                        Some(t) => Value::String(t.to_owned()),
47                        None => Value::Null,
48                    },
49                );
50                if let Some(custom) = custom {
51                    map.insert("custom".to_owned(), custom.clone());
52                }
53                map.serialize(serializer)
54            }
55        }
56    }
57}
58
59impl<'de> Deserialize<'de> for MetadataObject {
60    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
61    where
62        D: serde::Deserializer<'de>,
63    {
64        let mut map = serde_json::Map::deserialize(deserializer)?;
65
66        let type_ = map
67            .remove("type")
68            .ok_or(de::Error::missing_field("type"))?
69            .as_u64()
70            .ok_or(de::Error::custom("`type` is not an integer"))?;
71        let rest = Value::Object(map);
72
73        match type_ {
74            0 => {
75                let title = match rest.get("title") {
76                    None | Some(Value::Null) => None,
77                    Some(t) => Some(
78                        t.as_str()
79                            .ok_or(de::Error::custom("`title` is not a string"))?
80                            .to_owned(),
81                    ),
82                };
83                let thumbnail_url = match rest.get("thumbnailUrl") {
84                    None | Some(Value::Null) => None,
85                    Some(t) => Some(
86                        t.as_str()
87                            .ok_or(de::Error::custom("`thumbnailUrl` is not a string"))?
88                            .to_owned(),
89                    ),
90                };
91                Ok(Self::Generic {
92                    title,
93                    thumbnail_url,
94                    custom: rest.get("custom").cloned(),
95                })
96            }
97            _ => Err(de::Error::custom(format!("Unknown metadata type {type_}"))),
98        }
99    }
100}
101
102#[skip_serializing_none]
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104pub struct PlayMessage {
105    /// The MIME type (video/mp4)
106    pub container: String,
107    // The URL to load (optional)
108    pub url: Option<String>,
109    // The content to load (i.e. a DASH manifest, json content, optional)
110    pub content: Option<String>,
111    // The time to start playing in seconds
112    pub time: Option<f64>,
113    // The desired volume (0-1)
114    pub volume: Option<f64>,
115    // The factor to multiply playback speed by (defaults to 1.0)
116    pub speed: Option<f64>,
117    // HTTP request headers to add to the play request Map<string, string>
118    pub headers: Option<HashMap<String, String>>,
119    pub metadata: Option<MetadataObject>,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize_repr, Serialize_repr)]
123#[repr(u8)]
124pub enum ContentType {
125    #[default]
126    Playlist = 0,
127}
128
129#[skip_serializing_none]
130#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
131pub struct MediaItem {
132    /// The MIME type (video/mp4)
133    pub container: String,
134    /// The URL to load (optional)
135    pub url: Option<String>,
136    /// The content to load (i.e. a DASH manifest, json content, optional)
137    pub content: Option<String>,
138    /// The time to start playing in seconds
139    pub time: Option<f64>,
140    /// The desired volume (0-1)
141    pub volume: Option<f64>,
142    /// The factor to multiply playback speed by (defaults to 1.0)
143    pub speed: Option<f64>,
144    /// Indicates if the receiver should preload the media item
145    pub cache: Option<bool>,
146    /// Indicates how long the item content is presented on screen in seconds
147    #[serde(rename = "showDuration")]
148    pub show_duration: Option<f64>,
149    /// HTTP request headers to add to the play request Map<string, string>
150    pub headers: Option<HashMap<String, String>>,
151    pub metadata: Option<MetadataObject>,
152}
153
154impl From<PlayMessage> for MediaItem {
155    fn from(msg: PlayMessage) -> Self {
156        Self {
157            container: msg.container,
158            url: msg.url,
159            content: msg.content,
160            time: msg.time,
161            volume: msg.volume,
162            speed: msg.speed,
163            headers: msg.headers,
164            metadata: msg.metadata,
165            ..Default::default()
166        }
167    }
168}
169
170#[skip_serializing_none]
171#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
172pub struct PlaylistContent {
173    #[serde(rename = "contentType")]
174    pub variant: ContentType,
175    pub items: Vec<MediaItem>,
176    /// Start position of the first item to play from the playlist
177    pub offset: Option<u64>, // int or float?
178    /// The desired volume (0-1)
179    pub volume: Option<f64>,
180    /// The factor to multiply playback speed by (defaults to 1.0)
181    pub speed: Option<f64>,
182    /// Count of media items should be pre-loaded forward from the current view index
183    #[serde(rename = "forwardCache")]
184    pub forward_cache: Option<u64>,
185    /// Count of media items should be pre-loaded backward from the current view index
186    #[serde(rename = "backwardCache")]
187    pub backward_cache: Option<u64>,
188    pub metadata: Option<MetadataObject>,
189}
190
191#[skip_serializing_none]
192#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
193pub struct PlaybackUpdateMessage {
194    // The time the packet was generated (unix time milliseconds)
195    #[serde(rename = "generationTime")]
196    pub generation_time: u64,
197    // The playback state
198    pub state: crate::PlaybackState,
199    // The current time playing in seconds
200    pub time: Option<f64>,
201    // The duration in seconds
202    pub duration: Option<f64>,
203    // The playback speed factor
204    pub speed: Option<f64>,
205    // The playlist item index currently being played on receiver
206    #[serde(rename = "itemIndex")]
207    pub item_index: Option<u64>,
208}
209
210#[skip_serializing_none]
211#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
212pub struct InitialSenderMessage {
213    #[serde(rename = "displayName")]
214    pub display_name: Option<String>,
215    #[serde(rename = "appName")]
216    pub app_name: Option<String>,
217    #[serde(rename = "appVersion")]
218    pub app_version: Option<String>,
219}
220
221#[skip_serializing_none]
222#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
223pub struct LivestreamCapabilities {
224    /// https://datatracker.ietf.org/doc/draft-murillo-whep/
225    pub whep: Option<bool>,
226}
227
228#[skip_serializing_none]
229#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
230pub struct AVCapabilities {
231    pub livestream: Option<LivestreamCapabilities>,
232}
233
234#[skip_serializing_none]
235#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
236pub struct ReceiverCapabilities {
237    pub av: Option<AVCapabilities>,
238}
239
240#[skip_serializing_none]
241#[allow(dead_code)]
242#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
243pub struct InitialReceiverMessage {
244    #[serde(rename = "displayName")]
245    pub display_name: Option<String>,
246    #[serde(rename = "appName")]
247    pub app_name: Option<String>,
248    #[serde(rename = "appVersion")]
249    pub app_version: Option<String>,
250    #[serde(rename = "playData")]
251    pub play_data: Option<PlayMessage>,
252    #[serde(rename = "experimentalCapabilities")]
253    pub experimental_capabilities: Option<ReceiverCapabilities>,
254}
255
256#[skip_serializing_none]
257#[allow(dead_code)]
258#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
259pub struct PlayUpdateMessage {
260    #[serde(rename = "generationTime")]
261    pub generation_time: Option<u64>,
262    #[serde(rename = "playData")]
263    pub play_data: Option<PlayMessage>,
264}
265
266#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
267pub struct SetPlaylistItemMessage {
268    #[serde(rename = "itemIndex")]
269    pub item_index: u64,
270}
271
272#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
273pub enum KeyNames {
274    ArrowLeft,
275    ArrowRight,
276    ArrowUp,
277    ArrowDown,
278    Enter,
279}
280
281#[allow(dead_code)]
282impl KeyNames {
283    pub fn all() -> Vec<String> {
284        vec![
285            "ArrowLeft".to_owned(),
286            "ArrowRight".to_owned(),
287            "ArrowUp".to_owned(),
288            "ArrowDown".to_owned(),
289            "Enter".to_owned(),
290        ]
291    }
292}
293
294#[derive(Debug, PartialEq, Eq, Clone, Hash)]
295pub enum EventSubscribeObject {
296    MediaItemStart,
297    MediaItemEnd,
298    MediaItemChanged,
299    KeyDown { keys: Vec<String> },
300    KeyUp { keys: Vec<String> },
301}
302
303impl Serialize for EventSubscribeObject {
304    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
305    where
306        S: serde::Serializer,
307    {
308        let mut map = serde_json::Map::new();
309        let type_val: u64 = match self {
310            EventSubscribeObject::MediaItemStart => 0,
311            EventSubscribeObject::MediaItemEnd => 1,
312            EventSubscribeObject::MediaItemChanged => 2,
313            EventSubscribeObject::KeyDown { .. } => 3,
314            EventSubscribeObject::KeyUp { .. } => 4,
315        };
316
317        map.insert("type".to_owned(), json!(type_val));
318
319        let keys = match self {
320            EventSubscribeObject::KeyDown { keys } => Some(keys),
321            EventSubscribeObject::KeyUp { keys } => Some(keys),
322            _ => None,
323        };
324        if let Some(keys) = keys {
325            map.insert("keys".to_owned(), json!(keys));
326        }
327
328        map.serialize(serializer)
329    }
330}
331
332impl<'de> Deserialize<'de> for EventSubscribeObject {
333    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
334    where
335        D: serde::Deserializer<'de>,
336    {
337        let mut map = serde_json::Map::deserialize(deserializer)?;
338        let type_ = map
339            .remove("type")
340            .ok_or(de::Error::missing_field("type"))?
341            .as_u64()
342            .ok_or(de::Error::custom("`type` is not an integer"))?;
343        let rest = Value::Object(map);
344
345        match type_ {
346            0 => Ok(Self::MediaItemStart),
347            1 => Ok(Self::MediaItemEnd),
348            2 => Ok(Self::MediaItemChanged),
349            3 | 4 => {
350                let keys = get_from_map!(rest, "keys")?
351                    .as_array()
352                    .ok_or(de::Error::custom("`type` is not an array"))?
353                    .iter()
354                    .map(|v| v.as_str().map(|s| s.to_owned()))
355                    .collect::<Option<Vec<String>>>()
356                    .ok_or(de::Error::custom("`type` is not an array of strings"))?;
357                if type_ == 3 {
358                    Ok(Self::KeyDown { keys })
359                } else {
360                    Ok(Self::KeyUp { keys })
361                }
362            }
363            _ => Err(de::Error::custom(format!("Unknown event type {type_}"))),
364        }
365    }
366}
367
368#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
369pub struct SubscribeEventMessage {
370    pub event: EventSubscribeObject,
371}
372
373#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
374pub struct UnsubscribeEventMessage {
375    pub event: EventSubscribeObject,
376}
377
378#[derive(Debug, PartialEq, Eq, Clone, Copy)]
379#[repr(u8)]
380pub enum EventType {
381    MediaItemStart = 0,
382    MediaItemEnd = 1,
383    MediaItemChange = 2,
384    KeyDown = 3,
385    KeyUp = 4,
386}
387
388#[derive(Debug, PartialEq, Clone)]
389#[allow(clippy::large_enum_variant)]
390pub enum EventObject {
391    MediaItem {
392        variant: EventType,
393        item: MediaItem,
394    },
395    Key {
396        variant: EventType,
397        key: String,
398        repeat: bool,
399        handled: bool,
400    },
401}
402
403impl Serialize for EventObject {
404    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
405    where
406        S: serde::Serializer,
407    {
408        let mut map = serde_json::Map::new();
409
410        match self {
411            EventObject::MediaItem { variant, item } => {
412                map.insert("type".to_owned(), json!(*variant as u8));
413                map.insert(
414                    "item".to_owned(),
415                    serde_json::to_value(item).map_err(ser::Error::custom)?,
416                );
417            }
418            EventObject::Key {
419                variant,
420                key,
421                repeat,
422                handled,
423            } => {
424                map.insert("type".to_owned(), json!(*variant as u8));
425                map.insert(
426                    "key".to_owned(),
427                    serde_json::to_value(key).map_err(ser::Error::custom)?,
428                );
429                map.insert(
430                    "repeat".to_owned(),
431                    serde_json::to_value(repeat).map_err(ser::Error::custom)?,
432                );
433                map.insert(
434                    "handled".to_owned(),
435                    serde_json::to_value(handled).map_err(ser::Error::custom)?,
436                );
437            }
438        }
439
440        map.serialize(serializer)
441    }
442}
443
444impl<'de> Deserialize<'de> for EventObject {
445    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
446    where
447        D: de::Deserializer<'de>,
448    {
449        let mut map = serde_json::Map::deserialize(deserializer)?;
450        let type_ = map
451            .remove("type")
452            .ok_or(de::Error::missing_field("type"))?
453            .as_u64()
454            .ok_or(de::Error::custom("`type` is not an integer"))?;
455        let rest = Value::Object(map);
456
457        match type_ {
458            #[allow(clippy::manual_range_patterns)]
459            0 | 1 | 2 => {
460                let variant = match type_ {
461                    0 => EventType::MediaItemStart,
462                    1 => EventType::MediaItemEnd,
463                    _ => EventType::MediaItemChange,
464                };
465                let item = get_from_map!(rest, "item")?;
466                Ok(Self::MediaItem {
467                    variant,
468                    item: MediaItem::deserialize(item).map_err(de::Error::custom)?,
469                })
470            }
471            3 | 4 => {
472                let variant = if type_ == 3 {
473                    EventType::KeyDown
474                } else {
475                    EventType::KeyUp
476                };
477                Ok(Self::Key {
478                    variant,
479                    key: get_from_map!(rest, "key")?
480                        .as_str()
481                        .ok_or(de::Error::custom("`key` is not a string"))?
482                        .to_owned(),
483                    repeat: get_from_map!(rest, "repeat")?
484                        .as_bool()
485                        .ok_or(de::Error::custom("`repeat` is not a bool"))?,
486                    handled: get_from_map!(rest, "handled")?
487                        .as_bool()
488                        .ok_or(de::Error::custom("`handled` is not a bool"))?,
489                })
490            }
491            _ => Err(de::Error::custom(format!("Unknown event type {type_}"))),
492        }
493    }
494}
495
496#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
497pub struct EventMessage {
498    #[serde(rename = "generationTime")]
499    pub generation_time: u64,
500    pub event: EventObject,
501}
502
503pub use crate::v2::VolumeUpdateMessage;
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508
509    macro_rules! s {
510        ($s:expr) => {
511            ($s).to_string()
512        };
513    }
514
515    #[test]
516    fn serialize_metadata_object() {
517        assert_eq!(
518            &serde_json::to_string(&MetadataObject::Generic {
519                title: Some(s!("abc")),
520                thumbnail_url: Some(s!("def")),
521                custom: Some(serde_json::Value::Null),
522            })
523            .unwrap(),
524            r#"{"custom":null,"thumbnailUrl":"def","title":"abc","type":0}"#
525        );
526        assert_eq!(
527            &serde_json::to_string(&MetadataObject::Generic {
528                title: None,
529                thumbnail_url: None,
530                custom: Some(serde_json::Value::Null),
531            })
532            .unwrap(),
533            r#"{"custom":null,"thumbnailUrl":null,"title":null,"type":0}"#
534        );
535        assert_eq!(
536            &serde_json::to_string(&MetadataObject::Generic {
537                title: Some(s!("abc")),
538                thumbnail_url: Some(s!("def")),
539                custom: None,
540            })
541            .unwrap(),
542            r#"{"thumbnailUrl":"def","title":"abc","type":0}"#
543        );
544        assert_eq!(
545            &serde_json::to_string(&MetadataObject::Generic {
546                title: None,
547                thumbnail_url: None,
548                custom: None,
549            })
550            .unwrap(),
551            r#"{"thumbnailUrl":null,"title":null,"type":0}"#
552        );
553    }
554
555    #[test]
556    fn deserialize_metadata_object() {
557        assert_eq!(
558            serde_json::from_str::<MetadataObject>(
559                r#"{"type":0,"title":"abc","thumbnailUrl":"def","custom":null}"#
560            )
561            .unwrap(),
562            MetadataObject::Generic {
563                title: Some(s!("abc")),
564                thumbnail_url: Some(s!("def")),
565                custom: Some(serde_json::Value::Null),
566            }
567        );
568        assert_eq!(
569            serde_json::from_str::<MetadataObject>(r#"{"type":0,"custom":null}"#).unwrap(),
570            MetadataObject::Generic {
571                title: None,
572                thumbnail_url: None,
573                custom: Some(serde_json::Value::Null),
574            }
575        );
576        assert_eq!(
577            serde_json::from_str::<MetadataObject>(r#"{"type":0}"#).unwrap(),
578            MetadataObject::Generic {
579                title: None,
580                thumbnail_url: None,
581                custom: None,
582            }
583        );
584        assert!(serde_json::from_str::<MetadataObject>(r#"{"type":1"#).is_err());
585        assert_eq!(
586            serde_json::from_str::<MetadataObject>(
587                r#"{"thumbnailUrl":null,"title":null,"type":0}"#
588            )
589            .unwrap(),
590            MetadataObject::Generic {
591                title: None,
592                thumbnail_url: None,
593                custom: None,
594            }
595        );
596    }
597
598    #[test]
599    fn serialize_event_sub_obj() {
600        assert_eq!(
601            &serde_json::to_string(&EventSubscribeObject::MediaItemStart).unwrap(),
602            r#"{"type":0}"#
603        );
604        assert_eq!(
605            &serde_json::to_string(&EventSubscribeObject::MediaItemEnd).unwrap(),
606            r#"{"type":1}"#
607        );
608        assert_eq!(
609            &serde_json::to_string(&EventSubscribeObject::MediaItemChanged).unwrap(),
610            r#"{"type":2}"#
611        );
612        assert_eq!(
613            &serde_json::to_string(&EventSubscribeObject::KeyDown { keys: vec![] }).unwrap(),
614            r#"{"keys":[],"type":3}"#
615        );
616        assert_eq!(
617            &serde_json::to_string(&EventSubscribeObject::KeyDown { keys: vec![] }).unwrap(),
618            r#"{"keys":[],"type":3}"#
619        );
620        assert_eq!(
621            &serde_json::to_string(&EventSubscribeObject::KeyUp {
622                keys: vec![s!("abc"), s!("def")]
623            })
624            .unwrap(),
625            r#"{"keys":["abc","def"],"type":4}"#
626        );
627        assert_eq!(
628            &serde_json::to_string(&EventSubscribeObject::KeyDown {
629                keys: vec![s!("abc"), s!("def")]
630            })
631            .unwrap(),
632            r#"{"keys":["abc","def"],"type":3}"#
633        );
634        assert_eq!(
635            &serde_json::to_string(&EventSubscribeObject::KeyDown {
636                keys: vec![s!("\"\"")]
637            })
638            .unwrap(),
639            r#"{"keys":["\"\""],"type":3}"#
640        );
641    }
642
643    #[test]
644    fn deserialize_event_sub_obj() {
645        assert_eq!(
646            serde_json::from_str::<EventSubscribeObject>(r#"{"type":0}"#).unwrap(),
647            EventSubscribeObject::MediaItemStart
648        );
649        assert_eq!(
650            serde_json::from_str::<EventSubscribeObject>(r#"{"type":1}"#).unwrap(),
651            EventSubscribeObject::MediaItemEnd
652        );
653        assert_eq!(
654            serde_json::from_str::<EventSubscribeObject>(r#"{"type":2}"#).unwrap(),
655            EventSubscribeObject::MediaItemChanged
656        );
657        assert_eq!(
658            serde_json::from_str::<EventSubscribeObject>(r#"{"keys":[],"type":3}"#).unwrap(),
659            EventSubscribeObject::KeyDown { keys: vec![] }
660        );
661        assert_eq!(
662            serde_json::from_str::<EventSubscribeObject>(r#"{"keys":[],"type":4}"#).unwrap(),
663            EventSubscribeObject::KeyUp { keys: vec![] }
664        );
665        assert_eq!(
666            serde_json::from_str::<EventSubscribeObject>(r#"{"keys":["abc","def"],"type":3}"#)
667                .unwrap(),
668            EventSubscribeObject::KeyDown {
669                keys: vec![s!("abc"), s!("def")]
670            }
671        );
672        assert_eq!(
673            serde_json::from_str::<EventSubscribeObject>(r#"{"keys":["abc","def"],"type":4}"#)
674                .unwrap(),
675            EventSubscribeObject::KeyUp {
676                keys: vec![s!("abc"), s!("def")]
677            }
678        );
679        assert!(serde_json::from_str::<EventSubscribeObject>(r#""type":5}"#).is_err());
680    }
681
682    const EMPTY_TEST_MEDIA_ITEM: MediaItem = MediaItem {
683        container: String::new(),
684        url: None,
685        content: None,
686        time: None,
687        volume: None,
688        speed: None,
689        cache: None,
690        show_duration: None,
691        headers: None,
692        metadata: None,
693    };
694    const TEST_MEDIA_ITEM_JSON: &str = r#"{"container":""}"#;
695
696    #[test]
697    fn serialize_event_obj() {
698        assert_eq!(
699            serde_json::to_string(&EventObject::MediaItem {
700                variant: EventType::MediaItemStart,
701                item: EMPTY_TEST_MEDIA_ITEM.clone(),
702            })
703            .unwrap(),
704            format!(r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":0}}"#),
705        );
706        assert_eq!(
707            serde_json::to_string(&EventObject::MediaItem {
708                variant: EventType::MediaItemEnd,
709                item: EMPTY_TEST_MEDIA_ITEM.clone(),
710            })
711            .unwrap(),
712            format!(r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":1}}"#),
713        );
714        assert_eq!(
715            serde_json::to_string(&EventObject::MediaItem {
716                variant: EventType::MediaItemChange,
717                item: EMPTY_TEST_MEDIA_ITEM.clone(),
718            })
719            .unwrap(),
720            format!(r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":2}}"#),
721        );
722        assert_eq!(
723            &serde_json::to_string(&EventObject::Key {
724                variant: EventType::KeyDown,
725                key: s!(""),
726                repeat: false,
727                handled: false,
728            })
729            .unwrap(),
730            r#"{"handled":false,"key":"","repeat":false,"type":3}"#
731        );
732        assert_eq!(
733            &serde_json::to_string(&EventObject::Key {
734                variant: EventType::KeyUp,
735                key: s!(""),
736                repeat: false,
737                handled: false,
738            })
739            .unwrap(),
740            r#"{"handled":false,"key":"","repeat":false,"type":4}"#
741        );
742    }
743
744    #[test]
745    fn deserialize_event_obj() {
746        assert_eq!(
747            serde_json::from_str::<EventObject>(&format!(
748                r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":0}}"#
749            ))
750            .unwrap(),
751            EventObject::MediaItem {
752                variant: EventType::MediaItemStart,
753                item: EMPTY_TEST_MEDIA_ITEM.clone(),
754            }
755        );
756        assert_eq!(
757            serde_json::from_str::<EventObject>(&format!(
758                r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":1}}"#
759            ))
760            .unwrap(),
761            EventObject::MediaItem {
762                variant: EventType::MediaItemEnd,
763                item: EMPTY_TEST_MEDIA_ITEM.clone(),
764            }
765        );
766        assert_eq!(
767            serde_json::from_str::<EventObject>(&format!(
768                r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":2}}"#
769            ))
770            .unwrap(),
771            EventObject::MediaItem {
772                variant: EventType::MediaItemChange,
773                item: EMPTY_TEST_MEDIA_ITEM.clone(),
774            }
775        );
776        assert_eq!(
777            serde_json::from_str::<EventObject>(
778                r#"{"handled":false,"key":"","repeat":false,"type":3}"#
779            )
780            .unwrap(),
781            EventObject::Key {
782                variant: EventType::KeyDown,
783                key: s!(""),
784                repeat: false,
785                handled: false,
786            }
787        );
788        assert_eq!(
789            serde_json::from_str::<EventObject>(
790                r#"{"handled":false,"key":"","repeat":false,"type":4}"#
791            )
792            .unwrap(),
793            EventObject::Key {
794                variant: EventType::KeyUp,
795                key: s!(""),
796                repeat: false,
797                handled: false,
798            }
799        );
800        assert!(serde_json::from_str::<EventObject>(r#"{"type":5}"#).is_err());
801    }
802
803    #[test]
804    fn serialize_playlist_content() {
805        assert_eq!(
806            serde_json::to_string(&PlaylistContent {
807                variant: ContentType::Playlist,
808                items: Vec::new(),
809                offset: None,
810                volume: None,
811                speed: None,
812                forward_cache: None,
813                backward_cache: None,
814                metadata: None
815            })
816            .unwrap(),
817            r#"{"contentType":0,"items":[]}"#,
818        );
819        assert_eq!(
820            serde_json::to_string(&PlaylistContent {
821                variant: ContentType::Playlist,
822                items: Vec::new(),
823                offset: None,
824                volume: Some(1.0),
825                speed: Some(1.0),
826                forward_cache: None,
827                backward_cache: None,
828                metadata: None
829            })
830            .unwrap(),
831            r#"{"contentType":0,"items":[],"volume":1.0,"speed":1.0}"#,
832        );
833        assert_eq!(
834            serde_json::to_string(&PlaylistContent {
835                variant: ContentType::Playlist,
836                items: vec![MediaItem {
837                    container: "video/mp4".to_string(),
838                    url: Some("abc".to_string()),
839                    content: None,
840                    time: None,
841                    volume: None,
842                    speed: None,
843                    cache: None,
844                    show_duration: None,
845                    headers: None,
846                    metadata: None
847                }],
848                offset: None,
849                volume: None,
850                speed: None,
851                forward_cache: None,
852                backward_cache: None,
853                metadata: None
854            })
855            .unwrap(),
856            r#"{"contentType":0,"items":[{"container":"video/mp4","url":"abc"}]}"#,
857        );
858        assert_eq!(
859            serde_json::to_string(&PlaylistContent {
860                variant: ContentType::Playlist,
861                items: vec![MediaItem {
862                    container: "video/mp4".to_string(),
863                    url: Some("abc".to_string()),
864                    content: None,
865                    time: None,
866                    volume: None,
867                    speed: None,
868                    cache: None,
869                    show_duration: None,
870                    headers: None,
871                    metadata: None
872                }],
873                offset: None,
874                volume: Some(1.0),
875                speed: Some(1.0),
876                forward_cache: None,
877                backward_cache: None,
878                metadata: Some(MetadataObject::Generic {
879                    title: None,
880                    thumbnail_url: None,
881                    custom: None
882                })
883            })
884            .unwrap(),
885            r#"{"contentType":0,"items":[{"container":"video/mp4","url":"abc"}],"volume":1.0,"speed":1.0,"metadata":{"thumbnailUrl":null,"title":null,"type":0}}"#,
886        );
887    }
888}