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